@metagptx/deepflow-suite-cli 0.2.5 → 0.2.7

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.
package/README.md CHANGED
@@ -41,9 +41,9 @@ deepflow-suite doctor --source workspace --json
41
41
 
42
42
  The workspace distribution contains `deepflow-suite`, `df-workbench`,
43
43
  `df-task`, `df-taskdriver`, `df-agentctrl`, `df-agentrt`, and `df-agentrt-repo`.
44
- The three Runtime variant Skills (`df-agentrt-full`, `df-agentrt-git`, and
45
- `df-agentrt-preview`) remain service-local and are not part of the ordinary
46
- distribution.
44
+ The four Runtime variant Skills (`df-agentrt-full`, `df-agentrt-git`,
45
+ `df-agentrt-ci`, and `df-agentrt-preview`) are selected by Agent definitions
46
+ and are not part of the ordinary distribution.
47
47
 
48
48
  ## Publishing
49
49
 
@@ -2,13 +2,13 @@
2
2
  "schemaVersion": 1,
3
3
  "distribution": "deepflow-suite",
4
4
  "package": "@metagptx/deepflow-suite-cli",
5
- "version": "0.2.5",
5
+ "version": "0.2.7",
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.5",
11
+ "version": "0.2.7",
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": "afd23f0ad1d0c1967274391000eb8d4ad92a14071b8ef8ac9b101c24b5c325fe"
22
+ "sha256": "b84f868bf0002da6bc04872c9c59e1c748c0ffc2b543875595893f8ebe59c563"
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": 15347,
94
- "sha256": "2a45f890bffab946701ab38c07d7ccb91056d881b77bccffcf3a7c03ce35a8b8"
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
  },
@@ -1,5 +1,5 @@
1
1
  id: deepflow-suite
2
- version: 0.2.5
2
+ version: 0.2.7
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
@@ -160,6 +160,8 @@ df-task facts "$DF_TASK_ID" \
160
160
 
161
161
  ## 调度 Run 可以写什么
162
162
 
163
+ 派发前先按[原 Run 续办规则](references/continuation.md)核对目标、产物和责任归属,再决定续办还是新分工;不能仅按角色名、新消息或上一调用结束选择 new_run。
164
+
163
165
  | 命令 | 落成哪条事实 | 什么时候写 |
164
166
  | --- | --- | --- |
165
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.5",
3
+ "version": "0.2.7",
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.5",
3
+ "version": "0.2.7",
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.5",
30
- "@metagptx/deepflow-suite-cli-darwin-x64": "0.2.5",
31
- "@metagptx/deepflow-suite-cli-linux-arm64": "0.2.5",
32
- "@metagptx/deepflow-suite-cli-linux-x64": "0.2.5",
33
- "@metagptx/deepflow-suite-cli-win32-x64": "0.2.5"
29
+ "@metagptx/deepflow-suite-cli-darwin-arm64": "0.2.7",
30
+ "@metagptx/deepflow-suite-cli-darwin-x64": "0.2.7",
31
+ "@metagptx/deepflow-suite-cli-linux-arm64": "0.2.7",
32
+ "@metagptx/deepflow-suite-cli-linux-x64": "0.2.7",
33
+ "@metagptx/deepflow-suite-cli-win32-x64": "0.2.7"
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.5
2
+ version: 0.2.7
3
3
  kind: composite-skill
4
4
  requires:
5
5
  skills: