@gavin-hu/my-pi-agent 0.17.0 → 0.18.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 (112) hide show
  1. package/README.md +17 -9
  2. package/README.zh-CN.md +16 -9
  3. package/extensions/bytefly/README.md +25 -18
  4. package/extensions/bytefly/notify/types.ts +1 -1
  5. package/extensions/bytefly/panel/settings.ts +25 -18
  6. package/extensions/file-browse/README.md +60 -21
  7. package/extensions/file-browse/chrome.ts +83 -0
  8. package/extensions/file-browse/client.ts +62 -0
  9. package/extensions/file-browse/config.ts +12 -3
  10. package/extensions/file-browse/diff-html.ts +94 -0
  11. package/extensions/file-browse/html.ts +19 -147
  12. package/extensions/file-browse/live-client.ts +47 -1
  13. package/extensions/file-browse/live.ts +35 -14
  14. package/extensions/file-browse/rev.ts +42 -0
  15. package/extensions/file-browse/router.ts +25 -4
  16. package/extensions/file-browse/server.ts +7 -0
  17. package/extensions/file-browse/styles.ts +2 -0
  18. package/extensions/goal/plan-route.ts +8 -2
  19. package/extensions/job/README.md +7 -5
  20. package/extensions/job/index.ts +15 -7
  21. package/extensions/memory/README.md +3 -3
  22. package/extensions/plan/README.md +280 -86
  23. package/extensions/plan/commands.ts +56 -5
  24. package/extensions/plan/feedback.ts +300 -63
  25. package/extensions/plan/index.ts +1 -1
  26. package/extensions/plan/plan-index.ts +1 -1
  27. package/extensions/plan/plan-text.ts +18 -1
  28. package/extensions/plan/plans.ts +165 -57
  29. package/extensions/plan/review-prompt.ts +93 -0
  30. package/extensions/plan/review.ts +78 -98
  31. package/extensions/plan/runtime.ts +1 -1
  32. package/extensions/plan/split.ts +87 -0
  33. package/extensions/plan/steps.ts +80 -93
  34. package/extensions/plan/tools/enter.ts +8 -3
  35. package/extensions/plan/tools/exit.ts +101 -23
  36. package/extensions/plan/tools/write.ts +8 -3
  37. package/extensions/plan/tools.ts +1 -1
  38. package/extensions/plan/tui.ts +51 -8
  39. package/extensions/plan/types.ts +37 -3
  40. package/extensions/plan/web-chrome.ts +2 -1
  41. package/extensions/plan/web-client.ts +121 -177
  42. package/extensions/plan/web-collab.ts +207 -0
  43. package/extensions/plan/web-comments.ts +269 -0
  44. package/extensions/plan/web-page.ts +103 -17
  45. package/extensions/plan/web-router.ts +146 -30
  46. package/extensions/plan/web-share-session.ts +197 -0
  47. package/extensions/plan/web-share.ts +169 -0
  48. package/extensions/plan/web-streams.ts +124 -0
  49. package/extensions/plan/web-tokens.ts +63 -0
  50. package/extensions/plan/web.ts +193 -45
  51. package/extensions/question/README.md +3 -0
  52. package/extensions/question/dialogs.ts +91 -1
  53. package/extensions/question/tools.ts +2 -1
  54. package/extensions/session-bus/README.md +247 -0
  55. package/extensions/session-bus/commands.ts +165 -0
  56. package/extensions/session-bus/format.ts +124 -0
  57. package/extensions/session-bus/index.ts +69 -0
  58. package/extensions/session-bus/notice.ts +58 -0
  59. package/extensions/session-bus/protocol.ts +134 -0
  60. package/extensions/session-bus/result.ts +243 -0
  61. package/extensions/session-bus/rows.ts +169 -0
  62. package/extensions/session-bus/runtime.ts +575 -0
  63. package/extensions/session-bus/store.ts +329 -0
  64. package/extensions/session-bus/team.ts +102 -0
  65. package/extensions/session-bus/tools.ts +227 -0
  66. package/extensions/session-bus/tui.ts +394 -0
  67. package/extensions/session-bus/types.ts +98 -0
  68. package/extensions/subagent/README.md +1 -1
  69. package/extensions/watch/README.md +9 -5
  70. package/extensions/watch/index.ts +15 -9
  71. package/extensions/wechat/README.md +20 -33
  72. package/extensions/wechat/bridge.ts +8 -8
  73. package/extensions/wechat/commands.ts +2 -2
  74. package/extensions/wechat/deliver.ts +57 -37
  75. package/extensions/wechat/interaction.ts +31 -16
  76. package/extensions/wechat/notices.ts +7 -7
  77. package/extensions/wechat/peer-worker.ts +19 -34
  78. package/extensions/wechat/presence.ts +3 -4
  79. package/extensions/wechat/prompt.ts +39 -24
  80. package/extensions/wechat/reply-pipeline.ts +12 -4
  81. package/extensions/worktree/git.ts +5 -0
  82. package/extensions/worktree/registry.ts +11 -5
  83. package/extensions/worktree/status.ts +1 -3
  84. package/lib/README.md +6 -2
  85. package/lib/claims/overlap.ts +43 -0
  86. package/lib/http/router.ts +40 -13
  87. package/lib/http/server.ts +27 -13
  88. package/lib/interaction.ts +10 -0
  89. package/lib/notice.ts +29 -2
  90. package/lib/plan-entry.ts +7 -2
  91. package/lib/plans/sections.ts +172 -0
  92. package/lib/plans/setting.ts +52 -0
  93. package/lib/plans/split.ts +215 -0
  94. package/lib/plans/store.ts +19 -0
  95. package/lib/plans/team.ts +151 -0
  96. package/lib/qr.ts +64 -0
  97. package/lib/repo-key.ts +74 -0
  98. package/lib/report.ts +52 -33
  99. package/lib/requirements.ts +1 -2
  100. package/lib/tui/README.md +16 -2
  101. package/lib/tui/dock-screen.ts +85 -17
  102. package/lib/tui/line-pane.ts +15 -4
  103. package/lib/tui/tool.ts +23 -4
  104. package/lib/web/components.ts +1 -1
  105. package/package.json +3 -2
  106. package/skills/init/SKILL.md +80 -45
  107. package/skills/init/references/agents-md-quality.md +26 -2
  108. package/skills/init/references/interview-questions.md +36 -14
  109. package/skills/next/SKILL.md +78 -53
  110. package/skills/parallel-dev/SKILL.md +150 -0
  111. package/extensions/plan/config.ts +0 -27
  112. package/extensions/wechat/qrcode.ts +0 -46
package/README.md CHANGED
@@ -36,7 +36,8 @@ want the agent to *think first and work safely*:
36
36
  plan file that the agent then follows step by step.
37
37
  - **Delegate and parallelize.** Run specialist subagents in their own Pi
38
38
  processes, push a long build into a background job, and let a watcher tail a
39
- log until it matters — all while you keep the thread.
39
+ log until it matters — then run several sessions on one repository through a
40
+ shared bus, each owning its slice, all while you keep the thread.
40
41
  - **Never wreck the checkout.** Isolation is one command: the task moves onto its
41
42
  own branch in a `git worktree`, and every prompt is snapshotted so a bad turn
42
43
  is one `/rewind` away.
@@ -47,7 +48,7 @@ want the agent to *think first and work safely*:
47
48
  through a bridge to the live session.
48
49
 
49
50
  Everything is optional and independently toggleable. Nothing is forked, and no
50
- installed Pi file is modified: Pi discovers the resources through the `pi`
51
+ installed Pi file is written to: Pi discovers the resources through the `pi`
51
52
  manifest in [`package.json`](./package.json), so the package upgrades with Pi
52
53
  like any other.
53
54
 
@@ -86,7 +87,8 @@ Then, from inside Pi:
86
87
  - `/bytefly update` re-runs the package update check now and reports the
87
88
  updateable packages, this package's version and release URL, or a skip.
88
89
  - `/plan <task>` starts a task in read-only planning mode.
89
- - `/skill:init` creates or updates `AGENTS.md` through a short built-in interview and a plan you review first.
90
+ - `/skill:init` creates or updates the instruction file Pi reads through a short
91
+ built-in interview and a plan you review first.
90
92
 
91
93
  One thing the pack does on its own: once per interactive session, `bytefly`
92
94
  asks Pi's package manager which configured packages are behind, suppresses Pi's
@@ -99,14 +101,14 @@ and `PI_OFFLINE` skips the check;
99
101
 
100
102
  ## What you get
101
103
 
102
- 18 extensions, 3 packaged skills, and 2 themes, grouped by what you are trying
104
+ 19 extensions, 5 packaged skills, and 2 themes, grouped by what you are trying
103
105
  to do. Each extension's own `README.md` has the full tool and command surface.
104
106
 
105
107
  ### Plan and delegate
106
108
 
107
109
  | Extension | Tools and commands | What it does |
108
110
  |---|---|---|
109
- | [`plan`](./extensions/plan/) | `enter_plan_mode`, `write_plan`, `exit_plan_mode`, `cancel_plan_mode`, `set_plan_steps`, `/plan`, `--plan` | Read-only planning: the model investigates, saves a plan under `.pi/plans`, and you approve it before anything is written. Review in the TUI, or open it in a browser to leave inline comments. The `init` skill uses the same review to create or update a project's `AGENTS.md`. |
111
+ | [`plan`](./extensions/plan/) | `enter_plan_mode`, `write_plan`, `exit_plan_mode`, `cancel_plan_mode`, `set_plan_steps`, `/plan`, `--plan` | Read-only planning: the model investigates, saves a plan under `~/.pi/agent/plans/<repo-key>` — one store per repository, shared by its worktrees — and you approve it before anything is written; with `plan.split` on, the approval can also split it into slices that separate sessions join by name. Review in the TUI, or open it in a browser to leave inline comments: the page follows the plan file live, and you can hand one reviewer link per teammate so each comments under their own name. The `init` skill uses the same review to create or update a project's `AGENTS.md`. |
110
112
  | [`check`](./extensions/check/) | `run_check`, `/check` | Detect the project's test, lint, or typecheck command (or take `check.command`), run it in a supervised process, and turn failures into a navigable `file:line` list. |
111
113
  | [`subagent`](./extensions/subagent/) | `subagent`, `/agents` | Delegate a task to a specialist agent (`explorer`, `planner`, `reviewer`, `worker`, `researcher`, `tester`, `debugger`, `documenter`) in its own Pi process — single, parallel, chained, or in the background, with a footer chip while it runs and a finish notice. |
112
114
  | [`todo`](./extensions/todo/) | `todo`, `/todos` | A persistent task list (`pending` / `in_progress` / `completed`) with a one-line widget. |
@@ -114,6 +116,7 @@ to do. Each extension's own `README.md` has the full tool and command surface.
114
116
  | [`question`](./extensions/question/) | `ask_user_question` | Ask you one or more structured questions (labelled options plus free-form "Other") and wait. |
115
117
  | [`job`](./extensions/job/) | `job`, `/jobs` | Run long-lived shell commands in the background; inspect logs, wait, or kill them. |
116
118
  | [`watch`](./extensions/watch/) | `watch`, `/watch` | Tail a file or command output, match lines against patterns, and alert you or hand matching lines to the agent. |
119
+ | [`session-bus`](./extensions/session-bus/) | `session`, `/sessions` (screen) | Coordinate parallel Pi sessions on one repository: discover peers, exchange short messages/events, consume an inbox, join a slice of an approved split or claim paths before editing, with `/sessions` as a live board of peers, unread mail, and claims. Local, pull-based by default, with an opt-in inbox watch that raises this session's turn once it is idle; it never spawns an agent. |
117
120
  | [`context`](./extensions/context/) | `/context` | Show where the context window goes — system prompt, tool definitions, conversation by role, injected notes — and compact from the report with the before and after numbers. |
118
121
 
119
122
  ### Keep your work safe
@@ -129,7 +132,7 @@ to do. Each extension's own `README.md` has the full tool and command surface.
129
132
  | Extension | Tools and commands | What it does |
130
133
  |---|---|---|
131
134
  | [`web-access`](./extensions/web-access/) | `web_search`, `web_fetch` | Search the web (keyless DuckDuckGo by default, optional SearXNG or Brave) and fetch a page as readable text, with paging, `find`, per-hop SSRF checks, and optional PDF support. |
132
- | [`file-browse`](./extensions/file-browse/) | `/browse` | Start a loopback, read-only file browser rooted at the working directory, with Markdown preview, a fuzzy quick-open palette, and live status for a page left open. |
135
+ | [`file-browse`](./extensions/file-browse/) | `/browse` | Start a loopback, read-only file browser rooted at the working directory, with Markdown preview, a fuzzy quick-open palette, a `path:12-30` reference copied from a selection, live status for a page left open, and a reload hint when the folder it lists changes. |
133
136
  | [`file-read`](./extensions/file-read/) | `read_doc` | Extract text from a local PDF, DOCX, DOC, ODT, RTF, or XLSX in process, paged. |
134
137
  | [`file-open`](./extensions/file-open/) | `/open`, `open_path` | Open or reveal a file or directory in the OS-native application. |
135
138
  | [`wechat`](./extensions/wechat/) | `/wechat`, `send_wechat` | Bridge a WeChat chat to the live session over the Weixin iLink bot API; inbound text, images, and files become turns. |
@@ -148,10 +151,11 @@ advertised to the model, and its instructions load only when the task matches
148
151
 
149
152
  | Skill | What it does |
150
153
  |---|---|
151
- | [`init`](./skills/init/SKILL.md) | Creates or updates a project's `AGENTS.md` through a built-in interactive interview and plan review, from repository evidence. |
154
+ | [`init`](./skills/init/SKILL.md) | Creates or updates the instruction file Pi actually reads (`AGENTS.md`, or `AGENTS.override.md` when it shadows it) through a built-in interactive interview and plan review, deriving commands, generated paths, packages, and purpose from repository evidence. |
152
155
  | [`refactor`](./skills/refactor/SKILL.md) | Audits a project or path for refactoring opportunities: duplication, oversized modules, coupling, drift, and high-churn code with weak tests. Returns a ranked shortlist with evidence and verification commands; discovery is read-only. |
153
156
  | [`changelog`](./skills/changelog/SKILL.md) | Records one user-visible bullet in the same commit as the change, audits commits since the last tag for missing or weak entries, and curates a release section from the project's own rules. |
154
- | [`next`](./skills/next/SKILL.md) | Triages what to do next — loose work, a broken or unfinished promise — from the tree or from this conversation (`/skill:next session`), with the words and a path after the command saying what to ask and where to look. Read-only, and capped at a short ranked list with evidence and the command that starts each. |
157
+ | [`parallel-dev`](./skills/parallel-dev/SKILL.md) | Runs parallel development as a lead and a team: a lead accountable for the contract, shared files, merge order and a green baseline who also owns a workstream, one owner per disjoint workstream, a walking skeleton first, advisory path claims, a turn-boundary protocol, and independent verification. |
158
+ | [`next`](./skills/next/SKILL.md) | Suggests the next todos by priority — loose work, a broken or unfinished promise — from this conversation once it has done work, and from the tree only when it is empty, with the words and a path after the command saying what to ask and where to look. Read-only, and capped at a short numbered list with evidence and the command that starts each. |
155
159
 
156
160
  ### Themes
157
161
 
@@ -174,6 +178,10 @@ README use `bytefly-dark`.
174
178
  <td width="50%"><img src="assets/job.png" alt="The /jobs screen: a summary line, the job rows with status glyphs and elapsed times, and the focused job's command, cwd, and last output line" width="100%"></td>
175
179
  <td width="50%"><img src="assets/rewind.png" alt="The /rewind screen: prompt rows newest first, a diamond marking prompts with a code snapshot, and the focused prompt's snapshot detail" width="100%"></td>
176
180
  </tr>
181
+ <tr>
182
+ <td width="50%"><img src="assets/session.png" alt="The /sessions screen: a counts summary, session rows with unread and claim counts, the focused session's cwd, claims, and identity, and the Tab scope and watch hints" width="100%"></td>
183
+ <td width="50%"><img src="assets/watch.png" alt="The /watch screen: a summary line and watcher rows with live match counts and the focused watcher's pattern and source" width="100%"></td>
184
+ </tr>
177
185
  </table>
178
186
 
179
187
  ### Tool renderings
@@ -184,7 +192,7 @@ each tool's own renderer (or bytefly's generic fallback): the user nameplate and
184
192
  a notice, Pi's built-ins, and every tool this package registers. The rail glyph
185
193
  is the `chrome.rail` setting.
186
194
 
187
- ![A full-page reference of every tool block: the user nameplate, a notice, the turn rule, Pi's read, write, edit, bash, grep, find, and ls renderers, and every package tool — todo, goal, memory, job, watch, subagent, web_search, web_fetch, read_doc, open_path, run_doctor, run_check, send_wechat, ask_user_question, the plan tools, and the worktree tools](assets/tool-renderings.png)
195
+ ![A full-page reference of every tool block: the user nameplate, a notice, the turn rule, Pi's read, write, edit, bash, grep, find, and ls renderers, and every package tool — todo, goal, memory, job, watch, subagent, web_search, web_fetch, read_doc, open_path, run_doctor, run_check, send_wechat, ask_user_question, session, the plan tools, and the worktree tools](assets/tool-renderings.png)
188
196
 
189
197
  `bun run screenshot:tools` regenerates it; `bun run screenshot:tool-pngs` writes
190
198
  one image per tool under [`assets/tools/`](./assets/tools).
package/README.zh-CN.md CHANGED
@@ -33,14 +33,15 @@ Pi 提供一个精简而锐利的内核。这个包是在其之上的一层,
33
33
  - **先规划,再执行。** 只读的规划模式把一次任务变成一份可审阅的计划文件,智能体
34
34
  随后按步骤执行。
35
35
  - **委派与并行。** 在各自的 Pi 进程中运行专职子智能体,把耗时构建交给后台任务,
36
- 让 watcher 盯住日志直到出现关键行 —— 你继续把握主线。
36
+ 让 watcher 盯住日志直到出现关键行,还能让同一仓库上的多个会话通过共享总线协作、
37
+ 各自负责自己的一摊 —— 你继续把握主线。
37
38
  - **绝不搞乱检出区。** 隔离只需一条命令:任务切到自己分支的 `git worktree` 中,
38
39
  每次提示还会生成快照,因此一次糟糕的回合只需一个 `/rewind` 就能回退。
39
40
  - **跨会话记忆。** 持久笔记以纯 Markdown 保存,可读、可改、可纳入版本控制。
40
41
  - **连通网络与文件。** 在会话内搜索、抓取、浏览、打开和读取文档与表格,还可通过桥接
41
42
  在微信里直接回答当前会话。
42
43
 
43
- 一切都是可选的,且可独立开关。没有任何东西被分叉,也没有修改已安装的 Pi 文件:
44
+ 一切都是可选的,且可独立开关。没有任何东西被分叉,也没有写入任何已安装的 Pi 文件:
44
45
  Pi 通过 [`package.json`](./package.json) 里的 `pi` manifest 发现这些资源,因此
45
46
  该包能像其他包一样随 Pi 一起升级。
46
47
 
@@ -74,7 +75,7 @@ Windows、macOS 和 Linux 均可运行,完整检查套件在每个平台上都
74
75
  - `/bytefly update` 立即重跑包更新检查,报告可更新的包、本包版本与 release URL,
75
76
  或跳过原因。
76
77
  - `/plan <任务>` 以只读规划模式开始一个任务。
77
- - `/skill:init` 通过内置的简短访谈和先审阅的计划创建或更新 `AGENTS.md`。
78
+ - `/skill:init` 通过内置的简短访谈和先审阅的计划创建或更新 Pi 实际读取的指令文件。
78
79
 
79
80
  还有一件包会自行做的事:每次交互式会话启动时,`bytefly` 询问 Pi 的包管理器
80
81
  哪些已配置的包有更新,抑制 Pi 自己的启动更新块,并用主题化 notice 列出它们
@@ -85,14 +86,14 @@ changelog。新版本还会标在头部 —— 本包的与 Pi 自己的。不
85
86
 
86
87
  ## 包含内容
87
88
 
88
- 18 个扩展、4 个随包技能,以及 2 个主题,按你想达成的目标分组。每个扩展自己的 `README.md`
89
+ 19 个扩展、5 个随包技能,以及 2 个主题,按你想达成的目标分组。每个扩展自己的 `README.md`
89
90
  提供完整的工具与命令说明。
90
91
 
91
92
  ### 规划与委派
92
93
 
93
94
  | 扩展 | 工具与命令 | 说明 |
94
95
  |---|---|---|
95
- | [`plan`](./extensions/plan/) | `enter_plan_mode`, `write_plan`, `exit_plan_mode`, `cancel_plan_mode`, `set_plan_steps`, `/plan`, `--plan` | 只读规划:模型先调研,把计划保存到 `.pi/plans` 下,你在写入任何内容前先批准。可在 TUI 中审阅,或在浏览器中打开以留下行内评论。`init` 技能复用同一审阅流程来创建或更新项目的 `AGENTS.md`。 |
96
+ | [`plan`](./extensions/plan/) | `enter_plan_mode`, `write_plan`, `exit_plan_mode`, `cancel_plan_mode`, `set_plan_steps`, `/plan`, `--plan` | 只读规划:模型先调研,把计划保存到 `~/.pi/agent/plans/<repo-key>` 下(每个仓库一份,各 worktree 共享),你在写入任何内容前先批准;开启 `plan.split` 后,批准时还可以把计划拆成切片,由独立会话按名字加入。可在 TUI 中审阅,或在浏览器中打开以留下行内评论:页面会跟随计划文件实时更新,你也可以给每位同事各一个审阅链接,各自以绑定的名义评论。`init` 技能复用同一审阅流程来创建或更新项目的 `AGENTS.md`。 |
96
97
  | [`check`](./extensions/check/) | `run_check`, `/check` | 检测项目的测试、lint 或类型检查命令(或使用 `check.command`),在受监管的进程中运行,并把失败整理成可导航的 `file:line` 列表。 |
97
98
  | [`subagent`](./extensions/subagent/) | `subagent`, `/agents` | 把任务委派给专职智能体(`explorer`、`planner`、`reviewer`、`worker`、`researcher`、`tester`、`debugger`、`documenter`),运行在它自己的 Pi 进程中 —— 单个、并行、链式或在后台运行;后台运行时在页脚显示数量,完成后发出提示。 |
98
99
  | [`todo`](./extensions/todo/) | `todo`, `/todos` | 持久任务列表(`pending` / `in_progress` / `completed`),配一行式 widget。 |
@@ -100,6 +101,7 @@ changelog。新版本还会标在头部 —— 本包的与 Pi 自己的。不
100
101
  | [`question`](./extensions/question/) | `ask_user_question` | 向你提出一个或多个结构化问题(带标签的选项外加自由填写的“其他”)并等待回答。 |
101
102
  | [`job`](./extensions/job/) | `job`, `/jobs` | 在后台运行长时 shell 命令;查看日志、等待或结束它们。 |
102
103
  | [`watch`](./extensions/watch/) | `watch`, `/watch` | 跟踪文件或命令输出,按模式匹配行,并提醒你或把匹配的行交给智能体。 |
104
+ | [`session-bus`](./extensions/session-bus/) | `session`, `/sessions`(屏幕) | 在同一仓库的多个 Pi 会话之间协调:发现彼此、交换简短消息/事件、读取收件箱、加入已批准拆分中的一个切片或编辑前声明路径;`/sessions` 是显示对等会话、未读邮件和路径声明的实时面板。本地、默认拉取式;会话可以自行开启收件箱监视,在空闲时唤起本会话的一轮,从不创建智能体。 |
103
105
  | [`context`](./extensions/context/) | `/context` | 显示上下文窗口的去向 —— 系统提示、工具定义、按角色拆分的对话、注入的笔记 —— 并从报告中触发压缩,给出压缩前后的数字。 |
104
106
 
105
107
  ### 保护你的工作
@@ -115,7 +117,7 @@ changelog。新版本还会标在头部 —— 本包的与 Pi 自己的。不
115
117
  | 扩展 | 工具与命令 | 说明 |
116
118
  |---|---|---|
117
119
  | [`web-access`](./extensions/web-access/) | `web_search`, `web_fetch` | 搜索网络(默认无密钥的 DuckDuckGo,可选 SearXNG 或 Brave),并把页面抓取为可读文本,支持分页、`find`、逐跳 SSRF 检查以及可选的 PDF 支持。 |
118
- | [`file-browse`](./extensions/file-browse/) | `/browse` | 启动一个以工作目录为根的本地回环、只读文件浏览器,带 Markdown 预览、模糊快速打开面板,以及为常开页面提供的实时状态。 |
120
+ | [`file-browse`](./extensions/file-browse/) | `/browse` | 启动一个以工作目录为根的本地回环、只读文件浏览器,带 Markdown 预览、模糊快速打开面板、从选区复制 `path:12-30` 引用、为常开页面提供的实时状态,以及所列目录变化时的重新加载提示。 |
119
121
  | [`file-read`](./extensions/file-read/) | `read_doc` | 在进程内从本地 PDF、DOCX、DOC、ODT、RTF 或 XLSX 提取文本,支持分页。 |
120
122
  | [`file-open`](./extensions/file-open/) | `/open`, `open_path` | 用操作系统原生应用打开或定位文件/目录。 |
121
123
  | [`wechat`](./extensions/wechat/) | `/wechat`, `send_wechat` | 通过微信 iLink 机器人 API 把微信聊天桥接到实时会话;入站文本、图片和文件会变成回合。 |
@@ -132,10 +134,11 @@ changelog。新版本还会标在头部 —— 本包的与 Pi 自己的。不
132
134
 
133
135
  | 技能 | 说明 |
134
136
  |---|---|
135
- | [`init`](./skills/init/SKILL.md) | 基于仓库证据,通过内置的交互式访谈和计划审阅创建或更新项目的 `AGENTS.md`。 |
137
+ | [`init`](./skills/init/SKILL.md) | 通过内置的交互式访谈和计划审阅,基于仓库证据创建或更新 Pi 实际读取的指令文件(`AGENTS.md`;存在覆盖它的 `AGENTS.override.md` 时则是该文件),并从仓库推导命令、生成路径与包范围。 |
136
138
  | [`refactor`](./skills/refactor/SKILL.md) | 审计项目或路径中的重构机会:重复代码、过大的模块、耦合、结构漂移,以及测试薄弱且改动频繁的代码。返回带证据和验证命令的排序清单;发现阶段只读。 |
137
139
  | [`changelog`](./skills/changelog/SKILL.md) | 在变更的同一次提交中记录一条用户可见的条目,审计上一个 tag 以来缺失或薄弱的条目,并按项目自己的规则整理发布段落。 |
138
- | [`next`](./skills/next/SKILL.md) | 梳理接下来该做什么——未收尾的工作、已破坏或未兑现的承诺——可基于代码树或本次会话(`/skill:next session`),命令后的文字说明问什么、路径说明看哪里。只读,输出带证据和启动命令的简短排序清单。 |
140
+ | [`parallel-dev`](./skills/parallel-dev/SKILL.md) | 以“负责人加团队”的方式组织并行开发:负责人对契约、共享文件、合并顺序和绿色基线负责,同时自己承担一个工作流;每个互不重叠的工作流各有一名所有者,先做可运行的纵向骨架,声明路径,在每个回合的边界协作,并由他人独立验证。 |
141
+ | [`next`](./skills/next/SKILL.md) | 按优先级建议接下来的待办——未收尾的工作、已破坏或未兑现的承诺——会话已有工作时基于本次会话,仅当会话为空时才基于代码树;命令后的文字说明问什么、路径说明看哪里。只读,输出带证据和启动命令的简短编号清单。 |
139
142
 
140
143
  ### 主题
141
144
 
@@ -157,6 +160,10 @@ changelog。新版本还会标在头部 —— 本包的与 Pi 自己的。不
157
160
  <td width="50%"><img src="assets/job.png" alt="/jobs 屏幕:摘要行、带状态字形和耗时的任务行,以及焦点任务的命令、工作目录和最后一行输出" width="100%"></td>
158
161
  <td width="50%"><img src="assets/rewind.png" alt="/rewind 屏幕:从新到旧的提示行、标记有代码快照的菱形,以及焦点提示的快照详情" width="100%"></td>
159
162
  </tr>
163
+ <tr>
164
+ <td width="50%"><img src="assets/session.png" alt="/sessions 屏幕:计数摘要、带未读数和路径声明数的会话行、焦点会话的工作目录、声明和身份,以及 Tab 范围与监视提示" width="100%"></td>
165
+ <td width="50%"><img src="assets/watch.png" alt="/watch 屏幕:摘要行和带实时匹配数的监视行,以及焦点监视的模式与来源" width="100%"></td>
166
+ </tr>
160
167
  </table>
161
168
 
162
169
  ### 工具渲染
@@ -166,7 +173,7 @@ changelog。新版本还会标在头部 —— 本包的与 Pi 自己的。不
166
173
  (每个工具自身的渲染器或 bytefly 的通用回退)——用户铭牌与通知、Pi 内置工具,以及本包注册的每个工具。
167
174
  侧栏字形由 `chrome.rail` 设置。
168
175
 
169
- ![每个工具块的全页参考图:用户铭牌、通知、回合分隔线,Pi 的 read、write、edit、bash、grep、find、ls 渲染器,以及每个包内工具——todo、goal、memory、job、watch、subagent、web_search、web_fetch、read_doc、open_path、run_doctor、send_wechat、ask_user_question、plan 工具与 worktree 工具](assets/tool-renderings.png)
176
+ ![每个工具块的全页参考图:用户铭牌、通知、回合分隔线,Pi 的 read、write、edit、bash、grep、find、ls 渲染器,以及每个包内工具——todo、goal、memory、job、watch、subagent、web_search、web_fetch、read_doc、open_path、run_doctor、send_wechat、ask_user_question、session、plan 工具与 worktree 工具](assets/tool-renderings.png)
170
177
 
171
178
  `bun run screenshot:tools` 重新生成它;`bun run screenshot:tool-pngs` 在
172
179
  [`assets/tools/`](./assets/tools) 下为每个工具输出一张图。
@@ -327,7 +327,7 @@ See [The control center](#the-control-center).
327
327
  **Notify.** Off by default, one `notify` section in `bytefly.json` turns on OS
328
328
  notifications for unattended runs: when a full agent run settles, when a
329
329
  background job finishes, when the agent opens a prompt asking for input, and when
330
- a `watch` wake match fires while the agent is idle. The
330
+ an armed `watch` wake match fires as the agent becomes idle. The
331
331
  method follows the terminal (OSC 777, OSC 99, a Windows toast) or falls back to
332
332
  the platform notifier (`osascript`, `notify-send`); see
333
333
  [Notifications](#notifications).
@@ -674,6 +674,12 @@ it. There is nothing to probe, so `r` is not offered here; the metadata is read
674
674
  once when the section first appears, so an edited `SKILL.md` shows on the next
675
675
  open.
676
676
 
677
+ Themes and prompts have no section of their own, by decision rather than by
678
+ omission: a theme is one active selection — a scalar, global setting — and a
679
+ prompt template is a file Pi discovers from a directory, so neither is a scoped
680
+ enable list this screen's checkbox rows can write. Themes stay with `pi config`;
681
+ `ROADMAP.md` carries the reason under *Deliberately left thin*.
682
+
677
683
  **Settings.** The third section uses the same frame and the same `Tab` scope
678
684
  switch, with its own cursor. It is the shortlist a session changes: thirteen
679
685
  preferences on one flat line each, the value beside its label, and no group
@@ -692,7 +698,6 @@ Bytefly ────────────────────────
692
698
  ◼ Job finished On
693
699
  ◼ Watcher matched On
694
700
  ◻ Wake when a job finishes Off
695
- ◼ Offer the session goal On
696
701
  ◼ Automatic snapshots On
697
702
  ◼ Proactive health check On
698
703
  Currency CNY (¥)
@@ -718,7 +723,6 @@ Bytefly ────────────────────────
718
723
  | Job finished | `notify.jobFinished` | Off, On |
719
724
  | Watcher matched | `notify.matchFound` | Off, On |
720
725
  | Wake when a job finishes | `job.wakeOnFinish` | Off, On |
721
- | Offer the session goal | `plan.goalPrompt` | Off, On |
722
726
  | Automatic snapshots | `rewind.autoSnapshots` | Off, On |
723
727
  | Proactive health check | `doctor.proactive` | Off, On |
724
728
  | Currency | `price.currency.code` | CNY, USD |
@@ -756,15 +760,15 @@ terminal: adding a setting means removing one (`panel/panel.test.ts` holds the
756
760
  ceiling). Everything else stays in `bytefly.json` — the tuning numbers, the
757
761
  cosmetic labels, and the Pi preferences Pi's own `/settings` owns — and
758
762
  `test/settings-coverage.test.ts` keeps the split honest: every loader key is
759
- either a row or a declared JSON-only edit with the reason it left. Five
760
- `bytefly.json` sections therefore have no row at all (`chrome`, `goal`, `watch`,
761
- `worktree`, `memory`), as do `notify.title`, `doctor.timeoutMs`,
763
+ either a row or a declared JSON-only edit with the reason it left. Six
764
+ `bytefly.json` sections therefore have no row at all (`chrome`, `goal`, `plan`,
765
+ `watch`, `worktree`, `memory`), as do `notify.title`, `doctor.timeoutMs`,
762
766
  `price.currency.decimals`, `rewind.max`, `rewind.safetySnapshot`,
763
767
  `job.detachedByDefault`, and `watch.wakeOnMatch` inside sections that do. Each is
764
768
  documented in [`docs/configuration.md`](../../docs/configuration.md).
765
769
 
766
- Three of the six sections belong to other extensions (`rewind`, `job`, and
767
- `plan`). The screen cannot import their normalizers, so it reports the leaf as
770
+ Two of the five sections belong to other extensions (`rewind` and `job`). The
771
+ screen cannot import their normalizers, so it reports the leaf as
768
772
  the file states it and declares the loader's default; the same test holds the
769
773
  catalogue to every loader's defaults and bounds in both directions.
770
774
 
@@ -788,16 +792,19 @@ The non-`tui` form prints the headline and one `Label: Value` row per line, in
788
792
  screen order:
789
793
 
790
794
  ```
791
- bytefly: Settings · Global · 14 settings
795
+ bytefly: Settings · Global · 13 settings
792
796
  Bytefly startup TUI: Off
793
- Theme: system
794
- Desktop notifications: Off
797
+ Theme: bytefly-dark
798
+ Desktop notifications: On
795
799
  Turn finished: On
796
800
  Needs input: On
797
801
  Job finished: On
798
802
  Watcher matched: On
799
- Delivery method: Auto
800
- ...
803
+ Wake when a job finishes: On
804
+ Automatic snapshots: On
805
+ Proactive health check: On
806
+ Currency: CNY (¥)
807
+ Session budget: 5
801
808
  ```
802
809
 
803
810
  The doctor side probes every declared extension and reports each check as `ok`,
@@ -860,7 +867,7 @@ has a keyboard path.
860
867
  | rewind | `git` | — |
861
868
  | document | — | — |
862
869
  | subagent | a runnable nested `pi` | project-local agents need a trusted project |
863
- | plan | — | `git` for project-local `.pi/plans`; an interactive UI |
870
+ | plan | — | an interactive UI |
864
871
  | memory | — | `git` for the project-scope root |
865
872
  | file-browse | — | `git` for the branch chip; the browser opener is best-effort |
866
873
  | web-access | — | `unpdf` |
@@ -892,12 +899,12 @@ of `bytefly.json` (see [Configuration](#configuration)):
892
899
  - **A background job finished.** The [job](../job/) extension announces each
893
900
  terminal transition on the shared `pi.events` bus
894
901
  (`my-pi-agent/job-finished`); bytefly listens and never imports it.
895
- - **A watcher matched.** A [watch](../watch/) whose `wake` is on matched while the
896
- agent was idle, and announced it on `my-pi-agent/watch-match` with the cycle's
902
+ - **A watcher matched.** A [watch](../watch/) whose `wake` is on matched, and
903
+ announced it on `my-pi-agent/watch-match` with the cycle's
897
904
  count, the watcher, and its newest line. The body names that line, sanitized and
898
905
  clipped to 80 characters, because the line is the alert: a watch usually tails a
899
- build or an error log. A match the agent was busy for does not notify — it is
900
- collected for the next turn instead.
906
+ build or an error log. A match the agent was busy for notifies once that run
907
+ settles, when its wake is raised.
901
908
 
902
909
  ### Delivery method
903
910
 
@@ -21,7 +21,7 @@ export interface NotifyConfig {
21
21
  needsInput: boolean;
22
22
  /** Notify when a background `job` finishes. */
23
23
  jobFinished: boolean;
24
- /** Notify when a `watch` wake match fires while the agent is idle. */
24
+ /** Notify when a `watch` wake match fires, once the agent is idle. */
25
25
  matchFound: boolean;
26
26
  /** Delivery method; `auto` chooses by terminal and platform. */
27
27
  method: NotifyMethod;
@@ -9,7 +9,7 @@
9
9
  * The catalogue is the shortlist a session changes, not the whole document: every
10
10
  * row is one a workflow differs on, and each states its own rule so a value the
11
11
  * screen accepts is a value the loader keeps. The catalogue order is the screen's
12
- * order, and it is capped so the section fits a 24-row terminal (`settings.test.ts`
12
+ * order, and it is capped so the section fits a 24-row terminal (`panel.test.ts`
13
13
  * holds the ceiling); a new row has to displace one. Everything else stays
14
14
  * `bytefly.json` — a list (`doctor.skip`), a table (`price.rates`), a schedule
15
15
  * (`price.peak`), and the tuning numbers and labels this screen deliberately does
@@ -17,14 +17,15 @@
17
17
  * pane of any row whose section is still on the list.
18
18
  *
19
19
  * Three sections have a normalizer here, so the screen shows what the notifier,
20
- * the doctor, and the footer keep. The three owned by other extensions (`rewind`,
21
- * `job`, `plan`) are read as the file states them, because importing their
20
+ * the doctor, and the footer keep. The two owned by other extensions (`rewind`
21
+ * and `job`) are read as the file states them, because importing their
22
22
  * normalizer would be one extension importing another's module; each such row
23
23
  * declares the loader's default, and `test/settings-coverage.test.ts` holds the
24
24
  * pair together.
25
25
  */
26
26
 
27
27
  import { configSection, configValueAt } from "../../../lib/config.ts";
28
+ import { SPLIT_KEY, SPLIT_SECTION, SPLIT_SETTING_ID, SPLIT_SETTING_LABEL } from "../../../lib/plans/setting.ts";
28
29
  import { DEFAULT_CONFIG as DEFAULT_DOCTOR_CONFIG, normalizeConfig as normalizeDoctorConfig } from "../doctor/config.ts";
29
30
  import {
30
31
  CURRENCY_DEFAULTS,
@@ -38,7 +39,7 @@ import { DEFAULT_NOTIFY_CONFIG, normalizeNotifyConfig } from "../notify/config.t
38
39
  import type { Settings, WriteScope } from "../picker/types.ts";
39
40
 
40
41
  /** Where a preference is stored. */
41
- type SettingSource = "app" | "bytefly";
42
+ type SettingSource = "app" | "bytefly" | "plan";
42
43
 
43
44
  /** The row control a preference uses. */
44
45
  type SettingKind = "boolean" | "enum" | "text";
@@ -220,7 +221,7 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
220
221
  {
221
222
  id: "notify.matchFound",
222
223
  label: "Watcher matched",
223
- description: "Announce it when a `watch` whose `wake` is on matches while the agent is idle.",
224
+ description: "Announce it when a `watch` whose `wake` is on matches, once the agent is idle.",
224
225
  source: "bytefly",
225
226
  kind: "boolean",
226
227
  fallback: "true",
@@ -239,17 +240,6 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
239
240
  options: ON_OFF,
240
241
  config: { section: "job", path: ["wakeOnFinish"] },
241
242
  },
242
- {
243
- id: "plan.goalPrompt",
244
- label: "Offer the session goal",
245
- description: "Ask before recording an approved plan as the session goal; off records it without asking.",
246
- source: "bytefly",
247
- kind: "boolean",
248
- fallback: "true",
249
- json: "boolean",
250
- options: ON_OFF,
251
- config: { section: "plan", path: ["goalPrompt"] },
252
- },
253
243
  {
254
244
  id: "rewind.autoSnapshots",
255
245
  label: "Automatic snapshots",
@@ -306,14 +296,31 @@ export const SETTING_ROWS: readonly SettingDescriptor[] = [
306
296
  text: amountRule(0, MAX_BUDGET),
307
297
  config: { section: "price", path: ["budget"] },
308
298
  },
299
+ {
300
+ // The split's switch, with the label, id, and default taken from the module
301
+ // that reads it, so this row cannot describe a key nothing honours.
302
+ id: SPLIT_SETTING_ID,
303
+ label: SPLIT_SETTING_LABEL,
304
+ description:
305
+ "Let an approved plan carry a team of slices, which separate sessions join by name. Off keeps every plan one flow.",
306
+ source: "plan",
307
+ kind: "boolean",
308
+ fallback: "false",
309
+ json: "boolean",
310
+ options: ON_OFF,
311
+ config: { section: SPLIT_SECTION, path: [SPLIT_KEY] },
312
+ },
309
313
  ];
310
314
 
311
315
  /** The one refusal for a Project write while the project is untrusted. */
312
316
  export const UNTRUSTED_SETTINGS_NOTE = "Project is not trusted; settings can only be changed in Global scope.";
313
317
 
314
- /** The rows a scope can write: app rows are personal-file only. */
318
+ /**
319
+ * The rows a scope can write: app rows are personal-file only, and Project shows
320
+ * the rows backed by `bytefly.json` — whichever extension owns the section.
321
+ */
315
322
  export function rowsForScope(scope: WriteScope): readonly SettingDescriptor[] {
316
- return scope === "global" ? SETTING_ROWS : SETTING_ROWS.filter((row) => row.source === "bytefly");
323
+ return scope === "global" ? SETTING_ROWS : SETTING_ROWS.filter((row) => row.config !== undefined);
317
324
  }
318
325
 
319
326
  /**
@@ -56,8 +56,9 @@ pi -e . # load the whole @gavin-hu/my-pi-agen
56
56
  external images are shown as links rather than fetched.
57
57
  - **Sortable listings.** The Name / Size / Modified headers sort the current
58
58
  directory, directories first, and the choice is remembered per browser.
59
- - **Copy and wrap.** Copy a file's root-relative path or its contents, and
60
- toggle line wrapping for text and raw views.
59
+ - **Copy and wrap.** Copy a file's root-relative path or its contents, copy a
60
+ `path:12-30` reference for the lines you selected, and toggle line wrapping for
61
+ text and raw views.
61
62
  - **Theme toggle.** A header button switches between the dark and light
62
63
  palettes and remembers the choice per browser; until it is used, the palette
63
64
  follows the system `prefers-color-scheme`.
@@ -66,7 +67,8 @@ pi -e . # load the whole @gavin-hu/my-pi-agen
66
67
  highlighted match; Enter opens the file.
67
68
  - **Keyboard.** `/` or `Ctrl/Cmd+K` opens quick open, `-` folds or expands
68
69
  every folder in the tree, `u` toggles line wrapping, `r` switches rendered and
69
- raw Markdown, `n`/`p` step between a diff's hunks, and `?` opens a shortcut
70
+ raw Markdown, `y` copies a reference to the selected lines, `n`/`p` step
71
+ between a diff's hunks, and `?` opens a shortcut
70
72
  sheet. A diff's hunk headers pin to the top of the code viewport while its
71
73
  body scrolls.
72
74
  - **Read-only and loopback-scoped.** No editing, no writing, no LAN exposure.
@@ -88,11 +90,13 @@ pi -e . # load the whole @gavin-hu/my-pi-agen
88
90
  connecting (accent), stalled (amber, after three missed frames), or offline
89
91
  (red); the branch chip, the `● N changes +A −D` chip, and the change markers in
90
92
  the tree and the listing update in place; and the footer line reports `up 2m
91
- 14s · 37 requests`. The chip names its state and never counts, so nothing is
92
- repainted between frames. One Server-Sent Events stream per page carries it, on
93
- the same loopback server, so nothing extra is opened and nothing is written;
94
- that stream is also how the server notices the last page closing its window,
95
- after which it stops listening.
93
+ 14s · 37 requests`. The chip names its state and never counts, and a frame
94
+ whose change set did not move repaints nothing at all. One Server-Sent Events
95
+ stream per page carries it, on the same loopback server, so nothing extra is
96
+ opened and nothing is written; that stream is also how the server notices the
97
+ last page closing its window, after which it stops listening. A page that lists
98
+ a folder also checks that folder, and offers a `⟳ reload` control when a file
99
+ has appeared or gone on disk since it drew its rows.
96
100
  - **No dependencies.** `node:http` plus the inline sprite, CSS, and client
97
101
  script.
98
102
 
@@ -136,10 +140,24 @@ errors. The chip names its state rather than counting, so it is repainted only
136
140
  when that state changes. The same frame carries the branch (or detached head), the
137
141
  uncommitted-change totals and paths, the server's uptime, and the requests it
138
142
  has answered, so the branch chip, the changes chip, the markers, and the footer
139
- line are painted from one snapshot rather than four requests. Losing the stream
140
- leaves everything the page last showed in place: it is read-only, so a stale
141
- label is never a stale action. `"live": false` turns the whole surface off — no
142
- chip, no stream, no `/events` route — and so does JavaScript off.
143
+ line are painted from one snapshot rather than four requests. Each change set
144
+ also carries a short revision, and a frame whose revision the page already
145
+ painted does no work at all — so an idle tree costs no DOM, however many rows the
146
+ page drew. Losing the stream leaves everything the page last showed in place: it
147
+ is read-only, so a stale label is never a stale action. `"live": false` turns the
148
+ whole surface off — no chip, no stream, no `/events` route — and so does
149
+ JavaScript off.
150
+
151
+ A page that lists a folder carries the revision of the names it drew, and asks
152
+ `/api/dir-rev` for the folder's current one on the frame cadence — one directory
153
+ read every five seconds by default, skipped while the tab is hidden and stopped
154
+ for good once it has an answer. A difference reveals `⟳ reload` beside the live
155
+ chip, which reloads the page; the rows themselves are never touched, because a
156
+ page that could grow a row is a page that writes. A folder whose listing was
157
+ truncated, and a file, `/diff`, or error page, has nothing to compare and shows
158
+ no control. At most eight pages may hold the stream at once: a ninth is refused
159
+ and its chip reads `offline`, which keeps one local page from pinning the server
160
+ — and the git read behind each frame — alive.
143
161
 
144
162
  The stream is also how the server knows a page is still there: when the last one
145
163
  leaves, the port is released after ten seconds and the chip is cleared, because
@@ -212,7 +230,7 @@ document and the project document (project wins):
212
230
  "diffContext": 3, // unified diff context lines
213
231
  "maxDiffLines": 4000, // diff lines rendered before truncation
214
232
  "live": true, // stream live status to open pages
215
- "liveIntervalMs": 3000 // ms between status frames while a page is connected
233
+ "liveIntervalMs": 5000 // ms between status frames while a page is connected
216
234
  }
217
235
  ```
218
236
 
@@ -249,11 +267,18 @@ document and the project document (project wins):
249
267
  attributes; only the fixed `tok-*` class names reach the page.
250
268
  - **Event stream.** `/events` is a `GET` on the same loopback server, behind
251
269
  the same method and `Host` checks, and it only reads: the frame is the branch,
252
- the change paths, an uptime, and a request count, all of which the page it
253
- feeds already renders. It carries no page policy (`text/event-stream` is not a
254
- document), sends `Cache-Control: no-store`, and cannot be cached or replayed.
255
- A tick runs only while a page is attached, and the timer is `unref`ed, so an
256
- idle server does no work and a forgotten tab cannot hold the process open.
270
+ the change paths and their revision, an uptime, and a request count, all of
271
+ which the page it feeds already renders. It carries no page policy
272
+ (`text/event-stream` is not a document), sends `Cache-Control: no-store`, and
273
+ cannot be cached or replayed. A tick runs only while a page is attached, and
274
+ the timer is `unref`ed, so an idle server does no work and a forgotten tab
275
+ cannot hold the process open. At most eight pages may attach; a ninth is
276
+ answered `204`, the one status an event source never reconnects after.
277
+ - **Directory check.** `/api/dir-rev` answers one folder's listing revision, so
278
+ the page can tell whether its rows are stale. It goes through the same path
279
+ guard as every other route and reads one directory; its answer is a hash of the
280
+ names, not the names themselves, so it discloses nothing the page above it does
281
+ not already show.
257
282
  - **Caps.** `maxFileBytes` (inline rendering), `maxTextLines` (rendered lines),
258
283
  `maxThumbBytes` (thumbnails), and `maxDirEntries` (listings) bound every read.
259
284
  - **Git reads.** The header chip and the diff view run read-only git
@@ -271,9 +296,11 @@ document and the project document (project wins):
271
296
  approximated rather than parsed.
272
297
  - Live status updates what the page already contains: the header chips, the
273
298
  markers on the rows it rendered, and the footer line. A file that appears on
274
- disk joins the listing on the next reload, the `/diff` table and a file page's
275
- `changes` action stay page-load snapshots, and the branch chip is only patched
276
- when the page was served inside a repository.
299
+ disk does not join the listing by itself — the page offers `⟳ reload` instead —
300
+ and the check covers the folder the page lists, by name, not the folders inside
301
+ it, a listed file's contents, the `/diff` table, or a file page's `changes`
302
+ action, which stay page-load snapshots. The branch chip is only patched when
303
+ the page was served inside a repository.
277
304
  - `/browse` has no auth; a malicious local page could probe the port. The `Host`
278
305
  allowlist and the absence of CORS headers mitigate reads, and the server is
279
306
  read-only.
@@ -314,6 +341,18 @@ document and the project document (project wins):
314
341
  method and `Host`, parses the URL, matches a route, and runs every path
315
342
  through `paths.ts`. It returns a plain `ServeResponse`, so routing is tested
316
343
  without a socket; `server.ts` is the only part that binds a port.
344
+ - **One snapshot, one frame.** The status source is shared by every attached
345
+ page, so a tick costs one `git status`, one `git diff --stat`, and one
346
+ serialization however many tabs are open. The frame carries a revision of the
347
+ change set, and the client compares it before painting, which is why an idle
348
+ page does no DOM work. The cadence is what bounds those reads, so the default
349
+ is five seconds rather than matching the providers' two-second render cache.
350
+ - **A poll, not a watcher.** The folder check is one request from the page to
351
+ `/api/dir-rev`, not an `fs.watch`: recursive watching is the one part of
352
+ `node:fs` whose availability and event semantics differ per platform, and it
353
+ would add a long-lived resource to start and release around `close()`. The
354
+ check compares names, so it is a directory read rather than a stat pass, and it
355
+ stops at the first difference because the control, not the checker, is the fix.
317
356
  - **Progressive UI.** The page is server-rendered HTML with an inline style
318
357
  block and inline SVG icons. `client.ts` enhances it: the filter hides
319
358
  non-matching rows while keeping ancestors, disclosure triangles lazily