@metagptx/deepflow-suite-cli 0.3.0 → 0.3.2

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.
Files changed (32) hide show
  1. package/bundle/deepflow-release.json +61 -16
  2. package/bundle/skills/df-agentctrl/SKILL.md +9 -87
  3. package/bundle/skills/df-agentctrl/references/environment.md +40 -0
  4. package/bundle/skills/df-agentctrl/references/query-scope.md +50 -0
  5. package/bundle/skills/df-agentrt/SKILL.md +25 -61
  6. package/bundle/skills/df-agentrt/references/environment.md +29 -0
  7. package/bundle/skills/df-agentrt/references/invocation-result.md +11 -0
  8. package/bundle/skills/df-agentrt/references/query-scope.md +30 -0
  9. package/bundle/skills/df-agentrt/references/session-create.md +14 -0
  10. package/bundle/skills/df-agentrt-repo/SKILL.md +4 -0
  11. package/bundle/skills/df-task/SKILL.md +30 -92
  12. package/bundle/skills/df-task/references/environment.md +50 -0
  13. package/bundle/skills/df-task/references/fact-types.md +29 -3
  14. package/bundle/skills/df-task/references/query-scope.md +49 -0
  15. package/bundle/skills/df-task/references/reading.md +9 -0
  16. package/bundle/skills/df-taskdriver/SKILL.md +3 -3
  17. package/bundle/skills/df-workbench/SKILL.md +13 -1
  18. package/manifest.json +2 -1
  19. package/package.json +1 -1
  20. package/runtime/df-agentctrl/release.json +1 -1
  21. package/runtime/df-agentrt/release.json +1 -1
  22. package/runtime/df-agentrt/result.mjs +44 -0
  23. package/runtime/df-agentrt/runtime.mjs +36 -4
  24. package/runtime/df-agentrt/usage.mjs +2 -2
  25. package/runtime/df-task/commands.mjs +23 -12
  26. package/runtime/df-task/df-task.mjs +34 -8
  27. package/runtime/df-task/protocol.mjs +1 -1
  28. package/runtime/df-task/read.mjs +127 -0
  29. package/runtime/df-task/release.json +1 -1
  30. package/runtime/df-taskdriver/release.json +1 -1
  31. package/runtime/df-workbench/df-workbench.mjs +274 -14
  32. package/runtime/df-workbench/release.json +1 -1
@@ -15,108 +15,27 @@ CLI 由 `@metagptx/deepflow-suite-cli` 统一安装,本 Skill 不携带 CLI
15
15
 
16
16
  ## 任务描述
17
17
 
18
- 每轮工作先用 `df-task get "$DF_TASK_ID" --json` 读取 `description` 与 `description_version`。标题是简短名称,描述是当前需求,首条消息仅是历史事实;不能用首条消息替代描述。汇报前再次核对版本,若描述变化需重新核对范围与验收条件。
18
+ 每轮工作先读取当前 `description` 与 `description_version`。支持字段选择的 CLI 可用 `df-task get "$DF_TASK_ID" --fields id,description,description_version --json`,只在需要时追加明确的 metadata 路径;旧版用 `get --json` 保存到本轮文件再提取,不把完整 metadata 打进上下文。标题是简短名称,描述是当前需求,首条消息仅是历史事实;不能用首条消息替代描述。汇报前再次核对版本,若描述变化需重新核对范围与验收条件。
19
19
 
20
- ## 目标环境门禁与地址
21
-
22
- 每次调用服务前,必须先确认用户要操作的目标环境。在推进器拉起的 Run 里按下文「Run 内的目标环境」确定,不询问。其他情况下,用户没有明确说 `local`、本地、`dev`、开发环境或 `prod`、生产环境时,先询问:
23
-
24
- ```text
25
- 请指定目标环境:local(本地)、dev(开发环境)还是 prod(生产环境)?
26
- ```
27
-
28
- 环境一旦选定,本轮所有服务调用都必须保持在同一环境;不能因为连接失败就切换到另一环境,也不能把当前机器所在位置当成目标环境。
29
-
30
- | 目标环境 | 本地执行时的地址 | 在 dev-k8s 内执行时的地址 |
31
- | --- | --- | --- |
32
- | `local` | `http://127.0.0.1:8090` | 不适用 |
33
- | `dev` | `https://deepflowtask.dev.deepwisdomai.com` | `http://deepflowtask.deepflow.svc.cluster.local:8090` |
34
- | `prod` | 尚未配置 | 尚未配置 |
35
-
36
- 地址列描述的是同一目标环境的两种访问方式:本地 CLI 使用 dev Ingress,集群内 Agent 使用 dev Service DNS。确认环境后,可以用用户明确提供的 `--base-url` 覆盖该环境的默认地址,但该地址必须属于已选环境;如果地址与环境冲突,先停止并澄清,不能用它绕过环境门禁。`prod` 目前没有地址,任何 prod 操作都必须停止并说明尚未接入。
37
-
38
- 本地操作 dev 时示例:
39
-
40
- ```bash
41
- df-task --base-url https://deepflowtask.dev.deepwisdomai.com facts "$DF_TASK_ID" --json
42
- ```
20
+ ## 环境与身份
43
21
 
44
22
  ### Run 内的目标环境
45
23
 
46
- `DF_TASK_ID`、`DF_RUN_ID`、`DF_TASK_ACTOR_KIND` 都非空时,你运行在推进器拉起的 Run 里,没有用户可以询问。此时目标环境由部署决定:用注入的 `DF_TASK_BASE_URL` 对照上表反查所属环境,例如 `http://deepflowtask.deepflow.svc.cluster.local:8090` 即 `dev`。每一轮、每个 Run 都按同一规则确定,不需要也不应该等待任何人确认。
47
-
48
- 反查不到任何环境、地址属于 `prod`,或指令要求操作的环境与反查结果不一致时,停止,不调用服务、不改用其他地址,在最终回复里说明注入地址和冲突原因。推进器会照常记录本次执行结束,交给下一轮调度或人处理。
49
-
50
- 不在 Run 里时,只有在用户明确选择 `dev` 且 `DF_TASK_BASE_URL` 确认属于 dev 时,才可以复用它;用户选择 `local` 时必须忽略该值并使用 `http://127.0.0.1:8090`。用户没有先选环境都不能执行请求。
51
-
52
- 先用 `GET /healthz` 验证选定地址,再执行读写操作。健康检查失败时报告该环境不可达,不得静默改用 localhost 或其他环境。
53
-
54
- 下面的示例还用到两个变量,同样由 runtime 注入,同样不要自己编:`DF_TASK_ID` 是你这次执行所属的 Task,`DF_RUN_ID` 是你自己这个 Run 的标识(写事实时 payload 里的 `run_id` 填它)。如果它们是空的,说明这次执行不是由推进器兑现唤起起来的 —— 别猜一个填进去,写一条 `summary` 说明情况。
55
-
56
- **在 Run 里一律加 `--json`。** 不加 `--json` 的输出是给人看的排版,字段随时会调整。
57
-
58
- ```bash
59
- # 按本轮需要选择事实类型(选择标准见「读日志」)
60
- df-task facts "$DF_TASK_ID" --types user_message,artifact_published --json
61
- df-task facts "$DF_TASK_ID" --types user_message,artifact_published --after 128 --limit 100 --json
62
-
63
- # 追加一条事实:payload 写成 JSON 文件再传
64
- # 注意 heredoc 的标记不加引号,加了引号 $DF_RUN_ID 就不会展开
65
- cat > /tmp/artifact.json <<JSON
66
- {"run_id":"$DF_RUN_ID","kind":"diff","ref":"abc1234def5678","title":"登录按钮溢出修复"}
67
- JSON
68
- df-task append "$DF_TASK_ID" --type artifact_published --file /tmp/artifact.json --json
69
- ```
70
-
71
- ## 业务 metadata 与软范围查询
24
+ 托管 Run 中 `DF_TASK_ID/DF_RUN_ID/DF_TASK_ACTOR_KIND` 非空,`DF_TASK_BASE_URL` 由部署注入,绑定本 Run 的事实服务;不询问、不根据同名 Service DNS 猜成 dev,不覆盖写入身份,不因业务目标出现“生产”而切换流程服务。身份或地址缺失/冲突先报告,不能伪造或回退 localhost。只核对必要值,不输出完整环境。在 Run 内一律使用 `--json`。
72
25
 
73
- Task 可以携带业务方自定义的 JSON metadata。基础服务只约定顶层的
74
- `source`、`query_keys` 和 `data`:前两个用于可选的软范围查询,`data` 完全由业务方组织。
75
- 例如:
26
+ 外部调用须先确认 local/dev/prod,再按[环境与地址](references/environment.md)显式选址;用户未明确时先询问,连接失败不切换,跨服务保持同一环境。先验证所选地址 `/healthz`,不能用内部同名 DNS 猜环境。生产写操作仅限已有授权。
76
27
 
77
- ```bash
78
- df-task create \
79
- --title '订单分析' \
80
- --metadata '{"source":"workbench","query_keys":["account:u123"],"data":{"project":"orders"}}' \
81
- --json
82
- ```
28
+ ## metadata 与工具可用性
83
29
 
84
- `--metadata` 必须是 JSON 对象;没有 metadata 时服务保存 `{}`,不会改变原有流程。创建、
85
- 列表和单Task读取会返回 metadata。事实读取也支持同一范围过滤:
86
-
87
- ```bash
88
- SCOPE_SOURCE=workbench
89
- SCOPE_KEY=account:u123
90
-
91
- df-task ls \
92
- --metadata-source "$SCOPE_SOURCE" \
93
- --metadata-query-key "$SCOPE_KEY" \
94
- --json
95
- df-task get "$DF_TASK_ID" \
96
- --metadata-source "$SCOPE_SOURCE" \
97
- --metadata-query-key "$SCOPE_KEY" \
98
- --json
99
- df-task facts "$DF_TASK_ID" \
100
- --metadata-source "$SCOPE_SOURCE" \
101
- --metadata-query-key "$SCOPE_KEY" \
102
- --json
103
- df-task stream \
104
- --metadata-source "$SCOPE_SOURCE" \
105
- --metadata-query-key "$SCOPE_KEY" \
106
- --json
107
- ```
30
+ metadata 的 `source`、`query_keys` 用于可选软范围过滤,`data` 由业务方定义;它们不是权限边界。有业务上下文时沿用已确认的来源和具体 key,不能从 data 猜测。需要创建、查询或核对共享范围时读[范围查询说明](references/query-scope.md)。
108
31
 
109
- 查询规则:
32
+ 托管 Run 中 `df-task` 由 Runtime 镜像提供。命令缺失或 `--help` 无法执行时,报告执行环境异常并结束;不要在 Run 中安装、编译 CLI 或猜测绝对路径来继续。非托管的本地开发可按项目 README 安装 CLI。**注意不要把 runtime 的地址填成 Task 服务的地址** —— `df-agentrt` 和 `df-task` 是两个服务、两个端口、两套授权。
110
33
 
111
- - 同时提供 `source` 和 `query_key` 时,Task 必须匹配来源和查询标识;只提供其中一个时只按该条件过滤。`query_keys` 包含 `"*"` 的显式共享Task例外于这两个条件。
112
- - `query_keys` 可以包含多个标识;`"*"` 表示显式共享,匹配任意查询范围。
113
- - 不提供过滤参数仍然是历史上的全量查询。业务上下文存在时才携带过滤参数,不要把它当成权限边界。
114
- - 先从Task响应的 metadata 读取 `source` 和 `query_keys`,再把当前业务上下文中的具体 key 传给后续查询;不要从 `data` 中猜测过滤字段。
34
+ ## 创建来源与人工参与请求
115
35
 
116
- metadata 只影响读请求的范围。`append`、`dispatch`、`wait`、`handoff` 和 `title` 仍按明确的
117
- Task ID 操作,不支持 metadata 过滤参数;事实服务不会根据 metadata 做鉴权。
36
+ 涉及人的协作时,从 `get` 的不可变 `creator.kind/id` 核对创建来源;owner、当前成员、经办人及正文不能代替。读取来源未知时保持未知。是否选人、何时求助、授予什么责任,由当前 Agent 定义和本次授权判断。
118
37
 
119
- 托管 Run 中 `df-task` 由 Runtime 镜像提供。命令缺失或 `--help` 无法执行时,报告执行环境异常并结束;不要在 Run 中安装、编译 CLI 或猜测绝对路径来继续。非托管的本地开发可按项目 README 安装 CLI。**注意不要把 runtime 的地址填成 Task 服务的地址** —— `df-agentrt` 和 `df-task` 是两个服务、两个端口、两套授权。
38
+ 普通 `handoff --ask` 只写公开交接,不请求成员加入。需要实际人工参与时,读取[人工参与请求与回执](references/fact-types.md#请求实际人工参与),使用 `handoff --participation ensure_member --assignee <真实账号>`;明确授予该人主经办责任时才附加 `--task-assignee`。请求写入成功不等于成员、通知或分配已生效;按请求事实序号核对平台回执,部分成功、失败与未知分别保留。旧 CLI 缺少这些参数时,只有在已加载协议且平台支持的前提下,才可用通用 `append` 提交同一载荷,不能猜字段或把公开答复当作实际邀请。
120
39
 
121
40
  ## 读日志
122
41
 
@@ -147,6 +66,25 @@ df-task facts "$DF_TASK_ID" \
147
66
  - API 不支持按 `run_id` 过滤、精确查单个 `seq` 或倒序取尾页。返回中按关联字段核对 Run/引用;不要尝试不存在的参数。
148
67
  - `artifact_published` 只有引用坐标。需要 diff、spec、MR 正文时,用 `ref` 到对应系统读取。
149
68
 
69
+ ### 字段选择与有界分页
70
+
71
+ 以下是新 CLI 的可选读取能力;以实际 `df-task help get/facts` 为准,旧版继续使用上面的单页 JSON 及游标,不在 Run 中安装或反复试错。
72
+
73
+ ```bash
74
+ # get 的已知顶层字段;metadata 可选对象路径
75
+ df-task get "$DF_TASK_ID" --fields id,description,description_version --json
76
+ # --fields 作用于每条事实,分页信封保留;payload 可整体保留
77
+ df-task facts "$DF_TASK_ID" --types summary,run_outcome --fields seq,type,actor_id,payload --json
78
+ # 原文写本轮新文件,stdout 仅返回查询/数量/类型/完整性/文件摘要
79
+ df-task facts "$DF_TASK_ID" --types dispatch_intent,invocation_started,invocation_accepted,invocation_ended \
80
+ --all --output-file /tmp/dispatch-facts.json --json
81
+ ```
82
+
83
+ - `--fields` 必须显式指定且配合 `--json`。支持的顶层字段和对象路径、缺字段与 null 的区别见[读取契约](references/reading.md)。选择过的输出不证明其他字段没有信息;关键结论需要读原事实。
84
+ - `--all` 必须配合 `--output-file`、`--json`,不能同时使用 `--fields`。文件保留完整事实;清单不是事实正文,按当前判断从文件提取必要的关联和 payload。
85
+ - 默认最多 10 页、8 MiB,只有观察到空页才 `complete=true`。预算耗尽或请求失败会保留已接纳的完整页,返回 `complete=false`、`stop_reason` 和准确 `next_after_seq`,**退出 1**。不要把文件存在或读到部分事实当成读完;按缺口显式续读或报告未完成。
86
+ - 文件是本轮私有缓存,拒绝覆盖;不是跨 Session 产物引用,不替代 Task 事实。`complete` 仅限清单所列类型/过滤/起始游标与本次查询,仍遵守上面的晚提交、换类型和水位规则。
87
+
150
88
  ## 任务 Run 可以写什么
151
89
 
152
90
  | 命令 | 落成哪条事实 | 什么时候写 |
@@ -195,7 +133,7 @@ df-task facts "$DF_TASK_ID" \
195
133
 
196
134
  ## 输出与错误
197
135
 
198
- `--json` 下 stdout 是服务端原样返回的 JSON。退出码只有三个:`0` 成功、`1` 网络或服务端失败、`2` 本地参数无效。**参数错误不会发出任何请求**,所以拿到 `2` 时日志里一定没有任何痕迹。
136
+ 默认 `--json` 下 stdout 是服务端原样 JSON;显式 `--fields` 输出所选字段,`--all` 输出读取清单。退出码 `0` 表示读取/写入成功,`1` 表示请求、解码、文件写入失败或有界分页未完成,`2` 表示本地参数/路径预检失败(未发请求)。`complete=false` 必须作为未完成读取处理。
199
137
 
200
138
  追加成功返回那条事实,含它的 `seq`。`--idempotency-key` 重复提交同一个键时返回**已存在的那条事实**,HTTP 状态是 `200` 而不是 `201` —— 这不是错误,重试是安全的。
201
139
 
@@ -0,0 +1,50 @@
1
+ # 环境与地址
2
+
3
+
4
+ 每次调用服务前,必须先确认用户要操作的目标环境。在推进器拉起的 Run 里按下文「Run 内的目标环境」确定,不询问。其他情况下,用户没有明确说 `local`、本地、`dev`、开发环境或 `prod`、生产环境时,先询问:
5
+
6
+ ```text
7
+ 请指定目标环境:local(本地)、dev(开发环境)还是 prod(生产环境)?
8
+ ```
9
+
10
+ 环境一旦选定,本轮所有服务调用都必须保持在同一环境;不能因为连接失败就切换到另一环境,也不能把当前机器所在位置当成目标环境。
11
+
12
+ | 目标环境 | 集群外访问地址 | 对应环境集群内地址 |
13
+ | --- | --- | --- |
14
+ | `local` | `http://127.0.0.1:8090` | 不适用 |
15
+ | `dev` | `https://deepflowtask.dev.deepwisdomai.com` | `http://deepflowtask.deepflow.svc.cluster.local:8090` |
16
+ | `prod` | `https://deepflowtask.deepwisdomai.com` | `http://deepflowtask.deepflow.svc.cluster.local:8090` |
17
+
18
+ `prod` 指国内生产 `prod-cn`。集群内地址只在对应环境集群中访问;dev 与 prod 的 Service DNS 字符串相同,不能仅凭该字符串反查环境,也不能从本机直接访问它来代替生产入口。CLI 不会按环境名自动选址:本地调用须显式设置已选环境的地址;可以使用用户提供且已确认归属的 `--base-url`,地址与已选环境冲突时先澄清,连接失败不得改用其他环境。生产写操作仍只执行当前任务已授权的范围。
19
+
20
+ 本地操作 dev 时示例:
21
+
22
+ ```bash
23
+ df-task --base-url https://deepflowtask.dev.deepwisdomai.com facts "$DF_TASK_ID" --json
24
+ ```
25
+
26
+ ### Run 内的目标环境
27
+
28
+ `DF_TASK_ID`、`DF_RUN_ID`、`DF_TASK_ACTOR_KIND` 都非空时,你运行在推进器拉起的 Run 里。`DF_TASK_BASE_URL` 由部署注入,绑定当前 Run 所属的事实服务;沿用它访问本 Task,不重新询问环境、不根据同名 Service DNS 猜成 dev,也不因业务需求中出现“生产/开发”就重定向流程服务。业务项目的目标环境与 DeepFlow 流程服务的部署环境分别核对。
29
+
30
+ 注入地址缺失或与当前流程已确认的部署环境冲突时,停止相关调用并说明缺口;不回退 localhost、不自行改地址或伪造身份。不在 Run 里时,先由用户选择环境,再按上表显式选址;内部 DNS 需要已确认的集群访问上下文。
31
+
32
+ 先用 `GET /healthz` 验证选定地址,再执行读写操作。健康检查失败时报告该环境不可达,不得静默改用 localhost 或其他环境。
33
+
34
+ 下面的示例还用到两个变量,同样由 runtime 注入,同样不要自己编:`DF_TASK_ID` 是你这次执行所属的 Task,`DF_RUN_ID` 是你自己这个 Run 的标识(写事实时 payload 里的 `run_id` 填它)。如果它们是空的,说明这次执行不是由推进器兑现唤起起来的 —— 别猜一个填进去,写一条 `summary` 说明情况。
35
+
36
+ **在 Run 里一律加 `--json`。** 不加 `--json` 的输出是给人看的排版,字段随时会调整。
37
+
38
+ ```bash
39
+ # 按本轮需要选择事实类型(选择标准见「读日志」)
40
+ df-task facts "$DF_TASK_ID" --types user_message,artifact_published --json
41
+ df-task facts "$DF_TASK_ID" --types user_message,artifact_published --after 128 --limit 100 --json
42
+
43
+ # 追加一条事实:payload 写成 JSON 文件再传
44
+ # 注意 heredoc 的标记不加引号,加了引号 $DF_RUN_ID 就不会展开
45
+ cat > /tmp/artifact.json <<JSON
46
+ {"run_id":"$DF_RUN_ID","kind":"diff","ref":"abc1234def5678","title":"登录按钮溢出修复"}
47
+ JSON
48
+ df-task append "$DF_TASK_ID" --type artifact_published --file /tmp/artifact.json --json
49
+ ```
50
+
@@ -6,8 +6,8 @@
6
6
 
7
7
  | 类型 | 谁写 | 必填 | 可选 |
8
8
  | --- | --- | --- | --- |
9
- | `user_message` | 人 | `text` | `wake_policy`、`agent_ids` |
10
- | `external_result` | 外部系统 | `source`、`ref`、`result` | —— |
9
+ | `user_message` | 人或 bot | `text` | `wake_policy`、`agent_ids` |
10
+ | `external_result` | 外部系统或 bot | `source`、`ref`、`result` | —— |
11
11
 
12
12
  `wake_policy` 由产品端写入,缺失时按 `default` 处理。`passive` 表示多人对话里人和人之间的讨论,它**不触发**调度,被别的事件唤醒时也只能当作上下文,不能单独据此派活。`agent_ids` 只是人提到的 Agent,作为提示;派给谁仍由调度根据全局事实决定。
13
13
 
@@ -32,7 +32,7 @@
32
32
  | --- | --- | --- | --- |
33
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
- | `handoff_to_human` | `ask` | `assignee` | 交球和派活是同一条事实,区别只在 `ask` 的内容 |
35
+ | `handoff_to_human` | `ask` | `assignee`、`participation`、`task_assignee` | 交球和派活是同一条事实,区别只在 `ask` 的内容 |
36
36
  | `adjudication` | `subject`、`verdict` | `reason` | `subject` 是被裁决那条事实的**序号**(数字,不是字符串) |
37
37
  | `run_terminated` | `run_id`、`confirmed_status` | —— | 已经停完并确认之后记录的既成事实 |
38
38
 
@@ -48,3 +48,29 @@
48
48
  | `attempt_failed` | `watermark`、`reason` | `intent_seq` | 推进失败 |
49
49
 
50
50
  **「有 started 没有 accepted」是合法的中间状态**,含义是「正在启动,结果未知」。读日志时不要把它当成异常 —— 推进器会靠幂等键重投把它推进到 accepted。`run_kind` 只能是 `scheduler` 或 `task`;只有调度唤起带 `input_watermark`。
51
+
52
+ ## 请求实际人工参与
53
+
54
+ 公开 handoff 本身不等于邀请。平台支持时,scheduler_run 可用以下命令写入实际参与请求;账号须已由调用方核验,`ask` 向接收人说明具体协助事项和相关成果。
55
+
56
+ ```bash
57
+ # 邀请并定向通知;保留现有经办人
58
+ df-task handoff "$DF_TASK_ID" --participation ensure_member \
59
+ --assignee "$human_id" --ask "$handoff_text" --json
60
+ # 同时明确授予同一人主经办责任
61
+ df-task handoff "$DF_TASK_ID" --participation ensure_member \
62
+ --assignee "$human_id" --task-assignee "$assignment_json" \
63
+ --ask "$handoff_text" --json
64
+ ```
65
+
66
+ `assignment_json` 是 `{"kind":"human","id":"与 assignee 相同的真实账号"}`。省略该参数不改经办人,普通答复、补信息与审核不得因定向交接自动改派。通用 `append --type handoff_to_human --file <JSON>` 可提交同一协议:`ask`、`assignee`、`participation: "ensure_member"`,以及明确需要时的 `task_assignee`;不新增另一套操作语义。
67
+
68
+ 命令成功只确认请求事实已写入。读取 `external_result`,以 `source="human_participation"`、`ref="请求事实序号"` 关联回执;`details.request_seq` 应与该序号一致:
69
+
70
+ - `succeeded`:核对 `member_confirmed`、`member_id`、`notification_id`;请求分配时再核对 `task_assignee`,并读回当前成员与经办人。
71
+ - `failed`:保留错误码及部分成功字段。成员已加入或通知已生成,不代表分配也成功;不得把失败请求当作已交接全部责任。
72
+ - `unknown` 或尚无回执:结果未确认,后续核对同一请求;平台恢复会沿原请求兑现,不因暂未确认而重复邀请。后续真实成功回执可补充先前未知结果,按实际操作及当前状态判断。
73
+
74
+ 通知记录生成不证明人已阅读、回复或批准。邀请、经办人设置、人的实际处理和审核意见是不同事实;本协议不决定何时求助、选择谁或是否接受审核结果。
75
+
76
+ 创建身份从 `df-task get` 的不可变 `creator` 读取,不能从 owner、成员人数或任意 metadata 推断为 bot。未知身份保持未知。
@@ -0,0 +1,49 @@
1
+ # metadata 与软范围查询
2
+
3
+
4
+ Task 可以携带业务方自定义的 JSON metadata。基础服务只约定顶层的
5
+ `source`、`query_keys` 和 `data`:前两个用于可选的软范围查询,`data` 完全由业务方组织。
6
+ 例如:
7
+
8
+ ```bash
9
+ df-task create \
10
+ --title '订单分析' \
11
+ --metadata '{"source":"workbench","query_keys":["account:u123"],"data":{"project":"orders"}}' \
12
+ --json
13
+ ```
14
+
15
+ `--metadata` 必须是 JSON 对象;没有 metadata 时服务保存 `{}`,不会改变原有流程。创建、
16
+ 列表和单Task读取会返回 metadata。事实读取也支持同一范围过滤:
17
+
18
+ ```bash
19
+ SCOPE_SOURCE=workbench
20
+ SCOPE_KEY=account:u123
21
+
22
+ df-task ls \
23
+ --metadata-source "$SCOPE_SOURCE" \
24
+ --metadata-query-key "$SCOPE_KEY" \
25
+ --json
26
+ df-task get "$DF_TASK_ID" \
27
+ --metadata-source "$SCOPE_SOURCE" \
28
+ --metadata-query-key "$SCOPE_KEY" \
29
+ --json
30
+ df-task facts "$DF_TASK_ID" \
31
+ --metadata-source "$SCOPE_SOURCE" \
32
+ --metadata-query-key "$SCOPE_KEY" \
33
+ --json
34
+ df-task stream \
35
+ --metadata-source "$SCOPE_SOURCE" \
36
+ --metadata-query-key "$SCOPE_KEY" \
37
+ --json
38
+ ```
39
+
40
+ 查询规则:
41
+
42
+ - 同时提供 `source` 和 `query_key` 时,Task 必须匹配来源和查询标识;只提供其中一个时只按该条件过滤。`query_keys` 包含 `"*"` 的显式共享Task例外于这两个条件。
43
+ - `query_keys` 可以包含多个标识;`"*"` 表示显式共享,匹配任意查询范围。
44
+ - 不提供过滤参数仍然是历史上的全量查询。业务上下文存在时才携带过滤参数,不要把它当成权限边界。
45
+ - 先从Task响应的 metadata 读取 `source` 和 `query_keys`,再把当前业务上下文中的具体 key 传给后续查询;不要从 `data` 中猜测过滤字段。
46
+
47
+ metadata 只影响读请求的范围。`append`、`dispatch`、`wait`、`handoff` 和 `title` 仍按明确的
48
+ Task ID 操作,不支持 metadata 过滤参数;事实服务不会根据 metadata 做鉴权。
49
+
@@ -0,0 +1,9 @@
1
+ # CLI 读取契约
2
+
3
+ `--fields` 只在 `get/facts --json` 可用,默认 `--json` 不变。get 顶层可选 `id,title,description,description_version,metadata,created_at,updated_at`;facts 每条可选 `seq,type,actor_kind,actor_id,payload,idempotency_key,created_at`。metadata/payload 允许点分对象路径;无数组下标、表达式、通配符或父子路径重叠。不存在的路径省略,直接选中的 null 保留;数值不经过浮点转换。facts 的 task_id、next_after_seq 及其他信封字段保留。没有业务摘要、排序或正文裁剪。
4
+
5
+ `facts --all --output-file PATH --json` 可沿用 `--after/--types/--limit/--metadata-source/--metadata-query-key`。默认 page limit=200,max-pages=10,max-bytes=8388608;显式预算范围分别 1..100、1..67108864。字节预算按接纳前的 HTTP 响应计,超过预算的页整体不接纳,游标不跳过该页。页必须匹配 Task、严格递增,next_after_seq 必须等于该页末 seq;空页保留输入游标。
6
+
7
+ 清单字段:query(Task、地址、类型、过滤、起始游标、page limit)、complete、stop_reason、pages(接纳页数,含终止空页)、count、first_seq/last_seq(有事实时)、next_after_seq、type_counts、response_bytes、output_file、file_bytes、file_sha256。正文不会进入清单。文件包含 query、facts、complete、stop_reason、next_after_seq,权限 0600,原子发布且不覆盖。SHA 对应实际文件字节。
8
+
9
+ stop_reason:empty_page 表示本次见到空页;page_limit/byte_limit 表示预算耗尽;request_error/invalid_page 表示失败。除 empty_page 外 complete=false 且退出 1;本地参数/路径预检错误退出 2(未发请求)。本地写入失败不返回伪造的成功清单。显式续读须保持过滤条件,使用已返回的 next_after_seq 和新的文件名;先前文件不会被补写。complete 不承诺事务快照或不存在晚提交。
@@ -21,13 +21,13 @@ CLI 由 `@metagptx/deepflow-suite-cli` 统一安装,本 Skill 不携带 CLI
21
21
 
22
22
  环境一旦选定,本轮所有服务调用都必须保持在同一环境;不能因为连接失败就切换到另一环境,也不能把当前机器所在位置当成目标环境。
23
23
 
24
- | 目标环境 | 本地执行时的地址 | 在 dev-k8s 内执行时的地址 |
24
+ | 目标环境 | 集群外访问地址 | 对应环境集群内地址 |
25
25
  | --- | --- | --- |
26
26
  | `local` | `http://127.0.0.1:8081` | 不适用 |
27
27
  | `dev` | `https://deepflowtaskdriver.dev.deepwisdomai.com` | `http://deepflowtaskdriver.deepflow.svc.cluster.local:8081` |
28
- | `prod` | 尚未配置 | 尚未配置 |
28
+ | `prod` | `https://deepflowtaskdriver.deepwisdomai.com` | `http://deepflowtaskdriver.deepflow.svc.cluster.local:8081` |
29
29
 
30
- 地址列描述的是同一目标环境的两种访问方式:本地 CLI 使用 dev Ingress,集群内 Agent 使用 dev Service DNS。确认环境后,可以用用户明确提供的 `--base-url` 覆盖该环境的默认地址,但该地址必须属于已选环境;如果地址与环境冲突,先停止并澄清,不能用它绕过环境门禁。`prod` 目前没有地址,任何 prod 操作都必须停止并说明尚未接入。
30
+ `prod` 指国内生产 `prod-cn`。集群内地址只在对应环境集群中访问;dev 与 prod 的 Service DNS 字符串相同,不能仅凭该字符串反查环境,也不能从本机直接访问它来代替生产入口。CLI 不会按环境名自动选址:本地调用须显式设置已选环境的地址;可以使用用户提供且已确认归属的 `--base-url`,地址与已选环境冲突时先澄清,连接失败不得改用其他环境。生产写操作仍只执行当前任务已授权的范围。
31
31
 
32
32
  本地操作 dev 时示例:
33
33
 
@@ -15,6 +15,14 @@ CLI 由 `@metagptx/deepflow-suite-cli` 统一安装,本 Skill 不携带 CLI
15
15
 
16
16
  用户必须先明确目标环境:`local`、`dev` 或 `prod`。本轮不要因为连接失败自动切换环境。
17
17
 
18
+ | 环境 | Workbench 根地址 |
19
+ | --- | --- |
20
+ | `local` | `http://127.0.0.1:8793` |
21
+ | `dev` | `https://deepflowworkbench.dev.deepwisdomai.com` |
22
+ | `prod`(国内生产 `prod-cn`) | `https://deepflowworkbench.deepwisdomai.com` |
23
+
24
+ CLI 不会根据环境名自动切换地址。确认环境后显式设置 `DF_WORKBENCH_BASE_URL`;Token 与工作区也须属于该入口,不能复用 dev 身份去操作 prod。生产写操作只执行当前任务已授权的范围。
25
+
18
26
  CLI 配置:
19
27
 
20
28
  ```text
@@ -32,7 +40,11 @@ df-workbench config --json
32
40
 
33
41
  `me` 返回当前用户和可访问工作区;`config` 返回认证模式、当前工作区、Task 写入能力和 Workbench UI 能力。不要打印 Token、Cookie 或其他凭据。
34
42
 
35
- local 环境只允许从本机回环地址访问;OIDC 环境使用 Workbench personal token。Token 应通过公司内部配置或 Skill 平台提供,不在命令参数中传递。
43
+ local 环境只允许从本机回环地址访问,无需 Token;OIDC 环境使用 Workbench personal token。
44
+
45
+ Token 获取入口:使用公司账号登录目标环境 Workbench,打开「头像 → 个人设置 → 个人 Token → 创建 Token」,填写用途名称和到期时间。完整 Token 只在创建时显示一次,将其配置为 `DF_WORKBENCH_TOKEN`;`DF_WORKBENCH_BASE_URL` 使用同一环境的 Workbench 根地址。也可通过公司内部配置或 Skill 平台的个人凭据配置提供,不在命令参数中传递,不打印凭据。
46
+
47
+ 列表仅显示前缀和使用状态,无法找回完整 Token;遗失、到期或不再使用时,在同一入口撤销并按需新建。独立管理员账号不支持创建个人 Token,需使用公司账号登录。网页退出不会撤销 Token。
36
48
 
37
49
  ## 用户任务流程
38
50
 
package/manifest.json CHANGED
@@ -56,5 +56,6 @@
56
56
  "project": "deepflow-agent-runtime",
57
57
  "entry": "cli/df-agentrt.mjs"
58
58
  }
59
- ]
59
+ ],
60
+ "version": "0.3.2"
60
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metagptx/deepflow-suite-cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Agent-friendly installer and distribution CLI for the DeepFlow suite.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -1 +1 @@
1
- {"version":"0.3.0","project":"deepflow-agent-ctrl","sourceCommit":"e3aa5dee2eb31a7267e69f48127175deab664fe0","sourceDirty":false}
1
+ {"version":"0.3.2","project":"deepflow-agent-ctrl","sourceCommit":"dee1a88615b09bd979562902cebc631f81b1859b","sourceDirty":false}
@@ -1 +1 @@
1
- {"version":"0.3.0","project":"deepflow-agent-runtime","sourceCommit":"12624d320d032b65d8f4ed3fdd0193d33d95be7d","sourceDirty":false}
1
+ {"version":"0.3.2","project":"deepflow-agent-runtime","sourceCommit":"4492edf7279289b6ca1d29d3d2b444c0757f2b81","sourceDirty":false}
@@ -0,0 +1,44 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdtemp, open, link, rm } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+
5
+ const terminal = (status) => ['succeeded', 'failed', 'cancelled'].includes(status);
6
+ const statuses = ['pending', 'waiting_for_runtime', 'claimed', 'running', 'succeeded', 'failed', 'cancelled'];
7
+ export function resultView(inv, maxBytes) {
8
+ const view = { invocation_id: inv.id, session_id: inv.session_id, status: inv.status, terminal: terminal(inv.status), result_state: terminal(inv.status) ? 'missing' : 'pending', output_present: false, output: '', output_bytes: 0, output_returned_bytes: 0, output_truncated: false };
9
+ if (inv.finished_at) view.finished_at = inv.finished_at;
10
+ if (inv.error) view.error = inv.error;
11
+ const invalid = (message) => { view.result_state = 'invalid'; view.format_error = message; return { view, output: '' }; };
12
+ if (!statuses.includes(inv.status)) { view.result_state = 'invalid'; view.format_error = 'unknown invocation status'; }
13
+ if (!inv.result) return { view, output: '' };
14
+ let result;
15
+ try { result = JSON.parse(inv.result); } catch { return invalid('result is not a JSON object'); }
16
+ if (!result || typeof result !== 'object' || Array.isArray(result)) return invalid('result is not a JSON object');
17
+ for (const [field, target] of [['error', 'result_error'], ['failure_reason', 'failure_reason']]) {
18
+ if (!Object.hasOwn(result, field) || result[field] === null) continue;
19
+ if (typeof result[field] !== 'string') return invalid(`${field} is not a string`);
20
+ if (result[field]) view[target] = result[field];
21
+ }
22
+ if (!Object.hasOwn(result, 'output') || result.output === null) return { view, output: '' };
23
+ if (typeof result.output !== 'string') return invalid('output is not a string');
24
+ const bytes = Buffer.from(result.output);
25
+ view.output_present = true; view.output_bytes = bytes.length;
26
+ if (view.terminal) view.result_state = 'available';
27
+ view.output_sha256 = createHash('sha256').update(bytes).digest('hex');
28
+ let end = Math.min(maxBytes, bytes.length);
29
+ if (end < bytes.length) while (end > 0 && (bytes[end] & 0xc0) === 0x80) end--;
30
+ view.output = bytes.subarray(0, end).toString('utf8');
31
+ view.output_truncated = bytes.length > maxBytes;
32
+ view.output_returned_bytes = end;
33
+ return { view, output: result.output };
34
+ }
35
+
36
+ // Publish a private file atomically without replacing a concurrent destination.
37
+ export async function writeResultFile(destination, data) {
38
+ const directory = await mkdtemp(path.join(path.dirname(destination), '.invocation-result-'));
39
+ try {
40
+ const staging = path.join(directory, 'output'), file = await open(staging, 'wx', 0o600);
41
+ try { await file.writeFile(data); } finally { await file.close(); }
42
+ await link(staging, destination);
43
+ } finally { await rm(directory, { recursive: true, force: true }); }
44
+ }
@@ -1,9 +1,10 @@
1
- import { readFile } from 'node:fs/promises';
1
+ import { readFile, lstat } from 'node:fs/promises';
2
2
  import { spawn } from 'node:child_process';
3
3
  import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { setTimeout as delay } from 'node:timers/promises';
6
6
  import { usage } from './usage.mjs';
7
+ import { resultView, writeResultFile } from './result.mjs';
7
8
 
8
9
  const terminal = (status) => ['succeeded', 'failed', 'cancelled'].includes(status);
9
10
  const decode = (raw) => JSON.parse(raw, (_key, value, context) => typeof value === 'number' && !Number.isSafeInteger(value) && /^-?\d+$/.test(context.source) ? BigInt(context.source) : value);
@@ -49,11 +50,11 @@ export class RuntimeCLI {
49
50
  if (!addr) this.environment('MULTICA_SERVER_URL is not set, so there is no runtime API to talk to; this command only works inside a running task');
50
51
  return addr.replace(/\/+$/, '');
51
52
  }
52
- async request(method, route, body, { headers = {}, raw = false, stream = false, signal } = {}) {
53
+ async request(method, route, body, { headers = {}, raw = false, stream = false, signal, responseLimit = 0, timeout = false } = {}) {
53
54
  const addr = this.address();
54
55
  let response;
55
56
  try {
56
- response = await fetch(addr + route, { method, headers: { ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}), ...(stream ? { Accept: 'text/event-stream' } : {}), ...headers }, body: body === undefined ? undefined : typeof body === 'string' ? body : JSON.stringify(body), signal: signal || (!stream && this.self ? AbortSignal.timeout(30000) : undefined) });
57
+ response = await fetch(addr + route, { method, headers: { ...(body !== undefined ? { 'Content-Type': 'application/json' } : {}), ...(stream ? { Accept: 'text/event-stream' } : {}), ...headers }, body: body === undefined ? undefined : typeof body === 'string' ? body : JSON.stringify(body), signal: signal || (!stream && (this.self || timeout) ? AbortSignal.timeout(30000) : undefined) });
57
58
  } catch (error) { throw new Failure(`cannot reach the runtime API at ${addr}: ${error.message}`); }
58
59
  if (!response.ok) {
59
60
  const text = (await response.text()).slice(0, 65536); let error;
@@ -62,7 +63,16 @@ export class RuntimeCLI {
62
63
  }
63
64
  if (stream) return response.body;
64
65
  if (method === 'DELETE') { await response.body?.cancel(); return; }
65
- const text = await response.text();
66
+ let text;
67
+ if (responseLimit) {
68
+ const chunks = []; let bytes = 0;
69
+ for await (const chunk of response.body) {
70
+ bytes += chunk.length;
71
+ if (bytes > responseLimit) throw new Failure(`response exceeds ${responseLimit} bytes`);
72
+ chunks.push(chunk);
73
+ }
74
+ text = Buffer.concat(chunks).toString('utf8');
75
+ } else text = await response.text();
66
76
  try { const value = decode(text); return raw ? text.trim() : value; } catch (error) { throw new Failure(`decode response: ${error.message}`); }
67
77
  }
68
78
  async ownSession() {
@@ -86,6 +96,27 @@ export class RuntimeCLI {
86
96
  return encodeURIComponent(rest[0]);
87
97
  }
88
98
  async getJSON(route) { this.stdout.write(await this.request('GET', route, undefined, { raw: true }) + '\n'); }
99
+ async invocationCmd(command, args) {
100
+ const form = `usage: ${this.name} invocation result <invocation-id> [--json] [--max-bytes N] [--output-file PATH]`;
101
+ if (command !== 'result') this.fail(form);
102
+ if (args.includes('--help') || args.includes('-h')) { this.stdout.write(form + '\n'); return; }
103
+ const { v, rest } = flags(args, ['max-bytes', 'output-file'], ['json']);
104
+ numeric(v['max-bytes'], 'max-bytes', 1n << 20n);
105
+ const maxBytes = Number(v['max-bytes'] ?? 4096);
106
+ if (rest.length !== 1 || !rest[0].trim() || maxBytes < 1) this.fail(form);
107
+ let destination;
108
+ if ('output-file' in v) {
109
+ if (!v['output-file'].trim()) this.fail('--output-file must not be empty');
110
+ destination = path.resolve(v['output-file']);
111
+ try { await lstat(destination); this.fail(`output file already exists: ${destination}`); }
112
+ catch (error) { if (error.code !== 'ENOENT') this.fail(error.message); }
113
+ }
114
+ const inv = await this.request('GET', `/v1/invocations/${encodeURIComponent(rest[0])}`, undefined, { responseLimit: 64 << 20, timeout: true });
115
+ if (!inv || inv.id !== rest[0] || typeof inv.session_id !== 'string' || !inv.session_id || typeof inv.status !== 'string' || !inv.status) throw new Failure('invalid invocation identity or status in response');
116
+ const { view, output } = resultView(inv, maxBytes);
117
+ if (destination && view.output_present) { await writeResultFile(destination, output); view.output_file = destination; }
118
+ this.stdout.write(JSON.stringify(view, null, v.json ? undefined : 2) + '\n');
119
+ }
89
120
  printSession(s) {
90
121
  const rows = [['id', s.id], ['provider', s.provider], ['status', s.status]];
91
122
  if (s.client_ref) rows.push(['client_ref', s.client_ref]);
@@ -301,6 +332,7 @@ export class RuntimeCLI {
301
332
  }
302
333
  const [group, command, ...rest] = args;
303
334
  if (group === 'events') await this.events(args.slice(1));
335
+ else if (group === 'invocation') await this.invocationCmd(command, rest);
304
336
  else if (['session', 'repo', 'msg', 'ops'].includes(group)) {
305
337
  if (!command) this.groupUsage(group);
306
338
  else if (group === 'session') await this.sessionCmd(command, rest);
@@ -1,13 +1,13 @@
1
1
  export const usage = {
2
2
  "df-agentrt": {
3
- "": "df-agentrt -- DeepFlow Agent Runtime, from inside a task\n\nTHIS SESSION\n df-agentrt session info Which session this is:\n provider, model, working\n directory, repositories,\n and whether the\n conversation history\n survived into this turn.\n df-agentrt session turns [--session ID] [--json] Every turn; defaults to\n this session.\n df-agentrt events [--session ID] [--after N] The message stream. Also\n --limit N, --raw and\n --follow.\n df-agentrt repo list The repositories this\n session may check out.\n df-agentrt repo search <name> Find an application and\n the repository its code\n lives in.\n df-agentrt repo add <url|code> ... Make another repository\n available and check it out\n now. Prints the absolute\n path of each checkout.\n df-agentrt msg cancel [invocation-id] Ask a turn to stop;\n defaults to the current\n turn.\n\nOTHER SESSIONS\n df-agentrt session ls Sessions on this runtime.\n df-agentrt session get <id> [--json] One session in full.\n df-agentrt session create ... Start a new session. It\n runs an agent and costs\n money, and it does not end\n by itself.\n df-agentrt msg send <id> <text> Queue a turn on another\n session. Same caveat.\n\nRUNTIME\n df-agentrt ops runtimes [--json] Which execution engines\n are registered and alive.\n df-agentrt ops orphans Where the records and the\n world have drifted apart.\n\nRun `df-agentrt <group>` for the flags of one group.\n",
3
+ "": "df-agentrt -- DeepFlow Agent Runtime, from inside a task\n\nTHIS SESSION\n df-agentrt session info Which session this is:\n provider, model, working\n directory, repositories,\n and whether the\n conversation history\n survived into this turn.\n df-agentrt session turns [--session ID] [--json] Every turn; defaults to\n this session.\n df-agentrt events [--session ID] [--after N] The message stream. Also\n --limit N, --raw and\n --follow.\n df-agentrt repo list The repositories this\n session may check out.\n df-agentrt repo search <name> Find an application and\n the repository its code\n lives in.\n df-agentrt repo add <url|code> ... Make another repository\n available and check it out\n now. Prints the absolute\n path of each checkout.\n df-agentrt msg cancel [invocation-id] Ask a turn to stop;\n defaults to the current\n turn.\n\nOTHER SESSIONS\n df-agentrt invocation result <id> [--json] Read a bounded final result.\n --output-file saves full text.\n df-agentrt session ls Sessions on this runtime.\n df-agentrt session get <id> [--json] One session in full.\n df-agentrt session create ... Start a new session. It\n runs an agent and costs\n money, and it does not end\n by itself.\n df-agentrt msg send <id> <text> Queue a turn on another\n session. Same caveat.\n\nRUNTIME\n df-agentrt ops runtimes [--json] Which execution engines\n are registered and alive.\n df-agentrt ops orphans Where the records and the\n world have drifted apart.\n\nRun `df-agentrt <group>` for the flags of one group.\n",
4
4
  "session": "df-agentrt session <command>\n\n info This session. Includes whether the conversation history\n carried over into this turn -- if it did not, you have the\n same files but none of what was said before.\n turns [--session ID] [--json]\n Every turn; defaults to this session, oldest first.\n ls Sessions on this runtime.\n get <id> [--json] [--metadata-source S] [--metadata-query-key K]\n Another session in full.\n create --provider P [--model M] [--thinking-level L] [--instructions S]\n [--skill S ...] [--repo R ...] [--client-ref REF] [--mcp-config FILE]\n [--metadata JSON]\n [--idempotency-key KEY]\n Start a new session. It runs an agent and costs money, and\n nothing ends it for you.\n release <id> Stop a session accepting turns and ask for its working\n directory back.\n rm <id> Delete a session's record, message stream included.\n\nEverything about a session except its repository set is fixed at creation.\nFor repositories see `df-agentrt repo` -- those can be appended afterwards.\n",
5
5
  "repo": "df-agentrt repo <command>\n\n list The repositories this session may check out.\n search <name> Find an application and the repository its code\n lives in. Use this when you know what a thing is\n called but not where its code is.\n add <url|app-code> ... Make another repository available to this session\n [--ref REF] and check it out now. Prints the absolute path of\n each checkout on stdout; nothing else to run\n afterwards. --ref takes one repository only.\n\nAn argument containing \"://\" or \"@\" is treated as a clone url and stored\nverbatim; anything else is treated as an application code and looked up for\nyou. A code can never contain either, so the two cannot be confused.\n\nAppend-only: there is no remove. Removing a repository you are working inside\nwould break this turn.\n",
6
6
  "msg": "df-agentrt msg <command>\n\n send <session-id> <text> [--follow] [--idempotency-key KEY] [--raw]\n Queue a turn on another session. That session runs an agent and\n costs money; it is not this one. --follow waits for the turn to\n finish, which blocks you for as long as it runs.\n cancel [<turn-id>]\n Ask a turn to stop. With no argument, the turn you are executing\n right now -- you will be asked to wind down, not killed outright.\n",
7
7
  "ops": "df-agentrt ops <command>\n\n runtimes [--json] Which daemons have registered, and which are still alive.\n orphans [--fix] Where the records and the world have drifted apart.\n Reports only unless --fix is given; --fix fails stranded\n turns on other sessions and cannot be undone.\n"
8
8
  },
9
9
  "agentrt": {
10
- "": "agentrt -- DeepFlow Agent Runtime CLI\n\nUSAGE\n agentrt <group> <command> [flags]\n\nGROUPS\n session info | create | get | ls | turns | release | rm\n repo ls | add | search\n msg send | cancel\n events events <session> [--after N]\n ops runtimes | orphans\n\nENVIRONMENT\n AGENTRT_ADDR Base URL of the runtime API (default http://127.0.0.1:8141)\n\nRun `agentrt <group>` for that group's commands.\n",
10
+ "": "agentrt -- DeepFlow Agent Runtime CLI\n\nUSAGE\n agentrt <group> <command> [flags]\n\nGROUPS\n session info | create | get | ls | turns | release | rm\n invocation result <id> [--json] [--max-bytes N] [--output-file PATH]\n repo ls | add | search\n msg send | cancel\n events events <session> [--after N]\n ops runtimes | orphans\n\nENVIRONMENT\n AGENTRT_ADDR Base URL of the runtime API (default http://127.0.0.1:8141)\n\nRun `agentrt <group>` for that group's commands.\n",
11
11
  "session": "agentrt session <command>\n\n info <session-id> The same view as get. Named for the agent-side CLI,\n where it is the command for \"which session am I in\".\n create --provider P [--model M] [--thinking-level L] [--instructions S]\n [--skill S ...] [--repo R ...] [--client-ref REF] [--mcp-config FILE]\n [--idempotency-key KEY]\n get <session-id> [--json] [--metadata-source S] [--metadata-query-key K]\n\t ls [--status active|released] [--limit N] [--metadata-source S] [--metadata-query-key K]\n turns <session-id> [--json] (or --session <session-id>)\n Every turn of one session, oldest first -- the same\n timeline the web UI draws, from the same endpoint.\n release <session-id> Stop accepting turns; Phase 1 keeps the Worker workspace.\n Events and active workspace reads stay available afterwards.\n\t rm <session-id> Delete the record. Workspace archive is not a Phase 1 operation.\n\nEverything here except the repository set is fixed when the session is created.\nFor repositories, see `agentrt repo` -- they can be appended to afterwards.\n",
12
12
  "repo": "agentrt repo <command>\n\n ls <session-id> Repositories this session may check out.\n add <session-id> <url|app-code> ... Append repositories. Takes effect on the\n next checkout, including mid-turn.\n [--ref REF] Branch, tag or commit. One repository only.\n search <name> Find an application and its repository in\n the registry.\n\nAn argument containing \"://\" or \"@\" is taken as a clone URL and stored\nverbatim; anything else is taken as an application code and resolved against\nthe registry. A code can never contain either, so the two cannot be confused.\n\nAppend-only: there is no remove. Removing a repository the agent is working\ninside would break the turn in flight.\n",
13
13
  "msg": "agentrt msg <command>\n\n send <session-id> <text> [--follow] [--idempotency-key KEY] [--raw]\n Queue one turn. --follow attaches to the event stream and waits for\n the turn to finish, exiting non-zero if it failed.\n cancel <invocation-id>\n Ask for a running turn to stop. Returns as soon as the request is\n recorded, which is before the agent has actually wound down.\n",