@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,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { randomUUID
|
|
4
|
-
import { readFile
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { realpathSync } from "node:fs";
|
|
7
7
|
|
|
8
8
|
const DEFAULT_BASE_URL = "http://127.0.0.1:8793";
|
|
9
9
|
const MAX_MESSAGE_LENGTH = 20000;
|
|
@@ -39,7 +39,6 @@ export function parseArgs(argv, env = process.env) {
|
|
|
39
39
|
baseUrl: env.DF_WORKBENCH_BASE_URL || DEFAULT_BASE_URL,
|
|
40
40
|
token: env.DF_WORKBENCH_TOKEN || "",
|
|
41
41
|
workspace: env.DF_WORKBENCH_WORKSPACE_ID || "",
|
|
42
|
-
expectedActor: undefined,
|
|
43
42
|
json: false,
|
|
44
43
|
idempotencyKey: "",
|
|
45
44
|
message: "",
|
|
@@ -49,8 +48,6 @@ export function parseArgs(argv, env = process.env) {
|
|
|
49
48
|
scope: "mine",
|
|
50
49
|
squadId: undefined,
|
|
51
50
|
file: undefined,
|
|
52
|
-
attachmentIds: [],
|
|
53
|
-
participantIds: [],
|
|
54
51
|
};
|
|
55
52
|
const positionals = [];
|
|
56
53
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -70,7 +67,6 @@ export function parseArgs(argv, env = process.env) {
|
|
|
70
67
|
[
|
|
71
68
|
"--base-url",
|
|
72
69
|
"--workspace",
|
|
73
|
-
"--expected-actor",
|
|
74
70
|
"--idempotency-key",
|
|
75
71
|
"--message",
|
|
76
72
|
"--title",
|
|
@@ -79,11 +75,6 @@ export function parseArgs(argv, env = process.env) {
|
|
|
79
75
|
"--scope",
|
|
80
76
|
"--squad-id",
|
|
81
77
|
"--file",
|
|
82
|
-
"--attachment-id",
|
|
83
|
-
"--member-id",
|
|
84
|
-
"--expected-owner",
|
|
85
|
-
"--owner-id",
|
|
86
|
-
"--participant-id",
|
|
87
78
|
].includes(name)
|
|
88
79
|
) {
|
|
89
80
|
const parsed =
|
|
@@ -91,11 +82,6 @@ export function parseArgs(argv, env = process.env) {
|
|
|
91
82
|
if (inline === undefined) index += 1;
|
|
92
83
|
if (name === "--base-url") options.baseUrl = parsed;
|
|
93
84
|
if (name === "--workspace") options.workspace = parsed;
|
|
94
|
-
if (name === "--expected-actor") {
|
|
95
|
-
if (!parsed.trim() || parsed.length > 160 || /[\r\n]/.test(parsed))
|
|
96
|
-
throw new UsageError("--expected-actor 需要有效的用户 ID。");
|
|
97
|
-
options.expectedActor = parsed;
|
|
98
|
-
}
|
|
99
85
|
if (name === "--idempotency-key") options.idempotencyKey = parsed;
|
|
100
86
|
if (name === "--message") options.message = parsed;
|
|
101
87
|
if (name === "--title") options.title = parsed;
|
|
@@ -104,11 +90,6 @@ export function parseArgs(argv, env = process.env) {
|
|
|
104
90
|
if (name === "--scope") options.scope = parsed;
|
|
105
91
|
if (name === "--squad-id") options.squadId = parsed;
|
|
106
92
|
if (name === "--file") options.file = parsed;
|
|
107
|
-
if (name === "--attachment-id") options.attachmentIds.push(parsed);
|
|
108
|
-
if (name === "--member-id") options.memberId = parsed;
|
|
109
|
-
if (name === "--expected-owner") options.expectedOwner = parsed;
|
|
110
|
-
if (name === "--owner-id") options.ownerId = parsed;
|
|
111
|
-
if (name === "--participant-id") options.participantIds.push(parsed);
|
|
112
93
|
continue;
|
|
113
94
|
}
|
|
114
95
|
if (argument.startsWith("--"))
|
|
@@ -264,13 +245,6 @@ async function currentContext(options) {
|
|
|
264
245
|
throw new RequestError("Workbench 没有返回当前用户。", {
|
|
265
246
|
code: "identity_missing",
|
|
266
247
|
});
|
|
267
|
-
if (
|
|
268
|
-
options.expectedActor !== undefined &&
|
|
269
|
-
options.expectedActor !== me.user.id
|
|
270
|
-
)
|
|
271
|
-
throw new RequestError("Workbench 操作身份已变化;未提交业务请求。", {
|
|
272
|
-
code: "identity_changed",
|
|
273
|
-
});
|
|
274
248
|
return { me, workspace, actorId: me.user.id };
|
|
275
249
|
}
|
|
276
250
|
|
|
@@ -449,19 +423,11 @@ function help() {
|
|
|
449
423
|
df-workbench squad get <squad-id> [--json]
|
|
450
424
|
df-workbench squad create --file <配置.json> [--idempotency-key <key>] [--json]
|
|
451
425
|
df-workbench squad update <squad-id> --file <配置.json> [--idempotency-key <key>] [--json]
|
|
452
|
-
df-workbench
|
|
453
|
-
df-workbench task create --message <消息> [--title <标题>] [--squad-id <squad-id>] [--owner-id <user-id>] [--participant-id <user-id>] [--idempotency-key <key>]
|
|
454
|
-
df-workbench task member-options <task-id>
|
|
455
|
-
df-workbench task invite <task-id> --member-id <user-id> [--idempotency-key <key>]
|
|
456
|
-
df-workbench task transfer-owner <task-id> --member-id <user-id> --expected-owner <user-id> [--idempotency-key <key>]
|
|
426
|
+
df-workbench task create --message <消息> [--title <标题>] [--squad-id <squad-id>] [--idempotency-key <key>]
|
|
457
427
|
df-workbench task send <task-id> --message <消息> [--idempotency-key <key>]
|
|
458
|
-
df-workbench task lookup <DF-number> [--json]
|
|
459
428
|
df-workbench task get <task-id> [--json]
|
|
460
429
|
df-workbench task facts <task-id> [--after <seq>] [--limit <n>] [--json]
|
|
461
430
|
df-workbench task list [--scope <mine|workspace>] [--limit <n>] [--json]
|
|
462
|
-
df-workbench attachment upload --file <文件> [--idempotency-key <key>] [--json]
|
|
463
|
-
|
|
464
|
-
task create/send 可重复传入 --attachment-id <id>,每次最多 10 个。
|
|
465
431
|
|
|
466
432
|
全局配置:
|
|
467
433
|
DF_WORKBENCH_BASE_URL Workbench 根地址,默认 http://127.0.0.1:8793
|
|
@@ -469,7 +435,6 @@ task create/send 可重复传入 --attachment-id <id>,每次最多 10 个。
|
|
|
469
435
|
DF_WORKBENCH_WORKSPACE_ID 当前工作区覆盖值
|
|
470
436
|
--base-url <url> 覆盖 Workbench 根地址
|
|
471
437
|
--workspace <id> 覆盖当前工作区
|
|
472
|
-
--expected-actor <user-id> 固定操作身份;身份变化时停止请求
|
|
473
438
|
--json 输出机器可读 JSON`;
|
|
474
439
|
}
|
|
475
440
|
|
|
@@ -479,11 +444,6 @@ async function run(options) {
|
|
|
479
444
|
process.stdout.write(`${help()}\n`);
|
|
480
445
|
return;
|
|
481
446
|
}
|
|
482
|
-
if ((options.ownerId !== undefined || options.participantIds.length) && !(command === "task" && subject === "create")) throw new UsageError("--owner-id 和 --participant-id 仅用于 task create。");
|
|
483
|
-
if (options.ownerId !== undefined && !/^[\w-]{1,100}$/.test(options.ownerId)) throw new UsageError("初始负责人 ID 无效。");
|
|
484
|
-
if ((options.memberId !== undefined && !(command === "task" && ["invite","transfer-owner"].includes(subject))) ||
|
|
485
|
-
(options.expectedOwner !== undefined && !(command === "task" && subject === "transfer-owner")))
|
|
486
|
-
throw new UsageError("成员和负责人参数只用于对应的任务成员命令。");
|
|
487
447
|
if (
|
|
488
448
|
options.squadId !== undefined &&
|
|
489
449
|
!(command === "task" && subject === "create")
|
|
@@ -493,57 +453,9 @@ async function run(options) {
|
|
|
493
453
|
);
|
|
494
454
|
if (
|
|
495
455
|
options.file !== undefined &&
|
|
496
|
-
!(command === "squad" && ["create", "update"].includes(subject))
|
|
497
|
-
!(command === "attachment" && subject === "upload")
|
|
498
|
-
)
|
|
499
|
-
throw new UsageError(
|
|
500
|
-
"--file 仅用于 squad create/update 或 attachment upload。",
|
|
501
|
-
);
|
|
502
|
-
if (
|
|
503
|
-
options.attachmentIds.length &&
|
|
504
|
-
!(command === "task" && ["create", "send"].includes(subject))
|
|
505
|
-
)
|
|
506
|
-
throw new UsageError("--attachment-id 仅用于 task create/send。");
|
|
507
|
-
if (
|
|
508
|
-
options.attachmentIds.length > 10 ||
|
|
509
|
-
new Set(options.attachmentIds).size !== options.attachmentIds.length ||
|
|
510
|
-
options.attachmentIds.some((id) => !/^[\w-]{1,100}$/.test(id))
|
|
456
|
+
!(command === "squad" && ["create", "update"].includes(subject))
|
|
511
457
|
)
|
|
512
|
-
throw new UsageError("
|
|
513
|
-
if (command === "attachment" && subject === "upload") {
|
|
514
|
-
if (!options.file) throw new UsageError("attachment upload 需要 --file。");
|
|
515
|
-
const fileInfo = await stat(options.file);
|
|
516
|
-
if (!fileInfo.isFile() || !fileInfo.size || fileInfo.size > 4 * 1024 * 1024)
|
|
517
|
-
throw new UsageError("每个附件需为 1 字节至 4 MiB。");
|
|
518
|
-
const bytes = await readFile(options.file);
|
|
519
|
-
const filename = basename(options.file);
|
|
520
|
-
if (!bytes.length || bytes.length > 4 * 1024 * 1024)
|
|
521
|
-
throw new UsageError("每个附件需为 1 字节至 4 MiB。");
|
|
522
|
-
if (
|
|
523
|
-
!filename.trim() ||
|
|
524
|
-
filename.length > 180 ||
|
|
525
|
-
/[\x00-\x1f\x7f\\]/.test(filename)
|
|
526
|
-
)
|
|
527
|
-
throw new UsageError("附件名称无效。");
|
|
528
|
-
const context = await currentContext(options);
|
|
529
|
-
const value = await request(
|
|
530
|
-
options,
|
|
531
|
-
"/api/v1/task-attachments",
|
|
532
|
-
{
|
|
533
|
-
method: "POST",
|
|
534
|
-
key: idempotencyKey(options),
|
|
535
|
-
body: { filename, bytesBase64: bytes.toString("base64") },
|
|
536
|
-
},
|
|
537
|
-
context,
|
|
538
|
-
);
|
|
539
|
-
writeResult(
|
|
540
|
-
value,
|
|
541
|
-
options,
|
|
542
|
-
(value) =>
|
|
543
|
-
`已上传附件:${value.attachment?.filename} (${value.attachment?.id})`,
|
|
544
|
-
);
|
|
545
|
-
return;
|
|
546
|
-
}
|
|
458
|
+
throw new UsageError("--file 仅用于 squad create/update。");
|
|
547
459
|
if (command === "squad") return squadCommand(options, subject, rest);
|
|
548
460
|
if (command === "health") {
|
|
549
461
|
const value = await request(options, "/healthz");
|
|
@@ -570,44 +482,16 @@ async function run(options) {
|
|
|
570
482
|
);
|
|
571
483
|
return;
|
|
572
484
|
}
|
|
573
|
-
if (command === "workspace" && subject === "members") {
|
|
574
|
-
const context = await currentContext(options);
|
|
575
|
-
const value = await request(options,"/api/v1/members",{},context);
|
|
576
|
-
if (!Array.isArray(value.members)) throw new RequestError("工作区成员目录回执无效。",{code:"invalid_member_directory"});
|
|
577
|
-
writeResult(value,options,value => value.members.map(member=>`${member.userId} ${member.name || ""}`).join("\n"));return;
|
|
578
|
-
}
|
|
579
485
|
if (command !== "task") throw new UsageError(`不支持的命令:${command}`);
|
|
580
|
-
if (["member-options", "invite", "transfer-owner"].includes(subject)) {
|
|
581
|
-
const taskId = rest[0];
|
|
582
|
-
if (!taskId || !/^[\w-]{1,160}$/.test(taskId)) throw new UsageError("需要有效的 Task ID。");
|
|
583
|
-
const writes = subject !== "member-options";
|
|
584
|
-
if (writes && !/^[\w-]{1,100}$/.test(options.memberId || "")) throw new UsageError("需要 --member-id 指定有效的 DF 用户 ID。");
|
|
585
|
-
if (subject === "transfer-owner" && !/^[\w-]{1,100}$/.test(options.expectedOwner || "")) throw new UsageError("需要 --expected-owner 指定当前负责人 ID。");
|
|
586
|
-
const context = await currentContext(options);
|
|
587
|
-
const path = subject === "member-options" ? `/api/v1/task-ui/tasks/${encodeURIComponent(taskId)}/property-options`
|
|
588
|
-
: `/api/v1/tasks/${encodeURIComponent(taskId)}/${subject === "invite" ? "member-invitations" : "owner-transfer"}`;
|
|
589
|
-
const value = await request(options, path, writes ? {method:"POST", key:idempotencyKey(options), body:subject === "invite"
|
|
590
|
-
? {invitee_ids:[options.memberId]} : {new_owner_id:options.memberId,expected_owner_id:options.expectedOwner}} : {}, context);
|
|
591
|
-
writeResult(value, options, value => JSON.stringify(value));
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
594
486
|
if (subject === "create") {
|
|
595
487
|
const body = buildCreateBody(
|
|
596
488
|
options.message,
|
|
597
489
|
options.title,
|
|
598
490
|
options.squadId,
|
|
599
491
|
);
|
|
600
|
-
if (options.ownerId) {
|
|
601
|
-
if (![options.ownerId,...options.participantIds].every(id=>/^[\w-]{1,100}$/.test(id))) throw new UsageError("初始负责人和参与人 ID 无效。");
|
|
602
|
-
body.initial_owner_id=options.ownerId;
|
|
603
|
-
body.first_message="";
|
|
604
|
-
body.invitee_ids=[...new Set(options.participantIds)].sort();
|
|
605
|
-
} else if(options.participantIds.length) throw new UsageError("--participant-id 需要同时指定 --owner-id。");
|
|
606
|
-
if (options.attachmentIds.length && !options.ownerId)
|
|
607
|
-
body.attachment_ids = options.attachmentIds;
|
|
608
492
|
const context = await currentContext(options);
|
|
609
493
|
const key = idempotencyKey(options);
|
|
610
|
-
|
|
494
|
+
const value = await request(
|
|
611
495
|
options,
|
|
612
496
|
"/api/v1/tasks",
|
|
613
497
|
{
|
|
@@ -630,23 +514,6 @@ async function run(options) {
|
|
|
630
514
|
idempotencyKey: key,
|
|
631
515
|
},
|
|
632
516
|
);
|
|
633
|
-
if (options.ownerId) {
|
|
634
|
-
const task=value?.task,data=task?.metadata?.data;
|
|
635
|
-
if (!task?.id || data?.workspace_id!==context.workspace || data.owner_id!==options.ownerId || data.created_by!==context.actorId ||
|
|
636
|
-
![context.actorId,options.ownerId,...options.participantIds].every(id=>data.members?.includes(id))) throw new RequestError("已创建的任务初始负责人或成员回执不匹配,请保留原幂等键核对,不要重建。",{code:"task_metadata_mismatch",outcome:"unknown",taskId:task?.id,idempotencyKey:key});
|
|
637
|
-
// Current squad edits must never change a retry of the original message.
|
|
638
|
-
const agentId=data.initial_message_context?.agentId;
|
|
639
|
-
if (!agentId) throw new RequestError("任务已创建,但创建时的小队快照没有可接收首条消息的伙伴;请保留原任务与幂等键核对。",{code:"initial_agent_unavailable",outcome:"unknown",taskId:task.id,idempotencyKey:key});
|
|
640
|
-
const firstKey="initial-message:"+createHash("sha256").update(key).digest("hex");
|
|
641
|
-
let first;
|
|
642
|
-
try {
|
|
643
|
-
first=await request(options,`/api/v1/tasks/${encodeURIComponent(task.id)}/messages`,{method:"POST",key:firstKey,body:{text:messageValue(options.message),agentIds:[agentId],...(options.attachmentIds.length?{attachment_ids:options.attachmentIds}:{})}},context);
|
|
644
|
-
} catch(error) {
|
|
645
|
-
throw new RequestError(`任务 ${task.id} 已创建,首条消息回执尚待确认;保留幂等键 ${key} 重试,不换键或重建。`,{code:"initial_message_unconfirmed",outcome:"unknown",taskId:task.id,idempotencyKey:key,causeCode:error.code});
|
|
646
|
-
}
|
|
647
|
-
if (!Number.isSafeInteger(first?.fact?.seq) || first.fact.task_id!==task.id) throw new RequestError("初始消息回执不匹配;保留原任务与幂等键重试。",{code:"initial_message_unconfirmed",outcome:"unknown",taskId:task.id,idempotencyKey:key});
|
|
648
|
-
value={...value,first_fact:first.fact,first_fact_seq:first.fact.seq};
|
|
649
|
-
}
|
|
650
517
|
writeResult(value, options, taskSummary);
|
|
651
518
|
return;
|
|
652
519
|
}
|
|
@@ -658,16 +525,7 @@ async function run(options) {
|
|
|
658
525
|
const value = await request(
|
|
659
526
|
options,
|
|
660
527
|
`/api/v1/tasks/${encodeURIComponent(taskId)}/messages`,
|
|
661
|
-
{
|
|
662
|
-
method: "POST",
|
|
663
|
-
key,
|
|
664
|
-
body: {
|
|
665
|
-
text: messageValue(options.message),
|
|
666
|
-
...(options.attachmentIds.length
|
|
667
|
-
? { attachment_ids: options.attachmentIds }
|
|
668
|
-
: {}),
|
|
669
|
-
},
|
|
670
|
-
},
|
|
528
|
+
{ method: "POST", key, body: { text: messageValue(options.message) } },
|
|
671
529
|
context,
|
|
672
530
|
);
|
|
673
531
|
writeResult(
|
|
@@ -677,29 +535,6 @@ async function run(options) {
|
|
|
677
535
|
);
|
|
678
536
|
return;
|
|
679
537
|
}
|
|
680
|
-
if (subject === "lookup") {
|
|
681
|
-
const number = String(rest[0] || "").toUpperCase();
|
|
682
|
-
if (!/^DF-\d+$/.test(number)) throw new UsageError("task lookup 需要 DF 编号。");
|
|
683
|
-
const context = await currentContext(options);
|
|
684
|
-
for (let page = 0, offset = 0; page < 20; page++) {
|
|
685
|
-
const result = await request(options, `/api/v1/tasks?${new URLSearchParams({scope:"workspace",limit:"200",offset:String(offset)})}`, {}, context);
|
|
686
|
-
if (!Array.isArray(result.tasks) || typeof result.hasMore !== "boolean") throw new RequestError("工作区任务分页回执不完整,尚不能判断该编号是否存在。",{code:"task_lookup_incomplete"});
|
|
687
|
-
const matches = result.tasks.filter(task => task.number === number);
|
|
688
|
-
if (matches.length > 1) throw new RequestError("工作区中出现重复任务编号,请核实目标。",{code:"task_lookup_ambiguous"});
|
|
689
|
-
if (matches.length) {
|
|
690
|
-
const task = matches[0];
|
|
691
|
-
if (task.access === "overview") throw new RequestError(`${number} 存在,但当前账号尚未加入任务,请先获得参与权限。`,{code:"task_participation_required",status:403});
|
|
692
|
-
const value = await request(options, `/api/v1/tasks/${encodeURIComponent(task.id)}`, {}, context);
|
|
693
|
-
if (value.task?.id !== task.id || value.task?.number !== number || value.task?.metadata?.data?.workspace_id !== context.workspace || value.task?.access === "overview")
|
|
694
|
-
throw new RequestError("DF 编号、工作区或内容权限读回不一致,暂停操作。",{code:"task_lookup_mismatch"});
|
|
695
|
-
writeResult(value, options, taskSummary);return;
|
|
696
|
-
}
|
|
697
|
-
if (!result.hasMore) throw new RequestError(`当前工作区未找到 ${number},请确认编号。`,{code:"task_not_found",status:404});
|
|
698
|
-
if (!result.tasks.length) break;
|
|
699
|
-
offset += result.tasks.length;
|
|
700
|
-
}
|
|
701
|
-
throw new RequestError("工作区任务查询尚未完成,请稍后再试;不会新建任务。",{code:"task_lookup_incomplete"});
|
|
702
|
-
}
|
|
703
538
|
if (subject === "get") {
|
|
704
539
|
const taskId = rest[0];
|
|
705
540
|
if (!taskId) throw new UsageError("task get 需要 Task ID。");
|
|
@@ -769,13 +604,20 @@ async function run(options) {
|
|
|
769
604
|
}
|
|
770
605
|
|
|
771
606
|
export async function main(argv = process.argv.slice(2), env = process.env) {
|
|
607
|
+
if (argv[0] === '--cli-version') {
|
|
608
|
+
let version = 'workspace';
|
|
609
|
+
try { version = JSON.parse(await readFile(new URL('./release.json', import.meta.url), 'utf8')).version; } catch {}
|
|
610
|
+
const report = { name: 'df-workbench', version, entry: fileURLToPath(import.meta.url) };
|
|
611
|
+
process.stdout.write((argv.includes('--json') ? JSON.stringify(report) : `df-workbench ${version}`) + '\n');
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
772
614
|
const options = parseArgs(argv, env);
|
|
773
615
|
await run(options);
|
|
774
616
|
}
|
|
775
617
|
|
|
776
618
|
if (
|
|
777
619
|
process.argv[1] &&
|
|
778
|
-
import.meta.url ===
|
|
620
|
+
fileURLToPath(import.meta.url) === realpathSync(process.argv[1])
|
|
779
621
|
) {
|
|
780
622
|
main().catch((error) => {
|
|
781
623
|
if (error instanceof UsageError) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"0.3.0","project":"deepflow-workbench","sourceCommit":"6afd4dec2000e93a971c94d4f18126c10f74a300","sourceDirty":false}
|
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
import path from
|
|
2
|
-
import { fileURLToPath } from
|
|
3
|
-
import { cp, mkdir, rm, readFile } from
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { chmod, cp, mkdir, rm, readFile, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { execFile } from 'node:child_process';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { assembleBundle } from '../src/bundle.mjs';
|
|
7
|
+
import { loadManifest } from '../src/manifest.mjs';
|
|
4
8
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
await assembleBundle({
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
await
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
);
|
|
22
|
-
await
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
9
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
|
+
const workspaceRoot = path.resolve(packageRoot, '../..');
|
|
11
|
+
const packageJson = JSON.parse(await readFile(path.join(packageRoot, 'package.json'), 'utf8'));
|
|
12
|
+
const manifest = await loadManifest(path.join(packageRoot, 'manifest.json'));
|
|
13
|
+
if (manifest.version !== packageJson.version) throw Error('Manifest and npm package versions differ');
|
|
14
|
+
await promisify(execFile)(process.execPath, ['cli/generate-protocol.mjs', '--check'], { cwd: path.join(workspaceRoot, 'deepflow-task') });
|
|
15
|
+
await assembleBundle({ workspaceRoot, outputDir: path.join(packageRoot, 'bundle'), manifestPath: path.join(packageRoot, 'manifest.json') });
|
|
16
|
+
const runtime = path.join(packageRoot, 'runtime');
|
|
17
|
+
await rm(runtime, { recursive: true, force: true });
|
|
18
|
+
await mkdir(runtime, { recursive: true });
|
|
19
|
+
for (const cli of manifest.clis) {
|
|
20
|
+
const target = path.join(runtime, cli.name);
|
|
21
|
+
await cp(path.join(workspaceRoot, cli.project, 'cli'), target, {
|
|
22
|
+
recursive: true,
|
|
23
|
+
filter: (source) => !/(?:[\\/]contract(?:[\\/]|$)|\.test\.mjs$|[\\/]generate-protocol\.mjs$)/.test(source),
|
|
24
|
+
});
|
|
25
|
+
const { stdout } = await promisify(execFile)('git', ['rev-parse', 'HEAD'], { cwd: path.join(workspaceRoot, cli.project) });
|
|
26
|
+
const { stdout: status } = await promisify(execFile)('git', ['status', '--porcelain'], { cwd: path.join(workspaceRoot, cli.project) });
|
|
27
|
+
await writeFile(path.join(target, 'release.json'), JSON.stringify({ version: packageJson.version, project: cli.project, sourceCommit: stdout.trim(), sourceDirty: !!status.trim() }) + '\n');
|
|
28
|
+
await chmod(path.join(target, path.basename(cli.entry)), 0o755);
|
|
29
|
+
if (cli.name === 'df-agentrt') await chmod(path.join(target, 'agentrt.mjs'), 0o755);
|
|
30
|
+
}
|
package/src/bundle.mjs
CHANGED
|
@@ -65,7 +65,10 @@ async function assembleInto({ workspaceRoot, outputDir, manifestPath }) {
|
|
|
65
65
|
throw new Error(`missing or empty Skill source: ${skillFile}`);
|
|
66
66
|
}
|
|
67
67
|
const destination = path.join(outputDir, "skills", skill.name);
|
|
68
|
-
await cp(skill.sourcePath, destination, {
|
|
68
|
+
await cp(skill.sourcePath, destination, {
|
|
69
|
+
recursive: true, dereference: false,
|
|
70
|
+
filter: (source) => !/(?:_test\.go$|\.test\.mjs$|[\\/]__pycache__(?:[\\/]|$))/.test(source),
|
|
71
|
+
});
|
|
69
72
|
skills.push({
|
|
70
73
|
name: skill.name,
|
|
71
74
|
source: skill.source,
|
package/src/cli-install.mjs
CHANGED
|
@@ -1,140 +1,50 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import path from "node:path";
|
|
1
|
+
import { access, lstat, mkdir, mkdtemp, readFile, realpath, rename, rm, symlink, writeFile } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
child.once("error", reject);
|
|
10
|
-
child.once("exit", (code, signal) => {
|
|
11
|
-
if (code === 0) resolve();
|
|
12
|
-
else reject(new Error(`${command} ${args.join(" ")} exited with ${code ?? signal}`));
|
|
13
|
-
});
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function replaceLink(source, target) {
|
|
18
|
-
await mkdir(path.dirname(target), { recursive: true });
|
|
5
|
+
const destinationFor = (binDir) => binDir ? path.resolve(binDir) : path.join(os.homedir(), '.local', 'bin');
|
|
6
|
+
async function inspect(target) { try { return await lstat(target); } catch (error) { if (error.code === 'ENOENT') return null; throw error; } }
|
|
7
|
+
async function matches(target, source) {
|
|
19
8
|
try {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
const current = path.resolve(path.dirname(target), await readlink(target));
|
|
25
|
-
if (current === source) return "unchanged";
|
|
26
|
-
await unlink(target);
|
|
27
|
-
} catch (error) {
|
|
28
|
-
if (error.code !== "ENOENT") throw error;
|
|
29
|
-
}
|
|
30
|
-
await symlink(path.relative(path.dirname(target), source), target);
|
|
31
|
-
return "created";
|
|
9
|
+
if (process.platform === 'win32') return (await readFile(target, 'utf8')).includes(source);
|
|
10
|
+
return await realpath(target) === await realpath(source);
|
|
11
|
+
} catch { return false; }
|
|
32
12
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
13
|
+
export async function installWorkspaceClis({ workspaceRoot, manifest, binDir, force = false }) {
|
|
14
|
+
const destination = destinationFor(binDir), unchanged = [], targets = [];
|
|
15
|
+
for (const cli of manifest.clis) {
|
|
16
|
+
const source = path.resolve(workspaceRoot, cli.project, cli.entry);
|
|
36
17
|
await access(source);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
18
|
+
const target = path.join(destination, cli.name + (process.platform === 'win32' ? '.cmd' : ''));
|
|
19
|
+
if (await matches(target, source)) { unchanged.push(cli.name); continue; }
|
|
20
|
+
const stats = await inspect(target);
|
|
21
|
+
if (stats?.isDirectory() || (stats && !force)) throw Error(`refusing to replace existing CLI without --force: ${target}`);
|
|
22
|
+
targets.push({ name: cli.name, source, target });
|
|
40
23
|
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function installWorkspaceClis({ workspaceRoot, manifest, binDir, force = false }) {
|
|
44
|
-
const destination = binDir
|
|
45
|
-
? path.resolve(binDir)
|
|
46
|
-
: path.join(process.env.HOME ?? os.homedir(), ".local", "bin");
|
|
47
|
-
const result = { linked: [], built: [] };
|
|
48
|
-
|
|
49
24
|
await mkdir(destination, { recursive: true });
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const stats = await lstat(target);
|
|
54
|
-
if (!force) {
|
|
55
|
-
throw new Error(`refusing to replace existing CLI without --force: ${target}`);
|
|
56
|
-
}
|
|
57
|
-
if (stats.isDirectory()) throw new Error(`refusing to replace CLI directory: ${target}`);
|
|
58
|
-
} catch (error) {
|
|
59
|
-
if (error.code !== "ENOENT") throw error;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const backup = await mkdtemp(path.join(destination, ".deepflow-cli-backup-"));
|
|
64
|
-
const backedUp = [];
|
|
65
|
-
const touched = [];
|
|
25
|
+
const backup = await mkdtemp(path.join(destination, '.deepflow-cli-backup-'));
|
|
26
|
+
const backedUp = [], installed = [];
|
|
66
27
|
try {
|
|
67
|
-
for (const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
} catch (error) {
|
|
73
|
-
if (error.code !== "ENOENT") throw error;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
for (const cli of manifest.clis) {
|
|
78
|
-
const projectRoot = path.join(workspaceRoot, cli.project);
|
|
79
|
-
const target = path.join(destination, cli.name);
|
|
80
|
-
|
|
81
|
-
if (cli.name === "df-workbench") {
|
|
82
|
-
const source = path.join(projectRoot, "cli", "df-workbench.mjs");
|
|
83
|
-
if (!(await sourceExists(source))) throw new Error(`missing CLI source: ${source}`);
|
|
84
|
-
await replaceLink(source, target);
|
|
85
|
-
touched.push(cli.name);
|
|
86
|
-
result.linked.push(cli.name);
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (cli.name === "df-agentrt") {
|
|
91
|
-
await run("make", ["build"], { cwd: projectRoot, env: process.env });
|
|
92
|
-
const source = path.join(projectRoot, "deploy", "bin", "df-agentrt");
|
|
93
|
-
if (!(await sourceExists(source))) throw new Error(`missing built CLI: ${source}`);
|
|
94
|
-
await replaceLink(source, target);
|
|
95
|
-
touched.push(cli.name);
|
|
96
|
-
result.built.push(cli.name);
|
|
97
|
-
continue;
|
|
28
|
+
for (const entry of targets) {
|
|
29
|
+
if (await inspect(entry.target)) { await rename(entry.target, path.join(backup, entry.name)); backedUp.push(entry); }
|
|
30
|
+
if (process.platform === 'win32') await writeFile(entry.target, `@ECHO off\r\n"${process.execPath}" "${entry.source}" %*\r\n`);
|
|
31
|
+
else await symlink(path.relative(destination, entry.source), entry.target);
|
|
32
|
+
installed.push(entry);
|
|
98
33
|
}
|
|
99
|
-
|
|
100
|
-
await run("make", ["install-cli", `BIN_DIR=${destination}`], {
|
|
101
|
-
cwd: projectRoot,
|
|
102
|
-
env: process.env,
|
|
103
|
-
});
|
|
104
|
-
touched.push(cli.name);
|
|
105
|
-
result.built.push(cli.name);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
await rm(backup, { recursive: true, force: true });
|
|
109
|
-
return { binDir: destination, ...result };
|
|
34
|
+
return { binDir: destination, linked: installed.map((entry) => entry.name), unchanged };
|
|
110
35
|
} catch (error) {
|
|
111
|
-
for (const
|
|
112
|
-
for (const
|
|
36
|
+
for (const entry of installed) await rm(entry.target, { force: true });
|
|
37
|
+
for (const entry of backedUp) await rename(path.join(backup, entry.name), entry.target);
|
|
113
38
|
throw error;
|
|
114
|
-
} finally {
|
|
115
|
-
await rm(backup, { recursive: true, force: true });
|
|
116
|
-
}
|
|
39
|
+
} finally { await rm(backup, { recursive: true, force: true }); }
|
|
117
40
|
}
|
|
118
|
-
|
|
119
41
|
export async function verifyWorkspaceClis({ workspaceRoot, manifest, binDir }) {
|
|
120
|
-
const
|
|
121
|
-
? path.resolve(binDir)
|
|
122
|
-
: path.join(process.env.HOME ?? os.homedir(), ".local", "bin");
|
|
123
|
-
const missing = [];
|
|
124
|
-
const invalid = [];
|
|
42
|
+
const bin = destinationFor(binDir), missing = [], invalid = [];
|
|
125
43
|
for (const cli of manifest.clis) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (stats.isDirectory()) invalid.push({ name: cli.name, reason: "target is a directory" });
|
|
131
|
-
} catch (error) {
|
|
132
|
-
if (error.code === "ENOENT") {
|
|
133
|
-
missing.push(cli.name);
|
|
134
|
-
} else {
|
|
135
|
-
invalid.push({ name: cli.name, reason: error.message });
|
|
136
|
-
}
|
|
137
|
-
}
|
|
44
|
+
const target = path.join(bin, cli.name + (process.platform === 'win32' ? '.cmd' : ''));
|
|
45
|
+
const source = path.resolve(workspaceRoot, cli.project, cli.entry);
|
|
46
|
+
if (!await inspect(target)) missing.push(cli.name);
|
|
47
|
+
else if (!await matches(target, source)) invalid.push({ name: cli.name, reason: 'CLI points to another installation', expected: source, actual: target });
|
|
138
48
|
}
|
|
139
|
-
return { binDir:
|
|
49
|
+
return { binDir: bin, missing, invalid };
|
|
140
50
|
}
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -25,6 +25,8 @@ export async function doctor(options) {
|
|
|
25
25
|
const clis = await verifyPackageClis({
|
|
26
26
|
packageRoot: path.dirname(options.manifestPath),
|
|
27
27
|
expectedVersion: manifest.version,
|
|
28
|
+
checkPath: true,
|
|
29
|
+
env: options.env ?? process.env,
|
|
28
30
|
});
|
|
29
31
|
return {
|
|
30
32
|
ok: clis.ok,
|
package/src/linker.mjs
CHANGED
|
@@ -24,7 +24,7 @@ async function inspectTarget(targetPath) {
|
|
|
24
24
|
|
|
25
25
|
export async function installWorkspaceSkills({ workspaceRoot, targetDir, force = false, manifestPath }) {
|
|
26
26
|
const manifest = await loadManifest(manifestPath);
|
|
27
|
-
const skills = resolveSkillSources(manifest, workspaceRoot
|
|
27
|
+
const skills = resolveSkillSources(manifest, workspaceRoot);
|
|
28
28
|
const destination = targetDir ?? path.join(workspaceRoot, ".agents", "skills");
|
|
29
29
|
|
|
30
30
|
for (const skill of skills) {
|
|
@@ -68,7 +68,7 @@ export async function installWorkspaceSkills({ workspaceRoot, targetDir, force =
|
|
|
68
68
|
|
|
69
69
|
export async function verifyWorkspaceSkills({ workspaceRoot, targetDir, manifestPath }) {
|
|
70
70
|
const manifest = await loadManifest(manifestPath);
|
|
71
|
-
const skills = resolveSkillSources(manifest, workspaceRoot
|
|
71
|
+
const skills = resolveSkillSources(manifest, workspaceRoot);
|
|
72
72
|
const destination = targetDir ?? path.join(workspaceRoot, ".agents", "skills");
|
|
73
73
|
const missing = [];
|
|
74
74
|
const invalid = [];
|
package/src/manifest.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { readFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
|
|
5
|
-
import { CLI_NAMES, DISTRIBUTION,
|
|
5
|
+
import { CLI_NAMES, DISTRIBUTION, SKILL_NAMES } from "./constants.mjs";
|
|
6
6
|
|
|
7
7
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
8
|
|
|
@@ -10,6 +10,7 @@ export const defaultManifestPath = path.join(packageRoot, "manifest.json");
|
|
|
10
10
|
|
|
11
11
|
export async function loadManifest(manifestPath = defaultManifestPath) {
|
|
12
12
|
const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
13
|
+
if (!manifest.version) manifest.version = JSON.parse(await readFile(path.join(path.dirname(manifestPath), 'package.json'), 'utf8')).version;
|
|
13
14
|
validateManifest(manifest);
|
|
14
15
|
return manifest;
|
|
15
16
|
}
|
|
@@ -50,23 +51,16 @@ export function validateManifest(manifest) {
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
for (const cli of manifest.clis) {
|
|
53
|
-
if (!cli || typeof cli.name !== "string" || typeof cli.project !== "string" || typeof cli.
|
|
54
|
-
throw new Error("DeepFlow manifest CLIs must define name, project, and
|
|
54
|
+
if (!cli || typeof cli.name !== "string" || typeof cli.project !== "string" || typeof cli.entry !== "string") {
|
|
55
|
+
throw new Error("DeepFlow manifest CLIs must define name, project, and entry");
|
|
55
56
|
}
|
|
57
|
+
if (path.isAbsolute(cli.entry) || cli.entry.split(/[\\/]/).includes('..') || !cli.entry.endsWith('.mjs')) throw new Error(`invalid Node CLI entry: ${cli.name}`);
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
export function resolveSkillSources(manifest, workspaceRoot
|
|
61
|
+
export function resolveSkillSources(manifest, workspaceRoot) {
|
|
60
62
|
return manifest.skills.map((skill) => ({
|
|
61
63
|
...skill,
|
|
62
|
-
sourcePath: path.resolve(workspaceRoot,
|
|
64
|
+
sourcePath: path.resolve(workspaceRoot, skill.source),
|
|
63
65
|
}));
|
|
64
66
|
}
|
|
65
|
-
|
|
66
|
-
export function packageManifest() {
|
|
67
|
-
return {
|
|
68
|
-
distribution: DISTRIBUTION,
|
|
69
|
-
package: PACKAGE_NAME,
|
|
70
|
-
cli: "deepflow-suite",
|
|
71
|
-
};
|
|
72
|
-
}
|