pi-firecode 0.8.1 → 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 (79) hide show
  1. package/README.md +58 -24
  2. package/README.zh-CN.md +74 -0
  3. package/config.example.jsonc +47 -57
  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/dist/master/prompts/master.zh.md +22 -0
  8. package/dist/master/prompts/worker.en.md +1 -0
  9. package/dist/master/prompts/worker.zh.md +1 -0
  10. package/{review → dist/review}/prompts/review.en.md +7 -6
  11. package/{review → dist/review}/prompts/review.zh.md +7 -6
  12. package/dist/watcher/prompts/watch.en.md +43 -0
  13. package/{watcher → dist/watcher}/prompts/watch.zh.md +1 -1
  14. package/package.json +10 -27
  15. package/config.ts +0 -474
  16. package/deliver.ts +0 -32
  17. package/flame-frames.ts +0 -460
  18. package/format.ts +0 -80
  19. package/header.ts +0 -92
  20. package/herdr-client.ts +0 -60
  21. package/index.ts +0 -62
  22. package/jsonc.ts +0 -34
  23. package/master/event-card.ts +0 -106
  24. package/master/event-format.ts +0 -52
  25. package/master/index.ts +0 -1200
  26. package/master/prompt.ts +0 -26
  27. package/master/prompts/master.zh.md +0 -17
  28. package/master/prompts/worker.zh.md +0 -1
  29. package/master/role.ts +0 -13
  30. package/master/spawn.ts +0 -193
  31. package/master/state.ts +0 -201
  32. package/provider/claude-sub.ts +0 -129
  33. package/provider/openai-native/index.ts +0 -8
  34. package/provider/openai-native/src/compact-client.ts +0 -362
  35. package/provider/openai-native/src/config.ts +0 -297
  36. package/provider/openai-native/src/extension.ts +0 -93
  37. package/provider/openai-native/src/native-compaction.ts +0 -151
  38. package/provider/openai-native/src/native-details.ts +0 -157
  39. package/provider/openai-native/src/native-replay.ts +0 -253
  40. package/provider/openai-native/src/native-runtime.ts +0 -144
  41. package/provider/openai-native/src/options.ts +0 -85
  42. package/provider/openai-native/src/request-pipeline.ts +0 -21
  43. package/provider/openai-native/src/responses-input.ts +0 -433
  44. package/review/advisor.ts +0 -112
  45. package/review/card.ts +0 -385
  46. package/review/checkpoint.ts +0 -384
  47. package/review/evidence.ts +0 -232
  48. package/review/index.ts +0 -1246
  49. package/review/outcome.ts +0 -74
  50. package/review/progress.ts +0 -324
  51. package/review/prompt.ts +0 -210
  52. package/review/reviewer.ts +0 -356
  53. package/review/session.ts +0 -129
  54. package/review/state.ts +0 -905
  55. package/review/ui.ts +0 -528
  56. package/session/bark.ts +0 -157
  57. package/session/herdr-display.ts +0 -87
  58. package/session/presets.ts +0 -257
  59. package/session/rename.ts +0 -46
  60. package/session/stats.ts +0 -310
  61. package/session/working-flame.ts +0 -116
  62. package/statusbar/index.ts +0 -118
  63. package/statusbar/quota-cache.ts +0 -60
  64. package/statusbar/quota-parse.ts +0 -85
  65. package/statusbar/quota.ts +0 -203
  66. package/statusbar/render.ts +0 -181
  67. package/statusbar/tps.ts +0 -87
  68. package/theme.ts +0 -84
  69. package/tools/grouping.ts +0 -233
  70. package/tools/index.ts +0 -183
  71. package/tools/line.ts +0 -194
  72. package/tools/parts.ts +0 -143
  73. package/tools/timing.ts +0 -22
  74. package/watcher/card.ts +0 -85
  75. package/watcher/index.ts +0 -204
  76. package/watcher/observer.ts +0 -75
  77. package/watcher/transcript.ts +0 -64
  78. /package/{review → dist/review}/prompts/advisor.en.md +0 -0
  79. /package/{review → dist/review}/prompts/advisor.zh.md +0 -0
package/watcher/index.ts DELETED
@@ -1,204 +0,0 @@
1
- /**
2
- * Watcher 观察员:每个 turn 结束后异步评估主会话增量,要么沉默,要么发一条建议。
3
- * 与 Master、fire-review 各自独立注册;观察过程不落盘。
4
- */
5
- import type { Model } from "@earendil-works/pi-ai";
6
- import {
7
- getAgentDir,
8
- ModelRuntime,
9
- type ExtensionAPI,
10
- type ExtensionContext,
11
- } from "@earendil-works/pi-coding-agent";
12
- import { loadConfig, type WatcherConfig } from "../config.js";
13
- import { deliver } from "../deliver.js";
14
- import { formatModelName } from "../format.js";
15
- import { InProcessSessionPool } from "../master/spawn.js";
16
- import {
17
- adviceMessage,
18
- registerWatcherCardRenderer,
19
- WATCHER_MESSAGE_TYPE,
20
- type WatcherCard,
21
- } from "./card.js";
22
- import { createObserver, type Advice, type Observer } from "./observer.js";
23
- import { renderTurn } from "./transcript.js";
24
-
25
- /** review 模块发布的占用频道;观察员只订阅,不参与审查状态机。 */
26
- const REVIEW_OCCUPANCY_CHANNEL = "herdr:blocked";
27
- /** 观察会话自身上下文占比超过此值即重建。 */
28
- const CONTEXT_RESET_PERCENT = 70;
29
-
30
- interface WatcherDependencies {
31
- resolveModel?: (id: string) => Promise<Model<any>>;
32
- pool?: InProcessSessionPool;
33
- createObserver?: typeof createObserver;
34
- }
35
-
36
- interface WatcherRuntime {
37
- ctx: ExtensionContext;
38
- pending: string[];
39
- lastTurnIndex: number;
40
- evaluating: boolean;
41
- observer?: Observer;
42
- }
43
-
44
- export function registerWatcher(
45
- pi: ExtensionAPI,
46
- dependencies: WatcherDependencies = {},
47
- subsession = false,
48
- ): void {
49
- // 子会话不带观察员:级联抑制是代码规则,不靠进程环境。
50
- if (subsession) return;
51
- registerWatcherCardRenderer(pi);
52
- const loaded = loadWatcherConfiguration();
53
- // 配置有问题时拒绝启动:静默回退会拿用户没配的模型真实发起观察。
54
- if ("error" in loaded) {
55
- pi.registerCommand("fire-watch", {
56
- description: "翻转当前会话的观察员开关",
57
- handler: async (args, ctx) => ctx.ui.notify(args.trim() ? "/fire-watch 不接受参数" : loaded.error, "error"),
58
- });
59
- return;
60
- }
61
- const config = loaded;
62
- const pool = dependencies.pool ?? new InProcessSessionPool();
63
- const spawnObserver = dependencies.createObserver ?? createObserver;
64
- let runtime: WatcherRuntime | undefined;
65
- let reviewActive = false;
66
-
67
- const deactivate = (owner = runtime) => {
68
- if (!owner || runtime !== owner) return;
69
- runtime = undefined;
70
- owner.observer?.dispose();
71
- owner.observer = undefined;
72
- owner.ctx.ui.setStatus("watcher", undefined);
73
- };
74
- const resetObserver = (owner = runtime) => {
75
- if (!owner || runtime !== owner) return;
76
- runtime = {
77
- ctx: owner.ctx,
78
- pending: [],
79
- lastTurnIndex: owner.lastTurnIndex,
80
- evaluating: false,
81
- };
82
- owner.observer?.dispose();
83
- owner.observer = undefined;
84
- };
85
- const activate = (ctx: ExtensionContext): WatcherRuntime => {
86
- const owner = { ctx, pending: [], lastTurnIndex: 0, evaluating: false };
87
- runtime = owner;
88
- ctx.ui.setStatus("watcher", ctx.ui.theme.fg("dim", `👓 ${formatModelName(config.model)}/${config.thinking}`));
89
- return owner;
90
- };
91
- // 与指挥官事件同构:忙时卡片经 steer 队列句缝追加,歇透时走前门唤起(见 deliver.ts)。
92
- const speak = (owner: WatcherRuntime, advice: Advice, turnIndex: number) => {
93
- const card: WatcherCard = { note: advice.note, turnIndex };
94
- return deliver(pi, owner.ctx, { customType: WATCHER_MESSAGE_TYPE, content: adviceMessage(card), details: card });
95
- };
96
- const evaluate = async (owner: WatcherRuntime) => {
97
- owner.evaluating = true;
98
- try {
99
- while (runtime === owner && owner.pending.length && !reviewActive) {
100
- // 合并跳最新:评估期间到达的回合并进下一批,不排队补评估。
101
- const increment = owner.pending.splice(0).join("\n");
102
- const turnIndex = owner.lastTurnIndex;
103
- let observer = owner.observer;
104
- if (!observer) {
105
- const cwd = owner.ctx.cwd;
106
- const model = await (dependencies.resolveModel ?? resolveConfiguredModel)(config.model);
107
- if (runtime !== owner) return;
108
- observer = await spawnObserver({ cwd, model, thinking: config.thinking, pool });
109
- if (runtime !== owner) {
110
- observer.dispose();
111
- return;
112
- }
113
- owner.observer = observer;
114
- }
115
- const advice = await observer.evaluate(increment);
116
- if (runtime !== owner) return;
117
- if (advice) {
118
- await speak(owner, advice, turnIndex);
119
- if (runtime !== owner) return;
120
- }
121
- // 自身上下文快满时也重新入场:观察员只需要当下,不需要完整历史。
122
- if ((observer.contextPercent() ?? 0) >= CONTEXT_RESET_PERCENT) resetObserver(owner);
123
- }
124
- } catch (error) {
125
- // 被重新入场中断的评估不算故障:下一批增量会开一个新观察会话。
126
- if (runtime !== owner) return;
127
- owner.ctx.ui.notify(`观察员已停止:${error instanceof Error ? error.message : String(error)}`, "warning");
128
- deactivate(owner);
129
- } finally {
130
- owner.evaluating = false;
131
- }
132
- };
133
-
134
- pi.registerCommand("fire-watch", {
135
- description: "翻转当前会话的观察员开关",
136
- handler: async (args, ctx) => {
137
- if (args.trim()) {
138
- ctx.ui.notify("/fire-watch 不接受参数", "error");
139
- return;
140
- }
141
- if (runtime) {
142
- deactivate();
143
- ctx.ui.notify("观察员已关闭", "info");
144
- return;
145
- }
146
- activate(ctx);
147
- ctx.ui.notify("观察员已开启", "info");
148
- },
149
- });
150
-
151
- pi.on("session_start", (_event, ctx) => {
152
- deactivate();
153
- if (!config.enabled) return;
154
- activate(ctx);
155
- });
156
-
157
- pi.on("turn_end", (event) => {
158
- const owner = runtime;
159
- if (!owner) return;
160
- owner.pending.push(renderTurn(event, config.context));
161
- owner.lastTurnIndex = event.turnIndex;
162
- if (!owner.evaluating && !reviewActive) void evaluate(owner);
163
- });
164
-
165
- // fire-review 活跃期静默:不与对抗审查的反馈打架,增量留着审查完合并评估。
166
- pi.events?.on?.(REVIEW_OCCUPANCY_CHANNEL, (data: { active?: boolean } | undefined) => {
167
- reviewActive = Boolean(data?.active);
168
- const active = runtime;
169
- if (reviewActive || !active || active.evaluating || !active.pending.length) return;
170
- void evaluate(active);
171
- });
172
-
173
- // 主会话压缩:旧增量已不再对应主会话现场,观察员从当前尾部重新入场而不回放。
174
- pi.on("session_compact", () => resetObserver());
175
- pi.on("session_shutdown", () => deactivate());
176
- }
177
-
178
- function loadWatcherConfiguration(): WatcherConfig | { error: string } {
179
- let loaded: ReturnType<typeof loadConfig>;
180
- try {
181
- loaded = loadConfig();
182
- } catch (error) {
183
- return { error: `观察员配置读取失败:${error instanceof Error ? error.message : String(error)}` };
184
- }
185
- // features 也算阻断集:开关写成字符串 "false" 时 `!== false` 仍会注册,
186
- // 而启用观察员意味着每个回合都对模型发起真实调用。
187
- const problems = loaded.problems.filter((problem) =>
188
- problem.startsWith("watcher") || problem.startsWith("未知字段 watcher.")
189
- || problem.startsWith("config.jsonc") || problem.startsWith("features"));
190
- if (problems.length) return { error: `观察员配置有问题,已停止:${problems.join(";")}` };
191
- return loaded.config.watcher;
192
- }
193
-
194
- async function resolveConfiguredModel(id: string): Promise<Model<any>> {
195
- const runtime = await ModelRuntime.create({
196
- authPath: `${getAgentDir()}/auth.json`,
197
- modelsPath: `${getAgentDir()}/models.json`,
198
- });
199
- const slash = id.indexOf("/");
200
- const model = slash > 0 ? runtime.getModel(id.slice(0, slash), id.slice(slash + 1)) : undefined;
201
- // 扩展注册的 provider 在无扩展子会话里不可解析(实测),必须明确引导而不是静默失败。
202
- if (!model) throw new Error(`找不到模型:${id};观察员只能使用内置 provider 的模型`);
203
- return model;
204
- }
@@ -1,75 +0,0 @@
1
- /**
2
- * 观察会话:进程内 memory 子会话 + 只读工具 + 唯一自定义工具 advise。
3
- * 系统提示以 prompts/watch.zh.md 为唯一事实源(整体替换,不受项目文件改写)。
4
- */
5
- import { readFileSync } from "node:fs";
6
- import { dirname, join } from "node:path";
7
- import { fileURLToPath } from "node:url";
8
- import type { Model } from "@earendil-works/pi-ai";
9
- import { Type } from "@earendil-works/pi-ai";
10
- import type { InProcessSessionPool } from "../master/spawn.js";
11
- import type { ThinkingLevelValue } from "../config.js";
12
-
13
- const PROMPT_PATH = join(dirname(fileURLToPath(import.meta.url)), "prompts", "watch.zh.md");
14
- const OBSERVER_TOOLS = ["read", "grep", "find", "ls", "advise"];
15
-
16
- export interface Advice {
17
- note: string;
18
- }
19
-
20
- export interface Observer {
21
- /** 喂一段增量并等待评估;至多返回一条建议。 */
22
- evaluate(increment: string): Promise<Advice | undefined>;
23
- /** 观察会话自身上下文占比(百分数),未知时 undefined。 */
24
- contextPercent(): number | undefined;
25
- dispose(): void;
26
- }
27
-
28
- export interface ObserverOptions {
29
- cwd: string;
30
- model: Model<any>;
31
- thinking: ThinkingLevelValue;
32
- pool: InProcessSessionPool;
33
- }
34
-
35
- export async function createObserver(options: ObserverOptions): Promise<Observer> {
36
- let advice: Advice | undefined;
37
- const spawned = await options.pool.spawn({
38
- cwd: options.cwd,
39
- role: "observer",
40
- model: options.model,
41
- thinking: options.thinking,
42
- tools: OBSERVER_TOOLS,
43
- customTools: [adviseTool(() => advice, (next) => { advice = next; })],
44
- systemPrompt: { mode: "replace", text: readFileSync(PROMPT_PATH, "utf8") },
45
- contextFiles: true,
46
- persistence: { type: "memory" },
47
- });
48
- return {
49
- async evaluate(increment) {
50
- advice = undefined;
51
- await spawned.prompt(increment);
52
- return advice;
53
- },
54
- contextPercent: () => spawned.session.getContextUsage?.()?.percent ?? undefined,
55
- dispose: () => spawned.dispose(),
56
- };
57
- }
58
-
59
- function adviseTool(current: () => Advice | undefined, capture: (advice: Advice) => void) {
60
- return {
61
- name: "advise",
62
- label: "建议",
63
- description: "向主代理提交一条供权衡的观察建议;每次评估只接受一条,请只提最要紧的那一条。",
64
- parameters: Type.Object({
65
- note: Type.String({ description: "一句话说清问题与定位。" }),
66
- }),
67
- execute(_id: string, params: Record<string, unknown>) {
68
- if (current()) throw new Error("本次评估已提交过建议;余下的问题留到下一次评估");
69
- const note = typeof params.note === "string" ? params.note.trim() : "";
70
- if (!note) throw new Error("note 不能为空");
71
- capture({ note });
72
- return { content: [{ type: "text" as const, text: "已记录,本次评估结束。" }] };
73
- },
74
- };
75
- }
@@ -1,64 +0,0 @@
1
- /**
2
- * 主会话回合的增量渲染:喂给观察会话的唯一素材,纯函数。
3
- * minimal 省略 reasoning 与 diff 展开正文——观察员要的是「在做什么」,正文可以自己去读。
4
- */
5
- import type { TurnEndEvent } from "@earendil-works/pi-coding-agent";
6
- import type { WatcherContext } from "../config.js";
7
-
8
- const RESULT_BUDGET = 400;
9
-
10
- export function renderTurn(turn: TurnEndEvent, context: WatcherContext): string {
11
- const lines: string[] = [];
12
- for (const part of blocks(turn.message.content)) {
13
- if (part.type === "text" && typeof part.text === "string" && part.text.trim())
14
- lines.push(part.text.trim());
15
- if (part.type === "thinking" && context === "full" && typeof part.thinking === "string")
16
- lines.push(`(思考)${part.thinking.trim()}`);
17
- if (part.type === "toolCall") lines.push(toolCallLine(part, context));
18
- }
19
- for (const result of turn.toolResults) {
20
- const text = clipText(plainText(result.content), RESULT_BUDGET);
21
- lines.push(`${result.isError ? "✗" : "→"} ${result.toolName}:${text || "(无输出)"}`);
22
- }
23
- return `<turn index="${turn.turnIndex}">\n${lines.join("\n")}\n</turn>`;
24
- }
25
-
26
- function toolCallLine(part: Record<string, any>, context: WatcherContext): string {
27
- const args = part.arguments && typeof part.arguments === "object" ? part.arguments : {};
28
- const summary = Object.entries(args as Record<string, unknown>)
29
- .map(([key, value]) => `${key}=${clipText(argumentText(key, value, context), 200)}`)
30
- .join(" ");
31
- return `· ${part.name ?? "工具"} ${summary}`.trimEnd();
32
- }
33
-
34
- /** minimal 下 edit/write 的正文(新旧内容)只留长度:观察员判断的是动作,不是逐字 diff。 */
35
- function argumentText(key: string, value: unknown, context: WatcherContext): string {
36
- const text = typeof value === "string" ? value : JSON.stringify(value) ?? "";
37
- if (context === "full") return oneLine(text);
38
- return BODY_KEYS.has(key) ? `<${text.length} 字符>` : oneLine(text);
39
- }
40
-
41
- const BODY_KEYS = new Set(["content", "oldText", "newText", "old_text", "new_text", "edits"]);
42
-
43
- function blocks(content: unknown): Array<Record<string, any>> {
44
- return Array.isArray(content) ? content.filter((part) => !!part && typeof part === "object") : [];
45
- }
46
-
47
- function plainText(output: unknown): string {
48
- if (typeof output === "string") return output;
49
- if (Array.isArray(output))
50
- return output
51
- .map((part) => (part && typeof part === "object" && "text" in part ? String((part as any).text) : ""))
52
- .filter(Boolean)
53
- .join("\n");
54
- return "";
55
- }
56
-
57
- function oneLine(text: string): string {
58
- return text.replace(/\s+/gu, " ").trim();
59
- }
60
-
61
- function clipText(text: string, budget: number): string {
62
- const single = oneLine(text);
63
- return single.length > budget ? `${single.slice(0, budget)}…` : single;
64
- }
File without changes
File without changes