@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
@@ -1,20 +1,20 @@
1
1
  /**
2
2
  * Pure text for the live working line.
3
3
  *
4
- * Before the first tool starts it is a plain count (`Working… 42s`). After that
5
- * it names the last top-level tool and how many this run has started, then the
6
- * elapsed time (`edit · 3 tools · 42s`). Whole seconds, because the tick
7
- * advances once a second; negative and non-finite input reads as `0s`.
4
+ * A plain count while the run has started nothing (`working · 42s`), then how
5
+ * many top-level tools this run has started and the elapsed time
6
+ * (`working · 3 tools · 42s`). The line no longer names the tool: it is one word
7
+ * a reader can skim, and the transcript already draws each call as its own block.
8
+ * Whole seconds, because the tick advances once a second; negative and
9
+ * non-finite input reads as `0s`.
8
10
  */
9
11
 
10
- import { sanitizeLine, formatToolCount } from "../../../lib/format.ts";
12
+ import { formatToolCount } from "../../../lib/format.ts";
11
13
  import { SEPARATORS } from "../../../lib/tui/glyphs.ts";
12
14
  import { WORKING_CONFIG } from "./config.ts";
13
15
 
14
16
  /** What the current run has done so far, for the live line. */
15
17
  export interface WorkingRun {
16
- /** The last top-level tool started, or `null` before the first one. */
17
- readonly tool: string | null;
18
18
  /** Top-level tool calls started in this run. */
19
19
  readonly tools: number;
20
20
  }
@@ -30,13 +30,10 @@ export function formatElapsed(ms: number): string {
30
30
  return `${seconds}s`;
31
31
  }
32
32
 
33
- /** The working-row message: `Working… 4s` until a tool starts, then the run line. */
33
+ /** The working line's message: `working · 4s`, then `working · 3 tools · 4s`. */
34
34
  export function workingCounter(ms: number, run?: WorkingRun): string {
35
35
  const elapsed = formatElapsed(ms);
36
36
  const tools = run?.tools ?? 0;
37
- const tool = run?.tool ? sanitizeLine(run.tool) : "";
38
- if (tools > 0) {
39
- return [tool, formatToolCount(tools), elapsed].filter((part) => part !== "").join(SEPARATORS.item);
40
- }
41
- return `${WORKING_CONFIG.working}${WORKING_CONFIG.ellipsis} ${elapsed}`;
37
+ const count = tools > 0 ? formatToolCount(tools) : "";
38
+ return [WORKING_CONFIG.working, count, elapsed].filter((part) => part !== "").join(SEPARATORS.item);
42
39
  }
@@ -1,31 +1,30 @@
1
1
  /**
2
2
  * bytefly's live working line.
3
3
  *
4
- * Pi's working row shows a static `Working`, so a long run reads the same as a
5
- * short one. This surface advances the message once a second while the row is
6
- * up, and hands it back to Pi when the row comes down. Once a top-level tool
7
- * starts, the message also names that tool and how many this run has started,
8
- * turning the row into a live run line (`edit · 3 tools · 12s`).
4
+ * Pi's working indicator shows a static `Working`, so a long run reads the same as
5
+ * a short one. This surface advances the message once a second while the indicator
6
+ * is up, and hands it back to Pi when it comes down. Once a top-level tool starts,
7
+ * the message also counts this run's tools, turning the line on the composer's top
8
+ * border into `working · 3 tools · 12s`.
9
9
  *
10
- * The timer is tied to the row's visibility. The host shows the working
11
- * indicator in its own `turn_start` handler and clears it at `agent_end`
12
- * (`interactive-mode.js`), so the tick starts at `turn_start` and stops at
13
- * `agent_end` — the two moments the row appears and disappears. Turns inside
14
- * one run do not restart it: the host leaves the same indicator up across
15
- * turns, and `start` is a no-op while it is counting.
10
+ * The timer is tied to the indicator's visibility. The host shows it in its own
11
+ * `turn_start` handler and clears it at `agent_end` (`interactive-mode.js`), so the
12
+ * tick starts at `turn_start` and stops at `agent_end` — the two moments it appears
13
+ * and disappears. Turns inside one run do not restart it: the host leaves the same
14
+ * indicator up across turns, and `start` is a no-op while it is counting.
16
15
  *
17
16
  * A nested tool call (one carrying `parentToolCallId`, such as a codemode
18
- * script's inner call) is not counted and does not take over the line, so the
19
- * number matches the tool blocks the transcript shows. The tool events repaint
20
- * the row immediately; the one-second tick is still the only timer.
17
+ * script's inner call) is not counted, so the number matches the tool blocks the
18
+ * transcript shows. The tool events repaint the line immediately; the one-second
19
+ * tick is still the only timer.
21
20
  *
22
- * Compaction and auto-retry also replace the row, but both run outside this
21
+ * Compaction and auto-retry also replace the indicator, but both run outside this
23
22
  * window (`auto_retry_start` follows `agent_end`; the before-settle compaction
24
23
  * runs after it), and neither is an extension event, so there is nothing to
25
24
  * hook and nothing to undo.
26
25
  *
27
- * The handle is global (`setWorkingMessage` drives the single working row), so
28
- * the tick is cancelled and the message reset on stop and, belt and braces, on
26
+ * The handle is global (`setWorkingMessage` drives the single indicator), so the
27
+ * tick is cancelled and the message reset on stop and, belt and braces, on
29
28
  * `session_shutdown` — the factory itself only registers.
30
29
  */
31
30
 
@@ -50,14 +49,13 @@ export function registerWorkingTimer(pi: ExtensionAPI, deps: WorkingDeps = {}):
50
49
  let startedAt = 0;
51
50
  // A boolean, not `startedAt === 0`: an injected clock is allowed to start at 0.
52
51
  let active = false;
53
- // The last top-level tool and how many this run has started. Reset with the run.
54
- let tool: string | null = null;
52
+ // How many top-level tools this run has started. Reset with the run.
55
53
  let tools = 0;
56
54
 
57
- /** The current row text, from the run state and the elapsed time. */
58
- const message = (): string => workingCounter(now() - startedAt, { tool, tools });
55
+ /** The current line's text, from the run state and the elapsed time. */
56
+ const message = (): string => workingCounter(now() - startedAt, { tools });
59
57
 
60
- /** Repaint an active row now, so a tool start does not wait for the next tick. */
58
+ /** Repaint an active line now, so a tool start does not wait for the next tick. */
61
59
  const refresh = (ctx: ExtensionContext): void => {
62
60
  if (active) ctx.ui.setWorkingMessage(message());
63
61
  };
@@ -68,7 +66,6 @@ export function registerWorkingTimer(pi: ExtensionAPI, deps: WorkingDeps = {}):
68
66
  cancel = undefined;
69
67
  if (!active) return;
70
68
  active = false;
71
- tool = null;
72
69
  tools = 0;
73
70
  ctx.ui.setWorkingMessage(undefined);
74
71
  };
@@ -78,7 +75,6 @@ export function registerWorkingTimer(pi: ExtensionAPI, deps: WorkingDeps = {}):
78
75
  if (active) return;
79
76
  startedAt = now();
80
77
  active = true;
81
- tool = null;
82
78
  tools = 0;
83
79
  const tick = (): void => {
84
80
  ctx.ui.setWorkingMessage(message());
@@ -88,17 +84,16 @@ export function registerWorkingTimer(pi: ExtensionAPI, deps: WorkingDeps = {}):
88
84
  };
89
85
 
90
86
  // The host shows the working indicator on its own `turn_start` handler, so
91
- // this is the first frame the row is up.
87
+ // this is the first frame the indicator is up.
92
88
  pi.on("turn_start", (_event, ctx) => {
93
89
  if (ctx.mode !== "tui") return;
94
90
  start(ctx);
95
91
  });
96
92
 
97
93
  // A nested tool is an implementation detail of its parent run, not a
98
- // transcript block, so it neither counts nor takes over the line.
94
+ // transcript block, so it does not count toward the line.
99
95
  pi.on("tool_execution_start", (event, ctx) => {
100
96
  if (ctx.mode !== "tui" || !active || event.parentToolCallId) return;
101
- tool = event.toolName;
102
97
  tools += 1;
103
98
  refresh(ctx);
104
99
  });
@@ -12,10 +12,8 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-c
12
12
  import { resolveEffectiveCwd } from "../../lib/env.ts";
13
13
  import { planModeRefusal } from "../../lib/plan-entry.ts";
14
14
  import { isPlanModeEnabled } from "../../lib/plan-state.ts";
15
- import { withRailsSuppressed } from "../../lib/rails.ts";
16
15
  import { formatCheckReport, formatCheckSummary } from "./format.ts";
17
16
  import { CheckError, type CheckRuntime } from "./runtime.ts";
18
- import { CheckResultComponent, CheckRunComponent } from "./tui.ts";
19
17
  import type { CheckResult, CheckTarget } from "./types.ts";
20
18
 
21
19
  /** The accepted targets and their completion descriptions. */
@@ -42,6 +40,8 @@ function runLabel(commands: readonly string[]): string {
42
40
 
43
41
  /** The TUI loop: run, then list/detail, with rerun. */
44
42
  async function runTui(runtime: CheckRuntime, ctx: ExtensionCommandContext, target: CheckTarget): Promise<void> {
43
+ // The screens are the TUI's: a text run above and registration load neither.
44
+ const { CheckResultComponent, CheckRunComponent } = await import("./view/check-screens.ts");
45
45
  while (true) {
46
46
  let active: ReturnType<CheckRuntime["start"]>;
47
47
  try {
@@ -123,6 +123,7 @@ export function registerCheckCommand(pi: ExtensionAPI, runtime: CheckRuntime): v
123
123
  return;
124
124
  }
125
125
 
126
+ const { withRailsSuppressed } = await import("../../lib/rails.ts");
126
127
  await withRailsSuppressed(pi, () => runTui(runtime, ctx, target));
127
128
  },
128
129
  });
@@ -16,6 +16,7 @@ import type { Component } from "@earendil-works/pi-tui";
16
16
  import { sanitizeLine } from "../../lib/format.ts";
17
17
  import { clip } from "../../lib/tui/columns.ts";
18
18
  import { expandHint, GLYPHS, SEPARATORS } from "../../lib/tui/glyphs.ts";
19
+ import { RenderCache } from "../../lib/tui/render-cache.ts";
19
20
  import { listRow } from "../../lib/tui/rows.ts";
20
21
  import { paint, type TuiRole } from "../../lib/tui/tokens.ts";
21
22
  import { earlierHint } from "../../lib/tui/tool.ts";
@@ -134,6 +135,7 @@ export interface CheckResultInput {
134
135
  export class CheckResultBlock implements Component {
135
136
  private input: CheckResultInput;
136
137
  private theme: Theme;
138
+ private readonly cache = new RenderCache();
137
139
 
138
140
  constructor(input: CheckResultInput, theme: Theme) {
139
141
  this.input = input;
@@ -144,12 +146,19 @@ export class CheckResultBlock implements Component {
144
146
  setInput(input: CheckResultInput, theme: Theme): void {
145
147
  this.input = input;
146
148
  this.theme = theme;
149
+ this.cache.invalidate();
147
150
  }
148
151
 
149
- invalidate(): void {}
152
+ invalidate(): void {
153
+ this.cache.invalidate();
154
+ }
150
155
 
151
156
  render(width: number): string[] {
152
157
  const w = Math.max(1, width);
158
+ return this.cache.lines(w, undefined, () => this.build(w));
159
+ }
160
+
161
+ private build(w: number): string[] {
153
162
  const theme = this.theme;
154
163
  const { details, expanded } = this.input;
155
164
  const role = statusRole(details.status);
@@ -11,19 +11,19 @@
11
11
 
12
12
  import type { Theme } from "@earendil-works/pi-coding-agent";
13
13
  import { type Component, type TuiMouseEvent, type TuiMouseEventResult } from "@earendil-works/pi-tui";
14
- import { formatDuration, sanitizeLine } from "../../lib/format.ts";
15
- import { dockListFrame, screenHint, type ScreenHint, type ViewportRowsSource } from "../../lib/tui/chrome.ts";
16
- import { clip } from "../../lib/tui/columns.ts";
17
- import { fitRows, formatRange, isCloseKey, wheelDelta } from "../../lib/tui/cursor.ts";
18
- import { DockListScreen, type DetailContents } from "../../lib/tui/dock-screen.ts";
19
- import { SEPARATORS } from "../../lib/tui/glyphs.ts";
20
- import { LinePane } from "../../lib/tui/line-pane.ts";
21
- import { emptyState, listRow } from "../../lib/tui/rows.ts";
22
- import { titleRule } from "../../lib/tui/rules.ts";
23
- import { paint } from "../../lib/tui/tokens.ts";
24
- import { formatFailureDetail, formatFailureLocation, formatResultSummary } from "./format.ts";
25
- import { readSourceExcerpt } from "./source.ts";
26
- import type { CheckResult, Failure } from "./types.ts";
14
+ import { formatDuration, sanitizeLine } from "../../../lib/format.ts";
15
+ import { dockListFrame, screenHint, type ScreenHint, type ViewportRowsSource } from "../../../lib/tui/chrome.ts";
16
+ import { clip } from "../../../lib/tui/columns.ts";
17
+ import { fitRows, formatRange, isCloseKey, wheelDelta } from "../../../lib/tui/cursor.ts";
18
+ import { DockListScreen, type DetailContents } from "../../../lib/tui/dock-screen.ts";
19
+ import { SEPARATORS } from "../../../lib/tui/glyphs.ts";
20
+ import { LinePane } from "../../../lib/tui/line-pane.ts";
21
+ import { emptyState, listRow } from "../../../lib/tui/rows.ts";
22
+ import { titleRule } from "../../../lib/tui/rules.ts";
23
+ import { paint } from "../../../lib/tui/tokens.ts";
24
+ import { formatFailureDetail, formatFailureLocation, formatResultSummary } from "../format.ts";
25
+ import { readSourceExcerpt } from "../source.ts";
26
+ import type { CheckResult, Failure } from "../types.ts";
27
27
 
28
28
  const RUN_DEFAULT_ROWS = 20;
29
29
  const RUN_CHROME_ROWS = 5;
@@ -166,12 +166,7 @@ export class CheckResultComponent extends DockListScreen<Failure> {
166
166
  protected listHints(rows: readonly Failure[]): ScreenHint[] {
167
167
  // An empty list has nothing to open, so it offers only the two ways out.
168
168
  if (rows.length === 0) return ["Esc close", { text: "r rerun", color: "accent" }];
169
- return [
170
- "Esc close",
171
- { text: "Enter detail", color: "accent" },
172
- { text: "r rerun", color: "warning" },
173
- "↑/↓ select",
174
- ];
169
+ return ["Esc close", { text: "Enter detail", color: "accent" }, { text: "r rerun", color: "warn" }, "↑/↓ select"];
175
170
  }
176
171
 
177
172
  protected detailHints(): ScreenHint[] {
@@ -10,12 +10,10 @@
10
10
 
11
11
  import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
12
12
  import { formatTokens } from "../../lib/format.ts";
13
- import { withRailsSuppressed } from "../../lib/rails.ts";
14
13
  import { messageOf } from "../../lib/errors.ts";
15
14
  import { collectInputs } from "./collect.ts";
16
15
  import { formatReport } from "./format.ts";
17
16
  import { buildReport } from "./report.ts";
18
- import { ContextComponent } from "./tui.ts";
19
17
 
20
18
  /** The command's argument surface, shared by the description and the usage warning. */
21
19
  const CONTEXT_USAGE = "/context [compact [instructions]]";
@@ -37,6 +35,8 @@ async function openReport(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promi
37
35
  }
38
36
 
39
37
  // The dock screen owns the editor slot; hide the rails for its lifetime.
38
+ const { withRailsSuppressed } = await import("../../lib/rails.ts");
39
+ const { ContextComponent } = await import("./view/context-report.ts");
40
40
  await withRailsSuppressed(pi, () =>
41
41
  ctx.ui.custom<void>((tui, theme, _keybindings, done) => {
42
42
  return new ContextComponent({
@@ -14,17 +14,17 @@
14
14
 
15
15
  import type { CompactionResult, Theme } from "@earendil-works/pi-coding-agent";
16
16
  import { truncateToWidth, visibleWidth, type Component } from "@earendil-works/pi-tui";
17
- import { messageOf } from "../../lib/errors.ts";
18
- import { formatTokens } from "../../lib/format.ts";
19
- import { screenFooter, screenHint, type ViewportRowsSource } from "../../lib/tui/chrome.ts";
20
- import { ConfirmState } from "../../lib/tui/confirm.ts";
21
- import { clampScroll, fitRows, formatRange, isCloseKey, navigateScroll } from "../../lib/tui/cursor.ts";
22
- import { gaugeBar, gaugeRole } from "../../lib/tui/gauge.ts";
23
- import { BODY_INDENT } from "../../lib/tui/indent.ts";
24
- import { titleRule } from "../../lib/tui/rules.ts";
25
- import { paint, type TuiRole } from "../../lib/tui/tokens.ts";
26
- import { formatBudget, formatCompaction, formatDrift } from "./format.ts";
27
- import type { ContextBucket, ContextReport } from "./types.ts";
17
+ import { messageOf } from "../../../lib/errors.ts";
18
+ import { formatTokens } from "../../../lib/format.ts";
19
+ import { screenFooter, screenHint, type ViewportRowsSource } from "../../../lib/tui/chrome.ts";
20
+ import { ConfirmState } from "../../../lib/tui/confirm.ts";
21
+ import { clampScroll, fitRows, formatRange, isCloseKey, navigateScroll } from "../../../lib/tui/cursor.ts";
22
+ import { gaugeBar, gaugeRole } from "../../../lib/tui/gauge.ts";
23
+ import { BODY_INDENT } from "../../../lib/tui/indent.ts";
24
+ import { titleRule } from "../../../lib/tui/rules.ts";
25
+ import { paint, type TuiRole } from "../../../lib/tui/tokens.ts";
26
+ import { formatBudget, formatCompaction, formatDrift } from "../format.ts";
27
+ import type { ContextBucket, ContextReport } from "../types.ts";
28
28
 
29
29
  /** Fixed rows around the scrollable body: title, gauge, budget, blank, blank, footer, closing rule. */
30
30
  const CHROME_ROWS = 7;
@@ -71,6 +71,34 @@ function shareCell(share: number): string {
71
71
  return `${share.toFixed(1)}%`.padStart(6);
72
72
  }
73
73
 
74
+ /**
75
+ * The hue that identifies a context category, keyed by the bucket ids
76
+ * `report.ts` builds.
77
+ *
78
+ * Categories are what the screen tells apart at a glance; a bucket with no
79
+ * entry here — injected context and its children, summaries, shell output, a
80
+ * type this build has never seen — reads neutral, so the catch-all rows stay
81
+ * quiet and the coloured ones mean something. The same table answers for the
82
+ * largest-message list, so a tool result is one colour in both sections.
83
+ */
84
+ const CATEGORY_HUES: Readonly<Record<string, TuiRole>> = {
85
+ system: "hue1",
86
+ tools: "hue2",
87
+ user: "hue3",
88
+ assistant: "hue4",
89
+ toolResults: "hue5",
90
+ };
91
+
92
+ /** The hue for a bucket id, or `undefined` when the category is neutral. */
93
+ export function hueOf(id: string): TuiRole | undefined {
94
+ return CATEGORY_HUES[id];
95
+ }
96
+
97
+ /** The hue for a message role word; `tool result` is the bucket's own category. */
98
+ export function hueForRole(role: string): TuiRole | undefined {
99
+ return CATEGORY_HUES[role === "tool result" ? "toolResults" : role];
100
+ }
101
+
74
102
  /** Pad a label to its column with spaces, measured in terminal columns. */
75
103
  function padCell(text: string, width: number): string {
76
104
  const clipped = truncateToWidth(text, width, "…");
@@ -151,7 +179,7 @@ export class ContextComponent implements Component {
151
179
  private compositionLine(width: number): string {
152
180
  const label = paint(this.theme, "label", "Composition");
153
181
  const total = paint(this.theme, "meta", `${formatTokens(this.report.estimate)} estimate`);
154
- const full = ` ${label} · ${total}`;
182
+ const full = ` ${label}${paint(this.theme, "faint", " · ")}${total}`;
155
183
  return visibleWidth(full) <= width ? full : truncateToWidth(` ${label}`, width);
156
184
  }
157
185
 
@@ -161,12 +189,15 @@ export class ContextComponent implements Component {
161
189
  const labelWidth = bucket.depth > 0 ? 20 : 22;
162
190
  const label = padCell(bucket.label, labelWidth);
163
191
  const tokens = formatTokens(bucket.tokens).padStart(6);
164
- const share = shareCell(bucket.share);
165
- const note = bucket.note ? ` ${bucket.note}` : "";
192
+ // The row's hue carries the category; the label, the count, and the printed
193
+ // share still say everything the colour repeats.
194
+ const hue = hueOf(bucket.id) ?? "meta";
195
+ const share = paint(this.theme, hue, shareCell(bucket.share));
196
+ const note = bucket.note ? paint(this.theme, "faint", ` ${bucket.note}`) : "";
166
197
  const bar = gaugeBar(this.theme, {
167
198
  percent: bucket.share,
168
199
  blocks: BUCKET_BLOCKS,
169
- role: "meta",
200
+ role: hue,
170
201
  emptyRole: "faint",
171
202
  });
172
203
  const head = `${indent}${paint(this.theme, "label", label)}${paint(this.theme, "meta", tokens)}`;
@@ -204,7 +235,8 @@ export class ContextComponent implements Component {
204
235
  for (const block of report.largest) {
205
236
  const tokens = formatTokens(block.tokens).padStart(6);
206
237
  const role = padCell(block.role, 12);
207
- const candidate = ` ${paint(this.theme, "meta", tokens)} ${paint(this.theme, "label", role)}${block.label}`;
238
+ const hue = hueForRole(block.role) ?? "meta";
239
+ const candidate = ` ${paint(this.theme, "meta", tokens)} ${paint(this.theme, hue, role)}${paint(this.theme, "label", block.label)}`;
208
240
  lines.push(truncateToWidth(candidate, width));
209
241
  }
210
242
  }
@@ -221,9 +253,9 @@ export class ContextComponent implements Component {
221
253
  width,
222
254
  [
223
255
  { text: "c compact", color: "accent" },
224
- { text: "r refresh", color: "dim" },
225
- { text: "↑↓ scroll", color: "dim" },
226
- { text: "Esc close", color: "dim" },
256
+ { text: "r refresh", color: "faint" },
257
+ { text: "↑↓ scroll", color: "faint" },
258
+ { text: "Esc close", color: "faint" },
227
259
  ],
228
260
  BODY_INDENT,
229
261
  );
@@ -2,16 +2,14 @@
2
2
  * Configuration for the browse extension.
3
3
  *
4
4
  * The `file-browse` section of the shared `bytefly.json` (global then project,
5
- * project wins). Everything
6
- * is validated and clamped so a typo cannot produce a nonsensical server.
5
+ * project wins). Everything is validated and clamped so a typo cannot produce a
6
+ * nonsensical page. The port is not here: the session serves every page on one, and
7
+ * `serve.port` is where that is pinned.
7
8
  */
8
9
 
9
- import { randomInt } from "node:crypto";
10
10
  import { asBoolean, clampInteger, loadConfigSection } from "../../../lib/config.ts";
11
11
 
12
12
  export interface ServeConfig {
13
- /** TCP port on 127.0.0.1; `0` picks one random port fixed for the session, a positive value pins it. */
14
- port: number;
15
13
  /** Open the default browser when the server starts. */
16
14
  autoOpen: boolean;
17
15
  /** Files larger than this are not rendered inline. */
@@ -43,7 +41,6 @@ export interface ServeConfig {
43
41
  }
44
42
 
45
43
  export const DEFAULT_CONFIG: ServeConfig = {
46
- port: 0,
47
44
  autoOpen: true,
48
45
  maxFileBytes: 1024 * 1024,
49
46
  maxThumbBytes: 5 * 1024 * 1024,
@@ -60,12 +57,6 @@ export const DEFAULT_CONFIG: ServeConfig = {
60
57
  liveIntervalMs: 5000,
61
58
  };
62
59
 
63
- /** Base of the per-session port range. */
64
- export const PORT_BASE = 4780;
65
-
66
- /** Number of ports in the per-session range. */
67
- export const PORT_RANGE = 1000;
68
-
69
60
  /** Fastest live-status cadence a page may be given: short enough to watch, and a
70
61
  * floor so a page cannot outrun the git reads behind each frame. */
71
62
  export const LIVE_INTERVAL_MIN = 250;
@@ -76,7 +67,7 @@ export const LIVE_INTERVAL_MAX = 60_000;
76
67
  /**
77
68
  * How long the server keeps listening once the last page has gone. A reload or a
78
69
  * navigation detaches the stream and re-attaches within a moment, so the grace has
79
- * to outlast one; a closed window never re-attaches, and the port is released
70
+ * to outlast one; a closed window never re-attaches, and the prefix is given back
80
71
  * instead of listening for nobody until the session ends.
81
72
  */
82
73
  export const IDLE_STOP_MS = 10_000;
@@ -90,11 +81,6 @@ export const IDLE_STOP_MS = 10_000;
90
81
  */
91
82
  export const LIVE_MAX_CLIENTS = 8;
92
83
 
93
- /** A random port in the per-session range `PORT_BASE`–`PORT_BASE + PORT_RANGE - 1`. */
94
- export function randomPort(): number {
95
- return PORT_BASE + randomInt(PORT_RANGE);
96
- }
97
-
98
84
  /** Validate and clamp a raw config object over the defaults. */
99
85
  export function normalizeConfig(
100
86
  raw: Record<string, unknown> | undefined,
@@ -102,7 +88,6 @@ export function normalizeConfig(
102
88
  ): ServeConfig {
103
89
  if (!raw) return base;
104
90
  return {
105
- port: clampInteger(raw.port, base.port, 0, 65535),
106
91
  autoOpen: asBoolean(raw.autoOpen, base.autoOpen),
107
92
  maxFileBytes: clampInteger(raw.maxFileBytes, base.maxFileBytes, 0, 1024 * 1024 * 1024),
108
93
  maxThumbBytes: clampInteger(raw.maxThumbBytes, base.maxThumbBytes, 0, 1024 * 1024 * 1024),