@metagptx/deepflow-suite-cli 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,13 +2,13 @@
2
2
  "schemaVersion": 1,
3
3
  "distribution": "deepflow-suite",
4
4
  "package": "@metagptx/deepflow-suite-cli",
5
- "version": "0.2.4",
5
+ "version": "0.2.6",
6
6
  "platform": "any",
7
7
  "arch": "any",
8
8
  "cli": {
9
9
  "name": "deepflow-suite",
10
10
  "package": "@metagptx/deepflow-suite-cli",
11
- "version": "0.2.4",
11
+ "version": "0.2.6",
12
12
  "command": "deepflow-suite"
13
13
  },
14
14
  "skills": [
@@ -19,7 +19,7 @@
19
19
  {
20
20
  "path": "manifest.yaml",
21
21
  "size": 362,
22
- "sha256": "0c0675ab463b18a05d7903956cbd307a7952d71d0f1c8dfc672b1314c285e991"
22
+ "sha256": "287633496cff3a164e74b12076ba5a81e27f8793538af3d79308efb9e527f6cc"
23
23
  },
24
24
  {
25
25
  "path": "SKILL.md",
@@ -39,8 +39,8 @@
39
39
  },
40
40
  {
41
41
  "path": "SKILL.md",
42
- "size": 3502,
43
- "sha256": "9a0e81ec5df799b1fa7fb0ed3f7ec1ac97228c34baa8e6b83ea857e30e8aff85"
42
+ "size": 5308,
43
+ "sha256": "ea10a22d9c46cf6f61d5b88c03f910fc436663151c3977040a76e5294a18bdab"
44
44
  }
45
45
  ]
46
46
  },
@@ -78,10 +78,15 @@
78
78
  "size": 198,
79
79
  "sha256": "c8954184c2b3454a7b11d1089555374068ac44fb50d4f476ddf498d188ab988c"
80
80
  },
81
+ {
82
+ "path": "references/continuation.md",
83
+ "size": 4538,
84
+ "sha256": "7b3b288ed60b2222fce168c0d61939a00b9579c523ca7027cc69844a9035553f"
85
+ },
81
86
  {
82
87
  "path": "references/fact-types.md",
83
- "size": 3834,
84
- "sha256": "66382b303f33944939be564722081549a45f1fbf737aa5dfc15d92274823d469"
88
+ "size": 3974,
89
+ "sha256": "5dd10c73e652d018ce20617ec8a5e1becb55fb935f15a366831050063a8989fc"
85
90
  },
86
91
  {
87
92
  "path": "skill_test.go",
@@ -90,8 +95,8 @@
90
95
  },
91
96
  {
92
97
  "path": "SKILL.md",
93
- "size": 12929,
94
- "sha256": "7568468972badececb0a82fd47a90b044da30142ad1df12bcc45f6b8735c0cdb"
98
+ "size": 15557,
99
+ "sha256": "75f59ec7f50131ce5c9ad3a787a8467a1cafa246a95ec0bd281a5542efb72c71"
95
100
  }
96
101
  ]
97
102
  },
@@ -117,8 +122,8 @@
117
122
  },
118
123
  {
119
124
  "path": "references/maintenance.md",
120
- "size": 3388,
121
- "sha256": "ba0bfb12eba04bb6be090b43180a3dc672584e9ec63142fd3671755b57e27375"
125
+ "size": 5170,
126
+ "sha256": "e303df1eb9849bfe6611e2e6fad37079b5d5934068a66173b8f11efc92343f2a"
122
127
  },
123
128
  {
124
129
  "path": "scripts/to_snapshot.py",
@@ -127,8 +132,8 @@
127
132
  },
128
133
  {
129
134
  "path": "SKILL.md",
130
- "size": 8078,
131
- "sha256": "72f692d4daed01c78a360bc7cd4e541a66d1f23e7b3b43df4836c02eae9d5efd"
135
+ "size": 8514,
136
+ "sha256": "130b60ff2535eebc7103ef16be9458900a51ebfe8e0897607d419690639a5c46"
132
137
  }
133
138
  ]
134
139
  },
@@ -138,8 +143,8 @@
138
143
  "files": [
139
144
  {
140
145
  "path": "SKILL.md",
141
- "size": 10329,
142
- "sha256": "a18557c3dac45c0ef90eee38c2e31099c5045c9b898c842e5e16cecce8d5452a"
146
+ "size": 12164,
147
+ "sha256": "16ce0068a76a210231cb92913d12a39c2c94a779216e84125639febfcc0ce73f"
143
148
  }
144
149
  ]
145
150
  },
@@ -1,5 +1,5 @@
1
1
  id: deepflow-suite
2
- version: 0.2.4
2
+ version: 0.2.6
3
3
  kind: composite-skill
4
4
  requires:
5
5
  skills:
@@ -97,6 +97,8 @@ metadata 归属于 Agent 定义本身,而不是某个版本。当前 CLI 只
97
97
 
98
98
  ## 发现与读取
99
99
 
100
+ 目录的 `description` 应让调度器理解职责、所需输入、产出、完成条件和同一责任的续办范围;这些是定义内容,不是新增控制面字段。维护定义时将角色流程写在执行定义中,不要求调度器硬编码角色名称。选新能力前应先根据 Task 事实确认是否已有承担该目标/产物的 Run;目录描述本身不记录某个 Task 的所有权或 Session 状态。
101
+
100
102
  1. 有业务上下文时使用上面的 metadata 过滤;没有上下文时使用`list --json`保持历史全量行为。根据任务和Agent的`description`选择合适定义;`name`允许重复,身份以`id`为准。
101
103
  2. 从选中目录项取`id`和`default_version`,按明确版本读取完整定义:
102
104
 
@@ -1,5 +1,13 @@
1
1
  # Agent定义维护
2
2
 
3
+ ## 定义的事实源
4
+
5
+ 目标环境 Agent Control 数据库中的定义和版本历史是环境内 Agent 的事实源。通过 CLI/API 维护实际定义;仓库保存服务实现、通用维护规则和格式示例,不维护 dev/prod Agent 完整定义的长期副本,也不在服务部署时用仓库文件覆盖数据库。
6
+
7
+ 修改前从目标环境读取当前版本,生成临时可写快照,在其上修改并审查差异。保存后通过 `get`、`versions`、`diff` 核验,交付记录引用环境、Agent ID、变更前后版本及关键差异;临时快照不是另一套待同步的定义来源。定义维护与服务代码发布是独立操作,合并文档或代码 MR 不代表环境定义已经更新。
8
+
9
+ 描述应说明职责、输入、输出、完成条件和同一责任的续办范围,让调用方按契约选择能力,不依赖角色名称。当前 Task 的产物责任及 Run/Session 映射仍来自 Task 事实,不写入可复用的 Agent 定义。
10
+
3
11
  ## 快照格式
4
12
 
5
13
  新建时可复制本Skill的[示例](../assets/agent.json),然后按任务填写:
@@ -49,6 +57,10 @@ df-agentctrl save <agent-id> --file agent.json --change-note '调整评审指令
49
57
 
50
58
  `--change-note`覆盖文件里的说明,`--change-note=`可显式清空。保存成功返回`{"agent": {...}, "version": {...}}`,从响应取实际版本号,不假设仍是读取版本加一(其他管理员可能同时保存)。
51
59
 
60
+ `save` 是完整覆盖且立即成为默认版本。保存前重新读取,核对基线版本和元数据;若有变化,合并最新内容后重新审查,不能提交旧快照覆盖。当前 API 没有 CAS,重读只能缩小并发窗口,保存后还须核对返回版本与当前默认版本;发生竞争时报告实际版本,不通过反复保存抢回默认值。
61
+
62
+ 保存后按明确版本读回,核对修改字段及保留的名称、Skills、MCP、metadata 和启停状态,并用 `diff` 检查变更范围。新默认定义用于后续按该版本创建的 Session;既有 Session 保留已注入的 AgentData,不会被保存操作更新。工具、Skill 分发和服务部署的可用性须独立核实,定义更新不能作为这些能力已上线的证据。
63
+
52
64
  ## 版本与回退
53
65
 
54
66
  ```bash
@@ -23,7 +23,9 @@ multica repo checkout <URL>
23
23
  其余的 `multica` 子命令敲下去都会被明确拒绝,stderr 上会告诉你这里没有这个东西、
24
24
  以及改做什么。那些拒绝信息是给你看的,照着做就行,**不要换个写法重试**。
25
25
 
26
- 你自己的能力在 `df-agentrt` 上,已经在 PATH 里,不需要定位路径。
26
+ 你自己的能力在 `df-agentrt` 上。Runtime 镜像通过标准 PATH 提供 `df-task`、`df-agentctrl`、`df-agentrt` 和 `multica` shim,普通 shell 与登录 shell 均应可直接调用。命令缺失或 `--help` 失败属于执行环境异常:报告并结束,不要在 Run 中安装、编译 CLI 或猜绝对路径反复尝试。
27
+
28
+ Skill 内容通过 provider 的原生目录交付,以当前会话的 Skill 目录清单为准。清单中的 `r0` 等是根目录别名,打开前先展开,不能拼成 `skills/r0/...`。Codex 的 Runtime Skills 在 `$CODEX_HOME/skills/<slug>/SKILL.md`;Claude 在任务工作目录的 `.claude/skills/<slug>/SKILL.md`。支持文件相对该 Skill 目录读取。`$DEEPFLOW_WORKDIR` 表示任务工作目录,不是通用 Skill 根目录。
27
29
 
28
30
  ## 目标环境门禁与地址
29
31
 
@@ -85,8 +87,7 @@ release 只停止新轮次。归档、对象存储和本地清理属于后续生
85
87
 
86
88
  ## 取消是协作式的
87
89
 
88
- 被取消时你不会被当场杀掉:状态先变成 `cancelled`,你会收到停止的请求,
89
- 然后有时间收尾。收到取消请求就**尽快收尾并把已有的结论说出来**,
90
+ 取消请求被接受不等于执行已经停止:先出现 `cancel_requested`,终态由 Runtime 后续确认。你收到停止请求时应尽快收尾。收到取消请求就**尽快收尾并把已有的结论说出来**,
90
91
  不要假装无事发生继续跑,也不要因为被取消就什么都不留下。
91
92
 
92
93
  ---
@@ -97,7 +98,7 @@ release 只停止新轮次。归档、对象存储和本地清理属于后续生
97
98
  本会话(不需要会话 id,它自己知道是哪个)
98
99
  df-agentrt session info 你是谁:provider、model、工作目录、仓库,
99
100
  以及上一轮的对话历史有没有续上
100
- df-agentrt session turns 这个会话到目前为止的每一轮
101
+ df-agentrt session turns [--json] 这个会话到目前为止的每一轮
101
102
  df-agentrt events [--after N] 消息流,一行一条
102
103
  df-agentrt repo list 本会话可以检出哪些仓库
103
104
  df-agentrt repo search <应用名> 按应用名找仓库
@@ -106,15 +107,22 @@ release 只停止新轮次。归档、对象存储和本地清理属于后续生
106
107
 
107
108
  别的会话(必须显式给会话 id)
108
109
  df-agentrt session ls 这个 runtime 上的会话
109
- df-agentrt session get <会话id> 某个会话的完整信息
110
+ df-agentrt session get <会话id> [--json] 某个会话的完整信息
111
+ df-agentrt session turns --session <会话id> [--json] 指定会话的轮次状态
112
+ df-agentrt events --session <会话id> [--after N] [--limit N] [--raw]
113
+ 指定会话的消息流
110
114
  df-agentrt session create ... 新建会话 💰
111
115
  df-agentrt msg send <会话id> <文本> 给别的会话排一轮 💰
112
116
 
113
117
  运行时
114
- df-agentrt ops runtimes 执行引擎注册了哪些、还活着哪些
118
+ df-agentrt ops runtimes [--json] 执行引擎注册了哪些、还活着哪些
115
119
  df-agentrt ops orphans 记录和现实哪里对不上(只读)
116
120
  ```
117
121
 
122
+ 只读查询省略 `--session` 时,`session turns` 和 `events` 仍查询当前会话。查询其他会话必须显式给 Runtime Session ID;业务 Run ID 不是 Session ID。只在 `session get`、`session turns`、`ops runtimes` 使用 `--json`;`events --raw` 是带事件前缀的原始 payload 展示,不是纯 JSON。
123
+
124
+ 需要按已授权的指令取消另一轮时,先从 Task 的 `invocation_accepted` 事实取得准确的 Runtime Session/Invocation 映射,用 `session turns --session <session-id> --json` 核对,再执行 `df-agentrt msg cancel <invocation-id>`。不带参数会取消**当前轮**;不要把 Run ID 或 Session ID 传给 cancel。接受取消后,核对同一 Invocation 已到终态,再写 `run_terminated`;消息流安静或命令返回成功都不足以证明停止。
125
+
118
126
  ## 业务 metadata 与软范围查询
119
127
 
120
128
  Runtime Session 可以携带业务方自定义的 JSON metadata。基础服务只约定顶层的 source、
@@ -142,7 +150,7 @@ Runtime 也不会从登录身份或当前会话自动推断业务范围。
142
150
 
143
151
  当前 session info、session get 和 session ls 的人类可读输出不会打印 metadata 原文,
144
152
  不要把输出中没有 metadata 理解成 Session 没有 metadata。需要核对原值时读取 Runtime API 的
145
- JSON 响应;CLI 过滤仍然可以正常使用。
153
+ JSON 响应(`df-agentrt session get <session-id> --json`);CLI 过滤仍然可以正常使用。
146
154
 
147
155
  💰 这两条会**真的跑起来一个 agent**,花的是真钱。而且新建的会话**不会自己结束**——
148
156
  它会一直占着一个工作目录,直到有人显式 release 它。确认你真的需要另一个会话再用,
@@ -52,12 +52,9 @@ df-task --base-url https://deepflowtask.dev.deepwisdomai.com facts "$DF_TASK_ID"
52
52
  **在 Run 里一律加 `--json`。** 不加 `--json` 的输出是给人看的排版,字段随时会调整。
53
53
 
54
54
  ```bash
55
- # 读本 Task 的全部事实
56
- df-task facts "$DF_TASK_ID" --json
57
-
58
- # 只读某几类,或从某个序号往后读
55
+ # 按本轮需要选择事实类型(选择标准见「读日志」)
59
56
  df-task facts "$DF_TASK_ID" --types user_message,artifact_published --json
60
- df-task facts "$DF_TASK_ID" --after 128 --json
57
+ df-task facts "$DF_TASK_ID" --types user_message,artifact_published --after 128 --limit 100 --json
61
58
 
62
59
  # 追加一条事实:payload 写成 JSON 文件再传
63
60
  # 注意 heredoc 的标记不加引号,加了引号 $DF_RUN_ID 就不会展开
@@ -115,16 +112,36 @@ df-task stream \
115
112
  metadata 只影响读请求的范围。`append`、`dispatch`、`wait`、`handoff` 和 `title` 仍按明确的
116
113
  Task ID 操作,不支持 metadata 过滤参数;事实服务不会根据 metadata 做鉴权。
117
114
 
118
- 命令不存在时,本项目源码可用的话在 `deepflow-task/` 下运行 `make install-cli`,它把 `df-task` 装到 `$HOME/.local/bin`。**注意不要把 runtime 的地址填成 Task 服务的地址** —— `df-agentrt` 和 `df-task` 是两个服务、两个端口、两套授权。
115
+ 托管 Run 中 `df-task` 由 Runtime 镜像提供。命令缺失或 `--help` 无法执行时,报告执行环境异常并结束;不要在 Run 中安装、编译 CLI 或猜测绝对路径来继续。非托管的本地开发可按项目 README 安装 CLI。**注意不要把 runtime 的地址填成 Task 服务的地址** —— `df-agentrt` 和 `df-task` 是两个服务、两个端口、两套授权。
119
116
 
120
117
  ## 读日志
121
118
 
122
- 1. 先 `df-task facts "$DF_TASK_ID" --json` 通读一遍,搞清这件工作已经发生过什么
123
- 2. 关心产物就加 `--types artifact_published`;关心人说了什么就加 `--types user_message`
124
- 3. 产物事实里存的是**引用坐标,不是内容**。要看 diff、spec、MR 的正文,拿着 `ref` 自己去外部系统取
125
- 4. 要分页往后读就用上一页返回的 `next_after_seq` 当下一次的 `--after`
119
+ 先确定本轮要作出的判断,再按需选择事实;不要求每轮先通读全部日志。当前描述与版本用上文的 `get` 读取,事实用于补足决策依据。
120
+
121
+ | 要判断什么 | 优先读取的类型 |
122
+ | --- | --- |
123
+ | 用户新增的要求、补充信息或外部结果 | `user_message,external_result`;同时检查消息的 `wake_policy` |
124
+ | 是否已经派发、Run 对应哪个 Runtime Session/Invocation、是否仍在执行 | `dispatch_intent,invocation_started,invocation_accepted,invocation_ended,run_terminated,attempt_failed` |
125
+ | 执行结果、待办与阻塞 | `summary,run_outcome,help_requested`;核对交付物时再读 `artifact_published` |
126
+ | 已有等待、人工接手或裁决是否影响本轮 | `waiting_reason,handoff_to_human,adjudication`;涉及 Task 生命周期时再读 `task_lifecycle` |
126
127
 
127
- 序号有空洞,且落库顺序不保证与序号顺序一致。一次读不完就接着读,不要假设「序号连续就是读全了」。
128
+ 这些是选择标准,不是每轮必须执行的四组查询。调度 Run 在再次派发前要查清相关派发与执行事实,避免重复工作;任务 Run 按交给自己的目标和引用补充上下文,无需重建整个调度历史。已有摘要不能代替被引用的关键事实;证据不足、存在矛盾或无法定位关联时,扩大类型和时间范围,必要时通读日志。同一轮已读到且仍适用的事实可以复用。
129
+
130
+ ```bash
131
+ # 示例:核对是否已派发,以及对应的执行状态
132
+ df-task facts "$DF_TASK_ID" \
133
+ --types dispatch_intent,invocation_started,invocation_accepted,invocation_ended,run_terminated,attempt_failed \
134
+ --limit 100 --json
135
+ ```
136
+
137
+ 分页与完整性:
138
+
139
+ - 响应按 `seq` 升序,`--limit` 默认 200、最大 1000,**不是取最新 N 条**。`--after` 排除给定序号;下一页用响应的 `next_after_seq`,保持同一 Task、类型和 metadata 过滤条件。
140
+ - 首次读取某组类型时,没有已确认的读取进度就从头读。更换类型不能复用另一组的游标;Driver 的输入水位也不是 Agent 已读游标,不能直接拿它作 `--after` 跳过本轮触发事实。
141
+ - `next_after_seq` 始终返回,即使空页也会保留输入游标;它不是“还有下一页”的标志。非空页可用它继续读取,空页停止;要断言“没有派发/结果”等不存在结论,必须读完相关范围。分页未完、类型未覆盖或只读摘要时,不能把没看到当成不存在。
142
+ - 序号有空洞,落库顺序也不保证与序号顺序一致;游标表示本次查询进度,不代表之前的序号永远不会再出现。关键判断证据矛盾时回查相关范围,不按连续序号推断读全。
143
+ - API 不支持按 `run_id` 过滤、精确查单个 `seq` 或倒序取尾页。返回中按关联字段核对 Run/引用;不要尝试不存在的参数。
144
+ - `artifact_published` 只有引用坐标。需要 diff、spec、MR 正文时,用 `ref` 到对应系统读取。
128
145
 
129
146
  ## 任务 Run 可以写什么
130
147
 
@@ -143,6 +160,8 @@ Task ID 操作,不支持 metadata 过滤参数;事实服务不会根据 meta
143
160
 
144
161
  ## 调度 Run 可以写什么
145
162
 
163
+ 派发前先按[原 Run 续办规则](references/continuation.md)核对目标、产物和责任归属,再决定续办还是新分工;不能仅按角色名、新消息或上一调用结束选择 new_run。
164
+
146
165
  | 命令 | 落成哪条事实 | 什么时候写 |
147
166
  | --- | --- | --- |
148
167
  | `df-task dispatch <task-id> --agent <name> --instruction <text>` | `dispatch_intent`(`mode` 为 `new_run`,不带 `target_run_id`) | 把一块工作派给某个智能体,Driver 会派生一个新逻辑 Run |
@@ -0,0 +1,48 @@
1
+ # 调度:原 Run 续办与新工作
2
+
3
+ 调度器不需要知道具体角色的实现流程。Agent Control 的 description/明确版本定义描述职责、输入、输出、完成条件和续办范围;Task 事实记录哪个 Run 承担了哪项责任;Runtime 事实与查询用于判断 Session 是否可继续。三者不能相互替代。
4
+
5
+ ## 决策顺序
6
+
7
+ 1. 从本轮有效请求/反馈确定目标、所指产物、所需责任和工作范围。定位被反馈产物的 `artifact_published.run_id`,结合原 `dispatch_intent.instruction`、`summary` 与 Agent 定义核对责任归属。反馈的发布 Run 不一定是处理反馈的责任 Run;不能选择“最近的同名 Agent”或“最后发言的人”。没有产物时沿原目标与派发范围定位,证据不足就补读或说明歧义。
8
+ 2. 检查相关 dispatch、started、accepted、ended 和失败事实,读完对应分页。已有相同请求/反馈的待兑现、排队或执行意图时不重复派发。新的补充内容属于原责任时可以向原 Run 发一次 `message`;不要仅因原 invocation 仍在执行或已经结束而另建 Run。
9
+ 3. 目标/产物、责任、范围仍归原 Run 时续办它。`invocation_ended` 只结束一次调用,`run_outcome` 只是执行者主张;二者都不表示 Session 已销毁,也不排除补充交付、修复、复核或等待结果后的继续。
10
+ 4. 独立目标、不同责任或明确拆分才新建 Run。需要新分工时通过 Agent Control 目录和明确版本选择能力;不能按预设角色名单或固定流水线分派。
11
+ 5. 续办不可恢复时显式接管:先核对当前 Task 的真实 Session 映射及 Runtime 状态,写明不可恢复的证据、原 Run、产物、遗留工作、新责任人和上下文交接,再创建替代 Run。身份不明、映射尚未落库、查询失败都不等于不可恢复;不能静默退化为 new_run。没有可跨 Session 读取的产物时说明恢复缺口。
12
+
13
+ ## 精确协议
14
+
15
+ 通过 `invocation_started.payload.intent_seq` 关联原派发,读取 **`invocation_started.payload.run_id`** 的精确值;再用对应 `invocation_accepted` 确认同一 Run 的非空 Session。业务 Run、Runtime Session、Invocation 是不同身份。不要自行生成或推导 Run ID。
16
+
17
+ ```bash
18
+ # 新工作:不带 --run;payload 必须完全省略 target_run_id(空串/null 也不行)
19
+ df-task dispatch "$DF_TASK_ID" --agent <agent-id> \
20
+ --instruction '<目标、责任、范围、输入产物、交付条件>' --json
21
+
22
+ # 原责任续办:使用实际 Run,通常为 run-...,不是 UUID 校验
23
+ df-task dispatch "$DF_TASK_ID" --agent <原agent-id> --run <实际run-id> \
24
+ --instruction '<反馈来源、所指产物与revision、原责任、待办和完成条件>' --json
25
+ ```
26
+
27
+ 以 CLI 的 `--help` 为参数事实源。新工作 `mode=new_run`;续办 `mode=message` 且 `target_run_id` 必填。旧日志中 new_run 自带的随机 UUID 是历史占位,不能拿来续办或等待。message 不会替换原 Session 的 AgentData/版本;在续办指令内给出本轮新约束,不能假定默认定义更新已影响旧 Session。
28
+
29
+ `dispatch` 简写不提供版本和幂等键参数。需要固定新 Run 的 Agent 版本或对反馈幂等派发时,将 payload 写到 JSON 文件,用 `append`:
30
+
31
+ ```json
32
+ {"agent":"<已读agent-id>","agent_version":1,"mode":"message","target_run_id":"<实际run-id>","instruction":"<本轮续办指令>"}
33
+ ```
34
+
35
+ 示例版本替换为已核对版本;new_run 则删掉 `target_run_id` 字段并修改 mode。
36
+
37
+ ```bash
38
+ df-task append "$DF_TASK_ID" --type dispatch_intent --file /tmp/dispatch.json \
39
+ --idempotency-key '<Task:反馈seq:目标Run:动作>' --json
40
+ ```
41
+
42
+ 同一反馈使用稳定键,不用随机时间;写入超时先读回。新反馈或新 revision 是不同依据,不能复用旧键。message 创建新 Invocation,不是中断/取消命令。
43
+
44
+ ## 等待与完成
45
+
46
+ 核对交付契约,而非仅采信 `run_outcome`;缺交付或反馈属于原责任时回到原 Run。独立复核由不同责任承担,复核产物必须说明所针对的原产物/revision,便于后续定位责任方。
47
+
48
+ `waiting_reason` 只记录等待依据,不提供定时器、外部轮询或订阅。等待时指出依赖对象、负责 Run、所需事件和已有唤醒来源;没有外部事件接入时明确缺口并交给能提供结果/唤醒的人,不反复唤起新 Run 充当轮询器。派发后结束本轮,兑现与生命周期观测仍由 Driver 负责。
@@ -30,7 +30,7 @@
30
30
 
31
31
  | 类型 | 必填 | 可选 | 说明 |
32
32
  | --- | --- | --- | --- |
33
- | `dispatch_intent` | `agent`、`instruction`、`mode`;`mode=message` 时还必须有 `target_run_id` | —— | `new_run` 不带 `target_run_id`,由 Driver 根据 `task_id + intent_seq` 派生逻辑 Run;`message` 只能指向已有逻辑 Run。用 `df-task dispatch` 更省事 |
33
+ | `dispatch_intent` | `agent`、`instruction`、`mode`;`mode=message` 时还必须有 `target_run_id` | `agent_version`(Driver 读取的正整数版本) | `new_run` 必须省略 `target_run_id`,由 Driver 根据 `task_id + intent_seq` 派生逻辑 Run;`message` 指向已有逻辑 Run并复用原 Session/AgentData,不切换版本。简写 `df-task dispatch` 不支持版本/幂等键,需固定时用 `append` |
34
34
  | `waiting_reason` | `waiting_on`、`reason` | `waiting_run_id`(实际执行 ID) | 给人和给下一轮调度的上下文,**不是推进器的门** |
35
35
  | `handoff_to_human` | `ask` | `assignee` | 交球和派活是同一条事实,区别只在 `ask` 的内容 |
36
36
  | `adjudication` | `subject`、`verdict` | `reason` | `subject` 是被裁决那条事实的**序号**(数字,不是字符串) |
@@ -32,12 +32,36 @@ local 环境只允许从本机回环地址访问;OIDC 环境使用 Workbench p
32
32
 
33
33
  ## 用户任务流程
34
34
 
35
+ 需要指定业务小队时,先查询当前用户可见目录:
36
+
37
+ ```bash
38
+ df-workbench squad list --json
39
+ df-workbench squad get <squad-id> --json
40
+ ```
41
+
42
+ 读取成员、角色、revision及可用状态后按真实ID选择,不按名称猜测或替换。没有指定小队时沿用服务端系统默认/不可用回退行为。
43
+
35
44
  创建 Task:
36
45
 
37
46
  ```bash
38
47
  df-workbench task create --message '请检查登录页按钮溢出问题' --json
48
+ # 用户已明确选择小队时:
49
+ df-workbench task create --message '用户的自然需求' --squad-id <squad-id> --idempotency-key <stable-key> --json
50
+ ```
51
+
52
+ 选择小队不需要把角色ID或调度话术写进自然需求。创建后核对 `metadata.data.squad` 的小队ID、版本和成员角色;CLI会拒绝缺失/不一致的选队回执,但任务可能已经存在,必须保留Task ID和原key核查。不要换key创建另一Task。小队是建议性协作上下文,平台scheduler不由队长替代,不把快照存在当作所有成员已执行。
53
+
54
+ 仅在用户要求维护个人小队时使用:
55
+
56
+ ```bash
57
+ df-workbench squad create --file squad.json --idempotency-key <stable-key> --json
58
+ df-workbench squad update <personal-squad-id> --file squad-update.json --idempotency-key <stable-key> --json
39
59
  ```
40
60
 
61
+ 配置是JSON对象:`name`、`members:[{agentId,roles:[]}]`,可选 `description/steps/gates/practice`;更新还需当前 `revision`。角色可用 `clarify/design/build/review/verify`,无需队长;普通成员传 `roles:[]`,不要把快照里的 `member` 当作配置角色。只能用已确认的真实Agent ID,服务端权限、失效成员和revision错误需原样反馈,不自动放宽权限或改写配置。内置小队不可修改。目录返回的只读字段不要混入配置文件。
62
+
63
+ 这组命令需要包含该CLI源码的新Suite版本;旧版本报不支持时如实报告版本缺口,不绕过到Task事实服务写入。
64
+
41
65
  继续对话:
42
66
 
43
67
  ```bash
package/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "distribution": "deepflow-suite",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "skills": [
5
5
  {
6
6
  "name": "deepflow-suite",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metagptx/deepflow-suite-cli",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Agent-friendly installer and distribution CLI for the DeepFlow suite.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -26,11 +26,11 @@
26
26
  "access": "public"
27
27
  },
28
28
  "optionalDependencies": {
29
- "@metagptx/deepflow-suite-cli-darwin-arm64": "0.2.4",
30
- "@metagptx/deepflow-suite-cli-darwin-x64": "0.2.4",
31
- "@metagptx/deepflow-suite-cli-linux-arm64": "0.2.4",
32
- "@metagptx/deepflow-suite-cli-linux-x64": "0.2.4",
33
- "@metagptx/deepflow-suite-cli-win32-x64": "0.2.4"
29
+ "@metagptx/deepflow-suite-cli-darwin-arm64": "0.2.6",
30
+ "@metagptx/deepflow-suite-cli-darwin-x64": "0.2.6",
31
+ "@metagptx/deepflow-suite-cli-linux-arm64": "0.2.6",
32
+ "@metagptx/deepflow-suite-cli-linux-x64": "0.2.6",
33
+ "@metagptx/deepflow-suite-cli-win32-x64": "0.2.6"
34
34
  },
35
35
  "engines": {
36
36
  "node": ">=22.13.0"
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { randomUUID } from "node:crypto";
4
+ import { readFile } from "node:fs/promises";
4
5
  import { pathToFileURL } from "node:url";
5
6
 
6
7
  const DEFAULT_BASE_URL = "http://127.0.0.1:8793";
@@ -44,6 +45,8 @@ export function parseArgs(argv, env = process.env) {
44
45
  after: "",
45
46
  limit: "",
46
47
  scope: "mine",
48
+ squadId: undefined,
49
+ file: undefined,
47
50
  };
48
51
  const positionals = [];
49
52
  for (let index = 0; index < argv.length; index += 1) {
@@ -69,6 +72,8 @@ export function parseArgs(argv, env = process.env) {
69
72
  "--after",
70
73
  "--limit",
71
74
  "--scope",
75
+ "--squad-id",
76
+ "--file",
72
77
  ].includes(name)
73
78
  ) {
74
79
  const parsed =
@@ -82,6 +87,8 @@ export function parseArgs(argv, env = process.env) {
82
87
  if (name === "--after") options.after = parsed;
83
88
  if (name === "--limit") options.limit = parsed;
84
89
  if (name === "--scope") options.scope = parsed;
90
+ if (name === "--squad-id") options.squadId = parsed;
91
+ if (name === "--file") options.file = parsed;
85
92
  continue;
86
93
  }
87
94
  if (argument.startsWith("--"))
@@ -113,11 +120,20 @@ export function normalizeBaseUrl(value) {
113
120
  return url.toString().replace(/\/$/, "");
114
121
  }
115
122
 
116
- export function buildCreateBody(message, title = "") {
123
+ function squadId(value) {
124
+ if (typeof value !== "string" || !/^[\w-]{1,160}$/.test(value))
125
+ throw new UsageError(
126
+ "小队 ID 格式无效;请用 squad list 查询,使用系统默认小队时省略 --squad-id。",
127
+ );
128
+ return value;
129
+ }
130
+
131
+ export function buildCreateBody(message, title = "", selectedSquadId) {
117
132
  const text = String(message || "").trim();
118
133
  if (!text || text.length > MAX_MESSAGE_LENGTH)
119
134
  throw new UsageError(`消息需要 1–${MAX_MESSAGE_LENGTH} 个字符。`);
120
135
  const body = { description: text, conversation: { type: "task" } };
136
+ if (selectedSquadId !== undefined) body.squad_id = squadId(selectedSquadId);
121
137
  if (title) {
122
138
  const trimmed = title.trim();
123
139
  if (!trimmed || trimmed.length > 160)
@@ -271,11 +287,107 @@ function taskSummary(value) {
271
287
  ? "访问: 仅概览(请先申请加入并等待批准后读取完整内容)"
272
288
  : "",
273
289
  task.overviewState?.status ? `状态: ${task.overviewState.status}` : "",
290
+ task.access !== "overview" && task.metadata?.data?.squad
291
+ ? squadSummary(task.metadata.data.squad)
292
+ : "",
293
+ ]
294
+ .filter(Boolean)
295
+ .join("\n");
296
+ }
297
+
298
+ function squadSummary(squad) {
299
+ return [
300
+ `小队: ${squad.name || ""} (${squad.squadId || squad.id})`,
301
+ squad.squadVersion || squad.version
302
+ ? `版本: ${squad.squadVersion || squad.version}`
303
+ : "",
304
+ squad.status ? `状态: ${squad.status}` : "",
305
+ ...(squad.members || []).map(
306
+ (member) =>
307
+ `成员: ${member.role} · ${member.agentName || member.agentId || "未配置"}${member.unavailable ? "(不可用)" : ""}`,
308
+ ),
309
+ squad.practice ? `协作规则: ${squad.practice}` : "",
274
310
  ]
275
311
  .filter(Boolean)
276
312
  .join("\n");
277
313
  }
278
314
 
315
+ async function squadCommand(options, subject, rest) {
316
+ if (!["list", "get", "create", "update"].includes(subject))
317
+ throw new UsageError(`不支持的 squad 命令:${subject || ""}`);
318
+ const needsId = subject === "get" || subject === "update";
319
+ if (rest.length !== (needsId ? 1 : 0))
320
+ throw new UsageError(
321
+ `squad ${subject}${needsId ? " 需要一个小队 ID。" : " 不接受小队 ID。"}`,
322
+ );
323
+ const id = needsId ? squadId(rest[0]) : undefined;
324
+ const writing = subject === "create" || subject === "update";
325
+ if (writing && !options.file)
326
+ throw new UsageError(`squad ${subject} 需要 --file <配置.json>。`);
327
+ let body;
328
+ if (writing) {
329
+ try {
330
+ body = JSON.parse(await readFile(options.file, "utf8"));
331
+ } catch {
332
+ throw new UsageError(
333
+ "无法读取小队配置 JSON 文件;请检查路径与 JSON 格式。",
334
+ );
335
+ }
336
+ if (!body || typeof body !== "object" || Array.isArray(body))
337
+ throw new UsageError("小队配置必须是 JSON 对象。");
338
+ if (
339
+ subject === "update" &&
340
+ (!id.startsWith("personal-") ||
341
+ !Number.isSafeInteger(body.revision) ||
342
+ body.revision < 1)
343
+ )
344
+ throw new UsageError(
345
+ "只能更新个人小队,配置文件需要当前 revision;请先用 squad get 查询。",
346
+ );
347
+ }
348
+ const context = await currentContext(options);
349
+ if (!writing) {
350
+ const value = await request(options, "/api/v1/squads", {}, context);
351
+ if (!Array.isArray(value?.squads))
352
+ throw new RequestError("小队目录响应无效。", {
353
+ code: "invalid_response",
354
+ });
355
+ if (subject === "list") {
356
+ writeResult(
357
+ value,
358
+ options,
359
+ (value) => value.squads.map(squadSummary).join("\n\n") || "没有小队。",
360
+ );
361
+ } else {
362
+ const found = value.squads.find((squad) => squad.id === id);
363
+ if (!found)
364
+ throw new RequestError("小队不存在或不可访问。", {
365
+ status: 404,
366
+ code: "squad_not_found",
367
+ });
368
+ writeResult(found, options, squadSummary);
369
+ }
370
+ return;
371
+ }
372
+ const key = idempotencyKey(options);
373
+ const value = await request(
374
+ options,
375
+ id ? `/api/v1/squads/${encodeURIComponent(id)}` : "/api/v1/squads",
376
+ { method: "POST", key, body },
377
+ context,
378
+ );
379
+ if (typeof value?.id !== "string" || !value.id || (id && value.id !== id))
380
+ throw new RequestError(
381
+ `小队保存回执尚未确认;保留配置和幂等键 ${key} 核对,不要换键重建。`,
382
+ {
383
+ code: "squad_receipt_mismatch",
384
+ outcome: "unknown",
385
+ idempotencyKey: key,
386
+ },
387
+ );
388
+ writeResult(value, options, squadSummary);
389
+ }
390
+
279
391
  export function requestFailureHint(error) {
280
392
  if (error?.code === "task_participation_required")
281
393
  return "当前任务仅可查看概览;请先在 Workbench 中申请加入并等待批准。";
@@ -306,7 +418,11 @@ function help() {
306
418
  df-workbench me [--json]
307
419
  df-workbench config [--json]
308
420
  df-workbench health [--json]
309
- df-workbench task create --message <消息> [--title <标题>] [--idempotency-key <key>]
421
+ df-workbench squad list [--json]
422
+ df-workbench squad get <squad-id> [--json]
423
+ df-workbench squad create --file <配置.json> [--idempotency-key <key>] [--json]
424
+ df-workbench squad update <squad-id> --file <配置.json> [--idempotency-key <key>] [--json]
425
+ df-workbench task create --message <消息> [--title <标题>] [--squad-id <squad-id>] [--idempotency-key <key>]
310
426
  df-workbench task send <task-id> --message <消息> [--idempotency-key <key>]
311
427
  df-workbench task get <task-id> [--json]
312
428
  df-workbench task facts <task-id> [--after <seq>] [--limit <n>] [--json]
@@ -327,6 +443,19 @@ async function run(options) {
327
443
  process.stdout.write(`${help()}\n`);
328
444
  return;
329
445
  }
446
+ if (
447
+ options.squadId !== undefined &&
448
+ !(command === "task" && subject === "create")
449
+ )
450
+ throw new UsageError(
451
+ "--squad-id 仅用于 task create,不会修改已有任务的小队。",
452
+ );
453
+ if (
454
+ options.file !== undefined &&
455
+ !(command === "squad" && ["create", "update"].includes(subject))
456
+ )
457
+ throw new UsageError("--file 仅用于 squad create/update。");
458
+ if (command === "squad") return squadCommand(options, subject, rest);
330
459
  if (command === "health") {
331
460
  const value = await request(options, "/healthz");
332
461
  writeResult(value, options, (value) => JSON.stringify(value));
@@ -354,6 +483,11 @@ async function run(options) {
354
483
  }
355
484
  if (command !== "task") throw new UsageError(`不支持的命令:${command}`);
356
485
  if (subject === "create") {
486
+ const body = buildCreateBody(
487
+ options.message,
488
+ options.title,
489
+ options.squadId,
490
+ );
357
491
  const context = await currentContext(options);
358
492
  const key = idempotencyKey(options);
359
493
  const value = await request(
@@ -362,10 +496,23 @@ async function run(options) {
362
496
  {
363
497
  method: "POST",
364
498
  key,
365
- body: buildCreateBody(options.message, options.title),
499
+ body,
366
500
  },
367
501
  context,
368
502
  );
503
+ if (
504
+ body.squad_id &&
505
+ value?.task?.metadata?.data?.squad?.squadId !== body.squad_id
506
+ )
507
+ throw new RequestError(
508
+ `任务可能已创建,但小队回执不一致;Task ${value?.task?.id || "未知"},请保留幂等键 ${key} 核对,不要换键重建。`,
509
+ {
510
+ code: "task_metadata_mismatch",
511
+ outcome: "unknown",
512
+ taskId: value?.task?.id,
513
+ idempotencyKey: key,
514
+ },
515
+ );
369
516
  writeResult(value, options, taskSummary);
370
517
  return;
371
518
  }
@@ -1,5 +1,5 @@
1
1
  id: deepflow-suite
2
- version: 0.2.4
2
+ version: 0.2.6
3
3
  kind: composite-skill
4
4
  requires:
5
5
  skills: