pi-firecode 0.8.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +35 -15
  2. package/activity.ts +92 -0
  3. package/busy.ts +188 -0
  4. package/config.example.jsonc +38 -47
  5. package/config.ts +198 -172
  6. package/deliver.ts +100 -13
  7. package/flame.ts +149 -0
  8. package/format.ts +84 -31
  9. package/header.ts +208 -82
  10. package/index.ts +17 -11
  11. package/master/actions.ts +293 -0
  12. package/master/activity-list.ts +281 -0
  13. package/master/event-card.ts +18 -37
  14. package/master/event-format.ts +71 -40
  15. package/master/guard.ts +46 -0
  16. package/master/index.ts +88 -1036
  17. package/master/list-view.ts +129 -0
  18. package/master/outbox.ts +188 -0
  19. package/master/prompts/master.zh.md +17 -12
  20. package/master/prompts/worker.zh.md +1 -1
  21. package/master/role.ts +6 -1
  22. package/master/run.ts +315 -0
  23. package/master/runtime.ts +259 -0
  24. package/master/spawn.ts +79 -38
  25. package/master/state.ts +30 -6
  26. package/master/worker-view.ts +517 -0
  27. package/package.json +1 -1
  28. package/provider/claude-sub.ts +35 -13
  29. package/provider/openai-native/src/compact-client.ts +2 -14
  30. package/provider/openai-native/src/options.ts +4 -2
  31. package/review/advisor.ts +0 -2
  32. package/review/card.ts +36 -23
  33. package/review/checkpoint.ts +2 -8
  34. package/review/evidence.ts +42 -44
  35. package/review/index.ts +271 -525
  36. package/review/occupancy.ts +25 -0
  37. package/review/outcome.ts +31 -7
  38. package/review/prompt.ts +7 -8
  39. package/review/prompts/review.en.md +7 -6
  40. package/review/prompts/review.zh.md +7 -6
  41. package/review/reviewer.ts +2 -5
  42. package/review/session.ts +11 -30
  43. package/review/state.ts +32 -60
  44. package/review/ui.ts +30 -469
  45. package/round-recorder.ts +18 -0
  46. package/session/herdr-display.ts +1 -1
  47. package/session/presets.ts +130 -87
  48. package/session/quota.ts +123 -0
  49. package/session/stats.ts +4 -1
  50. package/statusbar/index.ts +242 -104
  51. package/statusbar/render.ts +95 -163
  52. package/theme.ts +2 -41
  53. package/tools/actions.ts +53 -0
  54. package/tools/assistant-view.ts +64 -0
  55. package/tools/click-anchor.ts +56 -0
  56. package/tools/group-view.ts +420 -0
  57. package/tools/grouping.ts +146 -221
  58. package/tools/host.ts +369 -0
  59. package/tools/index.ts +108 -105
  60. package/tools/line.ts +95 -58
  61. package/tools/machine.ts +78 -0
  62. package/tools/parts.ts +2 -2
  63. package/tools/round.ts +95 -0
  64. package/tools/turn-clock.ts +41 -0
  65. package/tools/turn-summary.ts +109 -0
  66. package/truncated-write.ts +18 -0
  67. package/watcher/card.ts +28 -22
  68. package/watcher/index.ts +16 -44
  69. package/watcher/observer.ts +3 -3
  70. package/watcher/prompts/watch.zh.md +1 -1
  71. package/watcher/transcript.ts +4 -12
  72. package/flame-frames.ts +0 -460
  73. package/review/progress.ts +0 -324
  74. package/session/bark.ts +0 -157
  75. package/session/working-flame.ts +0 -116
  76. package/statusbar/quota-cache.ts +0 -60
  77. package/statusbar/quota-parse.ts +0 -85
  78. package/statusbar/quota.ts +0 -203
  79. package/statusbar/tps.ts +0 -87
package/README.md CHANGED
@@ -1,40 +1,60 @@
1
1
  # FireCode
2
2
 
3
- FireCode 是一个模块化 Pi Package,提供终端状态与工具渲染、会话预设、`/fire-review` 对抗审查、`/fire-master` 进程内子代理委派和 `/fire-watch` 观察员。指挥官与观察员的新会话状态由配置决定,裸命令只翻转当前会话;各模块由功能开关独立注册,关闭任一模块不会改变其余模块。
3
+ FireCode 是一个 Pi 扩展包:把会话状态嵌进输入框边框、把每轮过程折叠成一行摘要,并提供对抗审查、多子代理指挥与观察员。各功能独立开关,关掉任一个不影响其余。
4
4
 
5
- ## 安装
6
-
7
- ```bash
8
- pi install git:github.com/Suge8/firecode@v0.6.1
5
+ ```text
6
+ ─ ⣾⣿⣷ 处理中 1m3s · ⠹ 审查 第2轮 1/3 · 1 阻断 ──────────── 观察员 指挥官 ─
7
+ 输入区
8
+ ─ 优化插件状态栏和工具展示 ───────────── gpt-6-astra/medium Fast · 42.3%/200k ─
9
9
  ```
10
10
 
11
- 也可在本地仓库中直接试用:
11
+ ## 安装
12
12
 
13
13
  ```bash
14
- pi -e .
14
+ pi install npm:pi-firecode
15
15
  ```
16
16
 
17
- Pi Package 拥有与 Pi 相同的本机权限;安装前应审阅源码。
17
+ 需要 Pi 1.1.0 及以上,升级用 `pi update`。扩展拥有与 Pi 相同的本机权限,安装前请审阅源码。
18
18
 
19
19
  ## 配置
20
20
 
21
- 运行配置不随包分发。将公开模板复制到 Pi Agent 目录后按需启用功能,再重启 Pi:
21
+ 配置不随包生效。把推荐模板复制到 Pi Agent 目录,再重启 Pi:
22
22
 
23
23
  ```bash
24
24
  agent_dir="${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}"
25
25
  mkdir -p "$agent_dir/extensions/firecode"
26
- curl -fsSL https://raw.githubusercontent.com/Suge8/firecode/v0.6.1/config.example.jsonc \
27
- -o "$agent_dir/extensions/firecode/config.jsonc"
26
+ curl -fsSL https://unpkg.com/pi-firecode/config.example.jsonc -o "$agent_dir/extensions/firecode/config.jsonc"
28
27
  ```
29
28
 
30
- 公开模板提供维护者当前的完整推荐配置:除 Bark 外功能全开,Master 与 Watcher 在新会话自动激活。Watcher 会在每个主会话回合后调用模型,OpenAI priority 会按供应商规则加价;复制前应确认列出的模型均已认证并接受额外费用。审查与观察员的模型必须显式写入运行配置,否则对应功能拒绝启动。缺少运行配置时,FireCode 会关闭可选功能并在会话启动时警告;配置模板本身不会被运行时读取。
29
+ - 模板里的模型一律写成 `"provider/model/thinking"`,换成你已登录的模型。
30
+ - 审查、指挥官、观察员会真实调用配置里的模型;某一节配错时该功能拒绝启动并提示,不会改用默认模型。
31
+ - 会额外花钱的:观察员每回合调用一次模型(模板里默认不自动开启);OpenAI 加速档按供应商规则加价。
32
+ - 没有配置文件时,可选功能全部关闭,并在会话启动时提示。
33
+
34
+ ## 功能
35
+
36
+ | 功能 | 入口 | 做什么 |
37
+ | --- | --- | --- |
38
+ | 输入框状态 | 自动 | 上边框显示进行状态、计时与审查进度,下边框显示会话标题、模型、思考档与上下文占用 |
39
+ | 过程折叠 | 自动;点摘要或 `Ctrl+O` 展开 | 每次输入折成一轮:一行摘要(耗时、均速)、最近 3 条中间回复的首句、最终回复全文 |
40
+ | 预设 | `/preset`、预设里配的快捷键 | 一键切换模型、思考档、工具集与附加指令 |
41
+ | 对抗审查 | `/fire-review` | 多个模型并行审查改动;没过自动修复再审,连续失败请顾问模型裁决 |
42
+ | 指挥官 | `/fire-master`(新会话默认开启) | 按角色表把工作派给子代理并行执行;输入框上方列出子代理,点一行看它的全过程并可直接补话 |
43
+ | 观察员 | `/fire-watch` | 每回合结束后用便宜模型评估新增内容,跑偏或过度工程时插话 |
44
+ | 用量 | `/quota`、`/tokens` | 查 Claude、Codex 订阅剩余额度;统计本地会话的 token 与成本 |
45
+ | 会话名 | `/rename`、`Ctrl+R` | 改当前会话名 |
46
+ | OpenAI 请求层 | `Ctrl+F` 切加速档 | 回答详略、加速档、可选的 OpenAI 原生上下文压缩 |
47
+ | Claude 订阅适配 | 自动 | 请求带 Claude Code 归因;令牌换发导致的 401 自动重试一次 |
48
+
49
+ 在配置的 `features` 里写 `false` 即可关闭对应功能,比如 `"features": { "watcher": false }`。
31
50
 
32
51
  ## 开发
33
52
 
34
- 需要 [Bun](https://bun.sh/) 和一个 pi-mono checkout。开发版 `pi` 在 `PATH` 中时,测试会自动定位它;否则设置 `PI_PACKAGES_DIR` 为 pi-mono 的 `packages/` 目录。
53
+ 需要 [Bun](https://bun.sh/) 和一份 pi-mono 源码:开发版 `pi` 在 `PATH` 中时测试会自动定位它,否则把 `PI_PACKAGES_DIR` 设为 pi-mono 的 `packages/` 目录。
35
54
 
36
55
  ```bash
37
- bun test
56
+ bun test # 测试
57
+ pi -e . # 在本地仓库直接试用
38
58
  ```
39
59
 
40
- 模块边界、状态机约束和领域术语见 `AGENTS.md`、各模块的 `AGENTS.md` 与 `CONTEXT.md`。
60
+ 模块边界、状态机约束与术语见 `AGENTS.md`、各模块的 `AGENTS.md` 与 `WORDS.md`。
package/activity.ts ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * 活动行:输入框上方子代理活动列表(master/activity-list.ts)的一行布局。
3
+ * 标记 名字 角色 · 当前动作 · 提醒 …… 耗时。宽度不够时的退让顺序:先缩名字列(整表一致,名字截短带 …)、
4
+ * 再丢角色(整表一致),最后截动作文字。卡住行的提醒比动作重要:先保提醒(放不下全写法换短写法),
5
+ * 动作截到看不出是哪条命令(不足 COMFORT_ACTION_WIDTH)就整段不显示,不留“操作 …”这种没信息的残片。
6
+ */
7
+ import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
8
+ import { visibleWidth } from "@earendil-works/pi-tui";
9
+ import { clip } from "./format.js";
10
+ import { HEAT_COLORS, paint } from "./flame.js";
11
+
12
+ /** 动作文字至少留这么宽才值得显示(一个字加省略号)。 */
13
+ const MIN_ACTION_WIDTH = 4;
14
+ /** 动作文字想要的最小宽度:保得住它才保名字全长与角色(约“操作 $ bun t…”)。 */
15
+ const COMFORT_ACTION_WIDTH = 12;
16
+ /** 名字列再窄就认不出是谁了。 */
17
+ const MIN_NAME_WIDTH = 9;
18
+ const SEP = " · ";
19
+
20
+ export interface ActivityRow {
21
+ /** 已着色的单格标记:火苗、◈、‖、◌、✓、✗。 */
22
+ mark: string;
23
+ name: string;
24
+ role: string;
25
+ /** 当前动作;为空时只显示角色(如空闲行)。 */
26
+ action: string;
27
+ /** 当前动作的语气:审查金色,失败红色,被中断黄色。 */
28
+ tone?: "review" | "failed" | "warning";
29
+ /** 动作后追加的黄色提醒(卡住行的“无输出”):比动作重要,宽度不够先换短写法,动作没信息就整段不显示。 */
30
+ note?: { full: string; short: string };
31
+ elapsed: string;
32
+ /** 已落定的行文字退为暗色,只有标记保留颜色。 */
33
+ settled?: boolean;
34
+ }
35
+
36
+ const pad = (text: string, width: number) => text + " ".repeat(Math.max(0, width - visibleWidth(text)));
37
+
38
+ /** 缩进、标记、名字列与耗时之外留给“角色 · 动作 · 提醒”的宽度。 */
39
+ function middleRoom(row: ActivityRow, width: number, nameWidth: number): number {
40
+ return width - (2 + 1 + 1 + nameWidth + 2 + visibleWidth(row.elapsed) + 1) - 1;
41
+ }
42
+
43
+ /** 一行至少要给动作和提醒留多少:动作想要的宽度加提醒的短写法。 */
44
+ function wanted(row: ActivityRow): number {
45
+ return (row.action ? COMFORT_ACTION_WIDTH : 0) + (row.note ? visibleWidth(row.note.short) : 0);
46
+ }
47
+
48
+ /** 名字列按剩余空间分配:每行都给动作留够了还有余,名字就不截;不够才缩到下限。 */
49
+ export function nameWidthFor(rows: readonly ActivityRow[], width: number): number {
50
+ const longest = Math.max(0, ...rows.map((row) => visibleWidth(row.name)));
51
+ const room = Math.min(...rows.map((row) => middleRoom(row, width, longest) - wanted(row)));
52
+ return room >= 0 ? longest : Math.min(longest, Math.max(MIN_NAME_WIDTH, longest + room));
53
+ }
54
+
55
+ /** 这一行在给定宽度下是否值得保留角色;列表据此整表决定,列才对得齐。 */
56
+ export function roleFits(row: ActivityRow, width: number, nameWidth: number): boolean {
57
+ return middleRoom(row, width, nameWidth) - visibleWidth(row.role) - SEP.length - wanted(row) >= 0;
58
+ }
59
+
60
+ export function renderActivityRow(
61
+ row: ActivityRow,
62
+ width: number,
63
+ nameWidth: number,
64
+ theme: Theme,
65
+ showRole = roleFits(row, width, nameWidth),
66
+ ): string {
67
+ const color = (base: ThemeColor) => (row.settled ? "dim" : base);
68
+ const head = ` ${row.mark} ${theme.fg(color("text"), pad(clip(row.name, nameWidth), nameWidth))}`;
69
+ const room = middleRoom(row, width, nameWidth);
70
+ if (room < 0) return clip(head, width, "end", "");
71
+ const paintAction = (text: string) =>
72
+ row.tone === "review" ? paint(HEAT_COLORS.gold, text)
73
+ : row.tone === "failed" ? theme.fg("error", text)
74
+ : row.tone === "warning" ? theme.fg("warning", text)
75
+ : theme.fg(color("muted"), text);
76
+ const actionRoom = showRole ? room - visibleWidth(row.role) - SEP.length : room;
77
+ const action = row.note ? withNote(row, actionRoom, paintAction, theme) : (
78
+ row.action && actionRoom >= MIN_ACTION_WIDTH ? paintAction(clip(row.action, actionRoom)) : "");
79
+ const role = theme.fg(color("muted"), row.role);
80
+ const middle = showRole ? (action ? `${role}${theme.fg("dim", SEP)}${action}` : role) : action;
81
+ return `${head} ${pad(middle, room + 1)}${theme.fg(color("muted"), row.elapsed)} `;
82
+ }
83
+
84
+ /** “动作 · 提醒”:提醒优先;剩下的位置装得下整条动作或至少能认出命令才带上动作。 */
85
+ function withNote(row: ActivityRow, room: number, paintAction: (text: string) => string, theme: Theme): string {
86
+ const { full, short } = row.note!;
87
+ const note = visibleWidth(full) <= room ? full : clip(short, room);
88
+ const actionRoom = room - visibleWidth(note) - SEP.length;
89
+ const fits = visibleWidth(row.action) <= actionRoom || actionRoom >= COMFORT_ACTION_WIDTH;
90
+ if (!row.action || !fits) return theme.fg("warning", note);
91
+ return `${paintAction(clip(row.action, actionRoom))}${theme.fg("warning", `${SEP}${note}`)}`;
92
+ }
package/busy.ts ADDED
@@ -0,0 +1,188 @@
1
+ /**
2
+ * “会话进行中”的单一事实:指挥官回合在跑 || 有子代理在飞(定义见 master/outbox.ts)|| 主会话 /fire-review 进行中(review 的占用频道)。
3
+ * 指挥官回合结束不等于歇下:回合结束后仍有子代理在飞、审查在跑,会话照旧进行中。
4
+ * Master 是在飞子代理数的唯一发布者;轮记录器、上边框、本轮摘要与轮次时钟都经 watchBusy 读同一个事实并消费同一个歇下边沿。
5
+ * 本段进行中的起点也只在这里记:首次变忙那一刻起,中途的人类输入与结果唤醒都不重置,歇下边沿报告整段事实:时长、终态、均速。
6
+ * 频道名与 payload 只在本文件定义。
7
+ */
8
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
9
+ import { formatDuration } from "./format.js";
10
+ import { OCCUPANCY_CHANNEL, type OccupancyPayload } from "./review/occupancy.js";
11
+
12
+ /** 进程内事件总线:在飞子代理数变化时发布 `{ inFlight }`,激活/停用同步。 */
13
+ export const WORKERS_CHANNEL = "firecode:workers";
14
+ export interface WorkersPayload {
15
+ inFlight: number;
16
+ /** Master 停用遗弃在飞子代理:归零只结束本段,不是歇下。 */
17
+ teardown?: true;
18
+ }
19
+
20
+ /**
21
+ * herdr 通用“进行中”频道,与 herdr:blocked 同构:消费者按 active 的 true/false 做计数配对。
22
+ * Master 只在在飞数 0↔正数跃迁时发布,保证配对;指挥官自己的回合 herdr 已由 agent_start/settled 得知。
23
+ */
24
+ export const HERDR_WORKING_CHANNEL = "herdr:working";
25
+ export interface HerdrWorkingPayload {
26
+ active: boolean;
27
+ label?: string;
28
+ }
29
+ export const HERDR_WORKING_LABEL = "子代理进行中";
30
+
31
+ export interface BusyView {
32
+ agentRunning: boolean;
33
+ inFlight: number;
34
+ /** 主会话 /fire-review 进行中(含修复与总结回合之间的等待):算会话进行中,审查时长计入这一段。 */
35
+ review: boolean;
36
+ /** 会话进行中 = 指挥官回合在跑 || 有子代理在飞 || 主会话审查进行中。 */
37
+ busy: boolean;
38
+ /** 本段进行中的起点(Date.now);当且仅当 busy 时存在。 */
39
+ since?: number;
40
+ }
41
+ export const IDLE: BusyView = { agentRunning: false, inFlight: 0, review: false, busy: false };
42
+
43
+ /**
44
+ * 本段最后一个指挥官回合的终态:宿主的回合中断信号(ctx.signal.aborted)为真即“已中断”——工具执行中被 Esc 时
45
+ * 宿主给的终态是 error(“The operation was aborted.”),不能只看 stopReason;其余按最后一条助手消息的 stopReason。
46
+ */
47
+ export type Outcome = "complete" | "aborted" | "error";
48
+ export interface SettledRound {
49
+ elapsed: number;
50
+ outcome: Outcome;
51
+ /**
52
+ * 均速(token/s):指挥官各回合的输出 token 之和除以模型请求墙钟之和,等子代理与跑工具不算分母。
53
+ * 任一请求失败、中断、未配对或压缩失败则整段不给,不出半截的数。
54
+ */
55
+ tps?: number;
56
+ }
57
+
58
+ /** 终态字样;完成不写字。 */
59
+ export const OUTCOME_TEXT: Record<Outcome, string> = { complete: "", aborted: "已中断", error: "请求失败" };
60
+ const RATE_FORMAT = new Intl.NumberFormat("en-US", { maximumSignificantDigits: 3, useGrouping: false });
61
+
62
+ /** 落定记录的展示片段(未着色):耗时,有均速再跟一段。上边框与摘要行共用。 */
63
+ export function roundTexts(round: SettledRound): string[] {
64
+ return [formatDuration(round.elapsed), ...(round.tps ? [`${RATE_FORMAT.format(round.tps)} tps`] : [])];
65
+ }
66
+
67
+ /** 本段的模型请求计时;requestMs 为 undefined 表示本段已无法给出均速。 */
68
+ interface Requests {
69
+ startedAt?: number;
70
+ requestMs?: number;
71
+ outputTokens: number;
72
+ }
73
+ const FRESH: Requests = { requestMs: 0, outputTokens: 0 };
74
+ /** 输出 token 少于这个数时均速没有意义(1 个 token 的快答算出来的 tps 只是噪声)。 */
75
+ const MIN_RATE_TOKENS = 20;
76
+
77
+ function settledRound(elapsed: number, outcome: Outcome, { startedAt, requestMs, outputTokens }: Requests): SettledRound {
78
+ const valid = outcome === "complete" && startedAt === undefined && requestMs && outputTokens >= MIN_RATE_TOKENS;
79
+ return { elapsed, outcome, ...(valid ? { tps: (outputTokens * 1_000) / requestMs } : {}) };
80
+ }
81
+
82
+ export interface BusyHandlers {
83
+ /** 任一来源变化后调用(含歇下那一次,先于 onSettled)。 */
84
+ onChange?(view: BusyView, ctx: ExtensionContext | undefined): void;
85
+ /** 会话歇下边沿:busy 由真变假时触发一次,带本段进行中的总时长与终态。两个来源——agent_settled 时在飞数为 0,或在飞数归零时指挥官已空闲。 */
86
+ onSettled?(ctx: ExtensionContext | undefined, round: SettledRound): void;
87
+ }
88
+
89
+ function outcomeOf(messages: readonly { role: string; stopReason?: string }[]): Outcome {
90
+ const stop = messages.findLast((message) => message.role === "assistant")?.stopReason;
91
+ return stop === "aborted" || stop === "error" ? stop : "complete";
92
+ }
93
+
94
+ const HUBS = Symbol.for("firecode.busy");
95
+
96
+ /**
97
+ * 会话进行中的唯一判定与歇下边沿:上边框与轮次时钟都只订阅这里,不各自拼装。
98
+ * 每个 pi 只有一份状态机,首个订阅者安装宿主事件,之后只追加订阅;登记挂在 globalThis 上,
99
+ * 宿主按文件加载模块副本时同一个 pi 仍只命中一份。
100
+ * 指挥官回合以 agent_start → agent_settled(且 ctx.isIdle())为界。在飞数归零与回合落定先后不定
101
+ * (闲时前门投递在宿主记录这条消息后才算送达,见 deliver.ts),歇下必须在两个来源都满足的那一刻触发。
102
+ * 拆会话(session_shutdown)与 Master 停用遗弃子代理只结束本段,不发歇下边沿。
103
+ */
104
+ export function watchBusy(pi: ExtensionAPI, handlers: BusyHandlers): void {
105
+ const hubs = ((globalThis as Record<symbol, unknown>)[HUBS] ??= new WeakMap()) as WeakMap<ExtensionAPI, BusyHandlers[]>;
106
+ const subscribers = hubs.get(pi);
107
+ if (subscribers) {
108
+ subscribers.push(handlers);
109
+ return;
110
+ }
111
+ const list = [handlers];
112
+ hubs.set(pi, list);
113
+ installBusy(pi, list);
114
+ }
115
+
116
+ function installBusy(pi: ExtensionAPI, subscribers: readonly BusyHandlers[]): void {
117
+ let agentRunning = false;
118
+ let inFlight = 0;
119
+ let review = false;
120
+ /** 本段起点;有值即进行中。 */
121
+ let since: number | undefined;
122
+ let outcome: Outcome = "complete";
123
+ let requests = FRESH;
124
+ let ctx: ExtensionContext | undefined;
125
+ let closed = false;
126
+ const update = (teardown = false) => {
127
+ if (closed) return;
128
+ const now = Date.now();
129
+ const busy = agentRunning || inFlight > 0 || review;
130
+ if (busy && since === undefined) {
131
+ since = now;
132
+ requests = FRESH;
133
+ }
134
+ const started = since;
135
+ if (!busy) since = undefined;
136
+ const view: BusyView = { agentRunning, inFlight, review, busy, since };
137
+ for (const subscriber of subscribers) subscriber.onChange?.(view, ctx);
138
+ if (busy || started === undefined || teardown) return;
139
+ const round = settledRound(now - started, outcome, requests);
140
+ for (const subscriber of subscribers) subscriber.onSettled?.(ctx, round);
141
+ };
142
+ pi.on("session_shutdown", () => {
143
+ closed = true;
144
+ });
145
+ pi.on("agent_end", (event, context) => {
146
+ outcome = context.signal?.aborted ? "aborted" : outcomeOf(event.messages);
147
+ });
148
+ pi.on("before_provider_request", () => {
149
+ // 上一次请求没有等到助手 message_end 就又发起:起止无法配对。
150
+ requests = { ...requests, startedAt: Date.now(), requestMs: requests.startedAt === undefined ? requests.requestMs : undefined };
151
+ });
152
+ pi.on("message_end", ({ message }) => {
153
+ if (message.role !== "assistant") return;
154
+ const duration = requests.startedAt === undefined ? 0 : Date.now() - requests.startedAt;
155
+ const output = message.usage.output;
156
+ const valid = requests.requestMs !== undefined && duration > 0 && Number.isFinite(output) && output > 0
157
+ && (message.stopReason === "stop" || message.stopReason === "toolUse");
158
+ requests = valid
159
+ ? { requestMs: requests.requestMs! + duration, outputTokens: requests.outputTokens + output }
160
+ : { requestMs: undefined, outputTokens: requests.outputTokens };
161
+ });
162
+ // 压缩的模型调用没有助手 message_end,不把它的起点借给下一条回复;压缩失败则本段不给均速。
163
+ const clearRequest = () => { requests = { ...requests, startedAt: undefined }; };
164
+ pi.on("session_before_compact", clearRequest);
165
+ pi.on("session_compact", clearRequest);
166
+ pi.on("session_compact_failed", () => { requests = { requestMs: undefined, outputTokens: requests.outputTokens }; });
167
+ pi.on("agent_start", (_event, context) => {
168
+ ctx = context;
169
+ agentRunning = true;
170
+ update();
171
+ });
172
+ pi.on("agent_settled", (_event, context) => {
173
+ ctx = context;
174
+ // 宿主在 agent_settled 期间可能已有排队/延后的动作(isIdle 为 false),紧接着会再 agent_start:不算回合结束。
175
+ agentRunning = context.isIdle() !== true;
176
+ update();
177
+ });
178
+ // 主会话审查算会话进行中:审查与修复、总结回合同属这一段,审查时长计入轮记录。
179
+ pi.events.on(OCCUPANCY_CHANNEL, (data) => {
180
+ review = (data as OccupancyPayload).active;
181
+ update();
182
+ });
183
+ pi.events.on(WORKERS_CHANNEL, (data) => {
184
+ const payload = data as WorkersPayload;
185
+ inFlight = payload.inFlight;
186
+ update(payload.teardown === true);
187
+ });
188
+ }
@@ -1,6 +1,7 @@
1
1
  {
2
- // 公开推荐配置:除 Bark 外功能全开。复制后重启 Pi。
3
- // Watcher 默认在每个主会话回合后调用模型;OpenAI priority 会按供应商规则加价。
2
+ // 公开推荐配置:功能全开。复制后重启 Pi。
3
+ // Watcher 新会话不自动激活,需要时 /fire-watch 翻转当前会话。
4
+ // 凡是指定模型的位置都写同一个模型原子:"provider/model/thinking",思考档取最后一段。
4
5
  "features": {
5
6
  "header": true,
6
7
  "statusbar": true,
@@ -10,8 +11,6 @@
10
11
  "stats": true,
11
12
  "claudeSub": true,
12
13
  "openaiNative": true,
13
- "workingFlame": true,
14
- "bark": false,
15
14
  "review": true,
16
15
  "master": true,
17
16
  "watcher": true
@@ -19,82 +18,74 @@
19
18
 
20
19
  "keys": {
21
20
  "rename": "ctrl+r",
22
- "cyclePreset": "ctrl+shift+u",
23
21
  "fast": "ctrl+f"
24
22
  },
25
23
 
26
- // nativeCompaction 使用 OpenAI 原生上下文压缩;priority 加钱换更快出字。
24
+ // nativeCompaction 使用 OpenAI 原生上下文压缩;priority 加钱换更快出字,默认关。
27
25
  "openai": {
28
26
  "nativeCompaction": true,
29
27
  "providers": {
30
- "openai-codex": { "textVerbosity": "low", "priority": true },
31
- "xai": { "priority": true }
28
+ "openai-codex": { "textVerbosity": "low", "priority": false }
32
29
  }
33
30
  },
34
31
 
35
- // 每个主会话回合后评估新增内容,会产生额外模型调用;需要开口时当场插话。
36
- "watcher": {
37
- "enabled": true,
38
- "model": "openai-codex/gpt-5.6-sol",
39
- "thinking": "low",
40
- "context": "minimal"
41
- },
42
-
43
- // 新会话默认激活指挥官;每个模型原子必须是 provider/model/thinking。
32
+ // 新会话默认激活指挥官。
44
33
  "master": {
45
34
  "autoActivate": true,
46
35
  "workerExcludeExtensions": [],
47
36
  "roles": {
48
37
  "调研员": {
49
- "model": "openai-codex/gpt-5.6-luna/xhigh",
50
- "use": "大规模并行调研,便宜,不承担实现"
38
+ "model": "anthropic/claude-haiku-5-5/low",
39
+ "use": "只读不改:联网搜索、查文档与版本、读日志、按线索定位代码与核对资料",
40
+ "fallback": ["openai-codex/gpt-6-luna/medium"]
51
41
  },
52
42
  "工程师": {
53
- "model": "openai-codex/gpt-5.6-sol/medium",
54
- "use": "常规实现与调试,速度快",
55
- "fallback": ["anthropic/claude-opus-5/medium"]
56
- },
57
- "全栈": {
58
- "model": "anthropic/claude-opus-5/medium",
59
- "use": "前后端综合实现与调试",
60
- "fallback": ["openai-codex/gpt-5.6-sol/medium"]
43
+ "model": "anthropic/claude-sonnet-5-5/high",
44
+ "use": "要改代码:实现、调试;需要深读代码找缺陷的审计也派它(只读)",
45
+ "fallback": ["openai-codex/gpt-6.1-sol/high"]
61
46
  },
62
- "架构师": {
63
- "model": "anthropic/claude-fable-5/high",
64
- "use": "高级架构顾问;费用高,启用前必须先征求用户确认"
47
+ "批量工": {
48
+ "model": "anthropic/claude-haiku-5-5/medium",
49
+ "use": "规格明确、靠测试或类型检查就能验收的批量改动:按模式迁移、改名、补样板;量大时按文件划界并行多派",
50
+ "fallback": ["openai-codex/gpt-6.1-sol/medium"]
65
51
  },
66
52
  "设计师": {
67
- "model": "kimi-coding/k3-256k/high",
68
- "use": "纯视觉设计;已有固定组件和设计方案时不适用"
53
+ "model": "anthropic/claude-opus-5-5/medium",
54
+ "use": "视觉、前端",
55
+ "fallback": ["openai-codex/gpt-6.1-sol/high"]
69
56
  },
70
57
  "哨兵": {
71
- "model": "openai-codex/gpt-5.6-luna/low",
72
- "use": "盯守 CI、部署与长测试等等待过程"
58
+ "model": "anthropic/claude-haiku-5-5/low",
59
+ "use": "CI、部署、长测试等待",
60
+ "fallback": ["anthropic/claude-sonnet-5-5/low"]
73
61
  }
74
62
  }
75
63
  },
76
64
 
77
65
  "presets": {
78
- "fable": { "provider": "anthropic", "model": "claude-fable-5", "thinkingLevel": "high", "key": "alt+1" },
79
- "opus5": { "provider": "anthropic", "model": "claude-opus-5", "thinkingLevel": "medium", "key": "alt+2" },
80
- "sol": { "provider": "openai-codex", "model": "gpt-5.6-sol", "thinkingLevel": "medium", "key": "alt+3" },
81
- "gemini": { "provider": "antigravity", "model": "gemini-3.7-flash", "thinkingLevel": "high", "key": "alt+4" },
82
- "ds": { "provider": "deepseek", "model": "deepseek-v4-flash", "thinkingLevel": "high", "key": "alt+5" },
83
- "k3-256": { "provider": "kimi-coding", "model": "k3-256k", "thinkingLevel": "high", "key": "alt+6" },
84
- "xai": { "provider": "xai", "model": "grok-4.6", "thinkingLevel": "high", "key": "alt+7" }
66
+ "sonnet": { "model": "anthropic/claude-sonnet-5-5/high", "key": "alt+1" },
67
+ "opus": { "model": "anthropic/claude-opus-5-5/high", "key": "alt+2" },
68
+ "fable": { "model": "anthropic/claude-fable-5-1/medium", "key": "alt+3" },
69
+ "astra": { "model": "openai-codex/gpt-6-astra/medium", "key": "alt+4" },
70
+ "sol": { "model": "openai-codex/gpt-6.1-sol/medium", "key": "alt+5" },
71
+ "ds": { "model": "deepseek/deepseek-flash/high", "key": "alt+6" }
85
72
  },
86
73
 
87
74
  // 审查只在用户或指挥官发起时调用模型。
88
75
  "review": {
89
- "advisor": { "model": "anthropic/claude-fable-5", "thinking": "high" },
90
- "reviewers": [
91
- { "model": "anthropic/claude-sonnet-5", "thinking": "medium" },
92
- { "model": "anthropic/claude-opus-5", "thinking": "medium" }
93
- ],
76
+ "advisor": "anthropic/claude-fable-5-1/high",
77
+ "reviewers": ["anthropic/claude-opus-5-5/low", "openai-codex/gpt-6.1-sol/low"],
94
78
  "maxRounds": 5,
95
79
  "advisorAfterFailures": 2,
96
- "timeoutMinutes": 20,
80
+ "timeoutMinutes": 40,
97
81
  "tools": ["read", "grep", "find", "ls", "bash"],
98
82
  "language": "zh"
83
+ },
84
+
85
+ // 激活后每个主会话回合评估新增内容,会产生额外模型调用;需要开口时当场插话。
86
+ "watcher": {
87
+ "enabled": false,
88
+ "model": "openai-codex/gpt-6.1-sol/medium",
89
+ "context": "minimal"
99
90
  }
100
91
  }