@metagptx/deepflow-suite-cli 0.1.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,10 +13,21 @@ deepflow-suite install --source package
13
13
  deepflow-suite doctor --source package --json
14
14
  ```
15
15
 
16
- The package installation mode copies the seven bundled Skills into
17
- `$CODEX_HOME/skills` (or `~/.codex/skills`). Use `--target` to choose another
18
- Skill directory and `--force` only when replacing an existing installation is
19
- intentional.
16
+ The public package includes the `deepflow-suite` entrypoint, the five service
17
+ CLI launchers (`df-workbench`, `df-task`, `df-taskdriver`, `df-agentctrl`, and
18
+ `df-agentrt`), and seven bundled Skills. npm automatically selects the matching
19
+ runtime package for `darwin-arm64`, `darwin-x64`, `linux-arm64`, `linux-x64`, or
20
+ `win32-x64`; the user does not need a DeepFlow checkout, Go, or Make.
21
+
22
+ `install --source package` copies the seven Skills into `$CODEX_HOME/skills`
23
+ (or `~/.codex/skills`) and verifies all six command launchers plus the selected
24
+ runtime package. `doctor --source package --json` reports the same complete
25
+ distribution check. Use `--target` to choose another Skill directory and
26
+ `--force` only when replacing an existing installation is intentional.
27
+
28
+ If npm is run with `--omit=optional`, package installation fails before changing
29
+ the Skill directory and reports the missing platform runtime package. Reinstall
30
+ without `--omit=optional` to obtain the four Go-backed CLIs.
20
31
 
21
32
  ## Workspace development
22
33
 
@@ -40,10 +51,14 @@ The package is prepared for public scoped publication like Console CLI:
40
51
 
41
52
  ```bash
42
53
  npm run test
54
+ npm run build:runtime
55
+ npm run check:runtime
43
56
  npm run pack:dry-run
44
57
  npm publish
45
58
  ```
46
59
 
47
- `prepack` materializes the seven Skill bundle and its release manifest. The
48
- published `deepflow-suite` command never overwrites an existing Skill or CLI
49
- file without `--force`.
60
+ Publish the five platform runtime packages before publishing the main package;
61
+ all six entrypoints and platform packages must use the same version. `prepack`
62
+ materializes the seven Skill bundle, the Workbench Node entrypoint, and the
63
+ runtime release metadata. The published installer never overwrites an existing
64
+ Skill or CLI file without `--force`.
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runRuntimeCli } from "../src/cli-launcher.mjs";
4
+
5
+ try {
6
+ process.exitCode = await runRuntimeCli("df-agentctrl", process.argv.slice(2));
7
+ } catch (error) {
8
+ console.error(error.message);
9
+ process.exitCode = 1;
10
+ }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runRuntimeCli } from "../src/cli-launcher.mjs";
4
+
5
+ try {
6
+ process.exitCode = await runRuntimeCli("df-agentrt", process.argv.slice(2));
7
+ } catch (error) {
8
+ console.error(error.message);
9
+ process.exitCode = 1;
10
+ }
package/bin/df-task.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runRuntimeCli } from "../src/cli-launcher.mjs";
4
+
5
+ try {
6
+ process.exitCode = await runRuntimeCli("df-task", process.argv.slice(2));
7
+ } catch (error) {
8
+ console.error(error.message);
9
+ process.exitCode = 1;
10
+ }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runRuntimeCli } from "../src/cli-launcher.mjs";
4
+
5
+ try {
6
+ process.exitCode = await runRuntimeCli("df-taskdriver", process.argv.slice(2));
7
+ } catch (error) {
8
+ console.error(error.message);
9
+ process.exitCode = 1;
10
+ }
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runWorkbench } from "../src/cli-launcher.mjs";
4
+
5
+ try {
6
+ process.exitCode = await runWorkbench(process.argv.slice(2));
7
+ } catch (error) {
8
+ console.error(error.message);
9
+ process.exitCode = 1;
10
+ }
@@ -2,13 +2,13 @@
2
2
  "schemaVersion": 1,
3
3
  "distribution": "deepflow-suite",
4
4
  "package": "@metagptx/deepflow-suite-cli",
5
- "version": "0.1.2",
6
- "platform": "darwin",
7
- "arch": "arm64",
5
+ "version": "0.2.3",
6
+ "platform": "any",
7
+ "arch": "any",
8
8
  "cli": {
9
9
  "name": "deepflow-suite",
10
10
  "package": "@metagptx/deepflow-suite-cli",
11
- "version": "0.1.2",
11
+ "version": "0.2.3",
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": "494531d6a0532b6d7e5db03b4e3adbcc4eb8b3c754e2074091728a81ce7fffbd"
22
+ "sha256": "dc6578c11a62a29fee194a268ede90d1d8659b0f731787ca3fdab557bc39d51f"
23
23
  },
24
24
  {
25
25
  "path": "SKILL.md",
@@ -138,8 +138,8 @@
138
138
  "files": [
139
139
  {
140
140
  "path": "SKILL.md",
141
- "size": 10079,
142
- "sha256": "f1d8abb45c3bd7010b01a89393a16c143ed5718b092af9804f2fd849a2274f68"
141
+ "size": 10128,
142
+ "sha256": "baa0cac18c1683a0b2e52ab301854d8dc9e907ccd3ae451b2357b1780c2e3f33"
143
143
  }
144
144
  ]
145
145
  },
@@ -159,27 +159,37 @@
159
159
  {
160
160
  "name": "df-workbench",
161
161
  "project": "deepflow-workbench",
162
- "install": "project"
162
+ "install": "project",
163
+ "package": "@metagptx/deepflow-suite-cli",
164
+ "mode": "node"
163
165
  },
164
166
  {
165
167
  "name": "df-task",
166
168
  "project": "deepflow-task",
167
- "install": "make install-cli"
169
+ "install": "make install-cli",
170
+ "package": "platform-runtime",
171
+ "mode": "binary"
168
172
  },
169
173
  {
170
174
  "name": "df-taskdriver",
171
175
  "project": "deepflow-task-driver",
172
- "install": "make install-cli"
176
+ "install": "make install-cli",
177
+ "package": "platform-runtime",
178
+ "mode": "binary"
173
179
  },
174
180
  {
175
181
  "name": "df-agentctrl",
176
182
  "project": "deepflow-agent-ctrl",
177
- "install": "make install-cli"
183
+ "install": "make install-cli",
184
+ "package": "platform-runtime",
185
+ "mode": "binary"
178
186
  },
179
187
  {
180
188
  "name": "df-agentrt",
181
189
  "project": "deepflow-agent-runtime",
182
- "install": "make build"
190
+ "install": "make build",
191
+ "package": "platform-runtime",
192
+ "mode": "binary"
183
193
  }
184
194
  ]
185
195
  }
@@ -1,5 +1,5 @@
1
1
  id: deepflow-suite
2
- version: 0.1.2
2
+ version: 0.2.3
3
3
  kind: composite-skill
4
4
  requires:
5
5
  skills:
@@ -169,7 +169,7 @@ key 时每次新建。同 key 只保留首次成功写入的配置,released
169
169
  ### 有三件事不归你做
170
170
 
171
171
  `session release`、`session rm`、`ops orphans --fix` **不要用**。它们都是替别人做决定:
172
- 回收工作目录由控制面决定,删会话会抹掉永久可见的消息流,判死滞留轮次动的是别的会话。
172
+ release 会停止会话接收新轮次(第一期不会归档 Worker 工作目录),删会话会抹掉永久可见的消息流,判死滞留轮次动的是别的会话。
173
173
  二进制不会拦你——它会照做,所以这条只能靠你守住。
174
174
 
175
175
  发现某个会话像是卡住了、或者某个工作目录该回收了,**在回复里说出来**,
package/manifest.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "distribution": "deepflow-suite",
3
- "version": "0.1.2",
3
+ "version": "0.2.3",
4
4
  "skills": [
5
5
  {
6
6
  "name": "deepflow-suite",
@@ -42,27 +42,37 @@
42
42
  {
43
43
  "name": "df-workbench",
44
44
  "project": "deepflow-workbench",
45
- "install": "project"
45
+ "install": "project",
46
+ "package": "@metagptx/deepflow-suite-cli",
47
+ "mode": "node"
46
48
  },
47
49
  {
48
50
  "name": "df-task",
49
51
  "project": "deepflow-task",
50
- "install": "make install-cli"
52
+ "install": "make install-cli",
53
+ "package": "platform-runtime",
54
+ "mode": "binary"
51
55
  },
52
56
  {
53
57
  "name": "df-taskdriver",
54
58
  "project": "deepflow-task-driver",
55
- "install": "make install-cli"
59
+ "install": "make install-cli",
60
+ "package": "platform-runtime",
61
+ "mode": "binary"
56
62
  },
57
63
  {
58
64
  "name": "df-agentctrl",
59
65
  "project": "deepflow-agent-ctrl",
60
- "install": "make install-cli"
66
+ "install": "make install-cli",
67
+ "package": "platform-runtime",
68
+ "mode": "binary"
61
69
  },
62
70
  {
63
71
  "name": "df-agentrt",
64
72
  "project": "deepflow-agent-runtime",
65
- "install": "make build"
73
+ "install": "make build",
74
+ "package": "platform-runtime",
75
+ "mode": "binary"
66
76
  }
67
77
  ]
68
78
  }
package/package.json CHANGED
@@ -1,16 +1,22 @@
1
1
  {
2
2
  "name": "@metagptx/deepflow-suite-cli",
3
- "version": "0.1.2",
3
+ "version": "0.2.3",
4
4
  "description": "Agent-friendly installer and distribution CLI for the DeepFlow suite.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "bin": {
8
- "deepflow-suite": "bin/deepflow-suite.js"
8
+ "deepflow-suite": "bin/deepflow-suite.js",
9
+ "df-workbench": "bin/df-workbench.js",
10
+ "df-task": "bin/df-task.js",
11
+ "df-taskdriver": "bin/df-taskdriver.js",
12
+ "df-agentctrl": "bin/df-agentctrl.js",
13
+ "df-agentrt": "bin/df-agentrt.js"
9
14
  },
10
15
  "files": [
11
16
  "bin/",
12
17
  "bundle/",
13
18
  "manifest.json",
19
+ "runtime/",
14
20
  "skills/",
15
21
  "src/",
16
22
  "scripts/",
@@ -19,11 +25,20 @@
19
25
  "publishConfig": {
20
26
  "access": "public"
21
27
  },
28
+ "optionalDependencies": {
29
+ "@metagptx/deepflow-suite-cli-darwin-arm64": "0.2.3",
30
+ "@metagptx/deepflow-suite-cli-darwin-x64": "0.2.3",
31
+ "@metagptx/deepflow-suite-cli-linux-arm64": "0.2.3",
32
+ "@metagptx/deepflow-suite-cli-linux-x64": "0.2.3",
33
+ "@metagptx/deepflow-suite-cli-win32-x64": "0.2.3"
34
+ },
22
35
  "engines": {
23
36
  "node": ">=22.13.0"
24
37
  },
25
38
  "scripts": {
26
39
  "test": "node --test tests/*.test.mjs",
40
+ "build:runtime": "node scripts/build-runtime-packages.mjs",
41
+ "check:runtime": "node scripts/build-runtime-packages.mjs --check",
27
42
  "prepack": "node scripts/build-package.mjs",
28
43
  "pack:dry-run": "npm pack --dry-run"
29
44
  }
@@ -0,0 +1,463 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { randomUUID } from "node:crypto";
4
+ import { pathToFileURL } from "node:url";
5
+
6
+ const DEFAULT_BASE_URL = "http://127.0.0.1:8793";
7
+ const MAX_MESSAGE_LENGTH = 20000;
8
+
9
+ export class UsageError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = "UsageError";
13
+ this.exitCode = 2;
14
+ }
15
+ }
16
+
17
+ export class RequestError extends Error {
18
+ constructor(message, details = {}) {
19
+ super(message);
20
+ this.name = "RequestError";
21
+ this.status = details.status || 0;
22
+ this.code = details.code || "request_failed";
23
+ this.details = details;
24
+ this.exitCode = 1;
25
+ }
26
+ }
27
+
28
+ function valueAfter(argv, index, option) {
29
+ const value = argv[index + 1];
30
+ if (!value || value.startsWith("--"))
31
+ throw new UsageError(`${option} 需要一个值。`);
32
+ return value;
33
+ }
34
+
35
+ export function parseArgs(argv, env = process.env) {
36
+ const options = {
37
+ baseUrl: env.DF_WORKBENCH_BASE_URL || DEFAULT_BASE_URL,
38
+ token: env.DF_WORKBENCH_TOKEN || "",
39
+ workspace: env.DF_WORKBENCH_WORKSPACE_ID || "",
40
+ json: false,
41
+ idempotencyKey: "",
42
+ message: "",
43
+ title: "",
44
+ after: "",
45
+ limit: "",
46
+ };
47
+ const positionals = [];
48
+ for (let index = 0; index < argv.length; index += 1) {
49
+ const argument = argv[index];
50
+ if (argument === "--json") {
51
+ options.json = true;
52
+ continue;
53
+ }
54
+ if (argument === "--help" || argument === "-h") {
55
+ options.help = true;
56
+ continue;
57
+ }
58
+ const equals = argument.indexOf("=");
59
+ const name = equals === -1 ? argument : argument.slice(0, equals);
60
+ const inline = equals === -1 ? undefined : argument.slice(equals + 1);
61
+ if (
62
+ [
63
+ "--base-url",
64
+ "--workspace",
65
+ "--idempotency-key",
66
+ "--message",
67
+ "--title",
68
+ "--after",
69
+ "--limit",
70
+ ].includes(name)
71
+ ) {
72
+ const parsed =
73
+ inline === undefined ? valueAfter(argv, index, name) : inline;
74
+ if (inline === undefined) index += 1;
75
+ if (name === "--base-url") options.baseUrl = parsed;
76
+ if (name === "--workspace") options.workspace = parsed;
77
+ if (name === "--idempotency-key") options.idempotencyKey = parsed;
78
+ if (name === "--message") options.message = parsed;
79
+ if (name === "--title") options.title = parsed;
80
+ if (name === "--after") options.after = parsed;
81
+ if (name === "--limit") options.limit = parsed;
82
+ continue;
83
+ }
84
+ if (argument.startsWith("--"))
85
+ throw new UsageError(`不支持的选项:${argument}`);
86
+ positionals.push(argument);
87
+ }
88
+ options.positionals = positionals;
89
+ return options;
90
+ }
91
+
92
+ export function normalizeBaseUrl(value) {
93
+ let url;
94
+ try {
95
+ url = new URL(value);
96
+ } catch {
97
+ throw new UsageError(`Workbench 地址无效:${value}`);
98
+ }
99
+ if (
100
+ !["http:", "https:"].includes(url.protocol) ||
101
+ url.username ||
102
+ url.password ||
103
+ url.search ||
104
+ url.hash ||
105
+ (url.pathname !== "/" && url.pathname !== "")
106
+ )
107
+ throw new UsageError(
108
+ "Workbench 地址必须是没有凭据、路径、查询参数或片段的 HTTP(S) 根地址。",
109
+ );
110
+ return url.toString().replace(/\/$/, "");
111
+ }
112
+
113
+ export function buildCreateBody(message, title = "") {
114
+ const text = String(message || "").trim();
115
+ if (!text || text.length > MAX_MESSAGE_LENGTH)
116
+ throw new UsageError(`消息需要 1–${MAX_MESSAGE_LENGTH} 个字符。`);
117
+ const body = { description: text, conversation: { type: "task" } };
118
+ if (title) {
119
+ const trimmed = title.trim();
120
+ if (!trimmed || trimmed.length > 160)
121
+ throw new UsageError("任务标题需要 1–160 个字符。");
122
+ body.title = trimmed;
123
+ }
124
+ return body;
125
+ }
126
+
127
+ function messageValue(value) {
128
+ const text = String(value || "").trim();
129
+ if (!text || text.length > MAX_MESSAGE_LENGTH)
130
+ throw new UsageError(`消息需要 1–${MAX_MESSAGE_LENGTH} 个字符。`);
131
+ return text;
132
+ }
133
+
134
+ function integerOption(value, option, fallback, minimum, maximum) {
135
+ if (value === "") return fallback;
136
+ if (!/^\d+$/.test(value)) throw new UsageError(`${option} 必须是整数。`);
137
+ const parsed = Number(value);
138
+ if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum)
139
+ throw new UsageError(`${option} 必须在 ${minimum}–${maximum} 之间。`);
140
+ return parsed;
141
+ }
142
+
143
+ function idempotencyKey(options) {
144
+ const key = options.idempotencyKey || randomUUID();
145
+ if (!/^[\w.:/-]{8,160}$/.test(key))
146
+ throw new UsageError("--idempotency-key 格式无效。");
147
+ return key;
148
+ }
149
+
150
+ function requestHeaders(options, context = {}, key) {
151
+ const headers = { Accept: "application/json" };
152
+ if (options.token) headers.Authorization = `Bearer ${options.token}`;
153
+ if (context.workspace) headers["X-Deepflow-Workspace"] = context.workspace;
154
+ if (context.actorId) headers["X-Deepflow-Expected-Actor"] = context.actorId;
155
+ if (key) headers["Idempotency-Key"] = key;
156
+ return headers;
157
+ }
158
+
159
+ async function request(options, path, requestOptions = {}, context = {}) {
160
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
161
+ const url = new URL(path, `${baseUrl}/`);
162
+ const headers = requestHeaders(options, context, requestOptions.key);
163
+ if (requestOptions.body !== undefined)
164
+ headers["Content-Type"] = "application/json";
165
+ let response;
166
+ try {
167
+ response = await fetch(url, {
168
+ method: requestOptions.method || "GET",
169
+ headers,
170
+ body:
171
+ requestOptions.body === undefined
172
+ ? undefined
173
+ : JSON.stringify(requestOptions.body),
174
+ redirect: "error",
175
+ signal: AbortSignal.timeout(15000),
176
+ });
177
+ } catch (error) {
178
+ throw new RequestError(
179
+ `无法连接 Workbench:${error instanceof Error ? error.message : String(error)}`,
180
+ { cause: error },
181
+ );
182
+ }
183
+ let data;
184
+ try {
185
+ data = await response.json();
186
+ } catch {
187
+ throw new RequestError("Workbench 没有返回有效 JSON。", {
188
+ status: response.status,
189
+ code: "invalid_response",
190
+ });
191
+ }
192
+ if (!response.ok)
193
+ throw new RequestError(
194
+ data.message || `Workbench 请求失败(HTTP ${response.status})。`,
195
+ {
196
+ status: response.status,
197
+ code: data.code || "request_failed",
198
+ response: data,
199
+ },
200
+ );
201
+ return data;
202
+ }
203
+
204
+ async function currentContext(options) {
205
+ const me = await request(
206
+ options,
207
+ "/api/v1/me",
208
+ {},
209
+ options.workspace ? { workspace: options.workspace } : {},
210
+ );
211
+ const workspace = options.workspace || me.workspaceId;
212
+ if (!workspace)
213
+ throw new RequestError("Workbench 没有返回当前工作区。", {
214
+ code: "workspace_missing",
215
+ });
216
+ if (
217
+ Array.isArray(me.workspaces) &&
218
+ !me.workspaces.some((item) => item.id === workspace)
219
+ )
220
+ throw new RequestError(`当前用户无权访问工作区:${workspace}`, {
221
+ status: 403,
222
+ code: "workspace_forbidden",
223
+ });
224
+ if (!me.user?.id)
225
+ throw new RequestError("Workbench 没有返回当前用户。", {
226
+ code: "identity_missing",
227
+ });
228
+ return { me, workspace, actorId: me.user.id };
229
+ }
230
+
231
+ async function loadConfig(options) {
232
+ const context = await currentContext(options);
233
+ const [platform, auth, readiness, capabilities] = await Promise.all([
234
+ request(options, "/api/v1/platform-state"),
235
+ request(options, "/api/v1/auth/config"),
236
+ request(options, "/api/v1/task-service-readiness", {}, context),
237
+ request(options, "/api/v1/task-ui/capabilities", {}, context),
238
+ ]);
239
+ return {
240
+ baseUrl: normalizeBaseUrl(options.baseUrl),
241
+ authentication: auth,
242
+ platform,
243
+ identity: context.me.user,
244
+ workspace: context.me.workspaces?.find(
245
+ (item) => item.id === context.workspace,
246
+ ) || { id: context.workspace },
247
+ workspaces: context.me.workspaces || [],
248
+ taskService: readiness,
249
+ taskUi: capabilities,
250
+ };
251
+ }
252
+
253
+ function writeResult(value, options, human) {
254
+ if (options.json) {
255
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
256
+ return;
257
+ }
258
+ process.stdout.write(`${human(value)}\n`);
259
+ }
260
+
261
+ function taskSummary(value) {
262
+ const task = value.task || value;
263
+ return [
264
+ `Task: ${task.id || "未知"}`,
265
+ task.title ? `标题: ${task.title}` : "",
266
+ task.workspaceId ? `工作区: ${task.workspaceId}` : "",
267
+ ]
268
+ .filter(Boolean)
269
+ .join("\n");
270
+ }
271
+
272
+ function factsSummary(value) {
273
+ const facts = Array.isArray(value.facts) ? value.facts : [];
274
+ const lines = [`事实数量: ${facts.length}`];
275
+ for (const fact of facts.slice(-8)) {
276
+ const text =
277
+ fact.payload?.text || fact.payload?.reason || fact.type || "未知事实";
278
+ lines.push(
279
+ `#${fact.seq ?? "?"} ${fact.type || "unknown"}: ${String(text).replaceAll("\n", " ").slice(0, 160)}`,
280
+ );
281
+ }
282
+ if (value.next_after_seq !== undefined)
283
+ lines.push(`下一游标: ${value.next_after_seq}`);
284
+ return lines.join("\n");
285
+ }
286
+
287
+ function help() {
288
+ return `用法:
289
+ df-workbench me [--json]
290
+ df-workbench config [--json]
291
+ df-workbench health [--json]
292
+ df-workbench task create --message <消息> [--title <标题>] [--idempotency-key <key>]
293
+ df-workbench task send <task-id> --message <消息> [--idempotency-key <key>]
294
+ df-workbench task get <task-id> [--json]
295
+ df-workbench task facts <task-id> [--after <seq>] [--limit <n>] [--json]
296
+ df-workbench task list [--limit <n>] [--json]
297
+
298
+ 全局配置:
299
+ DF_WORKBENCH_BASE_URL Workbench 根地址,默认 http://127.0.0.1:8793
300
+ DF_WORKBENCH_TOKEN OIDC 环境使用的 personal token
301
+ DF_WORKBENCH_WORKSPACE_ID 当前工作区覆盖值
302
+ --base-url <url> 覆盖 Workbench 根地址
303
+ --workspace <id> 覆盖当前工作区
304
+ --json 输出机器可读 JSON`;
305
+ }
306
+
307
+ async function run(options) {
308
+ const [command, subject, ...rest] = options.positionals;
309
+ if (options.help || !command || command === "help") {
310
+ process.stdout.write(`${help()}\n`);
311
+ return;
312
+ }
313
+ if (command === "health") {
314
+ const value = await request(options, "/healthz");
315
+ writeResult(value, options, (value) => JSON.stringify(value));
316
+ return;
317
+ }
318
+ if (command === "me") {
319
+ const context = await currentContext(options);
320
+ writeResult(
321
+ context.me,
322
+ options,
323
+ (value) =>
324
+ `用户: ${value.user.name} (${value.user.id})\n工作区: ${value.workspaceId}`,
325
+ );
326
+ return;
327
+ }
328
+ if (command === "config") {
329
+ const value = await loadConfig(options);
330
+ writeResult(
331
+ value,
332
+ options,
333
+ (config) =>
334
+ `用户: ${config.identity.name} (${config.identity.id})\n工作区: ${config.workspace.name || config.workspace.id} (${config.workspace.id})\n认证: ${config.authentication.mode} / ${config.authentication.configured ? "已配置" : "未配置"}\nTask 写入: ${config.taskService.writeAvailable ? "可用" : "不可用"}`,
335
+ );
336
+ return;
337
+ }
338
+ if (command !== "task") throw new UsageError(`不支持的命令:${command}`);
339
+ if (subject === "create") {
340
+ const context = await currentContext(options);
341
+ const key = idempotencyKey(options);
342
+ const value = await request(
343
+ options,
344
+ "/api/v1/tasks",
345
+ {
346
+ method: "POST",
347
+ key,
348
+ body: buildCreateBody(options.message, options.title),
349
+ },
350
+ context,
351
+ );
352
+ writeResult(value, options, taskSummary);
353
+ return;
354
+ }
355
+ if (subject === "send") {
356
+ const taskId = rest[0];
357
+ if (!taskId) throw new UsageError("task send 需要 Task ID。");
358
+ const context = await currentContext(options);
359
+ const key = idempotencyKey(options);
360
+ const value = await request(
361
+ options,
362
+ `/api/v1/tasks/${encodeURIComponent(taskId)}/messages`,
363
+ { method: "POST", key, body: { text: messageValue(options.message) } },
364
+ context,
365
+ );
366
+ writeResult(
367
+ value,
368
+ options,
369
+ (value) => `消息已发送,事实序号:${value.fact?.seq ?? "未知"}`,
370
+ );
371
+ return;
372
+ }
373
+ if (subject === "get") {
374
+ const taskId = rest[0];
375
+ if (!taskId) throw new UsageError("task get 需要 Task ID。");
376
+ const context = await currentContext(options);
377
+ const value = await request(
378
+ options,
379
+ `/api/v1/tasks/${encodeURIComponent(taskId)}`,
380
+ {},
381
+ context,
382
+ );
383
+ writeResult(value, options, taskSummary);
384
+ return;
385
+ }
386
+ if (subject === "facts") {
387
+ const taskId = rest[0];
388
+ if (!taskId) throw new UsageError("task facts 需要 Task ID。");
389
+ const context = await currentContext(options);
390
+ const after = integerOption(
391
+ options.after,
392
+ "--after",
393
+ 0,
394
+ 0,
395
+ Number.MAX_SAFE_INTEGER,
396
+ );
397
+ const limit = integerOption(options.limit, "--limit", 200, 1, 1000);
398
+ const query = new URLSearchParams({
399
+ after_seq: String(after),
400
+ limit: String(limit),
401
+ });
402
+ const value = await request(
403
+ options,
404
+ `/api/v1/tasks/${encodeURIComponent(taskId)}/facts?${query}`,
405
+ {},
406
+ context,
407
+ );
408
+ writeResult(value, options, factsSummary);
409
+ return;
410
+ }
411
+ if (subject === "list") {
412
+ const context = await currentContext(options);
413
+ const limit = integerOption(options.limit, "--limit", 50, 1, 200);
414
+ const value = await request(
415
+ options,
416
+ `/api/v1/tasks?scope=mine&limit=${limit}`,
417
+ {},
418
+ context,
419
+ );
420
+ writeResult(
421
+ value,
422
+ options,
423
+ (value) =>
424
+ (value.tasks || [])
425
+ .map((task) => `${task.id} ${task.title || ""}`.trim())
426
+ .join("\n") || "没有任务。",
427
+ );
428
+ return;
429
+ }
430
+ throw new UsageError(`不支持的 task 命令:${subject || ""}`);
431
+ }
432
+
433
+ export async function main(argv = process.argv.slice(2), env = process.env) {
434
+ const options = parseArgs(argv, env);
435
+ await run(options);
436
+ }
437
+
438
+ if (
439
+ process.argv[1] &&
440
+ import.meta.url === pathToFileURL(process.argv[1]).href
441
+ ) {
442
+ main().catch((error) => {
443
+ if (error instanceof UsageError) {
444
+ process.stderr.write(`参数错误:${error.message}\n\n${help()}\n`);
445
+ } else if (error instanceof RequestError) {
446
+ const suffix = error.details?.response?.code
447
+ ? ` [${error.details.response.code}]`
448
+ : error.code
449
+ ? ` [${error.code}]`
450
+ : "";
451
+ process.stderr.write(`请求失败${suffix}:${error.message}\n`);
452
+ if (error.details?.status === 401 || error.details?.status === 403)
453
+ process.stderr.write(
454
+ "请检查 DF_WORKBENCH_TOKEN、工作区和目标环境配置。\n",
455
+ );
456
+ } else {
457
+ process.stderr.write(
458
+ `失败:${error instanceof Error ? error.message : String(error)}\n`,
459
+ );
460
+ }
461
+ process.exitCode = error.exitCode || 1;
462
+ });
463
+ }
@@ -1,12 +1,26 @@
1
1
  import path from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
+ import { cp, mkdir, rm, readFile } from "node:fs/promises";
3
4
 
4
5
  import { assembleBundle } from "../src/bundle.mjs";
6
+ import { buildRuntimePackages } from "../src/runtime-build.mjs";
5
7
 
6
8
  const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
7
9
  const workspaceRoot = path.resolve(packageRoot, "../..");
10
+ const packageJson = JSON.parse(await readFile(path.join(packageRoot, "package.json"), "utf8"));
8
11
  await assembleBundle({
9
12
  workspaceRoot,
10
13
  outputDir: path.join(packageRoot, "bundle"),
11
14
  manifestPath: path.join(packageRoot, "manifest.json"),
12
15
  });
16
+ await rm(path.join(packageRoot, "runtime"), { recursive: true, force: true });
17
+ await mkdir(path.join(packageRoot, "runtime"), { recursive: true });
18
+ await cp(
19
+ path.join(workspaceRoot, "deepflow-workbench", "cli", "df-workbench.mjs"),
20
+ path.join(packageRoot, "runtime", "df-workbench.mjs"),
21
+ );
22
+ await buildRuntimePackages({
23
+ workspaceRoot,
24
+ outputRoot: path.join(packageRoot, "platform-packages"),
25
+ version: packageJson.version,
26
+ });
@@ -0,0 +1,38 @@
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,5 +1,5 @@
1
1
  id: deepflow-suite
2
- version: 0.1.2
2
+ version: 0.2.3
3
3
  kind: composite-skill
4
4
  requires:
5
5
  skills:
package/src/bundle.mjs CHANGED
@@ -78,8 +78,8 @@ async function assembleInto({ workspaceRoot, outputDir, manifestPath }) {
78
78
  distribution: manifest.distribution,
79
79
  package: PACKAGE_NAME,
80
80
  version: manifest.version,
81
- platform: process.platform,
82
- arch: process.arch,
81
+ platform: "any",
82
+ arch: "any",
83
83
  cli: {
84
84
  name: CLI_NAME,
85
85
  package: PACKAGE_NAME,
@@ -0,0 +1,71 @@
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 };
@@ -5,6 +5,7 @@ import { verifyInstalledBundle } from "../bundle-installer.mjs";
5
5
  import { verifyWorkspaceSkills } from "../linker.mjs";
6
6
  import { verifyWorkspaceClis } from "../cli-install.mjs";
7
7
  import { loadManifest } from "../manifest.mjs";
8
+ import { verifyPackageClis } from "../package-cli.mjs";
8
9
 
9
10
  export async function doctor(options) {
10
11
  if (options.source === "package" || options.source === "bundle") {
@@ -13,11 +14,24 @@ export async function doctor(options) {
13
14
  try {
14
15
  const manifest = await loadManifest(options.manifestPath);
15
16
  const release = await verifyInstalledBundle({ bundleDir, targetDir, expectedVersion: manifest.version });
17
+ if (options.source === "bundle") {
18
+ return {
19
+ ok: true,
20
+ bundleDir,
21
+ targetDir,
22
+ release,
23
+ };
24
+ }
25
+ const clis = await verifyPackageClis({
26
+ packageRoot: path.dirname(options.manifestPath),
27
+ expectedVersion: manifest.version,
28
+ });
16
29
  return {
17
- ok: true,
30
+ ok: clis.ok,
18
31
  bundleDir,
19
32
  targetDir,
20
33
  release,
34
+ clis,
21
35
  };
22
36
  } catch (error) {
23
37
  return { ok: false, bundleDir, error: error.message };
@@ -5,20 +5,30 @@ import { installBundleSkills } from "../bundle-installer.mjs";
5
5
  import { installWorkspaceSkills } from "../linker.mjs";
6
6
  import { installWorkspaceClis } from "../cli-install.mjs";
7
7
  import { loadManifest } from "../manifest.mjs";
8
+ import { verifyPackageClis } from "../package-cli.mjs";
8
9
 
9
10
  export async function install(options) {
10
11
  if (options.source === "package") {
11
12
  const packageRoot = path.dirname(options.manifestPath);
12
13
  const manifest = await loadManifest(options.manifestPath);
13
14
  const bundleDir = options.bundleDir ?? path.join(packageRoot, "bundle");
15
+ const clis = await verifyPackageClis({
16
+ packageRoot,
17
+ expectedVersion: manifest.version,
18
+ });
19
+ if (!clis.ok) {
20
+ throw new Error(`package CLI verification failed: ${JSON.stringify(clis)}`);
21
+ }
22
+ const skills = await installBundleSkills({
23
+ bundleDir,
24
+ targetDir: options.targetDir ?? defaultCodexSkillsDir(),
25
+ force: options.force,
26
+ expectedVersion: manifest.version,
27
+ });
14
28
  return {
15
- skills: await installBundleSkills({
16
- bundleDir,
17
- targetDir: options.targetDir ?? defaultCodexSkillsDir(),
18
- force: options.force,
19
- expectedVersion: manifest.version,
20
- }),
21
- clis: { package: "@metagptx/deepflow-suite-cli" },
29
+ ok: clis.ok,
30
+ skills,
31
+ clis,
22
32
  };
23
33
  }
24
34
  if (options.source !== "workspace") {
@@ -0,0 +1,66 @@
1
+ import { access, lstat } from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import { resolveInstalledPackageRoot } from "./cli-launcher.mjs";
5
+ import { verifyRuntimePackage } from "./runtime-build.mjs";
6
+ import { runtimeBinaryNames, runtimePackageForPlatform } from "./runtime-package.mjs";
7
+
8
+ const PACKAGE_BINARIES = ["deepflow-suite", "df-workbench", ...runtimeBinaryNames()];
9
+
10
+ async function regularFile(filePath) {
11
+ try {
12
+ const stats = await lstat(filePath);
13
+ if (!stats.isFile()) return false;
14
+ await access(filePath);
15
+ return true;
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ export async function verifyPackageClis({
22
+ packageRoot,
23
+ expectedVersion,
24
+ platform = process.platform,
25
+ arch = process.arch,
26
+ }) {
27
+ const missing = [];
28
+ const invalid = [];
29
+ const binaries = [];
30
+ for (const name of PACKAGE_BINARIES) {
31
+ const filePath = path.join(packageRoot, "bin", `${name}.js`);
32
+ if (await regularFile(filePath)) binaries.push({ name, path: filePath });
33
+ else missing.push(name);
34
+ }
35
+
36
+ const packageName = runtimePackageForPlatform(platform, arch);
37
+ const platformInfo = { platform, arch, package: packageName };
38
+ if (!packageName) {
39
+ invalid.push({ name: "platform", reason: `unsupported DeepFlow CLI platform: ${platform}/${arch}` });
40
+ } else {
41
+ try {
42
+ const runtimeRoot = resolveInstalledPackageRoot(packageRoot, packageName);
43
+ await verifyRuntimePackage({
44
+ packageRoot: runtimeRoot,
45
+ target: platformInfo,
46
+ expectedVersion,
47
+ });
48
+ } catch (error) {
49
+ invalid.push({ name: packageName, reason: error.message });
50
+ }
51
+ }
52
+
53
+ const workbenchEntry = path.join(packageRoot, "runtime", "df-workbench.mjs");
54
+ if (!await regularFile(workbenchEntry)) invalid.push({ name: "df-workbench", reason: "packaged Workbench CLI entry is missing" });
55
+
56
+ return {
57
+ ok: missing.length === 0 && invalid.length === 0,
58
+ platform: platformInfo,
59
+ package: packageRoot,
60
+ binaries,
61
+ missing,
62
+ invalid,
63
+ };
64
+ }
65
+
66
+ export { PACKAGE_BINARIES };
@@ -0,0 +1,193 @@
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
+ }
@@ -0,0 +1,31 @@
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
+ }