pi-firecode 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +55 -41
  2. package/README.zh-CN.md +74 -0
  3. package/config.example.jsonc +21 -22
  4. package/dist/config.example.jsonc +90 -0
  5. package/dist/index.js +11464 -0
  6. package/dist/master/prompts/master.en.md +22 -0
  7. package/{master → dist/master}/prompts/master.zh.md +3 -3
  8. package/dist/master/prompts/worker.en.md +1 -0
  9. package/dist/watcher/prompts/watch.en.md +43 -0
  10. package/package.json +10 -27
  11. package/activity.ts +0 -92
  12. package/busy.ts +0 -188
  13. package/config.ts +0 -500
  14. package/deliver.ts +0 -119
  15. package/flame.ts +0 -149
  16. package/format.ts +0 -133
  17. package/header.ts +0 -218
  18. package/herdr-client.ts +0 -60
  19. package/index.ts +0 -68
  20. package/jsonc.ts +0 -34
  21. package/master/actions.ts +0 -293
  22. package/master/activity-list.ts +0 -281
  23. package/master/event-card.ts +0 -87
  24. package/master/event-format.ts +0 -83
  25. package/master/guard.ts +0 -46
  26. package/master/index.ts +0 -252
  27. package/master/list-view.ts +0 -129
  28. package/master/outbox.ts +0 -188
  29. package/master/prompt.ts +0 -26
  30. package/master/role.ts +0 -18
  31. package/master/run.ts +0 -315
  32. package/master/runtime.ts +0 -259
  33. package/master/spawn.ts +0 -234
  34. package/master/state.ts +0 -225
  35. package/master/worker-view.ts +0 -517
  36. package/provider/claude-sub.ts +0 -151
  37. package/provider/openai-native/index.ts +0 -8
  38. package/provider/openai-native/src/compact-client.ts +0 -350
  39. package/provider/openai-native/src/config.ts +0 -297
  40. package/provider/openai-native/src/extension.ts +0 -93
  41. package/provider/openai-native/src/native-compaction.ts +0 -151
  42. package/provider/openai-native/src/native-details.ts +0 -157
  43. package/provider/openai-native/src/native-replay.ts +0 -253
  44. package/provider/openai-native/src/native-runtime.ts +0 -144
  45. package/provider/openai-native/src/options.ts +0 -87
  46. package/provider/openai-native/src/request-pipeline.ts +0 -21
  47. package/provider/openai-native/src/responses-input.ts +0 -433
  48. package/review/advisor.ts +0 -110
  49. package/review/card.ts +0 -398
  50. package/review/checkpoint.ts +0 -378
  51. package/review/evidence.ts +0 -230
  52. package/review/index.ts +0 -992
  53. package/review/occupancy.ts +0 -25
  54. package/review/outcome.ts +0 -98
  55. package/review/prompt.ts +0 -209
  56. package/review/reviewer.ts +0 -353
  57. package/review/session.ts +0 -110
  58. package/review/state.ts +0 -877
  59. package/review/ui.ts +0 -89
  60. package/round-recorder.ts +0 -18
  61. package/session/herdr-display.ts +0 -87
  62. package/session/presets.ts +0 -300
  63. package/session/quota.ts +0 -123
  64. package/session/rename.ts +0 -46
  65. package/session/stats.ts +0 -313
  66. package/statusbar/index.ts +0 -256
  67. package/statusbar/render.ts +0 -113
  68. package/theme.ts +0 -45
  69. package/tools/actions.ts +0 -53
  70. package/tools/assistant-view.ts +0 -64
  71. package/tools/click-anchor.ts +0 -56
  72. package/tools/group-view.ts +0 -420
  73. package/tools/grouping.ts +0 -158
  74. package/tools/host.ts +0 -369
  75. package/tools/index.ts +0 -186
  76. package/tools/line.ts +0 -231
  77. package/tools/machine.ts +0 -78
  78. package/tools/parts.ts +0 -143
  79. package/tools/round.ts +0 -95
  80. package/tools/timing.ts +0 -22
  81. package/tools/turn-clock.ts +0 -41
  82. package/tools/turn-summary.ts +0 -109
  83. package/truncated-write.ts +0 -18
  84. package/watcher/card.ts +0 -91
  85. package/watcher/index.ts +0 -176
  86. package/watcher/observer.ts +0 -75
  87. package/watcher/transcript.ts +0 -56
  88. /package/{master → dist/master}/prompts/worker.zh.md +0 -0
  89. /package/{review → dist/review}/prompts/advisor.en.md +0 -0
  90. /package/{review → dist/review}/prompts/advisor.zh.md +0 -0
  91. /package/{review → dist/review}/prompts/review.en.md +0 -0
  92. /package/{review → dist/review}/prompts/review.zh.md +0 -0
  93. /package/{watcher → dist/watcher}/prompts/watch.zh.md +0 -0
@@ -1,83 +0,0 @@
1
- /**
2
- * Master 事件正文的唯一产文处:第一行是给人看的标题“<名字> <结果词>”(名字是第一个空格前的词),
3
- * 给模型的指令放在后续正文。分节标记独占一行“<标记>:”;失败只由“错误:”分节表示——
4
- * 红色、失败计数与活动列表失败组都从这一个事实来(失败、审查停止、审查未完成;被中断不是失败)。
5
- * 折叠界面(tools/machine.ts)按标题、“错误:”分节与“耗时:本次运行 …”行投影,改格式两侧都要同步。
6
- */
7
- import { formatDuration } from "../format.js";
8
- import type { ReviewOutcome } from "../review/outcome.js";
9
-
10
- export const MASTER_EVENT_TYPE = "firecode-master-event";
11
-
12
- const SECTIONS = {
13
- reply: "回复:",
14
- error: "错误:",
15
- finalReply: "最终回复:",
16
- advice: "顾问意见:",
17
- } as const;
18
-
19
- const OBLIGATION = "此票有审查义务,请显式 review。";
20
-
21
- /**
22
- * 产出的事件:正文与是否落定类。落定类(已返回、失败、被中断、审查通过/停止/未完成)才带“本次运行”耗时,
23
- * 界面据此触发到达高亮;其余事件(待续跑、已切换模型、补充说明未送达)只带当前任务耗时。
24
- */
25
- export interface MasterEvent {
26
- body: string;
27
- settled: boolean;
28
- }
29
-
30
- const lines = (...parts: (string | undefined)[]) => parts.filter((part) => part !== undefined).join("\n");
31
- const settled = (...parts: (string | undefined)[]): MasterEvent => ({ body: lines(...parts), settled: true });
32
- const notice = (...parts: (string | undefined)[]): MasterEvent => ({ body: lines(...parts), settled: false });
33
-
34
- /**
35
- * 一次运行里用户在子代理全过程视图直接说的话(按顺序)。有就在标题结果词后注明来源、正文先列原话:
36
- * 指挥官据此知道这次运行不是它派的,只记下不向用户复述;成败、发落、在飞数与普通 send 完全相同。
37
- */
38
- export type ViewPrompts = readonly string[];
39
- const VIEW_MARK = "(你在子代理视图里直接派的)";
40
- const runTitle = (name: string, word: string, view: ViewPrompts) => `${name} ${word}${view.length ? VIEW_MARK : ""}`;
41
- const youSaid = (view: ViewPrompts) => view.map((prompt) => `你说:${prompt}`);
42
-
43
- export const masterEvent = {
44
- returned: (name: string, reply: string, obligation = false, view: ViewPrompts = []) =>
45
- settled(runTitle(name, "已返回", view), ...youSaid(view), SECTIONS.reply, reply, obligation ? OBLIGATION : undefined),
46
- failed: (name: string, error: string, obligation = false, view: ViewPrompts = []) =>
47
- settled(runTitle(name, "失败", view), ...youSaid(view), SECTIONS.error, error, obligation ? OBLIGATION : undefined),
48
- interrupted: (name: string, obligation: boolean, view: ViewPrompts = []) =>
49
- settled(runTitle(name, "被中断", view), ...youSaid(view), obligation ? "会话与审查义务均已保留,可 send 续派" : "会话已保留,可 send 续派"),
50
- reviewIncomplete: (name: string, reason: string, reply?: string) =>
51
- settled(`${name} 审查未完成`, SECTIONS.error, reason, ...(reply === undefined ? [] : [SECTIONS.finalReply, reply || "(无回复)"])),
52
- /** 审查终态;reply 是 Worker 最后一条回复。停止与未完成是失败:原因进“错误:”分节,顾问意见在其后。 */
53
- review(name: string, outcome: ReviewOutcome, reply: string): MasterEvent {
54
- const final = [SECTIONS.finalReply, reply || "(无回复)"];
55
- if (outcome.status === "passed") return settled(`${name} 审查通过(${outcome.rounds} 轮)`, ...final);
56
- if (outcome.status === "stopped")
57
- return settled(
58
- `${name} 审查停止(${outcome.rounds} 轮)`,
59
- SECTIONS.error,
60
- outcome.advisorAdvice ? `审查 ${outcome.rounds} 轮未通过,顾问叫停` : `审查 ${outcome.rounds} 轮用尽仍未通过`,
61
- ...(outcome.advisorAdvice ? [SECTIONS.advice, outcome.advisorAdvice] : []),
62
- ...final,
63
- );
64
- if (outcome.status === "failed") return masterEvent.reviewIncomplete(name, outcome.reason, reply);
65
- if (outcome.status === "error") return masterEvent.reviewIncomplete(name, `审查读取失败:${outcome.message}`);
66
- return masterEvent.reviewIncomplete(name, `审查结束时没有终态:${outcome.status}`);
67
- },
68
- /** 只给会话重载打断的回合:指挥官自己发起的 interrupt 它知道现场,不提醒。 */
69
- resumeReminder: (name: string) => notice(`${name} 待续跑`, "上次回合被会话重载打断,恢复后一直没有续派;请 send 续派或 kill 收口"),
70
- stranded: (name: string, texts: string[]) =>
71
- notice(`${name} 补充说明未送达`, `回合结束时有 ${texts.length} 条补充说明未送达,请重发:`, texts.join("\n---\n")),
72
- modelSwitched: (name: string, from: string, to: string, reason: string) =>
73
- notice(`${name} 已切换模型`, `已切换 ${from}→${to}(${reason}),正在同一会话自动续跑`),
74
- };
75
-
76
- /** 落定类正文末尾追加 Worker 本次运行耗时(子代理会话写下的轮记录给出);没有记录或非落定事件不追加,不用别处的计时冒充。 */
77
- export function withElapsed(event: MasterEvent, { run, task }: { run?: number; task?: number }): string {
78
- const parts = [
79
- ...(run === undefined || !event.settled ? [] : [`本次运行 ${formatDuration(run)}`]),
80
- ...(task === undefined ? [] : [`当前任务 ${formatDuration(task)}`]),
81
- ];
82
- return parts.length ? `${event.body}\n耗时:${parts.join(" · ")}` : event.body;
83
- }
package/master/guard.ts DELETED
@@ -1,46 +0,0 @@
1
- /**
2
- * Worker 会话里 Master 只注册这一件事:edit/write 只能落在当前 checkout 或系统临时目录内(按真实路径判断)。
3
- * 临时目录放行是因为调研报告、评测产物这类交付物本来就写在那里;拦掉只会逼 Worker 改用 bash 绕过守卫。
4
- */
5
- import { realpath } from "node:fs/promises";
6
- import { tmpdir } from "node:os";
7
- import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
8
- import { isToolCallEventType, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
-
10
- export function registerWorkerGuard(pi: ExtensionAPI): void {
11
- pi.on("tool_call", async (event, ctx) => {
12
- if (!isToolCallEventType("edit", event) && !isToolCallEventType("write", event)) return;
13
- const reason = await outsideCheckoutReason(event.input.path, ctx.cwd);
14
- if (reason) return { block: true, reason };
15
- });
16
- }
17
-
18
- /** os.tmpdir() 在 macOS 是每用户的 /var/folders/…,/tmp 是另一处,两处都是临时目录。 */
19
- const TEMP_ROOTS = [tmpdir(), "/tmp"];
20
-
21
- async function outsideCheckoutReason(path: string, cwd: string): Promise<string | undefined> {
22
- const target = await canonicalWritePath(resolve(cwd, path));
23
- const roots = await Promise.all([cwd, ...TEMP_ROOTS].map(canonicalWritePath));
24
- return roots.some((root) => within(root, target)) ? undefined : `子代理只能修改当前 checkout 或系统临时目录:${path}`;
25
- }
26
-
27
- function within(root: string, target: string): boolean {
28
- const local = relative(root, target);
29
- return local !== ".." && !local.startsWith(`..${sep}`) && !isAbsolute(local);
30
- }
31
-
32
- /** 目标可能还不存在:沿祖先找到第一个真实存在的目录取真实路径,再拼回缺失的部分。 */
33
- async function canonicalWritePath(path: string): Promise<string> {
34
- let ancestor = path;
35
- const missing: string[] = [];
36
- while (true) {
37
- try {
38
- return resolve(await realpath(ancestor), ...missing.reverse());
39
- } catch {
40
- const parent = dirname(ancestor);
41
- if (parent === ancestor) return path;
42
- missing.push(basename(ancestor));
43
- ancestor = parent;
44
- }
45
- }
46
- }
package/master/index.ts DELETED
@@ -1,252 +0,0 @@
1
- /**
2
- * /fire-master 的注册入口:激活与停用、命令、两个工具与生命周期。
3
- * 运行时事实在 runtime.ts,回合编排在 run.ts,七个动作在 actions.ts,发件箱在 outbox.ts,工具行在 list-view.ts。
4
- */
5
- import { StringEnum, Type } from "@earendil-works/pi-ai";
6
- import { getAgentDir, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
7
- import { HERDR_WORKING_CHANNEL, HERDR_WORKING_LABEL, WORKERS_CHANNEL, type HerdrWorkingPayload, type WorkersPayload, watchBusy } from "../busy.js";
8
- import { loadConfig, type MasterRole } from "../config.js";
9
- import { ToolLine } from "../tools/line.js";
10
- import { ACTION_HANDLERS, ACTIONS } from "./actions.js";
11
- import { registerMasterEventRenderer } from "./event-card.js";
12
- import { registerWorkerGuard } from "./guard.js";
13
- import {
14
- compactWorker, currentWorkerAction, expandedWorkerList, listMeta, renderSubagentsResult, statusText, subagentsCallParts,
15
- } from "./list-view.js";
16
- import { assembleMasterPrompt, readMasterPrompt } from "./prompt.js";
17
- import { armInterruptReminder, modelAtomText } from "./run.js";
18
- import { MasterRuntime, type MasterSetup } from "./runtime.js";
19
- import { InProcessSessionPool } from "./spawn.js";
20
- import { loadMasterState, masterStatePath, recoverMasterState, THINKING_LEVELS, type MasterState } from "./state.js";
21
-
22
- const MASTER_TOOL = "subagents";
23
- const MASTER_LIST_TOOL = "subagents_list";
24
- const MASTER_TOOLS = [MASTER_TOOL, MASTER_LIST_TOOL];
25
- const INTERRUPT_RESUME_MS = 5 * 60_000;
26
- /**
27
- * 一批结果陆续返回时,最后一条后静默这么久才唤醒空闲的指挥官:合并唤醒,单条结果最多多等这一下。
28
- * 这是业务语义的等待:“这一批还会不会马上再来一条”没有事件能回答。同批结果相邻间隔约 2~3 秒,而唤醒回合本身要数秒,
29
- * 1.5 秒足以把紧挨着的并进一次唤醒,回合开跑后再到的走句缝。
30
- */
31
- const WAKE_QUIET_MS = 1_500;
32
-
33
- interface MasterDependencies {
34
- pool?: InProcessSessionPool;
35
- interruptResumeMs?: number;
36
- wakeQuietMs?: number;
37
- }
38
-
39
- export function registerMaster(pi: ExtensionAPI, dependencies: MasterDependencies = {}, worker = false): void {
40
- // Worker 会话里只注册 checkout 守卫,不注册命令、工具与生命周期。
41
- if (worker) return registerWorkerGuard(pi);
42
- const loaded = loadConfig().master;
43
- const prompts = loadMasterPrompts();
44
- const startupError = "error" in loaded ? loaded.error : "error" in prompts ? prompts.error : undefined;
45
- const roster = "error" in loaded ? [] : loaded.config.roles;
46
- const autoActivate = "error" in loaded ? false : loaded.config.autoActivate;
47
- const requirePrompts = () => {
48
- if ("error" in prompts) throw new Error(prompts.error);
49
- return prompts;
50
- };
51
- // 在飞子代理数的唯一发布者;herdr:working 只在 0↔正数跃迁时发布,active 按计数配对。
52
- let publishedInFlight = 0;
53
- const publishInFlight = (count: number, teardown = false) => {
54
- if (count === publishedInFlight) return;
55
- const wasBusy = publishedInFlight > 0;
56
- publishedInFlight = count;
57
- pi.events.emit(WORKERS_CHANNEL, { inFlight: count, ...(teardown ? { teardown: true as const } : {}) } satisfies WorkersPayload);
58
- if (wasBusy !== count > 0)
59
- pi.events.emit(HERDR_WORKING_CHANNEL, { active: count > 0, label: HERDR_WORKING_LABEL } satisfies HerdrWorkingPayload);
60
- };
61
- // 事件末尾的“当前任务”耗时是给指挥官的时间信号(Opus 5.5 据已用时间安排并行);起点只取 busy.ts。
62
- let sessionSince: number | undefined;
63
- watchBusy(pi, { onChange: (view) => { sessionSince = view.since; } });
64
- const setup: MasterSetup = {
65
- pi,
66
- pool: dependencies.pool ?? new InProcessSessionPool(),
67
- roster,
68
- exclusions: "error" in loaded ? [] : loaded.config.workerExcludeExtensions,
69
- workerPrompt: () => requirePrompts().worker,
70
- reviewGate: reviewGateError(),
71
- interruptResumeMs: dependencies.interruptResumeMs ?? INTERRUPT_RESUME_MS,
72
- wakeQuietMs: dependencies.wakeQuietMs ?? WAKE_QUIET_MS,
73
- publishInFlight,
74
- sessionSince: () => sessionSince,
75
- };
76
- let runtime: MasterRuntime | undefined;
77
- registerMasterEventRenderer(pi);
78
-
79
- const setTools = (active: boolean) => {
80
- const tools = pi.getActiveTools().filter((name) => !MASTER_TOOLS.includes(name));
81
- pi.setActiveTools(active ? [...tools, ...MASTER_TOOLS] : tools);
82
- };
83
- const activate = (ctx: ExtensionContext, restored?: MasterState): MasterRuntime => {
84
- if (startupError) throw new Error(startupError);
85
- if (runtime) {
86
- runtime.ctx = ctx;
87
- return runtime;
88
- }
89
- const active = runtime = new MasterRuntime(setup, ctx, restored);
90
- setTools(true);
91
- if (active.store.discardedLegacyVersion !== undefined)
92
- ctx.ui.notify(`旧版 v${active.store.discardedLegacyVersion} 子代理池已丢弃并从空池重建;旧运行时进程不会纳入新池,请手动清理`, "warning");
93
- active.render();
94
- return active;
95
- };
96
- /** 会话关闭先清空当前 runtime,再释放池、订阅与定时器:迟到任务不写状态、投递、UI 或持久化。 */
97
- const deactivate = async () => {
98
- const active = runtime;
99
- runtime = undefined;
100
- active?.close();
101
- // 遗弃在飞子代理不是歇下:带 teardown 归零,busy.ts 只结束本段。
102
- publishInFlight(0, true);
103
- await setup.pool.disposeAll();
104
- setTools(false);
105
- };
106
- /** reload 恢复:在飞状态收敛为 idle + interruptedAt,补挂续跑提醒,重投未确认事件。 */
107
- const activateSession = (ctx: ExtensionContext): MasterRuntime => {
108
- if (runtime) return activate(ctx);
109
- let restored: MasterState | undefined;
110
- try {
111
- restored = loadMasterState(masterStatePath(getAgentDir(), ctx.sessionManager.getSessionId()));
112
- } catch {
113
- return activate(ctx);
114
- }
115
- const active = activate(ctx, restored);
116
- if (restored) {
117
- const recovered = recoverMasterState(restored);
118
- for (const worker of recovered.workers) {
119
- if (worker !== restored.workers.find((candidate) => candidate.name === worker.name))
120
- active.store.dispatch({ type: "UPSERT_WORKER", worker });
121
- if (worker.interruptedAt) armInterruptReminder(active, worker);
122
- }
123
- }
124
- active.outbox.replayUnacked(ctx);
125
- return active;
126
- };
127
-
128
- pi.registerCommand("fire-master", {
129
- description: "翻转当前会话的指挥官模式;status 查看状态",
130
- handler: async (args, ctx) => {
131
- const input = args.trim();
132
- if (input === "status") {
133
- ctx.ui.notify(runtime ? statusText(runtime.store.state.workers) : "指挥官模式未启动", "info");
134
- return;
135
- }
136
- if (input) {
137
- ctx.ui.notify("/fire-master 只接受 status;裸命令翻转开关", "error");
138
- return;
139
- }
140
- if (runtime) {
141
- await deactivate();
142
- ctx.ui.notify("指挥官模式已关闭", "info");
143
- return;
144
- }
145
- try {
146
- activateSession(ctx);
147
- ctx.ui.notify("指挥官模式已启动", "info");
148
- } catch (error) {
149
- ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
150
- }
151
- },
152
- });
153
-
154
- pi.on("input", (event) => {
155
- if (event.source !== "extension") runtime?.collapseList();
156
- });
157
-
158
- pi.on("before_agent_start", async (event) => {
159
- if (!runtime || !pi.getActiveTools().includes(MASTER_TOOL)) return;
160
- return {
161
- systemPrompt: `${event.systemPrompt}\n\n${assembleMasterPrompt(requirePrompts().master, rosterText(roster))}`,
162
- };
163
- });
164
-
165
- pi.registerTool({
166
- name: MASTER_LIST_TOOL,
167
- label: "子代理",
168
- description: "查看子代理池快照",
169
- renderShell: "self",
170
- renderCall: (_args, theme, ctx) =>
171
- new ToolLine({ label: "子代理", value: subagentsCallParts({ action: "list" }), clip: "end", theme, ctx }),
172
- renderResult: (result, options, theme, context) => {
173
- const details = result.details as { workers?: unknown } | undefined;
174
- context.state.meta = !context.isError && Array.isArray(details?.workers) ? listMeta(details.workers) : undefined;
175
- if (options.expanded && Array.isArray(details?.workers))
176
- return expandedWorkerList(details.workers, theme, context);
177
- return renderSubagentsResult(result, options, theme, context);
178
- },
179
- parameters: Type.Object({}),
180
- async execute() {
181
- const active = runtime;
182
- if (!active) throw new Error("subagents_list 只在 Master 中可用");
183
- const workers = active.store.state.workers.map(compactWorker);
184
- return {
185
- content: [{ type: "text" as const, text: JSON.stringify({ workers }) }],
186
- details: {
187
- workers: workers.map((worker) => ({ ...worker, currentAction: currentWorkerAction(worker, active.live.get(worker.name)) })),
188
- },
189
- };
190
- },
191
- });
192
-
193
- pi.registerTool({
194
- name: MASTER_TOOL,
195
- label: "子代理",
196
- description: "指挥官的七动作子代理接口:start 按角色新建,send 续派或切换角色,interrupt 中断,review 显式审查,tail 读轨迹,ack 确认落定,kill 收口移除;无 sleep/session。",
197
- renderShell: "self",
198
- renderCall: (args, theme, ctx) =>
199
- new ToolLine({ label: "子代理", value: subagentsCallParts(args as Record<string, unknown>), clip: "end", theme, ctx }),
200
- renderResult: renderSubagentsResult,
201
- parameters: Type.Object({
202
- action: StringEnum(ACTIONS, { description: "七动作之一;等待状态变化,不要用 sleep 轮询。" }),
203
- worker: Type.String({ description: "start 起简短任务名;其余动作填目标 Worker。" }),
204
- prompt: Type.Optional(Type.String({ description: "start/send 必填自包含任务说明,包括交付物、限制与验证要求。" })),
205
- // 角色词只来自角色表,代码不持有固定词表;代价是角色名拼错无法在加载时报出。
206
- role: Type.Optional(StringEnum(roster.map((entry) => entry.role), { description: "start 必填角色表中的角色;send 可选,传入时切换角色,省略则沿用。" })),
207
- thinking: Type.Optional(StringEnum(THINKING_LEVELS, { description: "可选思考档覆盖;省略时使用角色原子档或当前档。" })),
208
- cwd: Type.Optional(Type.String({ description: "Worker 工作目录的绝对路径;start 默认当前目录,send 给空闲 Worker 换检出时带上(同一会话重开)。" })),
209
- review: Type.Optional(Type.Boolean({ description: "按审查纪律为 start/send 记录义务;true 不自动开审。" })),
210
- }),
211
- async execute(_id, params: Record<string, unknown>, _signal, _update, ctx) {
212
- const active = runtime;
213
- if (!active) throw new Error("subagents 只在 Master 中可用");
214
- const handler = ACTION_HANDLERS[params.action as keyof typeof ACTION_HANDLERS];
215
- if (!handler) throw new Error(`未知 subagents action:${String(params.action)}`);
216
- return handler(active, params, ctx);
217
- },
218
- });
219
-
220
- pi.on("session_start", async (_event, ctx) => {
221
- await deactivate();
222
- if (!autoActivate) return;
223
- try {
224
- activateSession(ctx);
225
- } catch (error) {
226
- ctx.ui.notify(`指挥官模式恢复失败:${error instanceof Error ? error.message : String(error)}`, "error");
227
- }
228
- });
229
-
230
- pi.on("session_shutdown", () => deactivate());
231
- }
232
-
233
- function reviewGateError(): string | undefined {
234
- const loaded = loadConfig();
235
- if (loaded.config.features.review === false) return "fire-review 已关闭,不能挂审查义务或发起审查";
236
- return "error" in loaded.review ? loaded.review.error : undefined;
237
- }
238
-
239
- function loadMasterPrompts() {
240
- try {
241
- return { master: readMasterPrompt("master"), worker: readMasterPrompt("worker") };
242
- } catch (error) {
243
- return { error: error instanceof Error ? error.message : String(error) };
244
- }
245
- }
246
-
247
- function rosterText(models: MasterRole[]): string {
248
- return models.map((entry) => {
249
- const fallback = entry.fallback.length ? `,fallback ${entry.fallback.map(modelAtomText).join(" → ")}` : "";
250
- return `${entry.role}:${modelAtomText(entry)}(${entry.use}${fallback})`;
251
- }).join(";");
252
- }
@@ -1,129 +0,0 @@
1
- /** subagents / subagents_list 的工具行与 /fire-master status 文本:纯投影,不改状态。 */
2
- import { statSync } from "node:fs";
3
- import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
4
- import { formatDuration } from "../format.js";
5
- import { ToolLine, makeResultRenderer } from "../tools/line.js";
6
- import type { Part } from "../tools/parts.js";
7
- import type { WorkerLive } from "./runtime.js";
8
- import type { WorkerRef, WorkerStatus } from "./state.js";
9
-
10
- export const renderSubagentsResult = makeResultRenderer(false);
11
- const STATUS_WORD = { working: "工作", idle: "空闲", reviewing: "审查" } satisfies Record<WorkerStatus, string>;
12
- const ACTION_VERB: Record<string, string> = { start: "启动", list: "查看", kill: "移除", send: "发送", interrupt: "中断", review: "审查", tail: "近况", ack: "待命" };
13
-
14
- export function subagentsCallParts(args: Record<string, unknown>): Part[] {
15
- const action = typeof args.action === "string" ? args.action : "?";
16
- const parts: Part[] = [{ text: ACTION_VERB[action] ?? action, bold: true }];
17
- const target = optionalText(args.worker);
18
- if (target) parts.push({ text: ` ${target}`, color: "accent" });
19
- const role = optionalText(args.role);
20
- if ((action === "start" || action === "send") && role) parts.push({ text: ` · ${role}`, color: "muted" });
21
- const prompt = optionalText(args.prompt)?.split("\n", 1)[0];
22
- if (prompt && action === "start") parts.push({ text: ` — ${prompt}`, color: "muted" });
23
- return parts;
24
- }
25
-
26
- export type CompactWorker = ReturnType<typeof compactWorker>;
27
-
28
- /** 给模型的池快照:只有档案事实。 */
29
- export function compactWorker(worker: WorkerRef) {
30
- return {
31
- name: worker.name,
32
- role: worker.role,
33
- status: worker.status,
34
- model: worker.model,
35
- thinking: worker.thinking,
36
- session: worker.sessionPath,
37
- ...(worker.interruptedAt ? { interruptedAt: worker.interruptedAt } : {}),
38
- ...(worker.reviewNeeded ? { reviewNeeded: true } : {}),
39
- ...(worker.disposition ? { disposition: worker.disposition } : {}),
40
- };
41
- }
42
-
43
- /** 展开行用的当前动作:审查进度、落定时刻(reload 后退到会话文件 mtime)或最近的工具。 */
44
- export function currentWorkerAction(worker: CompactWorker, live: WorkerLive | undefined) {
45
- if (worker.status === "reviewing") return live?.reviewProgress && { kind: "review" as const, ...live.reviewProgress };
46
- if (worker.status === "idle") {
47
- let since = live?.idleAt;
48
- try {
49
- since ??= statSync(worker.session).mtimeMs;
50
- } catch {
51
- // 缺失档案仍可 list;真正恢复时由 send 明确报错。
52
- }
53
- return { kind: "idle" as const, ...(since ? { since } : {}) };
54
- }
55
- const current = [...(live?.currentTools.values() ?? [])].at(-1);
56
- return current ? { kind: "tool" as const, ...current } : undefined;
57
- }
58
-
59
- export function expandedWorkerList(
60
- workers: unknown[],
61
- theme: ExtensionContext["ui"]["theme"],
62
- context: Parameters<typeof renderSubagentsResult>[3],
63
- ) {
64
- return {
65
- invalidate() {},
66
- render(width: number): string[] {
67
- return ["", ...workers.flatMap((value) => {
68
- const worker = value as Record<string, unknown>;
69
- const action = worker.currentAction as {
70
- kind?: string;
71
- tool?: string;
72
- startedAt?: number;
73
- since?: number;
74
- round?: number;
75
- settled?: number;
76
- total?: number;
77
- } | undefined;
78
- const actionParts: Part[] = action?.kind === "tool" && action.tool && action.startedAt
79
- ? [{
80
- text: ` · ${action.tool} · 已 ${formatDuration(Math.max(0, Date.now() - action.startedAt))}`,
81
- color: "accent",
82
- }]
83
- : action?.kind === "idle"
84
- ? [{
85
- text: action.since ? ` · 落定 ${formatDuration(Date.now() - action.since)}前` : " · 已落定",
86
- color: "muted",
87
- }]
88
- : action?.kind === "review"
89
- ? [{ text: ` · 第 ${action.round} 轮 · 审查者 ${action.settled}/${action.total}`, color: "accent" }]
90
- : [];
91
- return new ToolLine({
92
- label: String(worker.name),
93
- value: [
94
- { text: roleStatusText(worker), color: "accent" },
95
- ...actionParts,
96
- { text: ` · ${String(worker.model).split("/").pop()}/${String(worker.thinking)}`, color: "muted" },
97
- ],
98
- clip: "end",
99
- theme,
100
- ctx: { ...context, state: {}, expanded: false },
101
- }).render(width);
102
- })];
103
- },
104
- };
105
- }
106
-
107
- export function listMeta(workers: unknown[]): Part[] {
108
- if (!workers.length) return [{ text: " — 池 0", color: "muted" }];
109
- return [{ text: ` — 池 ${workers.length}:${workers.map((value) => {
110
- const worker = value as Record<string, unknown>;
111
- return `${String(worker.name)} ${roleStatusText(worker)}`;
112
- }).join(" · ")}`, color: "muted" }];
113
- }
114
-
115
- export function statusText(workers: WorkerRef[]): string {
116
- return workers.length
117
- ? workers.map((worker) => `${worker.name} ${roleStatusText(worker)} ${worker.model.split("/").pop()}`).join("\n")
118
- : "没有子代理";
119
- }
120
-
121
- /** 角色为主的状态投影:「工程师·工作」;档案缺角色时退到纯状态词。 */
122
- function roleStatusText(worker: { role?: unknown; status?: unknown }): string {
123
- const status = STATUS_WORD[worker.status as WorkerStatus] ?? String(worker.status);
124
- return worker.role ? `${String(worker.role)}·${status}` : status;
125
- }
126
-
127
- function optionalText(value: unknown): string | undefined {
128
- return typeof value === "string" && value.trim() ? value.trim() : undefined;
129
- }