@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 +3 -3
- package/bundle/deepflow-release.json +18 -13
- package/bundle/skills/deepflow-suite/manifest.yaml +1 -1
- package/bundle/skills/df-agentctrl/SKILL.md +2 -0
- package/bundle/skills/df-agentctrl/references/maintenance.md +12 -0
- package/bundle/skills/df-task/SKILL.md +2 -0
- package/bundle/skills/df-task/references/continuation.md +48 -0
- package/bundle/skills/df-task/references/fact-types.md +1 -1
- package/bundle/skills/df-workbench/SKILL.md +24 -0
- package/manifest.json +1 -1
- package/package.json +6 -6
- package/runtime/df-workbench.mjs +150 -3
- package/skills/deepflow-suite/manifest.yaml +1 -1
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
|
|
45
|
-
`df-agentrt-preview`)
|
|
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
|
+
"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.
|
|
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": "
|
|
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":
|
|
43
|
-
"sha256": "
|
|
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":
|
|
84
|
-
"sha256": "
|
|
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":
|
|
94
|
-
"sha256": "
|
|
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":
|
|
121
|
-
"sha256": "
|
|
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":
|
|
131
|
-
"sha256": "
|
|
135
|
+
"size": 8514,
|
|
136
|
+
"sha256": "130b60ff2535eebc7103ef16be9458900a51ebfe8e0897607d419690639a5c46"
|
|
132
137
|
}
|
|
133
138
|
]
|
|
134
139
|
},
|
|
@@ -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` |
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metagptx/deepflow-suite-cli",
|
|
3
|
-
"version": "0.2.
|
|
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.
|
|
30
|
-
"@metagptx/deepflow-suite-cli-darwin-x64": "0.2.
|
|
31
|
-
"@metagptx/deepflow-suite-cli-linux-arm64": "0.2.
|
|
32
|
-
"@metagptx/deepflow-suite-cli-linux-x64": "0.2.
|
|
33
|
-
"@metagptx/deepflow-suite-cli-win32-x64": "0.2.
|
|
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"
|
package/runtime/df-workbench.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
}
|