@gavin-hu/my-pi-agent 0.20.0 → 0.22.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 (298) hide show
  1. package/README.md +46 -30
  2. package/README.zh-CN.md +40 -25
  3. package/assets/screens/board.png +0 -0
  4. package/assets/screens/chrome.png +0 -0
  5. package/assets/screens/file-browse.png +0 -0
  6. package/assets/screens/plan-review.png +0 -0
  7. package/assets/screens/plan.png +0 -0
  8. package/docs/extensions/README.md +22 -23
  9. package/docs/extensions/board.md +397 -168
  10. package/docs/extensions/bytefly.md +234 -197
  11. package/docs/extensions/check.md +15 -17
  12. package/docs/extensions/context.md +35 -15
  13. package/docs/extensions/file-browse.md +57 -63
  14. package/docs/extensions/file-open.md +9 -11
  15. package/docs/extensions/file-read.md +12 -14
  16. package/docs/extensions/goal.md +15 -17
  17. package/docs/extensions/job.md +54 -42
  18. package/docs/extensions/memory.md +17 -19
  19. package/docs/extensions/plan.md +197 -140
  20. package/docs/extensions/question.md +16 -18
  21. package/docs/extensions/rewind.md +17 -19
  22. package/docs/extensions/serve.md +145 -0
  23. package/docs/extensions/session-bus.md +38 -16
  24. package/docs/extensions/subagent.md +23 -25
  25. package/docs/extensions/team.md +83 -44
  26. package/docs/extensions/todo.md +18 -18
  27. package/docs/extensions/watch.md +31 -31
  28. package/docs/extensions/web-access.md +2 -4
  29. package/docs/extensions/wechat.md +34 -35
  30. package/docs/extensions/worktree.md +56 -50
  31. package/extensions/board/commands.ts +79 -25
  32. package/extensions/board/format.ts +43 -24
  33. package/extensions/board/index.ts +17 -4
  34. package/extensions/board/input.ts +4 -9
  35. package/extensions/board/join.ts +5 -20
  36. package/extensions/board/lazy-page.ts +40 -0
  37. package/extensions/board/page.ts +65 -93
  38. package/extensions/board/result.ts +30 -39
  39. package/extensions/board/rows.ts +62 -25
  40. package/extensions/board/schema.ts +49 -11
  41. package/extensions/board/snapshot.ts +8 -6
  42. package/extensions/board/tone.ts +22 -0
  43. package/extensions/board/tools.ts +154 -55
  44. package/extensions/board/transport/router.ts +3 -5
  45. package/extensions/board/transport/server.ts +59 -28
  46. package/extensions/board/types.ts +36 -42
  47. package/extensions/board/view/card.ts +229 -0
  48. package/extensions/board/view/client.ts +35 -16
  49. package/extensions/board/view/dialog.ts +169 -6
  50. package/extensions/board/view/empty.ts +47 -0
  51. package/extensions/board/view/keys.ts +13 -2
  52. package/extensions/board/view/live.ts +90 -28
  53. package/extensions/board/view/names.ts +13 -4
  54. package/extensions/board/view/page.ts +38 -13
  55. package/extensions/board/view/render.ts +219 -277
  56. package/extensions/board/view/styles.ts +130 -32
  57. package/extensions/board/view/team.ts +297 -76
  58. package/extensions/bytefly/block/fallback.ts +1 -1
  59. package/extensions/bytefly/doctor/runtime.ts +5 -2
  60. package/extensions/bytefly/footer/collect/label.ts +60 -0
  61. package/extensions/bytefly/footer/collect/snapshot.ts +8 -1
  62. package/extensions/bytefly/footer/config.ts +7 -8
  63. package/extensions/bytefly/footer/register.ts +33 -4
  64. package/extensions/bytefly/footer/render/footer.ts +18 -4
  65. package/extensions/bytefly/footer/render/key.ts +46 -0
  66. package/extensions/bytefly/footer/render/layout.ts +13 -3
  67. package/extensions/bytefly/footer/render/lines.ts +10 -7
  68. package/extensions/bytefly/footer/render/segments.ts +41 -38
  69. package/extensions/bytefly/footer/render/status.ts +0 -11
  70. package/extensions/bytefly/footer/types.ts +7 -0
  71. package/extensions/bytefly/header/component.ts +4 -20
  72. package/extensions/bytefly/header/config.ts +1 -1
  73. package/extensions/bytefly/index.ts +32 -6
  74. package/extensions/bytefly/mode/editor.ts +115 -0
  75. package/extensions/bytefly/panel/commands.ts +210 -120
  76. package/extensions/bytefly/panel/format.ts +4 -4
  77. package/extensions/bytefly/panel/settings.ts +73 -6
  78. package/extensions/bytefly/panel/tui/keys.ts +2 -6
  79. package/extensions/bytefly/update/cache.ts +1 -1
  80. package/extensions/bytefly/update/register.ts +4 -4
  81. package/extensions/bytefly/working/config.ts +5 -7
  82. package/extensions/bytefly/working/format.ts +10 -13
  83. package/extensions/bytefly/working/register.ts +22 -27
  84. package/extensions/check/commands.ts +3 -2
  85. package/extensions/check/render.ts +10 -1
  86. package/extensions/check/{tui.ts → view/check-screens.ts} +14 -19
  87. package/extensions/context/commands.ts +2 -2
  88. package/extensions/context/{tui.ts → view/context-report.ts} +51 -19
  89. package/extensions/file-browse/config/config.ts +4 -19
  90. package/extensions/file-browse/index.ts +89 -79
  91. package/extensions/file-browse/live/live.ts +1 -1
  92. package/extensions/file-browse/transport/router.ts +12 -4
  93. package/extensions/file-browse/transport/server.ts +61 -28
  94. package/extensions/file-browse/view/chrome.ts +4 -4
  95. package/extensions/file-browse/view/client.ts +3 -1
  96. package/extensions/file-browse/view/html.ts +73 -40
  97. package/extensions/file-browse/view/live-client.ts +2 -2
  98. package/extensions/file-browse/view/markdown.ts +13 -14
  99. package/extensions/file-browse/view/palette.ts +2 -2
  100. package/extensions/file-browse/view/raw-html.ts +9 -9
  101. package/extensions/file-browse/view/tree.ts +1 -1
  102. package/extensions/file-browse/view/urls.ts +20 -6
  103. package/extensions/file-read/render.ts +4 -4
  104. package/extensions/goal/plan-route.ts +2 -2
  105. package/extensions/goal/tui.ts +12 -2
  106. package/extensions/job/child.ts +527 -0
  107. package/extensions/job/clock.ts +142 -0
  108. package/extensions/job/commands.ts +4 -2
  109. package/extensions/job/config.ts +8 -1
  110. package/extensions/job/format.ts +22 -1
  111. package/extensions/job/log-writer.ts +13 -2
  112. package/extensions/job/registry.ts +63 -7
  113. package/extensions/job/render.ts +34 -0
  114. package/extensions/job/runtime.ts +296 -655
  115. package/extensions/job/session.ts +132 -19
  116. package/extensions/job/store.ts +7 -7
  117. package/extensions/job/tools.ts +21 -9
  118. package/extensions/job/types.ts +47 -0
  119. package/extensions/job/{tui.ts → view/jobs-screen.ts} +19 -99
  120. package/extensions/job/waiters.ts +44 -1
  121. package/extensions/memory/commands.ts +4 -2
  122. package/extensions/memory/{tui.ts → view/memory-picker.ts} +12 -12
  123. package/extensions/plan/commands.ts +12 -9
  124. package/extensions/plan/feedback.ts +62 -16
  125. package/extensions/plan/format.ts +8 -2
  126. package/extensions/plan/git-bash.ts +115 -16
  127. package/extensions/plan/index.ts +58 -51
  128. package/extensions/plan/plan-index.ts +4 -2
  129. package/extensions/plan/plan-text.ts +114 -0
  130. package/extensions/plan/plans.ts +5 -54
  131. package/extensions/plan/policy.ts +8 -10
  132. package/extensions/plan/review-lazy.ts +85 -0
  133. package/extensions/plan/review.ts +2 -2
  134. package/extensions/plan/runtime.ts +61 -41
  135. package/extensions/plan/sections.ts +1 -1
  136. package/extensions/plan/steps.ts +3 -3
  137. package/extensions/plan/todos.ts +1 -1
  138. package/extensions/plan/tools/cancel.ts +27 -62
  139. package/extensions/plan/tools/enter.ts +55 -81
  140. package/extensions/plan/tools/exit.ts +221 -238
  141. package/extensions/plan/tools/refusal.ts +18 -0
  142. package/extensions/plan/tools/steps.ts +75 -92
  143. package/extensions/plan/tools/write.ts +77 -120
  144. package/extensions/plan/tools.ts +188 -30
  145. package/extensions/plan/tui.ts +5 -5
  146. package/extensions/plan/types.ts +48 -5
  147. package/extensions/plan/web-blocks.ts +76 -5
  148. package/extensions/plan/web-chrome.ts +4 -0
  149. package/extensions/plan/web-client.ts +102 -2
  150. package/extensions/plan/web-collab.ts +4 -4
  151. package/extensions/plan/web-comments.ts +39 -8
  152. package/extensions/plan/web-composer.ts +14 -3
  153. package/extensions/plan/web-keys.ts +149 -25
  154. package/extensions/plan/web-lazy.ts +53 -0
  155. package/extensions/plan/web-markdown.ts +10 -68
  156. package/extensions/plan/web-outline.ts +22 -2
  157. package/extensions/plan/web-page.ts +56 -86
  158. package/extensions/plan/web-payload.ts +188 -0
  159. package/extensions/plan/web-plan.ts +23 -13
  160. package/extensions/plan/web-router.ts +18 -9
  161. package/extensions/plan/web-settle.ts +14 -7
  162. package/extensions/plan/web-share-session.ts +24 -55
  163. package/extensions/plan/web-share.ts +19 -37
  164. package/extensions/plan/web-write.ts +112 -0
  165. package/extensions/plan/web.ts +67 -131
  166. package/extensions/question/tools.ts +6 -2
  167. package/extensions/question/{tui.ts → view/question-screen.ts} +11 -11
  168. package/extensions/rewind/commands/commands.ts +6 -3
  169. package/extensions/rewind/state/runtime.ts +1 -1
  170. package/extensions/rewind/{view/status.ts → status.ts} +4 -4
  171. package/extensions/rewind/view/review-tui.ts +4 -4
  172. package/extensions/serve/config.ts +39 -0
  173. package/extensions/serve/index.ts +85 -0
  174. package/extensions/serve/registry.ts +260 -0
  175. package/extensions/session-bus/commands.ts +11 -6
  176. package/extensions/session-bus/join-request.ts +17 -9
  177. package/extensions/session-bus/mail-watch.ts +26 -17
  178. package/extensions/session-bus/protocol.ts +15 -3
  179. package/extensions/session-bus/resolve.ts +5 -7
  180. package/extensions/session-bus/result.ts +1 -27
  181. package/extensions/session-bus/runtime.ts +40 -12
  182. package/extensions/session-bus/store.ts +101 -19
  183. package/extensions/session-bus/tools.ts +5 -4
  184. package/extensions/session-bus/types.ts +19 -0
  185. package/extensions/session-bus/{tui.ts → view/session-list.ts} +16 -16
  186. package/extensions/subagent/agents.ts +1 -1
  187. package/extensions/subagent/commands.ts +4 -2
  188. package/extensions/subagent/format.ts +15 -15
  189. package/extensions/subagent/index.ts +1 -1
  190. package/extensions/subagent/render.ts +3 -3
  191. package/extensions/subagent/{background-tui.ts → view/agents-board.ts} +11 -11
  192. package/extensions/team/commands.ts +24 -5
  193. package/extensions/team/dispatch.ts +18 -4
  194. package/extensions/team/format.ts +17 -2
  195. package/extensions/team/index.ts +9 -8
  196. package/extensions/team/schema.ts +6 -5
  197. package/extensions/team/tools.ts +107 -45
  198. package/extensions/team/types.ts +3 -2
  199. package/extensions/todo/commands.ts +4 -2
  200. package/extensions/todo/format.ts +11 -0
  201. package/extensions/todo/render.ts +57 -0
  202. package/extensions/todo/runtime.ts +1 -1
  203. package/extensions/todo/tools.ts +1 -1
  204. package/extensions/todo/{tui.ts → view/todo-screen.ts} +18 -90
  205. package/extensions/todo/widget.ts +47 -0
  206. package/extensions/watch/commands.ts +4 -2
  207. package/extensions/watch/format.ts +36 -7
  208. package/extensions/watch/render.ts +57 -0
  209. package/extensions/watch/runtime.ts +27 -22
  210. package/extensions/watch/tools.ts +4 -4
  211. package/extensions/watch/{tui.ts → view/watch-screen.ts} +18 -80
  212. package/extensions/web-access/fetch/tool.ts +12 -4
  213. package/extensions/wechat/commands/commands.ts +18 -14
  214. package/extensions/wechat/index.ts +72 -37
  215. package/extensions/wechat/state/credentials.ts +1 -1
  216. package/extensions/wechat/state/state.ts +1 -1
  217. package/extensions/wechat/text/status.ts +112 -0
  218. package/extensions/wechat/types.ts +2 -2
  219. package/extensions/wechat/view/status-screen.ts +4 -103
  220. package/extensions/worktree/commands.ts +11 -9
  221. package/extensions/worktree/enter.ts +2 -2
  222. package/extensions/worktree/exit.ts +2 -2
  223. package/extensions/worktree/index.ts +5 -5
  224. package/extensions/worktree/screen.ts +3 -3
  225. package/extensions/worktree/status.ts +2 -2
  226. package/extensions/worktree/tools.ts +113 -116
  227. package/lib/README.md +28 -13
  228. package/lib/board/actions.ts +111 -0
  229. package/lib/board/artifact.ts +149 -0
  230. package/lib/board/card.ts +163 -52
  231. package/lib/board/config.ts +58 -0
  232. package/lib/board/contract.ts +113 -0
  233. package/lib/board/join.ts +38 -0
  234. package/lib/board/lanes.ts +182 -0
  235. package/lib/board/lead.ts +130 -0
  236. package/lib/board/limits.ts +78 -0
  237. package/lib/board/links.ts +31 -10
  238. package/lib/board/page-pointer.ts +30 -5
  239. package/lib/board/read.ts +83 -0
  240. package/lib/board/reason.ts +102 -0
  241. package/lib/board/round-text.ts +68 -6
  242. package/lib/board/round.ts +31 -15
  243. package/lib/board/rules.ts +151 -307
  244. package/lib/board/seat.ts +35 -0
  245. package/lib/board/session.ts +199 -51
  246. package/lib/board/skipped.ts +22 -0
  247. package/lib/board/steps.ts +487 -0
  248. package/lib/board/store.ts +119 -36
  249. package/lib/board/tone.ts +92 -0
  250. package/lib/board/view.ts +81 -63
  251. package/lib/bus-request.ts +83 -0
  252. package/{extensions/watch → lib}/child.ts +40 -21
  253. package/lib/env.ts +65 -7
  254. package/lib/format.ts +13 -0
  255. package/lib/http/index.ts +17 -11
  256. package/lib/http/mount.ts +142 -0
  257. package/lib/http/port.ts +30 -0
  258. package/lib/http/serve.ts +170 -46
  259. package/lib/http/server.ts +3 -3
  260. package/lib/lazy.ts +45 -0
  261. package/lib/plan-entry.ts +46 -21
  262. package/lib/plans/paths.ts +82 -0
  263. package/lib/policy.ts +9 -6
  264. package/lib/report.ts +3 -2
  265. package/lib/requirements.ts +10 -10
  266. package/lib/session-label.ts +129 -7
  267. package/lib/session-roster.ts +43 -1
  268. package/lib/session-tool.ts +39 -22
  269. package/lib/stray-fields.ts +7 -2
  270. package/lib/tool-names.ts +5 -10
  271. package/lib/tui/README.md +29 -11
  272. package/lib/tui/chrome.ts +16 -15
  273. package/lib/tui/cursor.ts +18 -3
  274. package/lib/tui/glyphs.ts +11 -11
  275. package/lib/tui/render-cache.ts +29 -0
  276. package/lib/tui/tokens.ts +33 -2
  277. package/lib/tui/tool.ts +33 -4
  278. package/lib/web/README.md +21 -0
  279. package/lib/web/client-runtime.ts +27 -1
  280. package/lib/web/components.ts +5 -1
  281. package/lib/web/index.ts +1 -0
  282. package/lib/web/markdown.ts +83 -0
  283. package/lib/web/markup.ts +20 -2
  284. package/lib/web/runtime.ts +9 -10
  285. package/package.json +8 -2
  286. package/skills/init/SKILL.md +5 -5
  287. package/skills/next/SKILL.md +3 -5
  288. package/extensions/board/config.ts +0 -36
  289. package/extensions/board/seat.ts +0 -28
  290. package/lib/tui/index.ts +0 -34
  291. package/skills/project-docs/SKILL.md +0 -133
  292. package/skills/project-docs/references/audit.md +0 -131
  293. package/skills/project-docs/references/changelog.md +0 -105
  294. package/skills/project-docs/references/discovery.md +0 -127
  295. package/skills/project-docs/references/readme.md +0 -80
  296. package/skills/project-docs/references/release.md +0 -84
  297. package/skills/project-docs/references/roadmap.md +0 -93
  298. package/skills/project-docs/scripts/state.ts +0 -316
@@ -10,35 +10,34 @@ default, and from `bytefly.json` otherwise.
10
10
 
11
11
  ## Capabilities
12
12
 
13
- Status records what a reader can rely on: **Shipped** is in a tagged release, **Unreleased** is complete in the current tree but not yet tagged, and **Partial** has one released part and one unreleased part. Rows are ordered by user value, highest first.
14
-
15
13
  ### Chrome
16
14
 
17
- | Capability | How | What you get | Status |
18
- |---|---|---|---|
19
- | Startup header | `quietStartup` | One two-tone nameplate with the agent and chrome versions and the live help keys, in place of Pi's startup header and resource listing. | Shipped |
20
- | Two-line footer | every `tui` session | State on one line — location, modes, and alerts — and meters on the other — context, spend, tokens, cache, and model — each shedding parts rather than clipping. | Shipped |
21
- | Extension status chips | `footerData.getExtensionStatuses()` | Worktree, browse, plan, and alert statuses appear in the footer, using each extension's own colour. A failed job is the one chip no width drops. | Shipped |
22
- | Priced session total | the `price` section of `bytefly.json` | The session's spend in yuan or dollars, with DeepSeek priced out of the box. | Shipped |
23
- | Delegated-run cost roll-up | the `↳` footer segment | A finished subagent's cost appears beside the session total, without merging the two ledgers. | Shipped |
24
- | User-message nameplate | every `tui` session | Each user message opens with a two-row `You` label and rule. | Shipped |
25
- | Turn receipt | the `bytefly-separator` entry | One rule per completed turn, recording its number, duration, tool count, and outcome. | Shipped |
26
- | Shared notices | the `my-pi-agent/notice` type | Any extension's notice renders at the transcript margin with a severity-coloured rail. | Shipped |
27
- | Compaction canvas | the shipped `themes/` palettes | Pi's `/compact` block keeps its content and loses its fill, so it sits on the canvas. | Shipped |
28
- | Tool-block frame | `registerToolRenderer` | Every tool block leads with its own call line and hangs its result from the transcript rail. | Shipped |
29
- | Working line | `ctx.ui.setWorkingMessage` | The working row counts seconds, then names the running tool and how many tools the run has started. | Shipped |
15
+ | Capability | How | What you get |
16
+ |---|---|---|
17
+ | Startup header | `quietStartup` | One two-tone nameplate with the agent and chrome versions and the live help keys, in place of Pi's startup header and resource listing. |
18
+ | Two-line footer | every `tui` session | State on one line — location, modes, and alerts — and meters on the other — context, spend, tokens, cache, and model — each shedding parts rather than clipping. |
19
+ | Extension status chips | `footerData.getExtensionStatuses()` | Worktree, browse, plan, and alert statuses appear in the footer, using each extension's own colour. A failed job is the one chip no width drops. |
20
+ | Priced session total | the `price` section of `bytefly.json` | The session's spend in yuan or dollars, with DeepSeek priced out of the box. |
21
+ | Delegated-run cost roll-up | the `↳` footer segment | A finished subagent's cost appears beside the session total, without merging the two ledgers. |
22
+ | User-message nameplate | every `tui` session | Each user message opens with a two-row `You` label and rule. |
23
+ | Turn receipt | the `bytefly-separator` entry | One rule per completed turn, recording its number, duration, tool count, and outcome. |
24
+ | Shared notices | the `my-pi-agent/notice` type | Any extension's notice renders at the transcript margin with a severity-coloured rail. |
25
+ | Compaction canvas | the shipped `themes/` palettes | Pi's `/compact` block keeps its content and loses its fill, so it sits on the canvas. |
26
+ | Tool-block frame | `registerToolRenderer` | Every tool block leads with its own call line and hangs its result from the transcript rail. |
27
+ | Working line | `ctx.ui.setWorkingMessage` | The composer's top border carries it beside Pi's spinner: the elapsed count, then how many tools the run has started. |
28
+ | Plan-mode chord | `Shift+Tab` at the main editor | Flips `auto` and plan through `plan`'s request channel; the footer mode segment and the composer border follow. Shift+Tab is taken from Pi's thinking cycle only while `plan` is loaded, and restored on shutdown. |
30
29
 
31
30
  ### Control center
32
31
 
33
- | Capability | How | What you get | Status |
34
- |---|---|---|---|
35
- | One frame, three sections | `/bytefly`, `/bytefly skills`, `/bytefly settings` | Extensions, skills, and settings in one dock: the worst problem in the header, each section's count on the tabs, the write scope at the right, and one body height for all three, so changing section never resizes the screen. | Shipped |
36
- | Resource rows and the block | `Space`, `Enter`, `?` | A name-first row — `worktree`, not `worktree/index.ts` — with one checkbox, one status glyph and one verdict word, and the focused row's findings, path, source, state, and health beside the list at 96 columns and under it below. | Shipped |
37
- | Grouped settings | `/bytefly settings` | Twelve preferences under `Startup`, `Notifications`, `Session`, and `Price`, each value in one aligned column and editable in place; `J` shows the stored JSON section. | Shipped |
38
- | Theme selection | the `Theme` row in `/bytefly settings` | `system` and the package's `bytefly-dark` and `bytefly-light` palettes are one selection; a reload applies it. | Shipped |
39
- | Filters and scope | `/`, `!`, `s`, `x` | Narrow the rows by text or to what needs attention — the chips on the tab row say which — and choose where a toggle lands, the personal or project `settings.json`, with `x` clearing a project override. | Shipped |
40
- | Reload after changes | `R` | `unsaved changes` joins the header while a write is pending, and `R` reloads to apply it instead of leaving the session to be restarted by hand. | Shipped |
41
- | Health report | `run_doctor`, `/bytefly doctor` | Prerequisites, parse errors, and aged rate tables as text and typed data. | Shipped |
32
+ | Capability | How | What you get |
33
+ |---|---|---|
34
+ | One frame, three sections | `/bytefly`, `/bytefly skills`, `/bytefly settings` | Extensions, skills, and settings in one dock: the worst problem in the header, each section's count on the tabs, the write scope at the right, and one body height for all three, so changing section never resizes the screen. |
35
+ | Resource rows and the block | `Space`, `Enter`, `?` | A name-first row — `worktree`, not `worktree/index.ts` — with one checkbox, one status glyph and one verdict word, and the focused row's findings, path, source, state, and health beside the list at 96 columns and under it below. |
36
+ | Grouped settings | `/bytefly settings` | Fifteen preferences under `Startup`, `Notifications`, `Session`, `Board`, and `Price`, each value in one aligned column and editable in place; `J` shows the stored JSON section. |
37
+ | Theme selection | the `Theme` row in `/bytefly settings` | `system` and the package's `bytefly-dark` and `bytefly-light` palettes are one selection; a reload applies it. |
38
+ | Filters and scope | `/`, `!`, `s`, `x` | Narrow the rows by text or to what needs attention — the chips on the tab row say which — and choose where a toggle lands, the personal or project `settings.json`, with `x` clearing a project override. |
39
+ | Reload after changes | `R` | `unsaved changes` joins the header while a write is pending, and `R` reloads to apply it instead of leaving the session to be restarted by hand. |
40
+ | Health report | `run_doctor`, `/bytefly doctor` | Prerequisites, parse errors, and aged rate tables as text and typed data. |
42
41
 
43
42
  ### Upkeep
44
43
 
@@ -229,7 +228,9 @@ A nameplate: a two-tone half-block mark from the active theme's `accent` /
229
228
  identity (`enhanced by bytefly` and the package version), a signature rule whose
230
229
  `accent` lead sits under the mark, and a grouped help block. The help keys
231
230
  resolve live from Pi's keybindings, so a rebound key shows the user's chord; the
232
- identity and help text are `meta` / `faint` so the mark keeps the eye. It opens
231
+ one raw hint, `shift+tab to toggle plan`, is bytefly's own editor chord rather
232
+ than a bindable Pi action. The identity and help text are `meta` / `faint` so the
233
+ mark keeps the eye. It opens
233
234
  with a blank row, so the nameplate clears the top edge. It is installed only in
234
235
  interactive (`tui`) sessions when Pi's `quietStartup` is `true` — the Settings
235
236
  row **Startup screen** — and `false`, `"header"`, and unset leave Pi's
@@ -261,15 +262,17 @@ identity, and grouped help are tuned in
261
262
 
262
263
  Two lines, each with a left and right zone:
263
264
 
264
- - **Line 1 — identity:** `pwd` and the session name on the left; on the right the
265
- alert, then the mode chips (`≡ plan`), then `⎇ branch · ⑂ worktree`, then the
266
- ports (`⊞ 5043 · ◉ 4909`) while a page is being served. The board page and the
267
- file browser each publish one status key, and the bar reads both as a single
268
- segment, so they share a weight, a position, and a floor.
269
- - **Line 2 — resources:** the context gauge and usage meters on the left, the
270
- child-run roll-up (`↳ 3 · ¥0.12`, present only once a subagent has finished),
271
- and the model and thinking level on the right. The gauge is anchored at column
272
- 0, so `%` is always in the same place.
265
+ - **Line 1 — identity:** `pwd` and the name this session is on the team as on the left —
266
+ the bus's roster label when it has one, and Pi's own session name when it has none, so a
267
+ session off the bus reads exactly as it did; on the right the
268
+ alert, then the remaining status chips (a job count, a rewind timeline, the
269
+ `✆` bridge), then `⎇ branch · ⑂ worktree`, then the session's page port
270
+ (`◉ 4780`) while anything is served on it. One server serves every page of a
271
+ session, so it is one number from one status key.
272
+ - **Line 2 — resources:** the mode (`▶ auto` or `≡ plan`), the context gauge and
273
+ usage meters on the left, the child-run roll-up (`↳ 3 · ¥0.12`, present only
274
+ once a subagent has finished), and the model and thinking level on the right.
275
+ The gauge follows the mode, so `%` stays put once the mode stops changing.
273
276
  The roll-up is its own number, never folded into the session's cost: a subagent
274
277
  runs in its own process, and the footer prices those runs with the same rate
275
278
  table and currency as the session so a currency change moves both. It covers
@@ -277,8 +280,8 @@ Two lines, each with a left and right zone:
277
280
  `/resume` (the events are not session entries).
278
281
 
279
282
  ```
280
- ~/repo/my-pi-agent ≡ plan · ⎇ main · ⑂ smoke
281
- ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↳ 2 · ¥0.06 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
283
+ ~/repo/my-pi-agent ⎇ main · ⑂ smoke
284
+ ≡ plan · ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↳ 2 · ¥0.06 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
282
285
  ```
283
286
 
284
287
  (The examples show the default `¥` symbol. A model that neither `rates` nor the
@@ -286,18 +289,17 @@ built-in table covers contributes nothing in `CNY` mode, so the bar shows no
286
289
  figure rather than a guessed one; `USD` mode adds Pi's recorded dollars for that
287
290
  record instead — see [Cost calculation](#cost-calculation).)
288
291
 
289
- With no plan mode the mode slot is simply absent:
292
+ With no plan mode the mode segment reads its other state:
290
293
 
291
294
  ```
292
295
  ~/repo/my-pi-agent ⎇ main · ⑂ smoke
293
- ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
296
+ ▶ auto · ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
294
297
  ```
295
298
 
296
- While a page is served, its port joins the run beside the worktree — both pages as
297
- one cluster, in a fixed order, so the pair reads as a pair:
299
+ While a page is served, the session's port joins the run beside the worktree:
298
300
 
299
301
  ```
300
- ~/repo/my-pi-agent ⎇ main · ⊞ 5043 · ◉ 4909
302
+ ~/repo/my-pi-agent ⎇ main · ◉ 4780
301
303
  ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 · ↑42k ↓8.0k · R96k CH 87% opus-4.5 · high
302
304
  ```
303
305
 
@@ -306,29 +308,29 @@ clipping. The branch keeps its name and is dropped when the name no longer
306
308
  fits, rather than collapsing to a bare glyph:
307
309
 
308
310
  ```
309
- ~/repo/my-pi-agent ≡ plan · ⎇ main · ⑂
310
- ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 opus-4.5 · high
311
+ ~/repo/my-pi-agent ⎇ main · ⑂
312
+ ≡ plan · ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 opus-4.5 · high
311
313
  ```
312
314
 
313
315
  ```
314
316
  ~/my-pi-agent ⎇ main
315
- ▰▰▰▰▰▰▱▱▱▱ 62% opus-4.5
317
+ ▶ auto · ▰▰▰▰▰▰▱▱▱▱ 62% opus-4.5
316
318
  ```
317
319
 
318
- A port is dropped before the pages it names are: the cluster compacts to
319
- `⊞ ◉` once the digits no longer fit, so the narrowest bar that fits the location
320
- chips still says a page is up.
320
+ The port drops the digits before the page they name: it compacts to `◉` once
321
+ the number no longer fits, so the narrowest bar that fits the location chips still
322
+ says a page is up.
321
323
 
322
324
  ```
323
- ~/repo/my-pi-agent ⎇ main · ⊞ ◉
324
- ▰▰▰▰▰▰▱▱▱▱ 62% · ¥0.31 opus-4.5 · high
325
+ ~/repo/my-pi-agent ⎇ main · ◉
326
+ ≡ plan · ▰▰▰▰▰▰▱▱▱▱ 62% · ¥0.31 opus-4.5 · high
325
327
  ```
326
328
 
327
329
  With a named session, the name follows the path and truncates before it drops:
328
330
 
329
331
  ```
330
332
  ~/repo/project · [feature-refac…] ⎇ main
331
- ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 opus-4.5 · high
333
+ ▶ auto · ▰▰▰▰▰▰▱▱▱▱ 62%/200k · ¥0.31 opus-4.5 · high
332
334
  ```
333
335
 
334
336
  Zero-value meters are omitted, so a fresh session shows only `?/window` instead
@@ -346,7 +348,8 @@ Each segment reads a theme role and carries its own drop order:
346
348
  | session | 1 left | muted, bracketed | after worktree |
347
349
  | branch | 1 right | muted (warning when detached) | last |
348
350
  | worktree | 1 right | success | first |
349
- | ports `⊞ 5043 · ◉ 4909` | 1 right | success glyphs + accent ports | first, in one step |
351
+ | port `◉ 4780` | 1 right | success glyph + accent port | first |
352
+ | mode `≡ plan` / `▶ auto` | 2 left | warning while planning, accent otherwise | never |
350
353
  | context gauge + `%` | 2 left | muted ≤70 · warning 71–90 · error >90 | never |
351
354
  | `/window` | 2 left | dim | before cost |
352
355
  | cost | 2 left | muted | after tokens |
@@ -357,23 +360,33 @@ Each segment reads a theme role and carries its own drop order:
357
360
  | thinking level | 2 right | thinking token | before cost |
358
361
 
359
362
  The worktree status is read from the `worktree` status key, so the
360
- [worktree](./worktree.md) extension and the bar agree. The ports are the `board`
361
- and `browse` status keys, published by [board](./board.md) and
362
- [file-browse](./file-browse.md) while their pages are served. One segment reads
363
- both, in a fixed order (board, then browse), each a two-tone `<glyph> <port>`
364
- with a green glyph and an accent port. That is one fact — "a page is up on this
365
- port" — so it gets one weight and one floor: the pair compacts from
366
- `⊞ 5043 · ◉ 4909` to `⊞ 5043 ◉ 4909` to `⊞ ◉`, and it drops as a unit, where
367
- separate chips once let the board port outlive the browse one by four columns
368
- for no reason a reader could see.
369
-
370
- The mode slot holds everything else an extension publishes: a plan chip, a
371
- job count, a rewind timeline, the `✆` bridge, and an alert's front position. A
372
- plan chip's trailing plan file name is dropped, so the slot reads `≡ plan` even
373
- though the built-in footer keeps the name, and an alert (a failed job) sorts to
374
- the front of it. It keeps each extension's own colour when it compacts, and it is
375
- droppable: a tight bar drops the slot rather than truncating the model, which
376
- sits at the end of the line.
363
+ [worktree](./worktree.md) extension and the bar agree. The port is the `http`
364
+ status key, published by [serve](./serve.md) while anything is mounted on the
365
+ session's server — one number, because one socket serves every page a session
366
+ opens. It is a two-tone `<glyph> <port>` with a green glyph and an accent port,
367
+ and it compacts from `◉ 4780` to `◉` before it drops, so a bar too narrow for
368
+ the number still says a page is up.
369
+
370
+ The mode segment leads the resource line: `≡ plan` in the warning role while
371
+ planning, `▶ auto` in accent otherwise. The bar draws that word itself and reads
372
+ only the presence of the `plan-mode` status key, so neither the plan file name nor
373
+ a chip truncated elsewhere can reach it. `auto` is not a
374
+ behaviour of its own: it names Pi's default mode, every tool as it ships and writes
375
+ included, so the segment states which mode the session is in and `≡ plan` is the one
376
+ override that withholds — a mode added later is a third value of this one chip, never a
377
+ second indicator saying what the session may do. `Shift+Tab` at
378
+ the main editor asks [plan](./plan.md) to flip it; the chip and the composer
379
+ border are read from the branch, so `/plan` and the model's own `plan` action `exit`
380
+ move them too. Bytefly unsets Pi's `app.thinking.cycle` chord while it holds the
381
+ key, so `/hotkeys` and the header stop claiming Shift+Tab, and restores it on
382
+ shutdown. A session without `plan` still reads `▶ auto` and keeps the thinking
383
+ cycle.
384
+
385
+ The status slot beside the branch holds everything else an extension publishes: a
386
+ job count, a rewind timeline, the `✆` bridge, and an alert's front position. An
387
+ alert (a failed job) sorts to the front of it. It keeps each extension's own
388
+ colour when it compacts, and it is droppable: a tight bar drops the slot rather
389
+ than truncating the model, which sits at the end of the line.
377
390
 
378
391
  ## Cost calculation
379
392
 
@@ -624,44 +637,50 @@ edges its own `Box` pads itself with, and hands it a theme whose `bg` is inert s
624
637
  it never paints a band the rest of the transcript does not have.
625
638
 
626
639
  The call line is one colour grammar, shared with Pi's built-ins: the tool name is
627
- `toolTitle` and bold and is the line's only accent; a primary subject (a query, a
628
- URL, a path, a status) reads in the body role (`label`), counts and summaries are
629
- `muted`, trailing options are `dim`, and the result body is `toolOutput`. The
630
- shipped themes point `toolTitle` at the accent and `label` at the body colour, so
631
- the name is the call line's one accent and the subject follows as body text; a
632
- theme that maps `toolTitle` elsewhere is respected rather than overridden.
640
+ in the `toolTitle` role, bold, and is the line's only accent; a primary subject (a
641
+ query, a URL, a path, a status) reads in the body role (`label`), counts and
642
+ summaries are `meta`, trailing options are `faint`, and the result body is
643
+ `toolOutput`. The shipped themes point `toolTitle` at the accent and `label` at
644
+ the body colour, so the name is the call line's one accent and the subject follows
645
+ as body text; a theme that maps `toolTitle` elsewhere is respected rather than
646
+ overridden.
633
647
 
634
648
  ## The working line
635
649
 
636
- Pi's working row shows a static `Working`, so a long run reads the same as a
637
- short one. While the row is up, bytefly advances its message once a second and
638
- hands it back to Pi when the row comes down. Before the first tool starts it is
639
- a count:
650
+ Pi's working indicator shows a static `Working`, so a long run reads the same as
651
+ a short one. It is drawn on the composer's top border while a run is up, and
652
+ bytefly advances its message once a second and hands it back to Pi when the run
653
+ settles. Before the first tool starts it is a count:
640
654
 
641
655
  ```
642
- Working… 12s
656
+ ── ◐ working · 12s ───────────────────
643
657
  ```
644
658
 
645
- Once a top-level tool starts, the row becomes a live run line: the tool, how
646
- many top-level tools this run has started, and the elapsed time.
659
+ Once a top-level tool starts, the border adds how many top-level tools this run
660
+ has started:
647
661
 
648
662
  ```
649
- edit · 3 tools · 12s
663
+ ── ◐ working · 3 tools · 12s ─────────
650
664
  ```
651
665
 
666
+ The spinner and the colour are Pi's: the indicator owns the `◐` frame and takes
667
+ the composer border's role, so it is `accent` in auto and `warning` while
668
+ planning. The line deliberately does not name the tool — the transcript already
669
+ draws each call as its own block, and one word reads faster than a name that
670
+ changes under you.
671
+
652
672
  A nested tool call (one carrying `parentToolCallId`, such as a codemode script's
653
- inner call) neither counts nor takes over the line, so the number matches the
654
- tool blocks in the transcript. The line keeps the last tool name while the
655
- model streams after it, and a tool event repaints the row immediately instead
656
- of waiting for the next tick. The line covers the whole run, tool calls
657
- included. It is tied to the row's visibility: it starts on `turn_start`, stops
673
+ inner call) does not count, so the number matches the tool blocks in the
674
+ transcript. A tool event repaints the border immediately instead of waiting for
675
+ the next tick. The line covers the whole run, tool calls
676
+ included. It is tied to the indicator's visibility: it starts on `turn_start`, stops
658
677
  on `agent_end`, and is reset on `session_shutdown`; turns inside one run do not
659
678
  restart it, and a retry starts a new run. The words and the interval are tuned
660
679
  in [`working/config.ts`](../../extensions/bytefly/working/config.ts).
661
680
 
662
681
  ## The control center
663
682
 
664
- <img src="../../assets/screens/bytefly.png" alt="The /bytefly control center at 120 columns: a `Bytefly` rule with `1 missing` at its right edge, the `Extensions 9 · Skills 0 · Settings 12` tabs with `writes Global` at the right, one name-first row per extension with its checkbox and verdict, and the focused extension's block beside the list — its findings and their fix, Path, Source, State, and Health" width="100%">
683
+ <img src="../../assets/screens/bytefly-extensions.png" alt="The /bytefly control center at 120 columns: a `Bytefly` rule with `1 missing` at its right edge, the `Extensions 9 · Skills 3 · Settings 15` tabs with `writes Global` at the right, one name-first row per extension with its checkbox and verdict, and the focused extension's block beside the list — its findings and their fix, Path, Source, State, and Health" width="100%">
665
684
 
666
685
  `/bytefly [extension] [refresh]` opens the control center on **Extensions**: a
667
686
  keyboard-driven dock of every resource Pi resolves for the session, so the list
@@ -785,9 +804,11 @@ finds the rows that need it.
785
804
  - Shows whether each one is enabled (`◼`) or disabled (`◻`) in settings.
786
805
  - Gives each row one verdict from the doctor's report: the worst severity's
787
806
  glyph (`!` degraded, `✕` missing) and its summary. A row that is `◻` reads
788
- `off` instead, because it will not load; a row the session's
789
- `PI_DISABLED_EXTENSIONS` excludes reads `env disabled`, so the two axes cannot
790
- contradict each other. A row with nothing to report leaves the glyph column
807
+ `off` instead, because it will not load; a row the session's environment
808
+ excludes — disabled through `PI_DISABLED_EXTENSIONS`, or an opt-in extension
809
+ that `PI_ENABLED_EXTENSIONS` does not name — reads `env disabled`, so the two
810
+ axes cannot contradict each other. The finding beside it names the switch that
811
+ would change the answer. A row with nothing to report leaves the glyph column
791
812
  blank and says `ok` in words when a report exists.
792
813
  - Toggles the focused extension with `Space`, writing `+pattern` / `-pattern`
793
814
  into `extensions` (and package filter objects) in the write scope. A row in
@@ -831,20 +852,24 @@ line until `Enter` expands it. There is nothing to probe, so `r` is not offered
831
852
  here; the metadata is read once when the section first appears, so an edited
832
853
  `SKILL.md` shows on the next open.
833
854
 
855
+ <img src="../../assets/screens/bytefly-skills.png" alt="The /bytefly Skills section at 120 columns: the `Extensions 9 · Skills 3 · Settings 15` tabs with `writes Global` at the right, the three skills this package ships under an `npm:pkg (user) (3)` heading with the focused `init` row marked `hidden`, and its block beside the list — Path, Source, State, the `/skill:init` call, `Model hidden from the model`, and the description the file declares under About" width="100%">
856
+
834
857
  Themes and prompts have no section of their own, by decision rather than by
835
858
  omission: a theme is one active selection — a scalar, global setting — and a
836
859
  prompt template is a file Pi discovers from a directory, so neither is a scoped
837
860
  enable list this screen's checkbox rows can write. Themes stay with `pi config`;
838
- `ROADMAP.md` carries the reason under *Deliberately left thin*.
861
+ [`architecture.md`](../architecture.md#documented-limits-with-no-planned-card) carries
862
+ the reason.
839
863
 
840
864
  **Settings.** The third section is the same frame over the curated preferences,
841
865
  grouped by what they do rather than by which file holds them: `Startup (2)`,
842
- `Notifications (5)`, `Session (3)`, and `Price (2)`. It is the shortlist a
843
- session changes: twelve preferences, each with its value in one aligned column
844
- instead of beside its label, so a column of values can be read down. Sixteen
845
- entries — twelve rows and their four headings — fit a 24-row terminal when the
846
- block sits beside the list; on a narrower one the list scrolls and the block
847
- keeps its three rows. The frame never grows past the tallest section, however
866
+ `Notifications (5)`, `Session (3)`, `Board (3)`, and `Price (2)`. It is the
867
+ shortlist a session changes: fifteen preferences, each with its value in one
868
+ aligned column instead of beside its label, so a column of values can be read
869
+ down. Twenty entries — fifteen rows and their five headings — want a 28-row
870
+ terminal to be seen whole when the block sits beside the list; on a shorter or
871
+ narrower one the list scrolls, says `showing 1–13 of 15`, and the block keeps its
872
+ three rows. The frame never grows past the tallest section, however
848
873
  tall the terminal is. In Project scope the two app rows drop out and the note
849
874
  `App settings are Global only.` stands above the list.
850
875
 
@@ -855,7 +880,7 @@ description, and `Stored in <origin> · <file>` — `Global`, `Project`,
855
880
  otherwise). `J` on a `bytefly.json` row appends that section as the active scope
856
881
  stores it, under a `── notify ──` rule.
857
882
 
858
- <img src="../../assets/screens/bytefly-settings.png" alt="The /bytefly Settings section at 120 columns: the `Startup (2)`, `Notifications (5)`, `Session (3)`, and `Price (2)` groups with each value in one aligned column, the `Extensions 9 · Skills 0 · Settings 12` tabs with `writes Global` at the right, and the focused row's block beside the list — its value, description, and the file the value is stored in" width="100%">
883
+ <img src="../../assets/screens/bytefly-settings.png" alt="The /bytefly Settings section at 120 columns: the `Startup (2)`, `Notifications (5)`, `Session (3)`, `Board (3)` and `showing 1–11 of 15` rows with each value in one aligned column, the `Extensions 9 · Skills 3 · Settings 15` tabs with `writes Global` at the right, and the focused row's block beside the list — its value, description, and the file the value is stored in" width="100%">
859
884
 
860
885
  | Group | Row | Source | Values |
861
886
  |---|---|---|---|
@@ -895,9 +920,10 @@ only view of the keys this screen leaves to JSON. A successful change marks the
895
920
  block `Saved · reload to apply` and puts `unsaved changes` in the header, and
896
921
  closing offers the same reload confirm as an extension or skill toggle.
897
922
 
898
- The list is capped at twelve rows so the screen keeps fitting a 24-row
923
+ The list is capped at fifteen rows so the screen keeps fitting a 28-row
899
924
  terminal: adding a setting means removing one (`panel/settings.test.ts` holds the
900
- ceiling). Everything else stays in `bytefly.json` — the tuning numbers, the
925
+ ceiling, and a 24-row terminal scrolls the list rather than the frame). Everything
926
+ else stays in `bytefly.json` — the tuning numbers, the
901
927
  cosmetic labels, and the Pi preferences Pi's own `/settings` owns — and
902
928
  `test/settings-coverage.test.ts` keeps the split honest: every loader key is
903
929
  either a row or a declared JSON-only edit with the reason it left. Most
@@ -920,7 +946,7 @@ Off, because it keeps Pi's header; the next toggle replaces it with `true` or
920
946
 
921
947
  App rows are **Global only**: Pi's setters for them write the personal
922
948
  `settings.json`, and only resource paths have public project setters. Project
923
- scope therefore lists the ten `bytefly.json` rows and a
949
+ scope therefore lists the thirteen `bytefly.json` rows and a
924
950
  `App settings are Global only.` note rather than promising a write that cannot
925
951
  happen. A `bytefly.json` row writes one leaf in the selected scope (a depth-1
926
952
  key, or a nested one such as `price.currency.code`) through a per-key writer, so
@@ -932,7 +958,7 @@ The non-`tui` form prints the headline and one `Label: Value` row per line, in
932
958
  screen order:
933
959
 
934
960
  ```
935
- bytefly: Settings · Global · 12 settings
961
+ bytefly: Settings · Global · 15 settings
936
962
  Startup screen: Off
937
963
  Theme: bytefly-dark
938
964
  Desktop notifications: On
@@ -1148,7 +1174,7 @@ install, and `PI_OFFLINE` all follow its package manager. The only extra read is
1148
1174
  this package, used just to name the version arrow and release URL. A failed
1149
1175
  version read drops those two pieces but keeps the package list and command.
1150
1176
 
1151
- The answer is cached in `<agentDir>/bytefly/update.json` as the package list,
1177
+ The answer is cached in `<agent-dir>/bytefly/update.json` as the package list,
1152
1178
  this package's optional version, the time it was read, and a key over the cwd and
1153
1179
  the effective package sources. A session that starts inside the 24-hour window
1154
1180
  reuses that snapshot — painting the marker and posting the notice from it —
@@ -1202,7 +1228,7 @@ why the line says so, and each source the manager reports is shown while it runs
1202
1228
  - **The list is a shortlist, not the whole document.** A row is one boolean,
1203
1229
  enum, string, or number; the tuning numbers, the labels, and the Pi
1204
1230
  preferences `/settings` owns stay in their files, and the catalogue is capped
1205
- at twelve rows so the screen fits a 24-row terminal. `doctor.skip` (an
1231
+ at fifteen rows so the screen fits a 28-row terminal. `doctor.skip` (an
1206
1232
  array), the `rates` table, and `price.peak` (a nested object) could never be a
1207
1233
  row; the rest are declared JSON-only with their reason in
1208
1234
  `test/settings-coverage.test.ts`.
@@ -1277,12 +1303,15 @@ why the line says so, and each source the manager reports is shown while it runs
1277
1303
  - **The displayed number is the total, rounded once** — not the sum of rounded
1278
1304
  records.
1279
1305
  - **`doctor` can only check what is declared.** A new extension with no
1280
- `REQUIREMENTS` entry is invisible to it — `test/structure.test.ts` fails on
1306
+ `REQUIREMENTS` entry is invisible to it — `test/prerequisites.test.ts` fails on
1281
1307
  exactly that, which is why the table cannot rot quietly.
1282
- - **The disabled check reads `PI_DISABLED_EXTENSIONS`** through `lib/env.ts`; it
1283
- does not read Pi's own `settings.json` disable list, so an extension disabled
1284
- only there may still be probed. The row labels that state `env disabled`,
1285
- separately from the picker's `off`.
1308
+ - **The disabled check reads the environment** through `lib/env.ts`: the disable
1309
+ list, and the opt-in list that a default-off extension needs. It does not read
1310
+ Pi's own `settings.json` disable list, so an extension disabled only there may
1311
+ still be probed. The row labels that state `env disabled`, separately from the
1312
+ picker's `off` — the picker's `Space` writes settings patterns and cannot set
1313
+ an environment variable, so an opt-in extension is switched on in the shell,
1314
+ not on that screen.
1286
1315
  - **A probe proves a binary runs, not that it works:** a `git` that crashes on a
1287
1316
  specific repository is still `ok`.
1288
1317
  - **Config scope is checked for `<agent-dir>/bytefly.json` and
@@ -1301,11 +1330,14 @@ why the line says so, and each source the manager reports is shown while it runs
1301
1330
  host lacks (or an OSC method off a TTY) and the notification is dropped; only
1302
1331
  `auto` inspects the environment. `doctor` now reports the mismatch as a
1303
1332
  `degraded` finding, which says what to change but does not fix it for you.
1304
- - **The working line is one global row.** `setWorkingMessage` drives the
1305
- single working row, so bytefly and any other extension setting it race — last
1306
- writer wins — and the message is reset to Pi's default at `agent_end`. If the
1307
- row is hidden (`ctx.ui.setWorkingVisible(false)`) the line has no surface,
1308
- and a reload mid-run does not resume the count until the next turn.
1333
+ - **The working line is one global message.** `setWorkingMessage` drives the
1334
+ single working indicator, so bytefly and any other extension setting it race — last
1335
+ writer wins — and the message is reset to Pi's default at `agent_end`. It is drawn
1336
+ on the composer's top border because the mode editor opts into Pi's embedded
1337
+ indicator; an editor that does not (or a host without `embedWorkingStatus`) puts the
1338
+ same line in a row above the composer instead. If the indicator is hidden
1339
+ (`ctx.ui.setWorkingVisible(false)`) the line has no surface, and a reload mid-run
1340
+ does not resume the count until the next turn.
1309
1341
 
1310
1342
  ## Non-goals
1311
1343
 
@@ -1330,7 +1362,7 @@ why the line says so, and each source the manager reports is shown while it runs
1330
1362
  - No sound, action buttons, notification history, or de-duplication; a
1331
1363
  notification is a fire-and-forget message, not an inbox.
1332
1364
  - No frozen per-run working duration: the line is live and is handed back to
1333
- Pi when the working row comes down, because the extension API exposes no hook
1365
+ Pi when the working indicator comes down, because the extension API exposes no hook
1334
1366
  to keep a finished run's total.
1335
1367
 
1336
1368
  The built-in footer's `(auto)` auto-compact indicator and `(sub)`
@@ -1349,7 +1381,7 @@ rather than showing a wrong value.
1349
1381
  | Tool blocks | `registerToolRenderer` returns, for every tool, `renderShell: "self"`; `renderCall` leaves the tool's own first line as the block's headline (a shell prompt shown as the tool name, its command repainted in the body colour, and a primary subject demoted to the body colour) and indents the rest of the call two columns under it, `renderResult` indents its body the same and leads it with the status-coloured rail, and each wraps the tool's own renderer (or a generic fallback). `edit` and `write` carry their result on the call body, so that body is railed too. |
1350
1382
  | Screen command | `pi.registerCommand("bytefly")` takes `[doctor] [settings] [extension] [refresh]`: `tui` mounts a dock screen via `ctx.ui.custom()` with the rails suppressed; other modes print through `ctx.ui.notify()` — the grouped listing, `run_doctor`'s report for `doctor`, or the flat settings listing with values for `settings`. A named extension is checked against the discovered set before any probe. Tab completes the subcommands, extension names after `doctor`, and `refresh`; `/bytefly settings` rejects a name. |
1351
1383
  | Turn separator | `registerEntryRenderer("bytefly-separator")` draws each stored receipt; `agent_start` opens it, top-level `tool_execution_start`/`tool_execution_end` count tools and failures, and `agent_settled` appends `pi.appendEntry("bytefly-separator", { turn, durationMs, tools, failures, aborted })` in `tui` mode only. The receipt fields are omitted when no `agent_start` was seen, so an older or partially observed entry stays `{ turn }`. |
1352
- | Working line | `turn_start` starts a 1s tick that drives `ctx.ui.setWorkingMessage` in `tui` mode; before a tool runs the message is `Working… Ns`, and each top-level `tool_execution_start` repaints it as `tool · N tools · Ns` (nested calls carrying `parentToolCallId` are skipped). `agent_end` restores Pi's default and cancels the tick (also on `session_shutdown`). No config file and no persisted state; the count lives in the register's closure. |
1384
+ | Working line | `turn_start` starts a 1s tick that drives `ctx.ui.setWorkingMessage` in `tui` mode; before a tool runs the message is `working · Ns`, and each top-level `tool_execution_start` repaints it as `working · N tools · Ns` (nested calls carrying `parentToolCallId` are skipped). It renders on the composer's top border, which the mode editor enables with `embedWorkingStatus`. `agent_end` restores Pi's default and cancels the tick (also on `session_shutdown`). No config file and no persisted state; the count lives in the register's closure. |
1353
1385
  | Notifications | `agent_settled` (skipped when aborted), `ui_prompt_start` only while `agent_start`…`agent_settled` marks a run active, and `my-pi-agent/job-finished` on `pi.events`; the `notify` section of `bytefly.json` gates each. Delivery writes an OSC 777/99 sequence to stdout or spawns `osascript`, `notify-send`, or `powershell.exe`. The bus subscription is created in `session_start` and released in `session_shutdown`, and the `job` extension never imports bytefly. |
1354
1386
  | Picker discovery | `SettingsManager.create(...)` plus `DefaultPackageManager.resolve({ onMissing: "skip" })`, both public exports; both views drop `bytefly`'s own entrypoint. |
1355
1387
  | Picker writes | `SettingsManager` setters (`setExtensionPaths`, `setPackages`, `setProjectExtensionPaths`, `setProjectPackages`) then `flush()`; `ctx.reload()` after the screen closes when changes are pending. |
@@ -1358,10 +1390,10 @@ rather than showing a wrong value.
1358
1390
  | Doctor probes | `run_doctor` and the screen share the session cache created by `registerDoctor`; the screen mounts on that cache and probes on the first Extensions view (the command's `refresh` forces it), while the text modes probe before printing, and `/bytefly refresh` and `refresh: true` re-probe. Probes spawn `<binary> --version`, lazily import optional packages, and read config files through `lib/requirements.ts`; nothing is ever written. |
1359
1391
  | Status routing | Reads `footerData.getExtensionStatuses()` (a custom footer replaces the built-in one entirely); `worktree` routes to line 1, plan/alert statuses to line 2's trailing slot. |
1360
1392
  | Release notice | `session_start` in `tui` mode patches `InteractiveMode.prototype.showNewVersionNotification` through the shared `patchPrototypeMethod`, so the release Pi resolved becomes `pi.sendMessage(piReleaseNotice(...))` under the shared notice type and `paintLatest` records its version for the agent line's marker (`onUpdate` re-installs the header). The callback posts once per load and only while the session is live; a throw calls the captured original, so Pi's own box is the fallback. `session_shutdown` restores the method and clears the marker. |
1361
- | Update check | `session_start` in `tui` mode installs a suppression on `DefaultPackageManager.prototype.checkForAvailableUpdates`, then reads `<agentDir>/bytefly/update.json`. A fresh entry whose key matches the cwd and effective package sources is recorded without a package-manager call; otherwise bytefly fires `fetchAvailableUpdates()` without awaiting, resolves this package's version through `fetchLatestVersion()` when Pi reports it, writes the snapshot, and records it. `onUpdate` re-installs the header, and `pi.sendMessage(updateNotice(...))` posts the one notice with its version arrow and release URL. `PI_OFFLINE` paints the cached marker only. `/bytefly update` calls the returned `UpdateHandle.recheck()`, which bypasses the window and returns an `UpdateOutcome`; `/bytefly upgrade` calls `UpdateHandle.apply()`, which runs `applyUpdates()` — Pi's `DefaultPackageManager.update()`, with its progress passed to `ctx.ui.notify` — and then re-reads the snapshot so the marker cannot outlive the version it named. A generation counter drops a background reply that lands after `session_shutdown`, a per-load flag keeps it to one announcement, and shutdown restores Pi's method. |
1393
+ | Update check | `session_start` in `tui` mode installs a suppression on `DefaultPackageManager.prototype.checkForAvailableUpdates`, then reads `<agent-dir>/bytefly/update.json`. A fresh entry whose key matches the cwd and effective package sources is recorded without a package-manager call; otherwise bytefly fires `fetchAvailableUpdates()` without awaiting, resolves this package's version through `fetchLatestVersion()` when Pi reports it, writes the snapshot, and records it. `onUpdate` re-installs the header, and `pi.sendMessage(updateNotice(...))` posts the one notice with its version arrow and release URL. `PI_OFFLINE` paints the cached marker only. `/bytefly update` calls the returned `UpdateHandle.recheck()`, which bypasses the window and returns an `UpdateOutcome`; `/bytefly upgrade` calls `UpdateHandle.apply()`, which runs `applyUpdates()` — Pi's `DefaultPackageManager.update()`, with its progress passed to `ctx.ui.notify` — and then re-reads the snapshot so the marker cannot outlive the version it named. A generation counter drops a background reply that lands after `session_shutdown`, a per-load flag keeps it to one announcement, and shutdown restores Pi's method. |
1362
1394
  | Config | `bytefly.json` from the agent directory and `<cwd>/.pi`; the `price` section is read once per footer install (a new session or a session-tree change), the `notify` and `doctor` sections at `session_start`, and the `chrome` section's rail glyph at `session_start`/`session_tree`. Nothing watches the document. The update check has no section: it is on whenever `PI_OFFLINE` is off, and its 24 h window is not configurable. |
1363
1395
  | Data sources | `footerData.getGitBranch()`, `footerData.getAvailableProviderCount()`, `ctx.getContextUsage()`, `ctx.sessionManager.getEntries()`, `ctx.sessionManager.getSessionName()`, `ctx.model`; subscribes to `footerData.onBranchChange` to repaint. |
1364
- | State storage | None besides the inert separator entries. The footer reads session entries and the price config (a memoized scan) and writes no tool-result `details`; it appends `bytefly-separator` entries excluded from LLM context. The screen derives its view from the picker runtime, the current reports, and the skill index, and persists extension and skill toggles through `settings.json`. The Settings section derives from a snapshot of the personal `settings.json` and both `bytefly.json` documents, and persists through the same `SettingsManager` setters or the per-leaf `bytefly.json` writer. Doctor's findings are session-scoped in memory only — cached for the session, never written to the session or a file. Notify holds only the config and the run-active flag in its closure. The update check holds this package's version in the per-load `UpdateSource` and one project-scoped snapshot in `<agentDir>/bytefly/update.json`. |
1396
+ | State storage | None besides the inert separator entries. The footer reads session entries and the price config (a memoized scan) and writes no tool-result `details`; it appends `bytefly-separator` entries excluded from LLM context. The screen derives its view from the picker runtime, the current reports, and the skill index, and persists extension and skill toggles through `settings.json`. The Settings section derives from a snapshot of the personal `settings.json` and both `bytefly.json` documents, and persists through the same `SettingsManager` setters or the per-leaf `bytefly.json` writer. Doctor's findings are session-scoped in memory only — cached for the session, never written to the session or a file. Notify holds only the config and the run-active flag in its closure. The update check holds this package's version in the per-load `UpdateSource` and one project-scoped snapshot in `<agent-dir>/bytefly/update.json`. |
1365
1397
  | Lifecycle | `session_start` installs the header when Pi's `quietStartup` is `true` and the footer, loads the chrome rail glyph, points the doctor runtime at the session cwd, probes the extensions with hard requirements when `bytefly.json`'s `doctor.proactive` is on and `quietStartup` is `true`, loads the notify config and subscribes to the job bus, and starts the package-update check; `session_tree` reinstalls the header and footer and reloads the rail; `session_shutdown` restores Pi's built-in header, footer, package-update method, and release-notice method, disposes the branch subscription, drops doctor's cached findings, drops the notify bus subscription, and clears the recorded versions. |
1366
1398
 
1367
1399
  ## Design notes
@@ -1467,18 +1499,19 @@ rather than showing a wrong value.
1467
1499
  the session's; it prices the `my-pi-agent/subagent-finished` events with the
1468
1500
  same pricer and renders them as `↳`, so "this session" keeps meaning the
1469
1501
  dispatching process and the delegated work stays visible beside it.
1470
- - **The gauge is anchored at column 0, and nothing can displace it.** A leading
1471
- mode slot used to push the context percentage left and right as plan mode came
1472
- and went, which is why the mode chip trailed the meters after a `│`; moving the
1473
- mode slot to the state line leaves the gauge as the resource line's first
1474
- segment, so the percentage cannot move at all.
1502
+ - **The mode leads the resource line, and the gauge follows it.** A tight bar used
1503
+ to push the context percentage around as plan mode came and went; putting the
1504
+ mode first gives the gauge a fixed offset after a segment of fixed width, so the
1505
+ percentage no longer moves as statuses are published. The mode is never
1506
+ dropped: a reader knows which mode the next request lands in before reading how
1507
+ much room is left.
1475
1508
  - **Statuses are routed, not repeated.** The built-in footer renders every
1476
1509
  `setStatus` value on its own line, so the bar sorts them by meaning instead:
1477
- `worktree` and `browse` are location, so the `◉ <port>` chip sits behind `⎇`
1478
- branch and `⑂` worktree; the `board` key is location too, and the two ports
1479
- are one segment because they are one fact, so they share a weight and a floor;
1480
- a plan is a mode, so it leads the state line beside them; a failed job is an
1481
- alert, so it is its own segment ahead of both. The
1510
+ `worktree` is location, so the `◉ <port>` chip sits behind `⎇`
1511
+ branch and `⑂` worktree, and it is the session's one page port, so it keeps a
1512
+ weight and a floor of its own; a plan is a mode, so its chip leads the resource
1513
+ line and bytefly fills in `▶ auto` when it is absent; a failed job is an alert,
1514
+ so it is its own segment ahead of the state chips. The
1482
1515
  [wechat](./wechat.md) extension publishes `✆ wechat` while its bridge runs,
1483
1516
  which is why the bar has to render `getExtensionStatuses()` at all.
1484
1517
  - **A state chip leads its zone, so the zone does not move under it.** The right
@@ -1641,7 +1674,7 @@ rather than showing a wrong value.
1641
1674
  The surfaces are folders: `header/`, `footer/` (the status bar and its price
1642
1675
  calculation), `message/` (the user-message marker), `notice/` (the shared notice),
1643
1676
  `block/` (the tool-block frame), `separator/`, `working/` (the live
1644
- run line on Pi's working row), `panel/` (the
1677
+ run line on the composer's top border), `panel/` (the
1645
1678
  unified screen and its merged model),
1646
1679
  `picker/` (discovery and the settings store), `doctor/` (the `run_doctor` tool
1647
1680
  and its probe runtime), `notify/`, and `update/` (Pi's package list, Pi's own
@@ -1659,10 +1692,13 @@ columns, gauge), so the look follows the active theme.
1659
1692
  | `header/identity.ts` | `IdentityInput` and `identityCells`: the agent and chrome cells, each with its `↑` marker, in the ladder that sheds the chrome marker, the chrome version, the chrome line, and finally the agent's marker with the row. |
1660
1693
  | `header/register.ts` | Installs/restores the header and owns its session lifecycle. |
1661
1694
  | `footer/register.ts` | Installs/restores the footer and owns its session lifecycle. |
1662
- | `footer/config.ts` | Bar tuning (`CONFIG`): thresholds, gauge, separators, icons, alert and plan status keys, labels. |
1695
+ | `footer/config.ts` | Bar tuning (`CONFIG`): thresholds, gauge, separators, icons, alert and mode status keys, labels. |
1663
1696
  | `footer/types.ts` | `StatusSnapshot`, `Segment`, `LineSpec`, `UsageRecord`, and footer/TUI shapes. |
1697
+ | `footer/budget.ts` | The session spend guardrail: whether the configured ceiling has been crossed and the one notice that says so — an alert, never a limit. |
1698
+ | `footer/delegation.ts` | The child-run ledger: the pricing inputs of the last delegated runs, versioned so the footer re-prices when a subagent finishes. |
1664
1699
  | `footer/collect/entries.ts` | Session entries → usage records, each resolved to the model in effect. |
1665
1700
  | `footer/collect/snapshot.ts` | Snapshot assembly; memoized pricing scan, context estimate, and session name per branch leaf. |
1701
+ | `footer/collect/label.ts` | The roster label the bar names this session by: one read of the bus's own record, then every label the bus publishes. |
1666
1702
  | `footer/price/types.ts` | `Currency`, `ModelRates`, `PeakWindow`/`PeakSchedule`, `ByteflyConfig`. |
1667
1703
  | `footer/price/config.ts` | The `price` section of `bytefly.json`: schema, defaults, built-in DeepSeek rates/tariff, currency defaults, normalizer, loader. |
1668
1704
  | `footer/price/rates.ts` | Model-key matching: `createRateLookup` splits every key once, splits a key at its first slash (so an OpenRouter model id may contain one), matches exact and wildcard most-specific-first, and remembers the last record's model. |
@@ -1675,9 +1711,11 @@ columns, gauge), so the look follows the active theme.
1675
1711
  | `footer/render/lines.ts` | Snapshot → two `LineSpec`s; status routing. |
1676
1712
  | `footer/render/layout.ts` | Width reduction and right-zone folding. |
1677
1713
  | `footer/render/footer.ts` | `StatusBar` component and footer factory. |
1714
+ | `footer/render/key.ts` | `snapshotKey`: the bar's repaint key, derived from the snapshot's own shape — sorted object keys, a map as sorted entries, a primitive by value — so a field added to `StatusSnapshot` is in the key the day it is added. |
1678
1715
  | `message/config.ts` | Nameplate tuning (`MESSAGE_CONFIG`): the label and the rule. |
1679
1716
  | `message/format.ts` | Pure `nameplate` and `styleUserMessage`: the two-row nameplate (label over a full-width rule) and its placement above the message. |
1680
1717
  | `message/register.ts` | Registers the Markdown transformer (the nameplate). |
1718
+ | `mode/editor.ts` | The main editor with `Shift+Tab` bound to plan mode: `isShiftTab` consumption, the mode-aware border (`success`/`warning`) applied in `render`, the `app.thinking.cycle` unbind/restore, and `attachModeKey(pi, ctx)` with its idempotent cleanup. Loads on the first TUI session through a dynamic import. |
1681
1719
  | `notice/config.ts` | Notice tuning (`NOTICE_CONFIG`): source/title/detail roles, the severity glyphs and roles, the shared rail roles, and the item cap. |
1682
1720
  | `notice/component.ts` | Pure `noticeLines` and `NoticeComponent`: the headline at the margin, the rail-led severity rows, sanitized and width-clipped. |
1683
1721
  | `notice/register.ts` | Registers the message renderer for the shared notice type and hands it the rail source. |
@@ -1697,7 +1735,7 @@ columns, gauge), so the look follows the active theme.
1697
1735
  | `panel/join.ts` | The item/report union with the unambiguous-name naming rule — a row is `worktree`, and a directory claims a report only when one resource uses that name — plus unique row keys, groups, ordering, and the skills model that labels rows from `SKILL.md`. |
1698
1736
  | `panel/filter.ts` | The two filters as predicates and chips: a text query over a row's name, path, and source, the attention switch, and what the tab row says is on. |
1699
1737
  | `panel/format.ts` | Non-TUI text: the resource headline and grouped listing with a per-row verdict, the header's attention segments, the one `rowBadge` the screen and the listing share, the section segments and their order, the Settings counts/headline, and the flat Settings listing. |
1700
- | `panel/settings.ts` | The curated Settings catalogue: the twelve descriptors in screen order, their four groups, the value labels (`Off`, `¥5.00`) and display origins, value cycling, stored-value mapping, validation, plus the per-section readers that resolve a row's leaf over the global and project documents. |
1738
+ | `panel/settings.ts` | The curated Settings catalogue: the fifteen descriptors in screen order, their five groups, the value labels (`Off`, `¥5.00`) and display origins, value cycling, stored-value mapping, validation, plus the per-section readers that resolve a row's leaf over the global and project documents. |
1701
1739
  | `panel/settings-runtime.ts` | The Settings runtime: snapshot cache, scope guards, app setters, per-leaf `bytefly.json` writes, dirty state, and change listeners. |
1702
1740
  | `panel/themes.ts` | Which themes the Theme row offers: the host's resolved list narrowed to the palettes under this package's `themes/`, identified by directory rather than name. |
1703
1741
  | `panel/prices.ts` | The `/bytefly prices` report: the unpriced models by key, the records behind each, and the one line saying where the keys go. It quotes no price — a rate is the user's to state. |
@@ -1737,74 +1775,73 @@ columns, gauge), so the look follows the active theme.
1737
1775
  | `update/check.ts` | Pi's package list through `DefaultPackageManager`, plus the registry `GET` that names this package's version for the marker, arrow, and release URL. |
1738
1776
  | `update/version.ts` | Pure semver parsing and `isNewerVersion`, including prerelease precedence. |
1739
1777
  | `update/state.ts` | `UpdateSource` and `createUpdateState` (one known version per subject per load), `paintLatest` (record and repaint the header only on a change), plus `UpdateOutcome`, the four answers an explicit re-check can give. |
1740
- | `update/cache.ts` | The project-scoped snapshot: `<agentDir>/bytefly/update.json`, the cwd/package key, the 24 h window in `isFresh`, a validated read that degrades to a miss, and the atomic write. |
1778
+ | `update/cache.ts` | The project-scoped snapshot: `<agent-dir>/bytefly/update.json`, the cwd/package key, the 24 h window in `isFresh`, a validated read that degrades to a miss, and the atomic write. |
1741
1779
  | `update/format.ts` | `updateMarker`, `updateNotice`, `releaseUrl`, `updateOutcomeText`, and `piReleaseNotice`, plus the shared command/link rows: the header's `↑0.12.0`, the multi-package notice, the line `/bytefly update` prints, and the notice that replaces Pi's `Update Available` box. |
1742
1780
  | `update/register.ts` | The package-update lifecycle: suppression, the cache-first session-start read, the async package-manager call, this package's version lookup, the `PI_OFFLINE` and `tui` gates, the once-per-load announcement, the shutdown generation guard, and the `recheck()` handle `/bytefly update` calls. |
1743
1781
  | `update/release.ts` | Pi's own release lifecycle: install the notification patch in `tui` mode, record the release's version for the agent-line marker, post the notice once per load while the session is live, and restore the method on shutdown. |
1744
1782
 
1745
1783
  ## Testing
1746
1784
 
1747
- `extensions/bytefly/` covers the install/restore/toggle lifecycle
1748
- (`extension`), the header's rendering, width shedding, and live theme
1749
- (`header/component`), its quiet-startup lifecycle (`header/register`),
1750
- the formatters (`footer/render/format`), the status-chip transforms
1751
- (`footer/render/status`), entry
1752
- reduction and model inheritance (`footer/collect/entries`), snapshot assembly
1753
- and its memoization (`footer/collect/snapshot`), rate matching
1754
- (`footer/price/rates`), window parsing
1755
- and peak evaluation in two zones (`footer/price/schedule`), the pricing formula,
1756
- the off-peak multiplier, the built-in DeepSeek table with no config file, and the
1757
- fallback (`footer/price/price`), the config schema, its clamping, the built-in
1758
- defaults and their replacement, and the two file layers
1759
- (`footer/price/config`), zone/priority
1760
- assignment
1761
- (`footer/render/lines`), the reduction, separator collapse, and right-zone
1762
- folding across a width sweep from 6 to 120 plus wide characters
1763
- (`footer/render/layout`), exact
1764
- golden output at fixed widths (`footer/render/render`), and the install/skip/
1765
- restore/toggle lifecycle plus a config-driven price (`extension`). The turn
1766
- separator is covered by its pure formatting (`separator/format`, including the
1767
- receipt's width ladder), its register/append/render/disable lifecycle
1768
- (`separator/register`), and the run stats it records. The picker helpers are
1769
- covered by the pure helpers (`picker/resources`), the toggle
1770
- transforms (`picker/toggle`), discovery with an injected factory
1771
- (`picker/discovery`), and the runtime state machine (`picker/runtime`). The
1772
- unified screen is covered by its pure model merge (`panel/join`), its two
1773
- filters and their chips (`panel/filter`), its text formatter (`panel/format`),
1774
- the settings catalogue and its cycling (`panel/settings`), the scope-aware
1775
- settings runtime (`panel/settings-runtime`, including a nested leaf write that
1776
- keeps its siblings), the skill metadata join and loader (`panel/skills`, on real
1777
- `SKILL.md` files), the key table whose hints and help it holds to one source
1778
- (`panel/keys`), the frame and the one height all three sections agree on
1779
- (`panel/layout`), the row grammar and the focused-row block beside and under the
1780
- list (`panel/views`), the two-pane split and its `… N more lines` clip
1781
- (`panel/panes`), the editing key map (`tui/editing`), the component's frames,
1782
- keys, sections, settings, and probe lifecycle (`panel/component`,
1783
- `panel/component-input`, `panel/component-sections`,
1784
- `panel/component-settings`, `panel/component-probing`), and the command through
1785
- the bytefly factory (`panel/commands`) — eighteen suites in all.
1786
- The doctor surface is covered by its pure report
1787
- formatting (`doctor/format`), its config loader (`doctor/config`), and
1788
- registration, the proactive notice, the session cache, and `run_doctor`
1789
- through the factory (`doctor/register`). The notify capability is covered by its section loader
1790
- (`notify/config`), the pure text builders (`notify/format`), method selection and
1791
- delivery over recording seams (`notify/dispatch`), and its four triggers,
1792
- reload, and shutdown through `registerNotify` (`notify/register`). The update
1793
- surface is covered by the version-ordering table (`update/version`), Pi's package
1794
- list and the injected registry read (`update/check`), the host shim and its
1795
- idempotent restore (`update/suppress`), the per-load store (`update/state`), the
1796
- marker, multi-package notice, release URL, and recheck wording (`update/format`),
1797
- the project-scoped snapshot and its window (`update/cache`), and its lifecycle —
1798
- a fresh matching entry reused without a package-manager call, a stale one re-read
1799
- and rewritten, silent on failure or an empty list, cache-only offline, skipped
1800
- outside `tui`, an explicit `recheck()` that ignores the window, and immunity to a
1801
- reply that lands after shutdown (`update/register`).
1802
- The user-message nameplate is covered by its pure transform
1803
- (`message/format`, including a render through pi-tui's real `Markdown`) and its
1804
- registration (`message/register`), plus the factory check in `extension`.
1805
- The notice surface is covered by its pure rows (`notice/component`), its
1806
- renderer registration (`notice/register`), and the shared contract
1807
- (`lib/notice`), plus the factory check in `extension`.
1808
- The tool-block frame is covered by its pure `toolBlockLines` (`block/component`)
1809
- and its generic fallback (`block/fallback`), plus the resolver's shell override,
1810
- self-shell carve-out, and fallback framing (`block/register`).
1785
+ Every suite sits beside the code it covers, so a slash-prefixed stem below
1786
+ resolves to `extensions/bytefly/<stem>.test.ts`.
1787
+
1788
+ `extension` covers the registration, the interactive surfaces, and the command
1789
+ wiring through the factory.
1790
+
1791
+ `chrome` covers the chrome that frames a session: the config and its pad store;
1792
+ the header's frames, width shedding, identity ladder, update marker, live theme,
1793
+ quiet-startup lifecycle, and terminal-height shapes; the transcript notice's rows
1794
+ and registration; the turn separator's formatting (including the receipt's width
1795
+ ladder), component, and register/append/render/disable lifecycle with the run
1796
+ stats it records; and the user-message nameplate's transform — through pi-tui's
1797
+ real `Markdown` — and its registration.
1798
+
1799
+ `footer/footer` covers the bar end to end: the price config, its schema, clamping,
1800
+ and defaults; rate matching, window parsing and peak evaluation in two zones, the
1801
+ pricing formula with its off-peak multiplier, the built-in DeepSeek table and the
1802
+ fallback, the usage records, and the delegation ledger; the snapshot reader and its
1803
+ memo, the unpriced notice, and the budget; the formatters, the status-chip
1804
+ transforms, the segment builders, line composition, and the repaint key that
1805
+ spares an unchanged frame a rebuild; the reduction, separator collapse, and
1806
+ right-zone folding across a width sweep from 6 to 120 plus wide characters; and the
1807
+ component with its install/skip/restore/toggle lifecycle and a config-driven price.
1808
+
1809
+ `block/block` covers the tool-block frame: `toolBlockLines`, the call-headline
1810
+ rewrites, `ToolBlock`'s memo, the generic and call fallbacks, and the resolver's
1811
+ shell override and self-shell carve-out.
1812
+
1813
+ `picker/picker` covers discovery with an injected factory, the resolver and its
1814
+ entries, the pattern helpers, and the runtime's toggling, scope, and project
1815
+ override across the four list operations.
1816
+
1817
+ `panel/…` covers the control centre, one suite per capability: `panel/model` (the
1818
+ two panes' merged model and the text formatter), `panel/filter` and its chips, the settings
1819
+ catalogue and its cycling (`panel/settings`), the scope-aware runtime
1820
+ (`panel/settings-runtime`, including a nested leaf write that keeps its siblings),
1821
+ the skill metadata join and loader (`panel/skills`, on real `SKILL.md` files), the
1822
+ key table whose hints and help it holds to one source (`panel/keys`), the frame and
1823
+ the one height all three sections agree on (`panel/layout`), the row grammar and the
1824
+ focused-row block beside and under the list (`panel/views`), the two-pane split and
1825
+ its `… N more lines` clip (`panel/panes`), the editing key map (`panel/editing`),
1826
+ the update section's view (`panel/update`), the discovery probe (`panel/probe`), the
1827
+ component's frames, keys, sections, settings, and probe lifecycle
1828
+ (`panel/component`, `panel/component-input`, `panel/component-sections`,
1829
+ `panel/component-settings`, `panel/component-probing`), and the command through the
1830
+ bytefly factory (`panel/commands`) — eighteen suites in all.
1831
+
1832
+ `doctor/doctor` covers the report counts and text, the config loader, the notify
1833
+ seam, the published search provider, the proactive probe, the session state,
1834
+ `run_doctor`, and the tool wiring.
1835
+
1836
+ `notify/notify` covers its config loader, method selection, delivery over recording
1837
+ seams, the text builders, and its four triggers plus the handle.
1838
+
1839
+ `update/update` covers the cache path and key, the cache read and write with its
1840
+ window, the check and the injected registry read, the version ordering, the
1841
+ formatters and the release notice, the prototype suppression with its idempotent
1842
+ restore, the per-load state, the lifecycle — a fresh matching entry reused without
1843
+ a package-manager call, a stale one re-read and rewritten, silent on failure or an
1844
+ empty list, cache-only offline, skipped outside `tui`, a `recheck()` that ignores
1845
+ the window, and a reply that lands after shutdown — and the `update` action.
1846
+
1847
+ `working/working` covers the elapsed format, the counter, and the timer registration.