@metagptx/deepflow-suite-cli 0.2.10 → 0.3.0
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 +44 -50
- package/bin/deepflow-suite.js +4 -4
- package/bundle/deepflow-release.json +21 -91
- package/bundle/skills/deepflow-suite/SKILL.md +7 -1
- package/bundle/skills/df-agentctrl/SKILL.md +92 -10
- package/bundle/skills/df-agentrt/SKILL.md +65 -14
- package/bundle/skills/df-agentrt-repo/SKILL.md +4 -0
- package/bundle/skills/df-task/SKILL.md +98 -26
- package/bundle/skills/df-taskdriver/SKILL.md +8 -4
- package/bundle/skills/df-workbench/SKILL.md +5 -9
- package/manifest.json +12 -30
- package/package.json +8 -17
- package/runtime/df-agentctrl/commands.mjs +93 -0
- package/runtime/df-agentctrl/df-agentctrl.mjs +119 -0
- package/runtime/df-agentctrl/release.json +1 -0
- package/runtime/df-agentrt/agentrt.mjs +3 -0
- package/runtime/df-agentrt/df-agentrt.mjs +3 -0
- package/runtime/df-agentrt/release.json +1 -0
- package/runtime/df-agentrt/runtime.mjs +326 -0
- package/runtime/df-agentrt/usage.mjs +16 -0
- package/runtime/df-task/commands.mjs +181 -0
- package/runtime/df-task/df-task.mjs +155 -0
- package/runtime/df-task/protocol.mjs +3 -0
- package/runtime/df-task/release.json +1 -0
- package/runtime/df-taskdriver/df-taskdriver.mjs +131 -0
- package/runtime/df-taskdriver/release.json +1 -0
- package/runtime/{df-workbench.mjs → df-workbench/df-workbench.mjs} +17 -175
- package/runtime/df-workbench/release.json +1 -0
- package/scripts/build-package.mjs +29 -25
- package/src/bundle.mjs +4 -1
- package/src/cli-install.mjs +35 -125
- package/src/commands/doctor.mjs +2 -0
- package/src/linker.mjs +2 -2
- package/src/manifest.mjs +7 -13
- package/src/package-cli.mjs +44 -60
- package/bin/df-agentctrl.js +0 -10
- package/bin/df-agentrt.js +0 -10
- package/bin/df-task.js +0 -10
- package/bin/df-taskdriver.js +0 -10
- package/bin/df-workbench.js +0 -10
- package/bundle/skills/deepflow-suite/manifest.yaml +0 -20
- package/bundle/skills/df-agentctrl/references/environment.md +0 -40
- package/bundle/skills/df-agentctrl/references/query-scope.md +0 -50
- package/bundle/skills/df-agentrt/references/environment.md +0 -29
- package/bundle/skills/df-agentrt/references/invocation-result.md +0 -11
- package/bundle/skills/df-agentrt/references/query-scope.md +0 -30
- package/bundle/skills/df-agentrt/references/session-create.md +0 -14
- package/bundle/skills/df-task/references/environment.md +0 -50
- package/bundle/skills/df-task/references/query-scope.md +0 -49
- package/bundle/skills/df-task/references/reading.md +0 -9
- package/bundle/skills/df-task/skill_test.go +0 -201
- package/bundle/skills/df-workbench/manifest.yaml +0 -5
- package/scripts/build-runtime-packages.mjs +0 -38
- package/skills/deepflow-suite/SKILL.md +0 -81
- package/skills/deepflow-suite/manifest.yaml +0 -20
- package/src/cli-launcher.mjs +0 -71
- package/src/runtime-build.mjs +0 -193
- package/src/runtime-package.mjs +0 -31
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
package skill
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"encoding/json"
|
|
5
|
-
"os"
|
|
6
|
-
"path/filepath"
|
|
7
|
-
"strings"
|
|
8
|
-
"testing"
|
|
9
|
-
|
|
10
|
-
"github.com/deepflow/deepflow-task/internal/authz"
|
|
11
|
-
"github.com/deepflow/deepflow-task/internal/model"
|
|
12
|
-
"github.com/deepflow/deepflow-task/internal/validate"
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
func readFile(t *testing.T, path string) string {
|
|
16
|
-
t.Helper()
|
|
17
|
-
data, err := os.ReadFile(path)
|
|
18
|
-
if err != nil {
|
|
19
|
-
t.Fatal(err)
|
|
20
|
-
}
|
|
21
|
-
return string(data)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
func TestSkillHasFrontmatter(t *testing.T) {
|
|
25
|
-
content := readFile(t, "SKILL.md")
|
|
26
|
-
if !strings.HasPrefix(content, "---\n") {
|
|
27
|
-
t.Fatal("SKILL.md must start with YAML frontmatter")
|
|
28
|
-
}
|
|
29
|
-
frontmatter, _, found := strings.Cut(strings.TrimPrefix(content, "---\n"), "\n---")
|
|
30
|
-
if !found {
|
|
31
|
-
t.Fatal("frontmatter is not closed")
|
|
32
|
-
}
|
|
33
|
-
for _, key := range []string{"name:", "description:"} {
|
|
34
|
-
if !strings.Contains(frontmatter, key) {
|
|
35
|
-
t.Errorf("frontmatter is missing %s", key)
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
if !strings.Contains(frontmatter, "name: df-task") {
|
|
39
|
-
t.Errorf("frontmatter name must be df-task, got %q", frontmatter)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
func TestSkillReferenceDocumentsEveryFactType(t *testing.T) {
|
|
44
|
-
// **十六种全要,不是只要智能体写得了的那九种。** 这份 reference 的作用是让智能体
|
|
45
|
-
// 读日志时认得每一条事实 —— 只遍历两个 Run 身份的可写集合,把「## 外部输入」
|
|
46
|
-
// 或「## 推进器写的」整张表删掉照样全绿,而 `external_result` 与 `invocation_ended`
|
|
47
|
-
// 是三条触发事实里的两条,认不出它们,读日志就读漏了最关键的两种。
|
|
48
|
-
reference := readFile(t, filepath.Join("references", "fact-types.md"))
|
|
49
|
-
for _, factType := range model.FactTypes() {
|
|
50
|
-
if !strings.Contains(reference, string(factType)) {
|
|
51
|
-
t.Errorf("references/fact-types.md 没有记 %s", factType)
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
// 「一共十六种」这个数字也要钉住。不钉的话,将来加第十六种类型时 Task 10 的
|
|
55
|
-
// `len(payloadSchemas) != len(model.FactTypes())` 会红,而这份文档会悄悄过期。
|
|
56
|
-
// **两个条件要各给自己的报错。** 合成一句 `if A || B` 的话,「有人把文档里那句话
|
|
57
|
-
// 删了而类型数还是 15」这一路会红出「文档写的是十六种,而 FactTypes() 有 15 种」
|
|
58
|
-
// —— 一句不含任何矛盾的话,看的人不知道该改文档还是改代码。而这一路恰好是最
|
|
59
|
-
// 常见的那一路:改文档比加第十六种类型常见得多。
|
|
60
|
-
if len(model.FactTypes()) != 16 {
|
|
61
|
-
t.Errorf("model.FactTypes() 有 %d 种,而 fact-types.md 与本断言都按十六种写的 —— 加了新类型要同时改这两处", len(model.FactTypes()))
|
|
62
|
-
}
|
|
63
|
-
if !strings.Contains(reference, "一共十六种事实") {
|
|
64
|
-
t.Error("fact-types.md 里找不到「一共十六种事实」这句 —— 这个数字是文档过期的唯一机械信号,不要删")
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
func TestSkillCommandTablesMatchTheAuthorizationMatrix(t *testing.T) {
|
|
69
|
-
// 两个方向都要验。只验「不该出现的没出现」,那么往调度一节里插一行
|
|
70
|
-
// `--type run_outcome` 照样全绿,而任务 Run 读到它就会去写,然后拿一个 403;
|
|
71
|
-
// 只验「该出现的出现了」,那么删掉一整行命令也照样全绿,而 Agent 在 Run 里
|
|
72
|
-
// 只有这份文档可读 —— 少一行,那条事实它就永远不会写。
|
|
73
|
-
content := readFile(t, "SKILL.md")
|
|
74
|
-
sections := map[model.ActorKind]string{
|
|
75
|
-
model.ActorTaskRun: "## 任务 Run 可以写什么",
|
|
76
|
-
model.ActorSchedulerRun: "## 调度 Run 可以写什么",
|
|
77
|
-
}
|
|
78
|
-
for kind, heading := range sections {
|
|
79
|
-
// strings.Cut 返回三个值:before、after、found。这里要的是标题**之后**那段,
|
|
80
|
-
// 所以第一个返回值丢掉 —— 接成两个变量编译就过不去。
|
|
81
|
-
_, afterHeading, found := strings.Cut(content, heading)
|
|
82
|
-
if !found {
|
|
83
|
-
t.Fatalf("SKILL.md is missing the %q section", heading)
|
|
84
|
-
}
|
|
85
|
-
// 截到下一个 `## ` 为止。两节各自只谈自己那个身份,所以「职责边界」一节里
|
|
86
|
-
// 那句「写一条 summary 或 help_requested 说明情况」不会被算进调度一节。
|
|
87
|
-
section, _, _ := strings.Cut(afterHeading, "\n## ")
|
|
88
|
-
for _, factType := range model.FactTypes() {
|
|
89
|
-
// 正向只认表格那一列的形状 `| ` + 类型名 + `` ` ``。用裸的 Contains 会被
|
|
90
|
-
// 同一节的正文喂饱 —— 「`run_outcome` 只是你的主张」「读日志时你看到的是
|
|
91
|
-
// `dispatch_intent`」这些句子里都有类型名,于是删掉对应那行命令照样全绿,
|
|
92
|
-
// 而 Agent 在 Run 里只有这份文档可读,少一行那条事实它就永远不会写。
|
|
93
|
-
inTable := strings.Contains(section, "| `"+string(factType)+"`")
|
|
94
|
-
// 反向要宽:正文里出现一个写不了的类型,同样会把 Agent 带去拿 403。
|
|
95
|
-
mentioned := strings.Contains(section, string(factType))
|
|
96
|
-
switch {
|
|
97
|
-
case authz.CanWrite(kind, factType) && !inTable:
|
|
98
|
-
t.Errorf("%s 的命令表里没有 %s,而这个身份写得了它", heading, factType)
|
|
99
|
-
case !authz.CanWrite(kind, factType) && mentioned:
|
|
100
|
-
t.Errorf("%s 一节提了 %s,而这个身份写它会拿 403", heading, factType)
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// 上面那两节里没有推进器的身份,所以唤起生命周期靠这一条兜:整份文档里都不许
|
|
105
|
-
// 出现「让 Agent 用 append 写它」的样子。
|
|
106
|
-
for _, factType := range authz.WritableBy(model.ActorDriver) {
|
|
107
|
-
if strings.Contains(content, string(factType)+" --file") {
|
|
108
|
-
t.Errorf("SKILL.md shows an agent writing %s; only the driver writes the invocation lifecycle", factType)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
func TestSkillStatesTheEnvironmentContract(t *testing.T) {
|
|
114
|
-
content := readFile(t, "SKILL.md")
|
|
115
|
-
// DF_TASK_ID 和 DF_RUN_ID 也在这份清单里:SKILL.md 的示例命令用了它们,
|
|
116
|
-
// 而它们由 runtime 注入、不由本服务定义。示例里出现却没说明来源,
|
|
117
|
-
// Agent 就会以为要自己编一个。
|
|
118
|
-
for _, needle := range []string{"DF_TASK_BASE_URL", "DF_TASK_ACTOR_KIND", "DF_TASK_ACTOR_ID", "DF_TASK_ID", "DF_RUN_ID", "--json"} {
|
|
119
|
-
if !strings.Contains(content, needle) {
|
|
120
|
-
t.Errorf("SKILL.md does not mention %s", needle)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// 推进器拉起的 Run 里没有人可问。环境门禁若在 Run 内也要求询问,调度 Agent
|
|
126
|
-
// 每一轮都会停下不读事实,而推进器照常记下执行结束——多轮执行就此静默断掉。
|
|
127
|
-
func TestSkillResolvesTheRunEnvironmentWithoutAsking(t *testing.T) {
|
|
128
|
-
content := readFile(t, "SKILL.md")
|
|
129
|
-
for _, needle := range []string{"### Run 内的目标环境", "`DF_TASK_BASE_URL` 由部署注入", "不根据同名 Service DNS 猜成 dev", "不询问"} {
|
|
130
|
-
if !strings.Contains(content, needle) {
|
|
131
|
-
t.Errorf("SKILL.md does not state %q", needle)
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
func TestSkillStatesFactsAreAppendOnly(t *testing.T) {
|
|
137
|
-
content := readFile(t, "SKILL.md")
|
|
138
|
-
if !strings.Contains(content, "只追加") {
|
|
139
|
-
t.Fatal("SKILL.md must state that facts are append-only")
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
func TestPayloadExamplesPassServerValidation(t *testing.T) {
|
|
144
|
-
entries, err := os.ReadDir(filepath.Join("assets", "payload-examples"))
|
|
145
|
-
if err != nil {
|
|
146
|
-
t.Fatal(err)
|
|
147
|
-
}
|
|
148
|
-
if len(entries) == 0 {
|
|
149
|
-
t.Fatal("there must be at least one payload example")
|
|
150
|
-
}
|
|
151
|
-
checked := 0
|
|
152
|
-
for _, entry := range entries {
|
|
153
|
-
// 只看 .json。这台机器上到处都是 .DS_Store,不过滤的话它会被当成
|
|
154
|
-
// 一个叫 ".DS_Store" 的事实类型,测试在一个与代码无关的原因上变红。
|
|
155
|
-
if !strings.HasSuffix(entry.Name(), ".json") {
|
|
156
|
-
continue
|
|
157
|
-
}
|
|
158
|
-
checked++
|
|
159
|
-
name := strings.TrimSuffix(entry.Name(), ".json")
|
|
160
|
-
factType := model.FactType(name)
|
|
161
|
-
if !model.KnownFactType(factType) {
|
|
162
|
-
t.Errorf("%s is not a fact type", entry.Name())
|
|
163
|
-
continue
|
|
164
|
-
}
|
|
165
|
-
raw := readFile(t, filepath.Join("assets", "payload-examples", entry.Name()))
|
|
166
|
-
if !json.Valid([]byte(raw)) {
|
|
167
|
-
t.Errorf("%s is not valid JSON", entry.Name())
|
|
168
|
-
continue
|
|
169
|
-
}
|
|
170
|
-
if err := validate.Payload(factType, []byte(raw)); err != nil {
|
|
171
|
-
t.Errorf("%s would be rejected by the server: %v", entry.Name(), err)
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if checked == 0 {
|
|
175
|
-
t.Fatal("the directory has entries but none of them is a .json example")
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
func TestEveryHandWrittenPayloadHasAnExample(t *testing.T) {
|
|
180
|
-
// 凡是 SKILL.md 让智能体自己写 payload 的事实类型,都要有一份能抄的样例。
|
|
181
|
-
// 语法糖那三种(dispatch / wait / handoff)的字段由 CLI 填,不需要样例。
|
|
182
|
-
// **这里两个身份都要走。** 只走任务 Run 的话,调度 Run 那两种手写类型就露在外面:
|
|
183
|
-
// `adjudication` 的 `subject` 是全服务唯一一个数字字段,没样例的调度智能体会写成
|
|
184
|
-
// 字符串然后吃一个 422;而日志只追加,它既改不了那条,也没有别的地方能学对。
|
|
185
|
-
sugar := map[model.FactType]bool{
|
|
186
|
-
model.FactDispatchIntent: true,
|
|
187
|
-
model.FactWaitingReason: true,
|
|
188
|
-
model.FactHandoffToHuman: true,
|
|
189
|
-
}
|
|
190
|
-
for _, kind := range []model.ActorKind{model.ActorTaskRun, model.ActorSchedulerRun} {
|
|
191
|
-
for _, factType := range authz.WritableBy(kind) {
|
|
192
|
-
if sugar[factType] {
|
|
193
|
-
continue
|
|
194
|
-
}
|
|
195
|
-
path := filepath.Join("assets", "payload-examples", string(factType)+".json")
|
|
196
|
-
if _, err := os.Stat(path); err != nil {
|
|
197
|
-
t.Errorf("%s 缺样例,而 %s 要手写这份 payload:%v", factType, kind, err)
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { readFile } from "node:fs/promises";
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
buildRuntimePackages,
|
|
9
|
-
verifyRuntimePackage,
|
|
10
|
-
} from "../src/runtime-build.mjs";
|
|
11
|
-
import { supportedRuntimePlatforms } from "../src/runtime-package.mjs";
|
|
12
|
-
|
|
13
|
-
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
14
|
-
const workspaceRoot = path.resolve(packageRoot, "../..");
|
|
15
|
-
|
|
16
|
-
function option(argv, name, fallback) {
|
|
17
|
-
const index = argv.indexOf(name);
|
|
18
|
-
return index === -1 ? fallback : argv[index + 1];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const argv = process.argv.slice(2);
|
|
22
|
-
const outputRoot = path.resolve(option(argv, "--output", path.join(packageRoot, "platform-packages")));
|
|
23
|
-
const version = option(argv, "--version", JSON.parse(await readFile(path.join(packageRoot, "package.json"), "utf8")).version);
|
|
24
|
-
const check = argv.includes("--check");
|
|
25
|
-
|
|
26
|
-
if (check) {
|
|
27
|
-
for (const target of supportedRuntimePlatforms()) {
|
|
28
|
-
await verifyRuntimePackage({
|
|
29
|
-
packageRoot: path.join(outputRoot, target.package.replace(/^@[^/]+\//, "")),
|
|
30
|
-
target,
|
|
31
|
-
expectedVersion: version,
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
console.log(JSON.stringify({ ok: true, outputRoot, version, packages: supportedRuntimePlatforms().map((target) => target.package) }, null, 2));
|
|
35
|
-
} else {
|
|
36
|
-
const result = await buildRuntimePackages({ workspaceRoot, outputRoot, version });
|
|
37
|
-
console.log(JSON.stringify({ ok: true, outputRoot, version, packages: result.packages }, null, 2));
|
|
38
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: deepflow-suite
|
|
3
|
-
description: DeepFlow 统一入口。常规任务默认使用 df-workbench;只有用户明确要求或 Workbench 事实无法解释阻塞时,才按顺序检查 Task、Task Driver、Agent Control 和 Agent Runtime。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# DeepFlow Suite
|
|
7
|
-
|
|
8
|
-
这是 DeepFlow 的统一用户和开发者入口。它开放完整的 DeepFlow 能力,但不默认调用整条链路:普通任务先走 `df-workbench`,只有确实需要解释推进、Agent 或 Runtime 问题时才升级到其他项目 CLI。
|
|
9
|
-
|
|
10
|
-
## 依赖
|
|
11
|
-
|
|
12
|
-
本 Skill 依赖以下项目级 Skill 和 CLI:
|
|
13
|
-
|
|
14
|
-
- `df-workbench`:用户身份、工作区、Workbench 配置和任务事实;
|
|
15
|
-
- `df-task`:Task 原始事实和事实落库核对;
|
|
16
|
-
- `df-taskdriver`:任务推进和唤起兑现诊断;
|
|
17
|
-
- `df-agentctrl`:Agent 定义、版本和可分配目录;
|
|
18
|
-
- `df-agentrt`:Session、Invocation、transcript 和运行现场。
|
|
19
|
-
- `df-agentrt-repo`:在 Runtime 会话中发现和追加代码仓库。
|
|
20
|
-
|
|
21
|
-
这些依赖由 `deepflow-suite` 安装器统一安装。不要因为某个依赖缺失就自行复制 Skill、克隆仓库或切换安装源。
|
|
22
|
-
|
|
23
|
-
## 环境门禁
|
|
24
|
-
|
|
25
|
-
用户没有明确说 `local`、本地、`dev`、开发环境或 `prod`、生产环境时,先询问:
|
|
26
|
-
|
|
27
|
-
```text
|
|
28
|
-
请指定目标环境:local(本地)、dev(开发环境)还是 prod(生产环境)?
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
环境一旦选定,本轮所有 Workbench、Task、Driver、Agent Control 和 Runtime 请求保持在同一环境。连接失败不能静默切换环境。
|
|
32
|
-
|
|
33
|
-
`prod` 当前指国内生产 `prod-cn`。各服务的地址和访问方式以对应服务 Skill 为准;先选择环境,再显式设置该服务的地址变量或参数,CLI 不会根据环境名自动选址。普通任务先设置 `DF_WORKBENCH_BASE_URL` 并走 Workbench;开发者只在需要时使用其他服务入口。Run 内使用服务 Skill 规定的部署注入上下文,不根据重复的 Service DNS 或业务项目目标环境切换流程服务。
|
|
34
|
-
|
|
35
|
-
## 默认路由
|
|
36
|
-
|
|
37
|
-
### 普通任务
|
|
38
|
-
|
|
39
|
-
创建任务、继续对话、查看任务和读取可见进度,只调用 `df-workbench`:
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
df-workbench me --json
|
|
43
|
-
df-workbench config --json
|
|
44
|
-
df-workbench task create --message '<用户原始请求>' --json
|
|
45
|
-
df-workbench task send <task-id> --message '<补充内容>' --json
|
|
46
|
-
df-workbench task get <task-id> --json
|
|
47
|
-
df-workbench task facts <task-id> --json
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
不要在普通用户入口直接调用 `df-task` 写接口,不要伪造 `DF_TASK_ACTOR_*`,不要自行创建 Run 或选择执行 Agent。
|
|
51
|
-
|
|
52
|
-
### 深度排障
|
|
53
|
-
|
|
54
|
-
用户询问“任务为什么卡住”“为什么没有执行”“查看 Session/Invocation”“检查 Driver/Agent/Runtime”时,按以下顺序只读检查:
|
|
55
|
-
|
|
56
|
-
```text
|
|
57
|
-
df-workbench
|
|
58
|
-
→ df-task
|
|
59
|
-
→ df-taskdriver
|
|
60
|
-
→ df-agentctrl
|
|
61
|
-
→ df-agentrt
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
前一步事实不足以解释问题时,才进入下一步。不要把某个服务可达描述成整条链路成功,也不要把未知状态描述成完成或失败。
|
|
65
|
-
|
|
66
|
-
## 写操作安全
|
|
67
|
-
|
|
68
|
-
- 普通排障优先只读;
|
|
69
|
-
- 取消、释放 Session、修复孤儿执行、启停或回退 Agent 前必须获得明确确认;
|
|
70
|
-
- 不直接修改 Task 事实;
|
|
71
|
-
- 不因诊断失败而重建 Session、Invocation 或切换环境;
|
|
72
|
-
- 所有服务请求保持在用户选定的环境。
|
|
73
|
-
|
|
74
|
-
## 输出边界
|
|
75
|
-
|
|
76
|
-
回复时区分:
|
|
77
|
-
|
|
78
|
-
1. 已由事实确认的结果;
|
|
79
|
-
2. Driver、Agent Control 或 Runtime 返回的诊断;
|
|
80
|
-
3. 仍无法确认的未知状态;
|
|
81
|
-
4. 需要用户或服务维护者采取的下一步。
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
id: deepflow-suite
|
|
2
|
-
version: 0.2.9
|
|
3
|
-
kind: composite-skill
|
|
4
|
-
requires:
|
|
5
|
-
skills:
|
|
6
|
-
- df-workbench
|
|
7
|
-
- df-task
|
|
8
|
-
- df-taskdriver
|
|
9
|
-
- df-agentctrl
|
|
10
|
-
- df-agentrt
|
|
11
|
-
- df-agentrt-repo
|
|
12
|
-
cli:
|
|
13
|
-
- df-workbench
|
|
14
|
-
- df-task
|
|
15
|
-
- df-taskdriver
|
|
16
|
-
- df-agentctrl
|
|
17
|
-
- df-agentrt
|
|
18
|
-
resolution:
|
|
19
|
-
managed_by: deepflow-suite
|
|
20
|
-
install_missing_dependencies: false
|
package/src/cli-launcher.mjs
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
|
|
7
|
-
import { verifyRuntimePackage } from "./runtime-build.mjs";
|
|
8
|
-
import {
|
|
9
|
-
runtimeBinaryFileName,
|
|
10
|
-
runtimeBinaryNames,
|
|
11
|
-
runtimePackageForPlatform,
|
|
12
|
-
} from "./runtime-package.mjs";
|
|
13
|
-
|
|
14
|
-
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
15
|
-
|
|
16
|
-
function defaultPackageRoot() {
|
|
17
|
-
return PACKAGE_ROOT;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function packageTarget(platform, arch) {
|
|
21
|
-
const packageName = runtimePackageForPlatform(platform, arch);
|
|
22
|
-
if (!packageName) throw new Error(`unsupported DeepFlow CLI platform: ${platform}/${arch}`);
|
|
23
|
-
return { platform, arch, package: packageName };
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function resolveInstalledPackageRoot(packageRoot, packageName) {
|
|
27
|
-
const require = createRequire(path.join(packageRoot, "package.json"));
|
|
28
|
-
try {
|
|
29
|
-
return path.dirname(require.resolve(`${packageName}/package.json`, { paths: [packageRoot] }));
|
|
30
|
-
} catch {
|
|
31
|
-
throw new Error(`optional runtime package ${packageName} is not installed; reinstall without --omit=optional`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function runProcess(command, args, env = process.env) {
|
|
36
|
-
return new Promise((resolve, reject) => {
|
|
37
|
-
const child = spawn(command, args, { env, stdio: "inherit" });
|
|
38
|
-
child.once("error", reject);
|
|
39
|
-
child.once("close", (code, signal) => resolve(code ?? (signal ? 1 : 0)));
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function runRuntimeCli(name, argv = [], options = {}) {
|
|
44
|
-
if (!runtimeBinaryNames().includes(name)) throw new Error(`unsupported runtime CLI: ${name}`);
|
|
45
|
-
const packageRoot = options.packageRoot ?? defaultPackageRoot();
|
|
46
|
-
const platform = options.platform ?? process.platform;
|
|
47
|
-
const arch = options.arch ?? process.arch;
|
|
48
|
-
const target = packageTarget(platform, arch);
|
|
49
|
-
const runtimeRoot = resolveInstalledPackageRoot(packageRoot, target.package);
|
|
50
|
-
const packageJson = JSON.parse(await readFile(path.join(packageRoot, "package.json"), "utf8"));
|
|
51
|
-
const release = await verifyRuntimePackage({
|
|
52
|
-
packageRoot: runtimeRoot,
|
|
53
|
-
target,
|
|
54
|
-
expectedVersion: packageJson.version,
|
|
55
|
-
});
|
|
56
|
-
const binary = release.binaries.find((entry) => entry.name === name);
|
|
57
|
-
const binaryPath = path.join(runtimeRoot, binary.path);
|
|
58
|
-
return runProcess(binaryPath, argv, options.env ?? process.env);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export async function runWorkbench(argv = [], options = {}) {
|
|
62
|
-
const packageRoot = options.packageRoot ?? defaultPackageRoot();
|
|
63
|
-
const entry = path.join(packageRoot, "runtime", "df-workbench.mjs");
|
|
64
|
-
return runProcess(process.execPath, [entry, ...argv], options.env ?? process.env);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export function runtimeLauncherTarget(platform = process.platform, arch = process.arch) {
|
|
68
|
-
return packageTarget(platform, arch);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export { runtimeBinaryFileName };
|
package/src/runtime-build.mjs
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { access, chmod, lstat, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { execFile as execFileCallback } from "node:child_process";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { promisify } from "node:util";
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
runtimeBinaryFileName,
|
|
10
|
-
runtimeBinaryNames,
|
|
11
|
-
runtimePackageForPlatform,
|
|
12
|
-
supportedRuntimePlatforms,
|
|
13
|
-
} from "./runtime-package.mjs";
|
|
14
|
-
|
|
15
|
-
const execFile = promisify(execFileCallback);
|
|
16
|
-
const SOURCE_PROJECTS = Object.freeze({
|
|
17
|
-
"df-task": { project: "deepflow-task", target: "./cmd/df-task" },
|
|
18
|
-
"df-taskdriver": { project: "deepflow-task-driver", target: "./cmd/df-taskdriver" },
|
|
19
|
-
"df-agentctrl": { project: "deepflow-agent-ctrl", target: "./cmd/df-agentctrl" },
|
|
20
|
-
"df-agentrt": { project: "deepflow-agent-runtime", target: "./cmd/df-agentrt-cli" },
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
function packageDirectoryName(packageName) {
|
|
24
|
-
return packageName.replace(/^@[^/]+\//, "");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function validateTarget(target) {
|
|
28
|
-
const expectedPackage = runtimePackageForPlatform(target.platform, target.arch);
|
|
29
|
-
if (!expectedPackage || target.package !== expectedPackage) {
|
|
30
|
-
throw new Error(`runtime package name does not match target ${target.platform}/${target.arch}`);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async function sha256(filePath) {
|
|
35
|
-
const contents = await readFile(filePath);
|
|
36
|
-
return {
|
|
37
|
-
size: contents.byteLength,
|
|
38
|
-
sha256: createHash("sha256").update(contents).digest("hex"),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function verifyRegularFile(filePath) {
|
|
43
|
-
const stats = await lstat(filePath);
|
|
44
|
-
if (!stats.isFile()) throw new Error(`runtime binary is not a regular file: ${filePath}`);
|
|
45
|
-
await access(filePath);
|
|
46
|
-
return stats;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export async function verifyRuntimePackage({ packageRoot, target, expectedVersion }) {
|
|
50
|
-
validateTarget(target);
|
|
51
|
-
const packageJson = JSON.parse(await readFile(path.join(packageRoot, "package.json"), "utf8"));
|
|
52
|
-
if (packageJson.name !== target.package) throw new Error(`runtime package name must be ${target.package}`);
|
|
53
|
-
if (packageJson.version !== expectedVersion) throw new Error(`runtime package version ${packageJson.version} does not match ${expectedVersion}`);
|
|
54
|
-
if (JSON.stringify(packageJson.os) !== JSON.stringify([target.platform])) {
|
|
55
|
-
throw new Error(`runtime package OS must be ${target.platform}`);
|
|
56
|
-
}
|
|
57
|
-
if (JSON.stringify(packageJson.cpu) !== JSON.stringify([target.arch])) {
|
|
58
|
-
throw new Error(`runtime package CPU must be ${target.arch}`);
|
|
59
|
-
}
|
|
60
|
-
const release = JSON.parse(await readFile(path.join(packageRoot, "deepflow-runtime-release.json"), "utf8"));
|
|
61
|
-
if (release.schemaVersion !== 1 || release.distribution !== "deepflow-suite" || release.package !== target.package) {
|
|
62
|
-
throw new Error("invalid DeepFlow runtime release metadata");
|
|
63
|
-
}
|
|
64
|
-
if (release.version !== expectedVersion || release.platform !== target.platform || release.arch !== target.arch) {
|
|
65
|
-
throw new Error("runtime release metadata does not match package target");
|
|
66
|
-
}
|
|
67
|
-
if (!Array.isArray(release.binaries) || release.binaries.length !== runtimeBinaryNames().length) {
|
|
68
|
-
throw new Error("runtime release must contain exactly four CLI binaries");
|
|
69
|
-
}
|
|
70
|
-
const expectedNames = runtimeBinaryNames().sort();
|
|
71
|
-
const actualNames = release.binaries.map((binary) => binary?.name).sort();
|
|
72
|
-
if (JSON.stringify(expectedNames) !== JSON.stringify(actualNames)) {
|
|
73
|
-
throw new Error("runtime release contains an unexpected CLI binary");
|
|
74
|
-
}
|
|
75
|
-
for (const binary of release.binaries) {
|
|
76
|
-
const expectedPath = path.posix.join("bin", runtimeBinaryFileName(binary.name, target.platform));
|
|
77
|
-
if (binary.path !== expectedPath || !Number.isSafeInteger(binary.size) || binary.size < 1
|
|
78
|
-
|| !/^[a-f0-9]{64}$/.test(binary.sha256)) {
|
|
79
|
-
throw new Error(`invalid runtime binary metadata: ${binary.name}`);
|
|
80
|
-
}
|
|
81
|
-
const binaryPath = path.join(packageRoot, binary.path);
|
|
82
|
-
const stats = await verifyRegularFile(binaryPath);
|
|
83
|
-
const digest = await sha256(binaryPath);
|
|
84
|
-
if (digest.size !== binary.size || digest.sha256 !== binary.sha256) {
|
|
85
|
-
throw new Error(`runtime binary hash does not match release metadata: ${binary.name}`);
|
|
86
|
-
}
|
|
87
|
-
if (target.platform !== "win32" && (stats.mode & 0o111) === 0) {
|
|
88
|
-
throw new Error(`runtime binary is not executable: ${binary.name}`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return release;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function buildGoBinary({ workspaceRoot, source, outputPath, target }) {
|
|
95
|
-
const projectRoot = path.join(workspaceRoot, source.project);
|
|
96
|
-
await execFile("go", ["build", "-trimpath", "-ldflags=-s -w", "-o", outputPath, source.target], {
|
|
97
|
-
cwd: projectRoot,
|
|
98
|
-
env: {
|
|
99
|
-
...process.env,
|
|
100
|
-
CGO_ENABLED: "0",
|
|
101
|
-
GOOS: target.platform === "win32" ? "windows" : target.platform,
|
|
102
|
-
GOARCH: target.arch === "x64" ? "amd64" : target.arch,
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function packageJsonForTarget(target, version) {
|
|
108
|
-
return {
|
|
109
|
-
name: target.package,
|
|
110
|
-
version,
|
|
111
|
-
description: `DeepFlow Suite CLI runtime binaries for ${target.platform}/${target.arch}.`,
|
|
112
|
-
license: "UNLICENSED",
|
|
113
|
-
files: ["bin/", "deepflow-runtime-release.json", "README.md"],
|
|
114
|
-
os: [target.platform],
|
|
115
|
-
cpu: [target.arch],
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
async function buildOnePackage({ workspaceRoot, outputRoot, version, target, buildBinary }) {
|
|
120
|
-
validateTarget(target);
|
|
121
|
-
const packageRoot = path.join(outputRoot, packageDirectoryName(target.package));
|
|
122
|
-
const stagingRoot = await mkdtemp(path.join(outputRoot, `.${packageDirectoryName(target.package)}.tmp-`));
|
|
123
|
-
try {
|
|
124
|
-
await mkdir(path.join(stagingRoot, "bin"), { recursive: true });
|
|
125
|
-
const binaries = [];
|
|
126
|
-
for (const name of runtimeBinaryNames()) {
|
|
127
|
-
const fileName = runtimeBinaryFileName(name, target.platform);
|
|
128
|
-
const relativePath = path.posix.join("bin", fileName);
|
|
129
|
-
const outputPath = path.join(stagingRoot, "bin", fileName);
|
|
130
|
-
const source = SOURCE_PROJECTS[name];
|
|
131
|
-
await buildBinary({ name, outputPath, source, target, workspaceRoot });
|
|
132
|
-
if (target.platform !== "win32") await chmod(outputPath, 0o755);
|
|
133
|
-
const digest = await sha256(outputPath);
|
|
134
|
-
binaries.push({ name, path: relativePath, ...digest });
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const release = {
|
|
138
|
-
schemaVersion: 1,
|
|
139
|
-
distribution: "deepflow-suite",
|
|
140
|
-
package: target.package,
|
|
141
|
-
version,
|
|
142
|
-
platform: target.platform,
|
|
143
|
-
arch: target.arch,
|
|
144
|
-
binaries,
|
|
145
|
-
};
|
|
146
|
-
await writeFile(path.join(stagingRoot, "package.json"), `${JSON.stringify(packageJsonForTarget(target, version), null, 2)}\n`);
|
|
147
|
-
await writeFile(path.join(stagingRoot, "deepflow-runtime-release.json"), `${JSON.stringify(release, null, 2)}\n`);
|
|
148
|
-
await writeFile(
|
|
149
|
-
path.join(stagingRoot, "README.md"),
|
|
150
|
-
`# ${target.package}\n\nPlatform runtime binaries for DeepFlow Suite ${version} (${target.platform}/${target.arch}).\n`,
|
|
151
|
-
);
|
|
152
|
-
await rm(packageRoot, { recursive: true, force: true });
|
|
153
|
-
await rename(stagingRoot, packageRoot);
|
|
154
|
-
return { package: target.package, directory: packageRoot, release };
|
|
155
|
-
} catch (error) {
|
|
156
|
-
await rm(stagingRoot, { recursive: true, force: true });
|
|
157
|
-
throw error;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export async function buildRuntimePackages({
|
|
162
|
-
workspaceRoot,
|
|
163
|
-
outputRoot,
|
|
164
|
-
version,
|
|
165
|
-
targets = supportedRuntimePlatforms(),
|
|
166
|
-
buildBinary = buildGoBinary,
|
|
167
|
-
}) {
|
|
168
|
-
if (!workspaceRoot) throw new Error("runtime build workspace root is required");
|
|
169
|
-
if (!outputRoot) throw new Error("runtime build output root is required");
|
|
170
|
-
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version)) {
|
|
171
|
-
throw new Error("runtime package version must be a semantic version");
|
|
172
|
-
}
|
|
173
|
-
await mkdir(outputRoot, { recursive: true });
|
|
174
|
-
const results = [];
|
|
175
|
-
for (const target of targets) {
|
|
176
|
-
results.push(await buildOnePackage({ workspaceRoot, outputRoot, version, target, buildBinary }));
|
|
177
|
-
}
|
|
178
|
-
return { packages: results.map((result) => result.package), results };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function runtimeSourceProjects() {
|
|
182
|
-
return structuredClone(SOURCE_PROJECTS);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function hostRuntimeTarget() {
|
|
186
|
-
return { platform: process.platform, arch: process.arch, package: runtimePackageForPlatform(process.platform, process.arch) };
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export async function buildHostRuntimePackage(options) {
|
|
190
|
-
const target = hostRuntimeTarget();
|
|
191
|
-
if (!target.package) throw new Error(`unsupported runtime platform: ${os.platform()}/${os.arch()}`);
|
|
192
|
-
return buildRuntimePackages({ ...options, targets: [target] });
|
|
193
|
-
}
|
package/src/runtime-package.mjs
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
const RUNTIME_BINARY_NAMES = Object.freeze([
|
|
2
|
-
"df-task",
|
|
3
|
-
"df-taskdriver",
|
|
4
|
-
"df-agentctrl",
|
|
5
|
-
"df-agentrt",
|
|
6
|
-
]);
|
|
7
|
-
|
|
8
|
-
const RUNTIME_TARGETS = Object.freeze([
|
|
9
|
-
Object.freeze({ platform: "darwin", arch: "arm64", package: "@metagptx/deepflow-suite-cli-darwin-arm64" }),
|
|
10
|
-
Object.freeze({ platform: "darwin", arch: "x64", package: "@metagptx/deepflow-suite-cli-darwin-x64" }),
|
|
11
|
-
Object.freeze({ platform: "linux", arch: "arm64", package: "@metagptx/deepflow-suite-cli-linux-arm64" }),
|
|
12
|
-
Object.freeze({ platform: "linux", arch: "x64", package: "@metagptx/deepflow-suite-cli-linux-x64" }),
|
|
13
|
-
Object.freeze({ platform: "win32", arch: "x64", package: "@metagptx/deepflow-suite-cli-win32-x64" }),
|
|
14
|
-
]);
|
|
15
|
-
|
|
16
|
-
export function supportedRuntimePlatforms() {
|
|
17
|
-
return RUNTIME_TARGETS.map((target) => ({ ...target }));
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function runtimePackageForPlatform(platform, arch) {
|
|
21
|
-
return RUNTIME_TARGETS.find((target) => target.platform === platform && target.arch === arch)?.package ?? null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function runtimeBinaryNames() {
|
|
25
|
-
return [...RUNTIME_BINARY_NAMES];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function runtimeBinaryFileName(name, platform) {
|
|
29
|
-
if (!RUNTIME_BINARY_NAMES.includes(name)) throw new Error(`unsupported runtime CLI: ${name}`);
|
|
30
|
-
return platform === "win32" ? `${name}.exe` : name;
|
|
31
|
-
}
|