@jennie-shawn/starwork 0.1.0-alpha.20 → 0.1.0-alpha.21
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 +91 -145
- package/cli/src/cli.js +662 -111
- package/cli/test/init.test.js +456 -87
- package/core/skill-management-spec.md +15 -1
- package/docs/README.md +2 -0
- package/docs/agent-install-guide.md +49 -84
- package/docs/ai-consultant-brief.md +648 -0
- package/docs/alpha-test-guide.md +94 -41
- package/docs/cli-capabilities.html +4 -4
- package/docs/cli-skill-registry.html +39 -11
- package/docs/issue-feedback-tracking-guide.md +447 -0
- package/docs/multiagent/01-codex-session-capabilities-and-starwork-implications.md +5 -5
- package/docs/multiagent/session-control-support-matrix.md +20 -0
- package/docs/product-direction.md +3 -2
- package/docs/roadmap.md +2 -0
- package/kit-skills/starworkSpawn/SKILL.md +23 -2
- package/package.json +1 -1
- package/skills/README.md +11 -1
- package/skills/starwork/SKILL.md +81 -0
- package/skills/starwork/references/install.md +64 -0
- package/skills/starwork/references/routing.md +44 -0
- package/skills/starworkDoctor/SKILL.md +22 -1
- package/skills/starworkInit/SKILL.md +32 -1
- package/skills/starworkKnowledge/SKILL.md +22 -1
- package/skills/starworkMultiagent/SKILL.md +149 -251
- package/skills/starworkMultiagent-spec.md +81 -182
package/cli/src/cli.js
CHANGED
|
@@ -143,6 +143,13 @@ const AGENT_DOCS_PLAN_SCHEMA = "starwork.agent_docs_plan.v0.1";
|
|
|
143
143
|
const AGENT_DOCS_DRAFT_DIR = path.join(".starwork", "drafts");
|
|
144
144
|
const LEGACY_AGENT_DOC_SIDECARS = ["AGENTS.starwork.md", "AGENTS.starwork-new.md", "README.starwork-new.md", "CLAUDE.starwork.md"];
|
|
145
145
|
const MANUAL_HANDOFF_STATUS = "manual_handoff_required";
|
|
146
|
+
const HOST_DELIVERY_STATUSES = new Set([
|
|
147
|
+
"delivered",
|
|
148
|
+
"delivered_via_codex_thread_tool",
|
|
149
|
+
"recorded_only",
|
|
150
|
+
MANUAL_HANDOFF_STATUS,
|
|
151
|
+
"failed"
|
|
152
|
+
]);
|
|
146
153
|
|
|
147
154
|
const KIT_BUNDLED_SKILLS = {
|
|
148
155
|
hub: [
|
|
@@ -320,6 +327,11 @@ async function init(argv) {
|
|
|
320
327
|
})
|
|
321
328
|
: null;
|
|
322
329
|
|
|
330
|
+
if (options.json && options.dryRun) {
|
|
331
|
+
console.log(JSON.stringify(renderInitPlanJson(plan, true), null, 2));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
323
335
|
printPlan(plan, options.dryRun);
|
|
324
336
|
if (adapterHosts.length && options.dryRun) {
|
|
325
337
|
console.log("");
|
|
@@ -351,7 +363,10 @@ async function init(argv) {
|
|
|
351
363
|
applyPlan(adapterPlan);
|
|
352
364
|
}
|
|
353
365
|
console.log("");
|
|
354
|
-
console.log("StarWork
|
|
366
|
+
console.log("StarWork 工作台已经创建好了。");
|
|
367
|
+
console.log("");
|
|
368
|
+
console.log("这次写入的是项目协作文件,不是业务代码。");
|
|
369
|
+
console.log("健康检查可以确认 AI 后续能找到项目说明、当前任务和协作规则。");
|
|
355
370
|
console.log("");
|
|
356
371
|
console.log("下一步建议:");
|
|
357
372
|
console.log(`1. 运行 starwork doctor --target ${plan.targetDir}`);
|
|
@@ -361,14 +376,15 @@ async function init(argv) {
|
|
|
361
376
|
console.log("4. 创建项目后,运行 starwork audit 巡检项目中心里的项目登记。");
|
|
362
377
|
} else {
|
|
363
378
|
if (hasAgentDocsDrafts(targetDir)) {
|
|
364
|
-
console.log("2. AI
|
|
379
|
+
console.log("2. 还有一步没有自动完成:你的项目已经有 AI 规则文件,所以 StarWork 只生成了待整合草稿,没有直接覆盖原文件。");
|
|
380
|
+
console.log("3. AI 入口文档需要 Skill 整合后再生效;请用 starworkInit 读取 .starwork/drafts/agent-docs-plan.json 和 proposed 草稿,确认后再合并最终入口。");
|
|
365
381
|
} else {
|
|
366
382
|
console.log("2. 打开 AGENTS.md,确认 AI 入口规则。");
|
|
367
383
|
}
|
|
368
384
|
if (adapterHosts.length) {
|
|
369
|
-
console.log(`
|
|
385
|
+
console.log(`4. 已生成 ${adapterHosts.join(", ")} 适配入口;运行 starwork doctor --target ${plan.targetDir} --host ${adapterHosts.length === 1 ? adapterHosts[0] : "all"} 再检查一次。`);
|
|
370
386
|
} else {
|
|
371
|
-
console.log("3.
|
|
387
|
+
console.log("3. 下一步你可以用 Codex / Claude Code / Cursor 打开这个目录,让 AI 先读项目规则。");
|
|
372
388
|
}
|
|
373
389
|
}
|
|
374
390
|
}
|
|
@@ -750,8 +766,14 @@ function parseArgs(argv) {
|
|
|
750
766
|
options.sessionName = readValue(argv, ++i, arg);
|
|
751
767
|
} else if (arg === "--from") {
|
|
752
768
|
options.from = readValue(argv, ++i, arg);
|
|
769
|
+
} else if (arg === "--to") {
|
|
770
|
+
options.to = readValue(argv, ++i, arg);
|
|
753
771
|
} else if (arg === "--message") {
|
|
754
772
|
options.message = readValue(argv, ++i, arg);
|
|
773
|
+
} else if (arg === "--delivery-tool") {
|
|
774
|
+
options.deliveryTool = readValue(argv, ++i, arg);
|
|
775
|
+
} else if (arg === "--host-delivery") {
|
|
776
|
+
options.hostDelivery = readValue(argv, ++i, arg);
|
|
755
777
|
} else if (arg === "--turns") {
|
|
756
778
|
options.turns = readValue(argv, ++i, arg);
|
|
757
779
|
} else if (arg === "--timeout") {
|
|
@@ -899,6 +921,14 @@ async function lanesCommand(argv) {
|
|
|
899
921
|
await lanesLaunch(argv.slice(1));
|
|
900
922
|
return;
|
|
901
923
|
}
|
|
924
|
+
if (subcommand === "message") {
|
|
925
|
+
await lanesMessage(argv.slice(1));
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
if (subcommand === "request") {
|
|
929
|
+
await lanesRequest(argv.slice(1));
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
902
932
|
if (subcommand === "share") {
|
|
903
933
|
await lanesShare(argv.slice(1));
|
|
904
934
|
return;
|
|
@@ -1029,19 +1059,19 @@ async function lanesBind(argv) {
|
|
|
1029
1059
|
if (!options.json) {
|
|
1030
1060
|
printGenericPlan(options.dryRun ? "绑定 Lane 预览(dry run):" : "绑定 Lane 计划:", plan.actions);
|
|
1031
1061
|
if (sessionName) {
|
|
1032
|
-
console.log(
|
|
1062
|
+
console.log(`宿主会话名需由 starworkMultiagent 在 Codex App 中调用 set_thread_title 后,再由本命令记录:${sessionName}`);
|
|
1033
1063
|
console.log("");
|
|
1034
1064
|
}
|
|
1035
1065
|
if (options.pin) {
|
|
1036
|
-
console.log("
|
|
1066
|
+
console.log("宿主置顶需由 starworkMultiagent 在 Codex App 中调用 set_thread_pinned;本命令只记录 StarWork binding。");
|
|
1037
1067
|
console.log("");
|
|
1038
1068
|
}
|
|
1039
1069
|
}
|
|
1040
1070
|
if (options.dryRun) return;
|
|
1041
1071
|
await confirmOrThrow(options, `是否将当前会话绑定到 Lane ${laneId}?`);
|
|
1042
1072
|
applyPlan(plan);
|
|
1043
|
-
const sessionNameSync =
|
|
1044
|
-
const pinSync =
|
|
1073
|
+
const sessionNameSync = createSessionNameRecordOnlyResult({ sessionName });
|
|
1074
|
+
const pinSync = createHostPinRecordOnlyResult({ requested: Boolean(options.pin) });
|
|
1045
1075
|
if (options.json) {
|
|
1046
1076
|
console.log(JSON.stringify(renderLanesBindResult({
|
|
1047
1077
|
workspaceRoot,
|
|
@@ -1176,7 +1206,7 @@ function printLanesHostStatus(report) {
|
|
|
1176
1206
|
console.log(` ${item.host.adapter || "host"} observation: ${item.host.status}${item.host.name ? `;name=${item.host.name}` : ""}${item.host.cwd ? `;cwd=${item.host.cwd}` : ""}${Number.isInteger(item.host.turn_count) ? `;turns=${item.host.turn_count}` : ""}`);
|
|
1177
1207
|
if (item.host.continue_command) console.log(` Continue: ${item.host.continue_command}`);
|
|
1178
1208
|
if (item.host.status === "notLoaded") {
|
|
1179
|
-
console.log(" 说明:notLoaded 表示 thread
|
|
1209
|
+
console.log(" 说明:notLoaded 表示 thread 可能存在于历史中,但当前宿主观察接口尚未加载;可显式使用 --load。");
|
|
1180
1210
|
}
|
|
1181
1211
|
if (item.host.warning) console.log(` Warning: ${item.host.warning}`);
|
|
1182
1212
|
}
|
|
@@ -1463,73 +1493,202 @@ async function lanesLaunch(argv) {
|
|
|
1463
1493
|
launchResults.forEach((result) => console.log(`- ${result.lane}${result.session_name ? ` -> ${result.session_name}` : ""}`));
|
|
1464
1494
|
return;
|
|
1465
1495
|
}
|
|
1466
|
-
await confirmOrThrow(options,
|
|
1467
|
-
let nextRegistryLanes = registry.lanes;
|
|
1468
|
-
let lanesState = readAgentLanesState(workspaceRoot);
|
|
1496
|
+
await confirmOrThrow(options, `是否生成 ${lanes.length} 个 Codex lane launch message?`);
|
|
1469
1497
|
for (const lane of lanes) {
|
|
1470
1498
|
const sessionName = buildLaneLaunchSessionName({ lane, workspaceRoot, explicitName: options.sessionName });
|
|
1471
1499
|
const launchMessage = renderMultiagentLaunchMessage({ lane, fromLane: options.from || "user", workspaceRoot, collaboration });
|
|
1472
|
-
const launch = await launchCodexLane({ message: launchMessage, workspaceRoot, timeout: parsePositiveInt(options.timeout, 90000) });
|
|
1473
|
-
const launchedThreadId = launch.thread_id || launch.created_thread_id || "";
|
|
1474
|
-
const session = launchedThreadId ? `codex:${launchedThreadId}` : "unbound";
|
|
1475
|
-
const sessionNameSync = launchedThreadId
|
|
1476
|
-
? await renameHostSessionBestEffort({ session, sessionName })
|
|
1477
|
-
: createSessionNameSyncResult({
|
|
1478
|
-
requested: Boolean(sessionName),
|
|
1479
|
-
supported: false,
|
|
1480
|
-
status: sessionName ? "skipped" : "not_requested",
|
|
1481
|
-
name: sessionName,
|
|
1482
|
-
warning: sessionName ? "No host thread was created to rename." : null
|
|
1483
|
-
});
|
|
1484
|
-
const bindingStatus = launch.thread_id && launch.status === "completed" ? "bound" : "unbound";
|
|
1485
|
-
if (launch.thread_id && launch.status === "completed") {
|
|
1486
|
-
const pinSync = pinHostThreadBestEffort({ session, requested: Boolean(options.pin) });
|
|
1487
|
-
nextRegistryLanes = nextRegistryLanes.map((item) => item.lane === lane.lane ? { ...item, current_session: session } : item);
|
|
1488
|
-
lanesState = updateAgentLaneHostState(lanesState, lane.lane, {
|
|
1489
|
-
host: "codex",
|
|
1490
|
-
current_session: session,
|
|
1491
|
-
thread_id: launch.thread_id,
|
|
1492
|
-
session_name: sessionName,
|
|
1493
|
-
pinned: pinSync.status === "ok",
|
|
1494
|
-
pin_status: pinSync.status,
|
|
1495
|
-
created_by: "starwork multiagent launch",
|
|
1496
|
-
created_at: new Date().toISOString(),
|
|
1497
|
-
last_host_status: {
|
|
1498
|
-
type: launch.status,
|
|
1499
|
-
observed_at: new Date().toISOString()
|
|
1500
|
-
}
|
|
1501
|
-
});
|
|
1502
|
-
launch.session_name_sync = sessionNameSync;
|
|
1503
|
-
launch.pin_sync = pinSync;
|
|
1504
|
-
}
|
|
1505
1500
|
launchResults.push({
|
|
1506
1501
|
lane: lane.lane,
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
session_id: launchedThreadId || undefined,
|
|
1502
|
+
adapter: "codex",
|
|
1503
|
+
status: MANUAL_HANDOFF_STATUS,
|
|
1510
1504
|
session_name: sessionName,
|
|
1511
|
-
launch_status:
|
|
1512
|
-
rename_status:
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1505
|
+
launch_status: MANUAL_HANDOFF_STATUS,
|
|
1506
|
+
rename_status: sessionName ? "requires_starworkMultiagent_tool" : "not_requested",
|
|
1507
|
+
binding_status: "unbound",
|
|
1508
|
+
message: launchMessage,
|
|
1509
|
+
instructions: "Codex App 标准路径必须由 starworkMultiagent 直接调用 create_thread;CLI 只生成 Launch Message,不创建或绑定 Codex thread。",
|
|
1510
|
+
warning: "CLI no longer launches Codex threads directly."
|
|
1516
1511
|
});
|
|
1517
1512
|
}
|
|
1518
|
-
actions.push(...buildLanesRegistryPlan(workspaceRoot, nextRegistryLanes).actions);
|
|
1519
|
-
actions.push(stateFileAction(workspaceRoot, lanesState));
|
|
1520
|
-
applyPlan({ targetDir: workspaceRoot, actions: dedupeActions(actions) });
|
|
1521
1513
|
if (options.json) {
|
|
1522
1514
|
console.log(JSON.stringify({ schema: "starwork.agent_lanes.launch.v0.3", launches: launchResults }, null, 2));
|
|
1523
1515
|
return;
|
|
1524
1516
|
}
|
|
1525
1517
|
console.log("");
|
|
1526
1518
|
launchResults.forEach((result) => {
|
|
1527
|
-
console.log(`Lane ${result.lane}: ${result.status}${result.
|
|
1528
|
-
|
|
1529
|
-
|
|
1519
|
+
console.log(`Lane ${result.lane}: ${result.status}${result.session_name ? ` -> ${result.session_name}` : ""}${result.warning ? ` - ${result.warning}` : ""}`);
|
|
1520
|
+
console.log(" 需要在 starworkMultiagent Skill 中调用 create_thread,成功返回 threadId 后再运行 multiagent bind 记录绑定。");
|
|
1521
|
+
console.log("");
|
|
1522
|
+
console.log(result.message.trimEnd());
|
|
1530
1523
|
});
|
|
1531
1524
|
}
|
|
1532
1525
|
|
|
1526
|
+
async function lanesMessage(argv) {
|
|
1527
|
+
const subcommand = argv[0];
|
|
1528
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
1529
|
+
printLanesMessageHelp();
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
if (subcommand === "launch") {
|
|
1533
|
+
await lanesMessageLaunch(argv.slice(1));
|
|
1534
|
+
return;
|
|
1535
|
+
}
|
|
1536
|
+
if (subcommand === "instruct") {
|
|
1537
|
+
await lanesMessageInstruct(argv.slice(1));
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
throw new Error(`未知 multiagent message 子命令:${subcommand}`);
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
async function lanesMessageLaunch(argv) {
|
|
1544
|
+
const options = parseArgs(argv);
|
|
1545
|
+
if (options.help) {
|
|
1546
|
+
printLanesMessageLaunchHelp();
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
const laneId = normalizeLaneId(options._?.[0], "lane");
|
|
1550
|
+
const workspaceRoot = requireWorkspaceRoot(path.resolve(options.target || process.cwd()));
|
|
1551
|
+
const state = readWorkspaceState(workspaceRoot);
|
|
1552
|
+
const collaboration = getCollaborationPaths(state);
|
|
1553
|
+
const registry = readLanesRegistry(workspaceRoot);
|
|
1554
|
+
const lane = findLaneOrThrow(registry.lanes, laneId);
|
|
1555
|
+
const sessionName = buildLaneLaunchSessionName({ lane, workspaceRoot, explicitName: options.sessionName });
|
|
1556
|
+
const message = renderMultiagentLaunchMessage({ lane, fromLane: options.from || "user", workspaceRoot, collaboration });
|
|
1557
|
+
const payload = {
|
|
1558
|
+
schema: "starwork.agent_lanes.message.v0.1",
|
|
1559
|
+
type: "launch",
|
|
1560
|
+
lane: laneId,
|
|
1561
|
+
session_name: sessionName,
|
|
1562
|
+
message
|
|
1563
|
+
};
|
|
1564
|
+
if (options.json) {
|
|
1565
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
console.log(message.trimEnd());
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
async function lanesMessageInstruct(argv) {
|
|
1572
|
+
const options = parseArgs(argv);
|
|
1573
|
+
if (options.help) {
|
|
1574
|
+
printLanesMessageInstructHelp();
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
const toLane = normalizeLaneId(options._?.[0], "to-lane");
|
|
1578
|
+
const fromLane = normalizeLaneId(options.from || "user", "from lane");
|
|
1579
|
+
if (!options.message) throw new Error("multiagent message instruct 需要 --message <text>。");
|
|
1580
|
+
const workspaceRoot = requireWorkspaceRoot(path.resolve(options.target || process.cwd()));
|
|
1581
|
+
const { requestId, message } = buildMultiagentInstructionPayload({
|
|
1582
|
+
workspaceRoot,
|
|
1583
|
+
toLane,
|
|
1584
|
+
fromLane,
|
|
1585
|
+
text: options.message,
|
|
1586
|
+
requestId: options.id
|
|
1587
|
+
});
|
|
1588
|
+
const payload = {
|
|
1589
|
+
schema: "starwork.agent_lanes.message.v0.1",
|
|
1590
|
+
type: "instruction",
|
|
1591
|
+
request_id: requestId,
|
|
1592
|
+
from_lane: fromLane,
|
|
1593
|
+
to_lane: toLane,
|
|
1594
|
+
message
|
|
1595
|
+
};
|
|
1596
|
+
if (options.json) {
|
|
1597
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
console.log(message.trimEnd());
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
async function lanesRequest(argv) {
|
|
1604
|
+
const subcommand = argv[0];
|
|
1605
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
1606
|
+
printLanesRequestHelp();
|
|
1607
|
+
return;
|
|
1608
|
+
}
|
|
1609
|
+
if (subcommand === "record") {
|
|
1610
|
+
await lanesRequestRecord(argv.slice(1));
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
throw new Error(`未知 multiagent request 子命令:${subcommand}`);
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
async function lanesRequestRecord(argv) {
|
|
1617
|
+
const options = parseArgs(argv);
|
|
1618
|
+
if (options.help) {
|
|
1619
|
+
printLanesRequestRecordHelp();
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
const fromLane = normalizeLaneId(options.from || "user", "from lane");
|
|
1623
|
+
const toLane = normalizeLaneId(options.to || options._?.[0], "to-lane");
|
|
1624
|
+
if (!options.message) throw new Error("multiagent request record 需要 --message <text>。");
|
|
1625
|
+
const hostDelivery = normalizeHostDeliveryStatus(options.hostDelivery || options.status || "");
|
|
1626
|
+
const deliveryTool = normalizeMarkdownCell(options.deliveryTool || "manual");
|
|
1627
|
+
const workspaceRoot = requireWorkspaceRoot(path.resolve(options.target || process.cwd()));
|
|
1628
|
+
const state = readWorkspaceState(workspaceRoot);
|
|
1629
|
+
const collaboration = getCollaborationPaths(state);
|
|
1630
|
+
const registry = readLanesRegistry(workspaceRoot);
|
|
1631
|
+
if (fromLane !== "user") findLaneOrThrow(registry.lanes, fromLane);
|
|
1632
|
+
findLaneOrThrow(registry.lanes, toLane);
|
|
1633
|
+
const requestId = options.id ? normalizeMarkdownCell(options.id) : buildLaneRequestId(toLane);
|
|
1634
|
+
const shared = readSharedContext(workspaceRoot);
|
|
1635
|
+
const requestRow = buildSharedRequestRow({
|
|
1636
|
+
id: requestId,
|
|
1637
|
+
from: fromLane,
|
|
1638
|
+
to: toLane,
|
|
1639
|
+
request: options.message,
|
|
1640
|
+
status: hostDelivery,
|
|
1641
|
+
hostDelivery,
|
|
1642
|
+
link: collaboration.shared
|
|
1643
|
+
});
|
|
1644
|
+
const lanesState = readAgentLanesState(workspaceRoot);
|
|
1645
|
+
const nextState = {
|
|
1646
|
+
...lanesState,
|
|
1647
|
+
requests: [...(lanesState.requests || []), {
|
|
1648
|
+
id: requestId,
|
|
1649
|
+
from: fromLane,
|
|
1650
|
+
to: toLane,
|
|
1651
|
+
message_type: "instruction",
|
|
1652
|
+
recorded_in: collaboration.shared,
|
|
1653
|
+
host_delivery: {
|
|
1654
|
+
status: hostDelivery,
|
|
1655
|
+
delivery_tool: deliveryTool,
|
|
1656
|
+
mode: "record_only"
|
|
1657
|
+
}
|
|
1658
|
+
}]
|
|
1659
|
+
};
|
|
1660
|
+
const actions = [
|
|
1661
|
+
...buildSharedContextPlan(workspaceRoot, {
|
|
1662
|
+
outputs: shared.outputs,
|
|
1663
|
+
requests: [...shared.requests, requestRow],
|
|
1664
|
+
agreements: shared.agreements
|
|
1665
|
+
}).actions,
|
|
1666
|
+
stateFileAction(workspaceRoot, nextState)
|
|
1667
|
+
];
|
|
1668
|
+
if (!options.json) printGenericPlan(options.dryRun ? "记录跨 lane 请求预览(dry run):" : "记录跨 lane 请求计划:", actions);
|
|
1669
|
+
if (options.dryRun) {
|
|
1670
|
+
if (options.json) console.log(JSON.stringify({ schema: "starwork.agent_lanes.request_record.v0.1", dry_run: true, request: requestRow }, null, 2));
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
await confirmOrThrow(options, `是否记录 ${fromLane} -> ${toLane} 的跨 lane 请求?`);
|
|
1674
|
+
applyPlan({ targetDir: workspaceRoot, actions });
|
|
1675
|
+
const result = {
|
|
1676
|
+
schema: "starwork.agent_lanes.request_record.v0.1",
|
|
1677
|
+
request: requestRow,
|
|
1678
|
+
host_delivery: {
|
|
1679
|
+
status: hostDelivery,
|
|
1680
|
+
delivery_tool: deliveryTool,
|
|
1681
|
+
mode: "record_only"
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
if (options.json) {
|
|
1685
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
console.log("");
|
|
1689
|
+
console.log(`已记录跨 lane 请求:${requestId}`);
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1533
1692
|
async function lanesShare(argv) {
|
|
1534
1693
|
const options = parseArgs(argv);
|
|
1535
1694
|
if (options.help) {
|
|
@@ -3818,6 +3977,25 @@ function getHubStatePathsForLanguage(language = "en") {
|
|
|
3818
3977
|
};
|
|
3819
3978
|
}
|
|
3820
3979
|
|
|
3980
|
+
function resolveWorkspaceRolePath(state, role, fallbackPath) {
|
|
3981
|
+
const mappings = state?.upgrade?.core_role_mapping;
|
|
3982
|
+
if (Array.isArray(mappings)) {
|
|
3983
|
+
const mapping = mappings.find((item) => item?.role === role && item.path);
|
|
3984
|
+
if (mapping) {
|
|
3985
|
+
return {
|
|
3986
|
+
role,
|
|
3987
|
+
path: normalizeSafeRelativePath(mapping.path, `upgrade core_role_mapping.${role}`),
|
|
3988
|
+
source: "upgrade.core_role_mapping"
|
|
3989
|
+
};
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
return {
|
|
3993
|
+
role,
|
|
3994
|
+
path: normalizeRelativePath(fallbackPath),
|
|
3995
|
+
source: "default"
|
|
3996
|
+
};
|
|
3997
|
+
}
|
|
3998
|
+
|
|
3821
3999
|
function checkPackInstallations(result, workspaceRoot, state) {
|
|
3822
4000
|
if (!Array.isArray(state.packs)) return;
|
|
3823
4001
|
for (const installedPack of state.packs) {
|
|
@@ -3918,6 +4096,7 @@ function checkUpgradeRoleMappings(result, workspaceRoot, state) {
|
|
|
3918
4096
|
|
|
3919
4097
|
function checkSkillInstallations(result, workspaceRoot, state) {
|
|
3920
4098
|
const manifestPath = path.join(workspaceRoot, ".starwork", "skills.json");
|
|
4099
|
+
let manifestSkills = [];
|
|
3921
4100
|
const skills = {
|
|
3922
4101
|
project_manifest: {
|
|
3923
4102
|
exists: fs.existsSync(manifestPath),
|
|
@@ -3925,6 +4104,7 @@ function checkSkillInstallations(result, workspaceRoot, state) {
|
|
|
3925
4104
|
count: 0
|
|
3926
4105
|
},
|
|
3927
4106
|
registry: null,
|
|
4107
|
+
required: [],
|
|
3928
4108
|
mounts: []
|
|
3929
4109
|
};
|
|
3930
4110
|
result.skills = skills;
|
|
@@ -3946,7 +4126,7 @@ function checkSkillInstallations(result, workspaceRoot, state) {
|
|
|
3946
4126
|
} else {
|
|
3947
4127
|
addCheck(result, "skills.project_manifest.schema", "fail", "项目 Skill 清单 schema 不正确。", ".starwork/skills.json");
|
|
3948
4128
|
}
|
|
3949
|
-
|
|
4129
|
+
manifestSkills = Array.isArray(manifest.skills) ? manifest.skills : [];
|
|
3950
4130
|
skills.project_manifest.count = manifestSkills.length;
|
|
3951
4131
|
for (const skill of manifestSkills) {
|
|
3952
4132
|
if (!skill?.id) {
|
|
@@ -3975,40 +4155,135 @@ function checkSkillInstallations(result, workspaceRoot, state) {
|
|
|
3975
4155
|
|
|
3976
4156
|
if (state.workspace_type === "hub") {
|
|
3977
4157
|
const hubPaths = getHubPaths(state);
|
|
3978
|
-
const
|
|
4158
|
+
const skillsRoot = resolveWorkspaceRolePath(state, "skills", hubPaths.formalSkills);
|
|
4159
|
+
const registryRelativePath = path.join(skillsRoot.path, "registry.json");
|
|
3979
4160
|
const registryPath = path.join(workspaceRoot, registryRelativePath);
|
|
3980
4161
|
skills.registry = {
|
|
3981
4162
|
exists: fs.existsSync(registryPath),
|
|
3982
4163
|
path: registryRelativePath,
|
|
4164
|
+
path_source: skillsRoot.source,
|
|
4165
|
+
role: skillsRoot.role,
|
|
3983
4166
|
count: 0
|
|
3984
4167
|
};
|
|
3985
4168
|
if (!skills.registry.exists) {
|
|
3986
4169
|
addCheck(result, "skills.registry.exists", "warn", "项目中心缺少托管 Skill 注册表。", registryRelativePath);
|
|
3987
|
-
return;
|
|
3988
|
-
}
|
|
3989
|
-
addCheck(result, "skills.registry.exists", "pass", "Project Center skill registry exists", registryRelativePath);
|
|
3990
|
-
let registry;
|
|
3991
|
-
try {
|
|
3992
|
-
registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
|
|
3993
|
-
} catch (error) {
|
|
3994
|
-
addCheck(result, "skills.registry.parse", "fail", `无法解析项目中心 Skill registry:${error.message}`, registryRelativePath);
|
|
3995
|
-
return;
|
|
3996
|
-
}
|
|
3997
|
-
if (registry.schema === "starwork.skill_registry.v0.1") {
|
|
3998
|
-
addCheck(result, "skills.registry.schema", "pass", "Project Center skill registry schema is valid", registryRelativePath);
|
|
3999
4170
|
} else {
|
|
4000
|
-
addCheck(result, "skills.registry.
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4171
|
+
addCheck(result, "skills.registry.exists", "pass", "Project Center skill registry exists", registryRelativePath);
|
|
4172
|
+
let registry;
|
|
4173
|
+
try {
|
|
4174
|
+
registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
|
|
4175
|
+
} catch (error) {
|
|
4176
|
+
addCheck(result, "skills.registry.parse", "fail", `无法解析项目中心 Skill registry:${error.message}`, registryRelativePath);
|
|
4177
|
+
registry = null;
|
|
4178
|
+
}
|
|
4179
|
+
if (registry) {
|
|
4180
|
+
if (registry.schema === "starwork.skill_registry.v0.1") {
|
|
4181
|
+
addCheck(result, "skills.registry.schema", "pass", "Project Center skill registry schema is valid", registryRelativePath);
|
|
4182
|
+
} else {
|
|
4183
|
+
addCheck(result, "skills.registry.schema", "fail", "项目中心 Skill registry schema 不正确。", registryRelativePath);
|
|
4184
|
+
}
|
|
4185
|
+
const registrySkills = Array.isArray(registry.skills) ? registry.skills : [];
|
|
4186
|
+
skills.registry.count = registrySkills.length;
|
|
4187
|
+
for (const skill of registrySkills) {
|
|
4188
|
+
if (!skill?.id) {
|
|
4189
|
+
addCheck(result, "skills.registry.id.exists", "fail", "项目中心 Skill registry 中存在缺少 id 的条目。", registryRelativePath);
|
|
4190
|
+
continue;
|
|
4191
|
+
}
|
|
4192
|
+
checkPathExists(result, workspaceRoot, path.join(skillsRoot.path, skill.id), "skills.registry.source.exists", `Project Center skill source exists: ${skill.id}`, `项目中心托管 Skill 缺少目录:${path.join(skillsRoot.path, skill.id)}`);
|
|
4193
|
+
}
|
|
4008
4194
|
}
|
|
4009
|
-
checkPathExists(result, workspaceRoot, path.join(hubPaths.formalSkills, skill.id), "skills.registry.source.exists", `Project Center skill source exists: ${skill.id}`, `项目中心托管 Skill 缺少目录:${path.join(hubPaths.formalSkills, skill.id)}`);
|
|
4010
4195
|
}
|
|
4011
4196
|
}
|
|
4197
|
+
|
|
4198
|
+
checkRequiredSkillInstallations(result, workspaceRoot, state, manifestSkills);
|
|
4199
|
+
}
|
|
4200
|
+
|
|
4201
|
+
function collectRequiredSkills(state) {
|
|
4202
|
+
if (state.workspace_type === "hub" && state.kit === "hub") {
|
|
4203
|
+
return (KIT_BUNDLED_SKILLS.hub || []).map((skill) => ({
|
|
4204
|
+
...skill,
|
|
4205
|
+
required_by: "kit:hub"
|
|
4206
|
+
}));
|
|
4207
|
+
}
|
|
4208
|
+
return [];
|
|
4209
|
+
}
|
|
4210
|
+
|
|
4211
|
+
function checkRequiredSkillInstallations(result, workspaceRoot, state, manifestSkills) {
|
|
4212
|
+
const requiredSkills = collectRequiredSkills(state);
|
|
4213
|
+
if (!requiredSkills.length) return;
|
|
4214
|
+
|
|
4215
|
+
const hubPaths = getHubPaths(state);
|
|
4216
|
+
const skillsRoot = resolveWorkspaceRolePath(state, "skills", hubPaths.formalSkills);
|
|
4217
|
+
const manifestIds = new Set(manifestSkills.map((skill) => skill?.id).filter(Boolean));
|
|
4218
|
+
const reports = [];
|
|
4219
|
+
|
|
4220
|
+
for (const required of requiredSkills) {
|
|
4221
|
+
const sourcePath = path.join(skillsRoot.path, required.id);
|
|
4222
|
+
const skillFilePath = path.join(sourcePath, "SKILL.md");
|
|
4223
|
+
const sourceExists = fs.existsSync(path.join(workspaceRoot, skillFilePath));
|
|
4224
|
+
const frontmatter = sourceExists
|
|
4225
|
+
? parseSkillFrontmatter(fs.readFileSync(path.join(workspaceRoot, skillFilePath), "utf8"))
|
|
4226
|
+
: {};
|
|
4227
|
+
const frontmatterOk = sourceExists && Boolean(frontmatter.name) && Boolean(frontmatter.description);
|
|
4228
|
+
const mounts = (required.install || [])
|
|
4229
|
+
.filter((install) => install.agent !== "hub")
|
|
4230
|
+
.map((install) => {
|
|
4231
|
+
const mountPath = normalizeRelativePath(install.path);
|
|
4232
|
+
return {
|
|
4233
|
+
agent: install.agent,
|
|
4234
|
+
path: mountPath,
|
|
4235
|
+
status: fs.existsSync(path.join(workspaceRoot, mountPath)) ? "ok" : "missing"
|
|
4236
|
+
};
|
|
4237
|
+
});
|
|
4238
|
+
const manifestStatus = manifestIds.has(required.id) ? "ok" : "missing";
|
|
4239
|
+
const missingMount = mounts.some((mount) => mount.status !== "ok");
|
|
4240
|
+
let status = "ok";
|
|
4241
|
+
if (!sourceExists) {
|
|
4242
|
+
status = "missing_source";
|
|
4243
|
+
} else if (!frontmatterOk) {
|
|
4244
|
+
status = "invalid_frontmatter";
|
|
4245
|
+
} else if (manifestStatus !== "ok") {
|
|
4246
|
+
status = "missing_manifest";
|
|
4247
|
+
} else if (missingMount) {
|
|
4248
|
+
status = "missing_mount";
|
|
4249
|
+
}
|
|
4250
|
+
|
|
4251
|
+
const report = {
|
|
4252
|
+
id: required.id,
|
|
4253
|
+
required_by: required.required_by,
|
|
4254
|
+
status,
|
|
4255
|
+
source: {
|
|
4256
|
+
path: sourcePath,
|
|
4257
|
+
status: sourceExists ? "ok" : "missing",
|
|
4258
|
+
path_source: skillsRoot.source
|
|
4259
|
+
},
|
|
4260
|
+
manifest: {
|
|
4261
|
+
path: ".starwork/skills.json",
|
|
4262
|
+
status: manifestStatus
|
|
4263
|
+
},
|
|
4264
|
+
mounts,
|
|
4265
|
+
frontmatter: {
|
|
4266
|
+
path: skillFilePath,
|
|
4267
|
+
status: frontmatterOk ? "ok" : (sourceExists ? "invalid" : "missing")
|
|
4268
|
+
},
|
|
4269
|
+
repair_hint: "这是 Hub Kit 自带 Skill。请重新运行受控的 StarWork Hub Kit 安装/同步流程,或按文档补齐项目中心内的 Kit Skill;不要把它安装成全局系统 Skill。"
|
|
4270
|
+
};
|
|
4271
|
+
reports.push(report);
|
|
4272
|
+
|
|
4273
|
+
if (status === "ok") {
|
|
4274
|
+
addCheck(result, `skills.required.${slugifyCheckId(required.id)}`, "pass", `Hub required Skill is installed: ${required.id}`, sourcePath);
|
|
4275
|
+
} else {
|
|
4276
|
+
const missingMounts = mounts.filter((mount) => mount.status !== "ok").map((mount) => mount.path);
|
|
4277
|
+
const details = [
|
|
4278
|
+
`应在项目中心内存在:${sourcePath}`,
|
|
4279
|
+
`应登记在:.starwork/skills.json`,
|
|
4280
|
+
...missingMounts.map((mountPath) => `应挂载给宿主:${mountPath}`)
|
|
4281
|
+
].join(";");
|
|
4282
|
+
addCheck(result, `skills.required.${slugifyCheckId(required.id)}`, "warn", `缺少 Hub 自带 Skill:${required.id}。${details}。建议:重新运行受控的 StarWork Hub Kit 安装/同步流程,或按文档补齐项目中心内的 Kit Skill。不要把它安装成全局系统 Skill。`, sourcePath);
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
result.skills.required = reports;
|
|
4012
4287
|
}
|
|
4013
4288
|
|
|
4014
4289
|
function checkPathExists(result, workspaceRoot, relativePath, id, passMessage, failMessage) {
|
|
@@ -4733,7 +5008,9 @@ function friendlyCheckLevel(level) {
|
|
|
4733
5008
|
}
|
|
4734
5009
|
|
|
4735
5010
|
function friendlyDoctorMessage(message) {
|
|
4736
|
-
|
|
5011
|
+
const text = String(message || "");
|
|
5012
|
+
if (text.includes("缺少 Hub 自带 Skill")) return text;
|
|
5013
|
+
return text
|
|
4737
5014
|
.replace(/这是一个可升级的历史模板工作区,但缺少 \.starwork\/workspace\.json。/g, "这个目录像旧版工作区,但还缺少 StarWork 工作台身份证(.starwork/workspace.json)。")
|
|
4738
5015
|
.replace(/检测到历史模板升级候选,置信度:(?:high|medium|low)。/g, "这个目录像旧版工作区,可以进一步判断如何整理。")
|
|
4739
5016
|
.replace(/检测到主库 \/ Hub 候选,置信度:(?:high|medium|low)。/g, "这个目录像项目中心,可以进一步判断如何接入 StarWork。")
|
|
@@ -4819,10 +5096,17 @@ async function choosePack(workspaceType, workspaceConfig, options) {
|
|
|
4819
5096
|
function printInitIntro(options, targetDir) {
|
|
4820
5097
|
if (options.yes || !process.stdin.isTTY) return;
|
|
4821
5098
|
console.log("");
|
|
4822
|
-
console.log("StarWork
|
|
5099
|
+
console.log("可以,我先简单说清楚 StarWork 在做什么。");
|
|
5100
|
+
console.log("");
|
|
5101
|
+
console.log("StarWork 是给 AI 协作准备的项目工作台。它会把项目说明、当前任务、协作规则、交接记录和健康检查入口放到固定位置,让 AI 每次进入项目时不用从零猜上下文。");
|
|
5102
|
+
console.log("");
|
|
5103
|
+
console.log("这次我会带你做三件事:");
|
|
5104
|
+
console.log("1. 确认这个工作台服务哪个项目;");
|
|
5105
|
+
console.log("2. 预览 StarWork 准备补哪些协作文件;");
|
|
5106
|
+
console.log("3. 你确认后再正式写入,并做一次检查。");
|
|
4823
5107
|
console.log("");
|
|
4824
5108
|
console.log(`目标目录:${targetDir}`);
|
|
4825
|
-
console.log("
|
|
5109
|
+
console.log("我会先预览,不会直接改你的业务代码,也不会直接覆盖已有 AI 规则文件。");
|
|
4826
5110
|
}
|
|
4827
5111
|
|
|
4828
5112
|
async function chooseLanguage(options) {
|
|
@@ -7345,6 +7629,31 @@ ${message}
|
|
|
7345
7629
|
`;
|
|
7346
7630
|
}
|
|
7347
7631
|
|
|
7632
|
+
function buildMultiagentInstructionPayload({ workspaceRoot, toLane, fromLane, text, requestId = "" }) {
|
|
7633
|
+
const state = readWorkspaceState(workspaceRoot);
|
|
7634
|
+
const collaboration = getCollaborationPaths(state);
|
|
7635
|
+
const registry = readLanesRegistry(workspaceRoot);
|
|
7636
|
+
const targetLane = findLaneOrThrow(registry.lanes, toLane);
|
|
7637
|
+
if (fromLane !== "user") findLaneOrThrow(registry.lanes, fromLane);
|
|
7638
|
+
const resolvedRequestId = normalizeMarkdownCell(requestId || buildLaneRequestId(toLane));
|
|
7639
|
+
return {
|
|
7640
|
+
requestId: resolvedRequestId,
|
|
7641
|
+
fromLane,
|
|
7642
|
+
toLane,
|
|
7643
|
+
targetLane,
|
|
7644
|
+
collaboration,
|
|
7645
|
+
message: renderMultiagentInstructionMessage({
|
|
7646
|
+
requestId: resolvedRequestId,
|
|
7647
|
+
fromLane,
|
|
7648
|
+
toLane,
|
|
7649
|
+
message: text,
|
|
7650
|
+
collaboration,
|
|
7651
|
+
targetLane,
|
|
7652
|
+
workspaceRoot
|
|
7653
|
+
})
|
|
7654
|
+
};
|
|
7655
|
+
}
|
|
7656
|
+
|
|
7348
7657
|
function parseLaneList(value) {
|
|
7349
7658
|
return String(value || "")
|
|
7350
7659
|
.split(",")
|
|
@@ -7355,11 +7664,53 @@ function parseLaneList(value) {
|
|
|
7355
7664
|
|
|
7356
7665
|
function buildLaneLaunchSessionName({ lane, workspaceRoot, explicitName }) {
|
|
7357
7666
|
const requestedName = normalizeMarkdownCell(explicitName || "");
|
|
7358
|
-
if (requestedName) return requestedName;
|
|
7359
|
-
const roleName =
|
|
7667
|
+
if (requestedName) return sanitizeLaneSessionName(requestedName);
|
|
7668
|
+
const roleName = deriveLaneRoleName(lane);
|
|
7360
7669
|
return normalizeMarkdownCell(`${roleName || "Agent"} Agent`);
|
|
7361
7670
|
}
|
|
7362
7671
|
|
|
7672
|
+
function deriveLaneRoleName(lane) {
|
|
7673
|
+
const fallback = humanizeLaneId(lane?.lane || "agent");
|
|
7674
|
+
const purpose = normalizeMarkdownCell(lane?.purpose || "");
|
|
7675
|
+
if (/^(根据|只负责|用于)\s*/u.test(purpose)) {
|
|
7676
|
+
return sanitizeLaneRoleName(fallback);
|
|
7677
|
+
}
|
|
7678
|
+
let roleName = purpose;
|
|
7679
|
+
roleName = roleName.split(/[::。;;,,、\n\r]/)[0].trim();
|
|
7680
|
+
roleName = roleName.replace(/^(只负责|主要负责|负责|用于|协助|根据)\s*/u, "").trim();
|
|
7681
|
+
if (!roleName || /^根据\s*/u.test(roleName) || /^(SPEC|spec|需求|任务)\b/.test(roleName)) {
|
|
7682
|
+
roleName = fallback;
|
|
7683
|
+
}
|
|
7684
|
+
roleName = roleName.replace(/^(生成|实现|维护|处理)\s+/u, "").trim() || fallback;
|
|
7685
|
+
return sanitizeLaneRoleName(roleName);
|
|
7686
|
+
}
|
|
7687
|
+
|
|
7688
|
+
function humanizeLaneId(laneId) {
|
|
7689
|
+
const normalized = normalizeMarkdownCell(laneId || "agent");
|
|
7690
|
+
const parts = normalized.split(/[-_]+/).filter(Boolean);
|
|
7691
|
+
if (!parts.length) return "Agent";
|
|
7692
|
+
return parts.map((part) => {
|
|
7693
|
+
if (/^[a-z]+$/i.test(part)) return `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`;
|
|
7694
|
+
return part;
|
|
7695
|
+
}).join(" ");
|
|
7696
|
+
}
|
|
7697
|
+
|
|
7698
|
+
function sanitizeLaneRoleName(value) {
|
|
7699
|
+
const sanitized = normalizeMarkdownCell(value)
|
|
7700
|
+
.replace(/[::。;;,,、\n\r].*$/u, "")
|
|
7701
|
+
.replace(/\s+/g, " ")
|
|
7702
|
+
.trim();
|
|
7703
|
+
return sanitized.slice(0, 24).trim() || "Agent";
|
|
7704
|
+
}
|
|
7705
|
+
|
|
7706
|
+
function sanitizeLaneSessionName(value) {
|
|
7707
|
+
const sanitized = normalizeMarkdownCell(value)
|
|
7708
|
+
.replace(/[\n\r]/g, " ")
|
|
7709
|
+
.replace(/\s+/g, " ")
|
|
7710
|
+
.trim();
|
|
7711
|
+
return sanitized.slice(0, 40).trim();
|
|
7712
|
+
}
|
|
7713
|
+
|
|
7363
7714
|
function resolveLaneLaunchHost({ options, lanes, workspaceRoot }) {
|
|
7364
7715
|
const explicit = typeof options.host === "string" ? options.host : (options.agent || options.adapter || "");
|
|
7365
7716
|
if (explicit) return normalizeAdapterHost(explicit);
|
|
@@ -7483,7 +7834,10 @@ async function observeHostSession(session, options = {}) {
|
|
|
7483
7834
|
};
|
|
7484
7835
|
}
|
|
7485
7836
|
if (parsed.host === "codex") {
|
|
7486
|
-
return
|
|
7837
|
+
return observeManualHostSession(parsed, {
|
|
7838
|
+
status: "use_starworkMultiagent_tool",
|
|
7839
|
+
warning: "Codex lane reading must be performed by starworkMultiagent with read_thread in the Codex App; CLI only reports the StarWork binding."
|
|
7840
|
+
});
|
|
7487
7841
|
}
|
|
7488
7842
|
if (parsed.host === "claude-code") {
|
|
7489
7843
|
return observeClaudeCodeSession(parsed, options);
|
|
@@ -8013,7 +8367,9 @@ function probeHostStandardSendCapability(host) {
|
|
|
8013
8367
|
host,
|
|
8014
8368
|
available: false,
|
|
8015
8369
|
mode: null,
|
|
8016
|
-
warning:
|
|
8370
|
+
warning: host === "codex"
|
|
8371
|
+
? "Codex delivery must be performed by starworkMultiagent with send_message_to_thread in the Codex App; CLI only records state and manual fallback."
|
|
8372
|
+
: `${host} standard background delivery capability is not available in this CLI runtime; low-level turn APIs are not used for multiagent instruct.`
|
|
8017
8373
|
};
|
|
8018
8374
|
}
|
|
8019
8375
|
|
|
@@ -8122,6 +8478,17 @@ function createSessionNameSyncResult({ requested, supported, status, name = "",
|
|
|
8122
8478
|
};
|
|
8123
8479
|
}
|
|
8124
8480
|
|
|
8481
|
+
function createSessionNameRecordOnlyResult({ sessionName }) {
|
|
8482
|
+
const requested = Boolean(sessionName);
|
|
8483
|
+
return createSessionNameSyncResult({
|
|
8484
|
+
requested,
|
|
8485
|
+
supported: false,
|
|
8486
|
+
status: requested ? "requires_starworkMultiagent_tool" : "not_requested",
|
|
8487
|
+
name: sessionName,
|
|
8488
|
+
warning: requested ? "Codex host title changes must be performed by starworkMultiagent with set_thread_title; CLI bind only records StarWork state." : null
|
|
8489
|
+
});
|
|
8490
|
+
}
|
|
8491
|
+
|
|
8125
8492
|
function printSessionNameSyncResult(result) {
|
|
8126
8493
|
if (!result.requested) return;
|
|
8127
8494
|
if (result.status === "ok") {
|
|
@@ -8142,6 +8509,15 @@ function createHostPinResult({ requested, supported, status, warning = null }) {
|
|
|
8142
8509
|
};
|
|
8143
8510
|
}
|
|
8144
8511
|
|
|
8512
|
+
function createHostPinRecordOnlyResult({ requested }) {
|
|
8513
|
+
return createHostPinResult({
|
|
8514
|
+
requested: Boolean(requested),
|
|
8515
|
+
supported: false,
|
|
8516
|
+
status: requested ? "requires_starworkMultiagent_tool" : "not_requested",
|
|
8517
|
+
warning: requested ? "Codex host pinning must be performed by starworkMultiagent with set_thread_pinned; CLI bind only records StarWork state." : null
|
|
8518
|
+
});
|
|
8519
|
+
}
|
|
8520
|
+
|
|
8145
8521
|
function pinHostThreadBestEffort({ session, requested }) {
|
|
8146
8522
|
if (!requested) {
|
|
8147
8523
|
return createHostPinResult({ requested: false, supported: false, status: "not_requested" });
|
|
@@ -8765,6 +9141,14 @@ function buildSharedRequestRow({ id, from, to, request, status, hostDelivery, li
|
|
|
8765
9141
|
};
|
|
8766
9142
|
}
|
|
8767
9143
|
|
|
9144
|
+
function normalizeHostDeliveryStatus(value) {
|
|
9145
|
+
const normalized = normalizeMarkdownCell(value || "");
|
|
9146
|
+
if (!HOST_DELIVERY_STATUSES.has(normalized)) {
|
|
9147
|
+
throw new Error(`--host-delivery 必须是 ${[...HOST_DELIVERY_STATUSES].join(" / ")}。`);
|
|
9148
|
+
}
|
|
9149
|
+
return normalized;
|
|
9150
|
+
}
|
|
9151
|
+
|
|
8768
9152
|
function parsePositiveInt(value, fallback) {
|
|
8769
9153
|
if (value == null || value === "") return fallback;
|
|
8770
9154
|
const parsed = Number.parseInt(value, 10);
|
|
@@ -9022,12 +9406,16 @@ function printGenericPlan(title, actions) {
|
|
|
9022
9406
|
}
|
|
9023
9407
|
|
|
9024
9408
|
function printPlan(plan, dryRun) {
|
|
9025
|
-
const creates = plan.actions.filter(
|
|
9026
|
-
const
|
|
9409
|
+
const creates = plan.actions.filter(actionCreatesFromUserView);
|
|
9410
|
+
const updates = plan.actions.filter(actionUpdatesFromUserView);
|
|
9027
9411
|
const createNew = plan.actions.filter((action) => action.mode === "create-new");
|
|
9412
|
+
const isExistingProject = isExistingProjectTarget(plan);
|
|
9028
9413
|
|
|
9029
9414
|
console.log("");
|
|
9030
9415
|
console.log(dryRun ? "创建工作台预览:" : "创建工作台计划:");
|
|
9416
|
+
if (dryRun) {
|
|
9417
|
+
console.log("这是预览,不会写入文件。");
|
|
9418
|
+
}
|
|
9031
9419
|
console.log("");
|
|
9032
9420
|
console.log(`工作台名称:${plan.workspaceName}`);
|
|
9033
9421
|
console.log(`工作台类型:${plan.workspaceLabel}`);
|
|
@@ -9045,23 +9433,107 @@ function printPlan(plan, dryRun) {
|
|
|
9045
9433
|
console.log(`会带上的 AI 使用说明:${plan.skills.map((skill) => skill.id).join("、")}`);
|
|
9046
9434
|
}
|
|
9047
9435
|
console.log("");
|
|
9436
|
+
if (isExistingProject) {
|
|
9437
|
+
console.log("检测到这是已有项目。");
|
|
9438
|
+
console.log("StarWork 会保留现有文件,先生成待整合草稿,不直接覆盖已有 AI 规则文件。");
|
|
9439
|
+
console.log("");
|
|
9440
|
+
}
|
|
9048
9441
|
|
|
9442
|
+
console.log("会创建:");
|
|
9049
9443
|
if (creates.length) {
|
|
9050
|
-
console.log(
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
console.log("");
|
|
9444
|
+
creates.forEach((action) => console.log(`- ${action.relativePath}`));
|
|
9445
|
+
} else {
|
|
9446
|
+
console.log("- 无");
|
|
9054
9447
|
}
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
9448
|
+
console.log("");
|
|
9449
|
+
|
|
9450
|
+
console.log("会更新:");
|
|
9451
|
+
if (updates.length) {
|
|
9452
|
+
updates.forEach((action) => console.log(`- ${action.relativePath}`));
|
|
9453
|
+
} else {
|
|
9454
|
+
console.log("- 无");
|
|
9059
9455
|
}
|
|
9456
|
+
console.log("");
|
|
9457
|
+
|
|
9458
|
+
console.log("不会改动:");
|
|
9459
|
+
console.log("- 你的业务代码");
|
|
9460
|
+
console.log("- 已有非空 AI 规则文件");
|
|
9060
9461
|
if (createNew.length) {
|
|
9061
|
-
console.log("
|
|
9462
|
+
console.log("- 已有同名文件会保留,StarWork 会另存旁路文件");
|
|
9062
9463
|
createNew.forEach((action) => console.log(`- ${path.relative(plan.targetDir, action.originalTarget)} -> ${action.relativePath}`));
|
|
9063
|
-
console.log("");
|
|
9064
9464
|
}
|
|
9465
|
+
console.log("");
|
|
9466
|
+
|
|
9467
|
+
console.log("需要你确认:");
|
|
9468
|
+
console.log("- 目标路径是否正确");
|
|
9469
|
+
console.log("- 是否接受这些 StarWork 协作文件");
|
|
9470
|
+
console.log("");
|
|
9471
|
+
}
|
|
9472
|
+
|
|
9473
|
+
function renderInitPlanJson(plan, dryRun) {
|
|
9474
|
+
return {
|
|
9475
|
+
schema: "starwork.init.plan_result.v0.1",
|
|
9476
|
+
target: plan.targetDir,
|
|
9477
|
+
dry_run: Boolean(dryRun),
|
|
9478
|
+
ok: true,
|
|
9479
|
+
workspace_type: plan.workspaceType,
|
|
9480
|
+
kit: plan.kit,
|
|
9481
|
+
language: plan.language,
|
|
9482
|
+
pack: plan.pack?.id || null,
|
|
9483
|
+
actions: plan.actions.map((action) => ({
|
|
9484
|
+
type: action.type,
|
|
9485
|
+
mode: action.mode,
|
|
9486
|
+
path: action.relativePath,
|
|
9487
|
+
status: action.mode === "exists" ? "exists" : "planned"
|
|
9488
|
+
})),
|
|
9489
|
+
user_summary: buildInitUserSummary(plan, dryRun)
|
|
9490
|
+
};
|
|
9491
|
+
}
|
|
9492
|
+
|
|
9493
|
+
function buildInitUserSummary(plan, dryRun) {
|
|
9494
|
+
const willCreate = plan.actions
|
|
9495
|
+
.filter(actionCreatesFromUserView)
|
|
9496
|
+
.map((action) => action.relativePath);
|
|
9497
|
+
const willUpdate = plan.actions
|
|
9498
|
+
.filter(actionUpdatesFromUserView)
|
|
9499
|
+
.map((action) => action.relativePath);
|
|
9500
|
+
return {
|
|
9501
|
+
product_purpose: "把项目整理成 AI 协作工作台",
|
|
9502
|
+
mode: dryRun ? "preview_no_write" : "write_after_confirmation",
|
|
9503
|
+
target_kind: isExistingProjectTarget(plan) ? "existing_project" : "new_workspace",
|
|
9504
|
+
will_create: willCreate,
|
|
9505
|
+
will_update: willUpdate,
|
|
9506
|
+
will_not_touch: [
|
|
9507
|
+
"你的业务代码",
|
|
9508
|
+
"已有非空 AI 规则文件"
|
|
9509
|
+
],
|
|
9510
|
+
needs_confirmation: [
|
|
9511
|
+
"目标路径是否正确",
|
|
9512
|
+
"是否接受这些 StarWork 协作文件"
|
|
9513
|
+
]
|
|
9514
|
+
};
|
|
9515
|
+
}
|
|
9516
|
+
|
|
9517
|
+
function actionCreatesFromUserView(action) {
|
|
9518
|
+
if (!action || action.mode === "exists" || action.mode === "skip") return false;
|
|
9519
|
+
if (action.type === "directory") return action.mode === "create";
|
|
9520
|
+
if (action.type === "symlink") return action.mode === "create" && !fs.existsSync(action.target);
|
|
9521
|
+
if (action.type !== "file") return false;
|
|
9522
|
+
if (action.mode === "create" || action.mode === "create-new") return true;
|
|
9523
|
+
if (action.mode === "overwrite" || action.mode === "overwrite-empty") return !fs.existsSync(action.target);
|
|
9524
|
+
return false;
|
|
9525
|
+
}
|
|
9526
|
+
|
|
9527
|
+
function actionUpdatesFromUserView(action) {
|
|
9528
|
+
if (!action || action.type !== "file") return false;
|
|
9529
|
+
if (action.mode !== "overwrite" && action.mode !== "overwrite-empty") return false;
|
|
9530
|
+
return fs.existsSync(action.target);
|
|
9531
|
+
}
|
|
9532
|
+
|
|
9533
|
+
function isExistingProjectTarget(plan) {
|
|
9534
|
+
if (!plan.targetExists || !fs.existsSync(plan.targetDir)) return false;
|
|
9535
|
+
const entries = fs.readdirSync(plan.targetDir).filter((entry) => entry !== ".DS_Store");
|
|
9536
|
+
return entries.length > 0;
|
|
9065
9537
|
}
|
|
9066
9538
|
|
|
9067
9539
|
function printSpawnPlan(plan, dryRun) {
|
|
@@ -9297,12 +9769,13 @@ function printInitHelp() {
|
|
|
9297
9769
|
Usage:
|
|
9298
9770
|
starwork init [options]
|
|
9299
9771
|
|
|
9300
|
-
|
|
9301
|
-
|
|
9772
|
+
starwork init 会把一个目录整理成 StarWork 工作台,让 AI 能找到项目说明、当前任务、协作规则和交接记录。
|
|
9773
|
+
|
|
9774
|
+
项目工作台默认加入通用工作能力;项目中心会自动加入项目中心管理能力。
|
|
9302
9775
|
|
|
9303
9776
|
Options:
|
|
9304
9777
|
--type <project|hub>
|
|
9305
|
-
project 创建项目工作台;hub
|
|
9778
|
+
project 创建项目工作台;hub 创建项目中心。
|
|
9306
9779
|
--pack <general|content-creator|hub-management|path>
|
|
9307
9780
|
--language <zh|en>
|
|
9308
9781
|
--name <name>
|
|
@@ -9313,11 +9786,13 @@ Options:
|
|
|
9313
9786
|
--adapter <codex|claude-code|cursor|trae|all>
|
|
9314
9787
|
初始化完成后继续生成对应 AI 工具适配入口。
|
|
9315
9788
|
--agent-docs <draft|skip|write>
|
|
9316
|
-
AI
|
|
9789
|
+
已有 AI 规则文件时,先生成待整合草稿,不覆盖原文件。
|
|
9317
9790
|
--target <path>
|
|
9318
9791
|
--dry-run
|
|
9792
|
+
预览将要写入的文件,不做真实改动。
|
|
9319
9793
|
--no-skills
|
|
9320
9794
|
--yes, -y
|
|
9795
|
+
确认执行,会真实写入 StarWork 工作台文件。
|
|
9321
9796
|
|
|
9322
9797
|
示例:
|
|
9323
9798
|
starwork init --type project --pack general --language zh --target ./my-workspace --yes
|
|
@@ -9533,7 +10008,7 @@ function printLanesHelp() {
|
|
|
9533
10008
|
console.log(`StarWork Multiagent
|
|
9534
10009
|
|
|
9535
10010
|
Usage:
|
|
9536
|
-
starwork multiagent <init|add|bind|release|status|read|instruct|handoff|continue|launch|share> [options]
|
|
10011
|
+
starwork multiagent <init|add|bind|release|status|read|instruct|handoff|continue|launch|message|request|share> [options]
|
|
9537
10012
|
|
|
9538
10013
|
Agent Lanes 用于同一项目内多个 Agent 会话按项目自定义职责位协作。
|
|
9539
10014
|
|
|
@@ -9547,7 +10022,9 @@ Subcommands:
|
|
|
9547
10022
|
instruct 向另一个 lane 发送格式化跨会话指令。
|
|
9548
10023
|
handoff 生成并记录人工交付消息,不后台发送。
|
|
9549
10024
|
continue 输出继续某个 lane 宿主会话的人工命令或步骤。
|
|
9550
|
-
launch
|
|
10025
|
+
launch 旧入口:为 Codex 生成 Launch Message;实际创建由 starworkMultiagent 调用 create_thread。
|
|
10026
|
+
message 生成标准 launch / instruction 消息模板,不写状态。
|
|
10027
|
+
request 记录已由 Skill 或人工完成的跨 lane 请求投递状态。
|
|
9551
10028
|
share 登记一个跨 lane 可读输出。
|
|
9552
10029
|
|
|
9553
10030
|
示例:
|
|
@@ -9555,7 +10032,8 @@ Subcommands:
|
|
|
9555
10032
|
starwork multiagent add review --purpose "审校和风险检查" --write "reviews/**,product/docs/**" --target ./my-workspace --yes
|
|
9556
10033
|
starwork multiagent bind research --session codex:manual-research-1 --session-name "Research Agent" --target ./my-workspace --yes
|
|
9557
10034
|
starwork multiagent status --host --target ./my-workspace --json
|
|
9558
|
-
starwork multiagent instruct development --from product-planning --message "请根据 SPEC 开始实现。" --target ./my-workspace --
|
|
10035
|
+
starwork multiagent message instruct development --from product-planning --message "请根据 SPEC 开始实现。" --target ./my-workspace --json
|
|
10036
|
+
starwork multiagent request record --from product-planning --to development --message "请根据 SPEC 开始实现。" --host-delivery delivered_via_codex_thread_tool --delivery-tool send_message_to_thread --target ./my-workspace --yes
|
|
9559
10037
|
`);
|
|
9560
10038
|
}
|
|
9561
10039
|
|
|
@@ -9608,9 +10086,8 @@ Options:
|
|
|
9608
10086
|
--yes, -y
|
|
9609
10087
|
|
|
9610
10088
|
说明:
|
|
9611
|
-
|
|
9612
|
-
--pin
|
|
9613
|
-
instruct 的自动投递由 CLI 运行时判断;没有标准后台投递能力时返回 manual_handoff_required。
|
|
10089
|
+
bind 只记录 StarWork lane binding,不调用 Codex host 工具。
|
|
10090
|
+
--session-name / --pin 需要 starworkMultiagent 先在 Codex App 中调用 set_thread_title / set_thread_pinned,本命令只记录结果。
|
|
9614
10091
|
`);
|
|
9615
10092
|
}
|
|
9616
10093
|
|
|
@@ -9725,6 +10202,80 @@ Options:
|
|
|
9725
10202
|
--json
|
|
9726
10203
|
--dry-run
|
|
9727
10204
|
--yes, -y
|
|
10205
|
+
|
|
10206
|
+
说明:
|
|
10207
|
+
Codex 标准路径下,CLI 不创建 thread、不改名、不置顶。请让 starworkMultiagent 调用 create_thread / set_thread_title / set_thread_pinned,成功后再用 bind 记录 StarWork 状态。
|
|
10208
|
+
`);
|
|
10209
|
+
}
|
|
10210
|
+
|
|
10211
|
+
function printLanesMessageHelp() {
|
|
10212
|
+
console.log(`StarWork Multiagent Message
|
|
10213
|
+
|
|
10214
|
+
Usage:
|
|
10215
|
+
starwork multiagent message <launch|instruct> [options]
|
|
10216
|
+
|
|
10217
|
+
说明:
|
|
10218
|
+
只生成 STARWORK:MULTIAGENT_MESSAGE 模板,不调用宿主、不写 shared/state。
|
|
10219
|
+
`);
|
|
10220
|
+
}
|
|
10221
|
+
|
|
10222
|
+
function printLanesMessageLaunchHelp() {
|
|
10223
|
+
console.log(`StarWork Multiagent Message Launch
|
|
10224
|
+
|
|
10225
|
+
Usage:
|
|
10226
|
+
starwork multiagent message launch <lane-id> [options]
|
|
10227
|
+
|
|
10228
|
+
Options:
|
|
10229
|
+
--target <path>
|
|
10230
|
+
--from <lane-id>
|
|
10231
|
+
--session-name <name>
|
|
10232
|
+
--json
|
|
10233
|
+
`);
|
|
10234
|
+
}
|
|
10235
|
+
|
|
10236
|
+
function printLanesMessageInstructHelp() {
|
|
10237
|
+
console.log(`StarWork Multiagent Message Instruct
|
|
10238
|
+
|
|
10239
|
+
Usage:
|
|
10240
|
+
starwork multiagent message instruct <to-lane> --from <from-lane> --message <text> [options]
|
|
10241
|
+
|
|
10242
|
+
Options:
|
|
10243
|
+
--target <path>
|
|
10244
|
+
--from <lane-id>
|
|
10245
|
+
--message <text>
|
|
10246
|
+
--id <request-id>
|
|
10247
|
+
--json
|
|
10248
|
+
`);
|
|
10249
|
+
}
|
|
10250
|
+
|
|
10251
|
+
function printLanesRequestHelp() {
|
|
10252
|
+
console.log(`StarWork Multiagent Request
|
|
10253
|
+
|
|
10254
|
+
Usage:
|
|
10255
|
+
starwork multiagent request record [options]
|
|
10256
|
+
|
|
10257
|
+
说明:
|
|
10258
|
+
只记录已发生的跨 lane 请求投递状态,不调用宿主。
|
|
10259
|
+
`);
|
|
10260
|
+
}
|
|
10261
|
+
|
|
10262
|
+
function printLanesRequestRecordHelp() {
|
|
10263
|
+
console.log(`StarWork Multiagent Request Record
|
|
10264
|
+
|
|
10265
|
+
Usage:
|
|
10266
|
+
starwork multiagent request record --from <lane> --to <lane> --message <text> --host-delivery <status> --delivery-tool <tool> [options]
|
|
10267
|
+
|
|
10268
|
+
Options:
|
|
10269
|
+
--target <path>
|
|
10270
|
+
--from <lane-id>
|
|
10271
|
+
--to <lane-id>
|
|
10272
|
+
--message <text>
|
|
10273
|
+
--host-delivery <delivered_via_codex_thread_tool|recorded_only|manual_handoff_required|failed>
|
|
10274
|
+
--delivery-tool <tool-name>
|
|
10275
|
+
--id <request-id>
|
|
10276
|
+
--json
|
|
10277
|
+
--dry-run
|
|
10278
|
+
--yes, -y
|
|
9728
10279
|
`);
|
|
9729
10280
|
}
|
|
9730
10281
|
|