zzhub-pipeline 流程架构图

Agent 驱动的内容发布状态机 —— 支持文章(article)与贴图(newspic)两条工作流路线,具备完整的中途续接能力

总览 Agent 编排主循环

Agent(LLM 编排器)通过反复调用 find-runstatus --view agent → 执行 next_action.action 来驱动流水线。每一步对 Agent 而言都是无状态的 —— 所有上下文均存储在状态文件中,断点续接无需记忆历史。

Agent 动作
CLI 命令(流水线)
外部协作(LLM / 人工)
终态
判断分支
flowchart TD START(["🤖 Agent 启动"]) --> FIND["find-run --workspace {ws} --active --view agent
查找当前活跃任务"] FIND --> EXISTS{"任务是否存在?"} EXISTS -- "不存在" --> INIT["init 新建任务
创建临时状态文件
mode=active, phase=prepare"] EXISTS -- "已存在" --> READ["读取状态文件
runs/{id}.json 或
posts/{slug}/workflow-state.json"] INIT --> STATUS["status --state {path} --view agent
获取当前状态与下一步动作"] READ --> STATUS STATUS --> NA["📋 输出 next_action.action
即下一步应执行的命令"] NA --> EXEC["Agent 执行 next_action.action"] EXEC --> CHECK{"mode == 'done'?"} CHECK -- "否,继续循环" --> STATUS CHECK -- "是" --> DONE(["✅ 任务完成"])

续接 next_action 决策树(断点续接核心)

buildTaskStatus()(位于 src/task-manager.ts)读取状态文件并按优先级(1→12) 依次判断,输出唯一的下一步动作。Agent 无需记住当前处于哪一步 —— 状态文件本身就是真相来源,任意时刻中断后重新执行 status 即可精准续接。

flowchart TD S["📄 读取 workflow state"] --> C1{"mode=done 或
phase=done?"} C1 -- "是" --> A1["✅ complete
任务已完成"] C1 -- "否" --> C2{"mode=failed?"} C2 -- "是" --> A2["🔧 reset-or-repair
需重置或修复"] C2 -- "否" --> C3{"mode=handoff?"} C3 -- "是" --> A3["📎 attach-body-images
或 resolve-handoff"] C3 -- "否" --> C4{"正文是否已挂载?
source_body_path
或 asset_path"} C4 -- "否" --> A4["✍️ attach-body
Worker 撰写正文"] C4 -- "是" --> C5{"body_inputs
待处理?"} C5 -- "是" --> A5["🖼️ attach-body-images
等待用户提供插图"] C5 -- "否" --> C6{"元数据是否就绪?
title, slug, date"} C6 -- "否" --> A6["📝 prepare
路由+作者+排版+元数据"] C6 -- "是" --> C7{"content_review
审核状态?"} C7 -- "needs_revision" --> A7["🔁 revise-content
Worker 改写正文"] C7 -- "unchecked
无 asset_path" --> A8["👀 review-content
Worker 审核内容"] C7 -- "passed
无 asset_path" --> A9["📦 prepare-finalize
生成正式产物到 posts/"] C7 -- "passed
已有 asset_path" --> C8{"phase.render
是否已完成?"} C8 -- "否,需渲染" --> A10["🎨 render
图片生成"] C8 -- "是" --> C9{"phase.publish
是否已完成?"} C9 -- "否,需发布" --> A11["🚀 publish
推送到各发布渠道"] C9 -- "是" --> A12["📝 prepare
(兜底)"] style A1 fill:#d1fae5,stroke:#10b981,color:#065f46 style A2 fill:#fee2e2,stroke:#ef4444,color:#991b1b style A3 fill:#fef3c7,stroke:#f59e0b,color:#92400e style A4 fill:#fef3c7,stroke:#f59e0b,color:#92400e style A5 fill:#fef3c7,stroke:#f59e0b,color:#92400e style A6 fill:#fef3c7,stroke:#f59e0b,color:#92400e style A7 fill:#ede9fe,stroke:#8b5cf6,color:#5b21b6 style A8 fill:#ede9fe,stroke:#8b5cf6,color:#5b21b6 style A9 fill:#fef3c7,stroke:#f59e0b,color:#92400e style A10 fill:#fef3c7,stroke:#f59e0b,color:#92400e style A11 fill:#fef3c7,stroke:#f59e0b,color:#92400e style A12 fill:#e5e7eb,stroke:#6b7280,color:#374151

阶段 三阶段流水线与子步骤

三个活跃阶段(prepare → render → publish)和两个终态(done / failed)。 每个阶段内的子步骤均可追踪;redo_hint 允许从 prepare 的特定子步骤重新进入。 reset 命令提供 8 种精确回退模式。

flowchart LR subgraph PREPARE["🔧 准备阶段 PREPARE"] direction TB P1["channel-route
解析路由与公众号账号"] --> P2["author-select
确定改写权限与风格模式"] P2 --> P3["style ✨
LLM 风格润色(外部)"] P3 --> P4["format
文本排版规则处理"] P4 --> P5["asset-meta
提取标题/日期/摘要"] P5 --> P6["body-inputs 扫描
识别插图标记"] P6 --> P7["review 👀
内容审核(外部)"] P7 -- "审核通过" --> P8["highlight-words
从标题提取高亮词"] P8 --> P9["asset-save
写入正式文件到 posts/ 目录"] end subgraph RENDER["🎨 渲染阶段 RENDER"] direction TB R1["image-plan
模板选择"] --> R2["body-inputs
收集插图图片"] R2 --> R3["adapter-render
调用图片渲染插件"] R3 --> R4["记录 render_assets
递增 render_version"] end subgraph PUBLISH["🚀 发布阶段 PUBLISH"] direction TB U1["收集发布路由
primary + extras"] --> U2["幂等性检查
版本匹配"] U2 --> U3["provider.publish()
微信公众号 / COS / 博客"] U3 --> U4["记录发布结果
成功 → done | 失败 → 可重试"] end PREPARE -- "prepare-finalize
阶段推进" --> RENDER RENDER -- "phase.render=done" --> PUBLISH PUBLISH -- "全部路由成功" --> DONE(["✅ DONE"]) PUBLISH -- "任一路由失败" --> PUBLISH style PREPARE fill:#fffbeb,stroke:#f59e0b,color:#92400e style RENDER fill:#f5f3ff,stroke:#8b5cf6,color:#5b21b6 style PUBLISH fill:#ecfdf5,stroke:#10b981,color:#065f46 style DONE fill:#f3f4f6,stroke:#6b7280,color:#374151
Reset 回退模式(8 种):
content — 完整重新准备(回到 prepare 起点)  redo.style — 仅重做风格润色  redo.format — 仅重做排版
redo.metadata — 仅重做元数据提取  redo.route — 仅重做路由解析  render — 重做渲染
publish — 重做发布  full — 完全放弃当前任务(mode=failed)
每种模式均会设置 redo_hint,确保 Agent 恢复时知道从哪个子步骤开始。

路线 wechat-article(文章)vs wechat-newspic(贴图)

两条路线在准备和发布阶段流程一致,差异集中在渲染阶段。文章路线生成封面 + 正文长图,发布为公众号草稿; 贴图路线根据内容长度自动选择单页卡片(poster-3-4)或多页图集(longform-3-4),发布为公众号图片消息。

📄 文章路线 wechat-article

flowchart TD A1["init 初始化
route=wechat-article"] --> A2["attach-body
挂载正文"] A2 --> A3["prepare
formatArticle() 排版"] A3 --> A4["review
content_review=passed"] A4 --> A5["prepare-finalize
创建 posts/{slug}/ 目录"] A5 --> A6["render
模板=wechat-cover-split"] A6 --> A7["imgx render-card
生成封面图 + 正文长图"] A7 --> A8["publish
微信公众号草稿接口"] A8 --> A9(["✅ 发布成功"]) style A6 fill:#dbeafe,stroke:#3b82f6 style A7 fill:#dbeafe,stroke:#3b82f6 style A8 fill:#dbeafe,stroke:#3b82f6

🖼️ 贴图路线 wechat-newspic

flowchart TD N1["init 初始化
route=wechat-newspic"] --> N2["attach-body
+ 可选 page_specs 分页规格"] N2 --> N3["prepare
formatArticle() 排版"] N3 --> N4["review
content_review=passed"] N4 --> N5["prepare-finalize"] N5 --> N6{"正文长度?"} N6 -- "短文本 ≤150字
或 pagination_mode=single" --> N7["render
模板=poster-3-4
生成单张卡片 PNG"] N6 -- "长文本 >150字
或 pagination_mode=multi" --> N8["render
模板=longform-3-4
生成多页图集 PNG"] N7 --> N9["publish
公众号图片消息接口"] N8 --> N10["⏸️ 可能暂停等待
用户提供插图"] N10 --> N9 N9 --> N11(["✅ 发布成功"]) style N6 fill:#fdf2f8,stroke:#ec4899 style N7 fill:#fdf2f8,stroke:#ec4899 style N8 fill:#fdf2f8,stroke:#ec4899 style N10 fill:#fdf2f8,stroke:#ec4899

状态文件生命周期

状态文件在流水线执行过程中经历两个存储位置。所有命令和 Agent 均透明解析正确的路径, 无需手动管理文件迁移。

flowchart LR subgraph TEMP["📂 临时区(准备阶段)"] T1["{workspace}/.zzhub-media/
runs/{run_id}.json
临时状态文件"] T2["{workspace}/.zzhub-media/
tmp/{run_id}/source-body.md
正文草稿"] end subgraph CANON["📁 正式区(prepare-finalize 之后)"] C1["{workspace}/posts/
{date-slug}/workflow-state.json
正式状态文件"] C2["{workspace}/posts/
{date-slug}/post.md
正式正文"] C3["{workspace}/posts/
{date-slug}/images/wechat/
文章渲染图"] C4["{workspace}/posts/
{date-slug}/images/newspic/
贴图渲染图"] end TEMP -- "prepare-finalize
固化到正式目录" --> CANON style TEMP fill:#fef2f2,stroke:#f87171,color:#991b1b style CANON fill:#ecfdf5,stroke:#10b981,color:#065f46

Handoff / Ingest 外部交接与续接

外部系统(或人工)可通过 JSON 文件交接任务。ingest-handoff 支持创建新任务或续接已有任务, 自动比对核心输入变化并按需重置派生状态,实现跨系统无缝衔接。

flowchart TD HF["📥 JSON 交接文件
publish_handoff 或
workflow_handoff"] --> INGEST["ingest-handoff 解析交接文件"] INGEST --> MODE{"handoff.mode?"} MODE -- "create 新建" --> INIT2["init 创建新任务"] MODE -- "resume 续接" --> FIND2["find-run 按 state_path
或 run_id 查找已有任务"] FIND2 --> COMPARE{"核心输入是否变化?
正文 / 标题 / 路由"} COMPARE -- "有变化" --> RESET["resetDerivedState()
全部阶段重置为 pending
保留 run_id 与 workspace"] COMPARE -- "无变化" --> POLICY{"仅 review_policy
发生变化?"} POLICY -- "是 + trust_user" --> AUTOPASS["自动设置
content_review=passed"] POLICY -- "完全无变化" --> KEEP["保持当前状态不变"] RESET --> STATUS2["status 重新计算 → next_action"] AUTOPASS --> STATUS2 KEEP --> STATUS2 style HF fill:#e0e7ff,stroke:#6366f1,color:#3730a3 style INGEST fill:#e0e7ff,stroke:#6366f1,color:#3730a3 style RESET fill:#fee2e2,stroke:#ef4444,color:#991b1b