one-code-extension 0.1.3 → 0.3.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 (300) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +200 -172
  3. package/agents/explore.md +9 -6
  4. package/agents/general-purpose.md +1 -1
  5. package/agents/plan.md +6 -4
  6. package/docs/guide/README.md +52 -0
  7. package/docs/guide/bring-your-claude-code-setup.md +126 -0
  8. package/docs/guide/configuration.md +171 -0
  9. package/docs/guide/differences-from-claude-code.md +150 -0
  10. package/docs/guide/doctor.md +86 -0
  11. package/docs/guide/hooks.md +109 -0
  12. package/docs/guide/installation.md +124 -0
  13. package/docs/guide/permissions-modes-and-auto-mode.md +450 -0
  14. package/docs/guide/providers-and-models.md +194 -0
  15. package/docs/guide/reference.md +224 -0
  16. package/docs/guide/sessions-and-context.md +190 -0
  17. package/docs/guide/skills-plugins-and-mcp.md +176 -0
  18. package/docs/guide/subagents-and-workflows.md +193 -0
  19. package/docs/guide/tasks-and-background-work.md +111 -0
  20. package/docs/guide/terminal-interface.md +155 -0
  21. package/docs/guide/tools.md +175 -0
  22. package/docs/guide/troubleshooting.md +175 -0
  23. package/extensions/ask-user/index.ts +65 -71
  24. package/extensions/ask-user/questions.ts +15 -28
  25. package/extensions/ask-user/widget.ts +533 -0
  26. package/extensions/auto-mode/actions.ts +9 -26
  27. package/extensions/auto-mode/classifier-prompt.ts +127 -0
  28. package/extensions/auto-mode/classifier.ts +305 -227
  29. package/extensions/auto-mode/config.ts +279 -77
  30. package/extensions/auto-mode/defaults.ts +17 -60
  31. package/extensions/auto-mode/instructions.ts +2 -1
  32. package/extensions/auto-mode/model-picker.ts +53 -31
  33. package/extensions/auto-mode/model-select.ts +153 -82
  34. package/extensions/auto-mode/paths.ts +13 -1
  35. package/extensions/auto-mode/pause.ts +8 -0
  36. package/extensions/auto-mode/prompt.ts +205 -238
  37. package/extensions/auto-mode/recoverability.ts +229 -0
  38. package/extensions/auto-mode/rules.ts +92 -75
  39. package/extensions/auto-mode/safety-floor.ts +43 -12
  40. package/extensions/auto-mode/sensitive.ts +11 -0
  41. package/extensions/auto-mode/setup-run.ts +245 -0
  42. package/extensions/auto-mode/setup.ts +235 -0
  43. package/extensions/auto-mode/shell-analysis.ts +332 -26
  44. package/extensions/auto-mode/transcript.ts +102 -0
  45. package/extensions/background/index.ts +256 -43
  46. package/extensions/background/monitor-batch.ts +58 -0
  47. package/extensions/background/registry.ts +9 -0
  48. package/extensions/background/wakeup.ts +45 -0
  49. package/extensions/bash/background.ts +47 -22
  50. package/extensions/bash/guards.ts +229 -0
  51. package/extensions/bash/index.ts +81 -19
  52. package/extensions/branding/index.ts +44 -18
  53. package/extensions/branding/startup.ts +23 -8
  54. package/extensions/claude-compat/index.ts +17 -2
  55. package/extensions/claude-context/index.ts +41 -12
  56. package/extensions/clear/index.ts +27 -0
  57. package/extensions/compaction/fit.ts +121 -0
  58. package/extensions/compaction/index.ts +176 -71
  59. package/extensions/compaction/prompt.ts +21 -1
  60. package/extensions/context-budget/budget.ts +61 -0
  61. package/extensions/context-budget/index.ts +66 -0
  62. package/extensions/context-management/index.ts +40 -16
  63. package/extensions/doctor/build.ts +226 -0
  64. package/extensions/doctor/cli.ts +99 -0
  65. package/extensions/doctor/compat.ts +532 -0
  66. package/extensions/doctor/dependencies.ts +186 -0
  67. package/extensions/doctor/fix-prompt.ts +125 -0
  68. package/extensions/doctor/index.ts +308 -0
  69. package/extensions/doctor/models.ts +231 -0
  70. package/extensions/doctor/presets.ts +208 -0
  71. package/extensions/doctor/report.ts +189 -0
  72. package/extensions/doctor/update-lookup.ts +46 -0
  73. package/extensions/doctor/viewer.ts +124 -0
  74. package/extensions/effort/index.ts +41 -4
  75. package/extensions/effort/slider.ts +33 -2
  76. package/extensions/file-tracker/index.ts +143 -20
  77. package/extensions/file-tracker/replay.ts +46 -0
  78. package/extensions/file-tracker/tracker.ts +51 -4
  79. package/extensions/footer/footer-line.ts +207 -0
  80. package/extensions/footer/index.ts +152 -0
  81. package/extensions/footer/pr.ts +53 -0
  82. package/extensions/hooks/executor.ts +57 -20
  83. package/extensions/hooks/formatter-notice.ts +76 -0
  84. package/extensions/hooks/index.ts +314 -73
  85. package/extensions/hooks/matcher.ts +35 -1
  86. package/extensions/hooks/plugin-hooks.ts +3 -3
  87. package/extensions/hooks/protocol.ts +58 -0
  88. package/extensions/hooks/subagent-bridge.ts +77 -0
  89. package/extensions/hooks/trust.ts +15 -4
  90. package/extensions/init/index.ts +26 -0
  91. package/extensions/init/prompt.ts +40 -0
  92. package/extensions/interrupted/index.ts +35 -0
  93. package/extensions/interrupted/line.ts +9 -0
  94. package/extensions/lib/abort.ts +15 -0
  95. package/extensions/lib/agent-loader.ts +183 -0
  96. package/extensions/lib/anthropic-payload.ts +15 -0
  97. package/extensions/lib/atomic-write.ts +56 -0
  98. package/extensions/lib/bridge-watch.ts +20 -0
  99. package/extensions/lib/cache-retention.ts +60 -0
  100. package/extensions/lib/capability-index.ts +466 -0
  101. package/extensions/lib/claude-context.ts +318 -27
  102. package/extensions/lib/claude-settings.ts +64 -0
  103. package/extensions/lib/consent-preview.ts +30 -0
  104. package/extensions/lib/deferred.ts +144 -13
  105. package/extensions/lib/favorites.ts +43 -0
  106. package/extensions/lib/fetch-timeout.ts +15 -0
  107. package/extensions/lib/frontmatter.ts +31 -0
  108. package/extensions/lib/git.ts +44 -2
  109. package/extensions/lib/hook-gate.ts +90 -0
  110. package/extensions/lib/interrupt.ts +94 -0
  111. package/extensions/lib/mcp-overrides.ts +61 -0
  112. package/extensions/lib/mcp-share.ts +25 -0
  113. package/extensions/lib/mcp-status.ts +34 -0
  114. package/extensions/lib/memory.ts +41 -6
  115. package/extensions/lib/model-facts.generated.json +2480 -0
  116. package/extensions/lib/model-facts.ts +162 -0
  117. package/extensions/lib/model-policy.ts +145 -189
  118. package/extensions/lib/model-tier.ts +480 -64
  119. package/extensions/lib/model-unusable.ts +37 -0
  120. package/extensions/lib/notifications.ts +320 -1
  121. package/extensions/lib/one-code-settings.ts +63 -0
  122. package/extensions/lib/original-command.ts +71 -0
  123. package/extensions/lib/package-version.ts +31 -0
  124. package/extensions/lib/paths.ts +39 -4
  125. package/extensions/lib/permission-gate.ts +192 -0
  126. package/extensions/lib/persisted-output.ts +18 -0
  127. package/extensions/lib/pi-ai-estimate.ts +160 -0
  128. package/extensions/lib/pi-version.ts +1 -1
  129. package/extensions/lib/plan-mode-channels.ts +11 -0
  130. package/extensions/lib/plugin-overrides.ts +24 -0
  131. package/extensions/lib/plugin-root.ts +44 -0
  132. package/extensions/lib/plugins.ts +233 -54
  133. package/extensions/lib/prefix-warm-gate.ts +157 -0
  134. package/extensions/lib/process-tree.ts +56 -0
  135. package/extensions/lib/reminders.ts +321 -68
  136. package/extensions/lib/scratchpad.ts +11 -8
  137. package/extensions/lib/session-lifecycle.ts +28 -0
  138. package/extensions/lib/settings-channels.ts +14 -0
  139. package/extensions/lib/shell-tasks.ts +54 -0
  140. package/extensions/lib/skill-overrides.ts +86 -0
  141. package/extensions/lib/skill-scan.ts +110 -0
  142. package/extensions/lib/text-width.ts +267 -0
  143. package/extensions/lib/tui-render.ts +281 -54
  144. package/extensions/lib/usage-bus.ts +75 -0
  145. package/extensions/lib/usage-tracker.ts +67 -0
  146. package/extensions/lib/worktree-isolation.ts +170 -0
  147. package/extensions/lsp/client.ts +69 -12
  148. package/extensions/lsp/format.ts +12 -2
  149. package/extensions/lsp/index.ts +273 -84
  150. package/extensions/lsp/install-hints.ts +43 -0
  151. package/extensions/lsp/keep-alive.ts +19 -0
  152. package/extensions/lsp/plugin-servers.ts +331 -0
  153. package/extensions/lsp/watcher.ts +157 -0
  154. package/extensions/mcp/announce.ts +99 -0
  155. package/extensions/mcp/client.ts +181 -22
  156. package/extensions/mcp/config.ts +55 -24
  157. package/extensions/mcp/index.ts +586 -86
  158. package/extensions/mcp/oauth/browser.ts +26 -0
  159. package/extensions/mcp/oauth/callback.ts +104 -0
  160. package/extensions/mcp/oauth/flow.ts +68 -0
  161. package/extensions/mcp/oauth/provider.ts +147 -0
  162. package/extensions/mcp/oauth/store.ts +86 -0
  163. package/extensions/mcp/panel/keys.ts +34 -0
  164. package/extensions/mcp/panel/model.ts +62 -0
  165. package/extensions/mcp/panel/render.ts +141 -0
  166. package/extensions/mcp/panel/state.ts +108 -0
  167. package/extensions/mcp/schema.ts +83 -6
  168. package/extensions/mcp/trust.ts +340 -0
  169. package/extensions/memory/entries.ts +163 -0
  170. package/extensions/memory/index.ts +119 -2
  171. package/extensions/memory/open-external.ts +97 -0
  172. package/extensions/memory/panel.ts +124 -0
  173. package/extensions/model-default/index.ts +74 -0
  174. package/extensions/notebook/index.ts +36 -18
  175. package/extensions/notebook/notebook.ts +110 -17
  176. package/extensions/permissions/index.ts +1012 -247
  177. package/extensions/permissions/matcher.ts +488 -57
  178. package/extensions/permissions/modes.ts +55 -7
  179. package/extensions/permissions/project-trust.ts +81 -0
  180. package/extensions/permissions/protected-paths.ts +16 -3
  181. package/extensions/permissions/rule-prose.ts +79 -0
  182. package/extensions/permissions/session-grant.ts +91 -0
  183. package/extensions/permissions/settings.ts +112 -33
  184. package/extensions/permissions/subagent-gate.ts +57 -0
  185. package/extensions/plan-mode/index.ts +143 -33
  186. package/extensions/plan-mode/reminder.ts +16 -19
  187. package/extensions/plan-mode/viewer.ts +40 -31
  188. package/extensions/plugins/counts.ts +68 -0
  189. package/extensions/plugins/index.ts +496 -32
  190. package/extensions/plugins/install/install.ts +167 -0
  191. package/extensions/plugins/install/paths.ts +23 -0
  192. package/extensions/plugins/install/registry.ts +72 -0
  193. package/extensions/plugins/marketplace/git.ts +103 -0
  194. package/extensions/plugins/marketplace/official.ts +36 -0
  195. package/extensions/plugins/marketplace/parse.ts +94 -0
  196. package/extensions/plugins/marketplace/registry.ts +60 -0
  197. package/extensions/plugins/marketplace/sync.ts +146 -0
  198. package/extensions/plugins/marketplace/types.ts +128 -0
  199. package/extensions/plugins/panel/keys.ts +52 -0
  200. package/extensions/plugins/panel/render.ts +315 -0
  201. package/extensions/plugins/panel/rows.ts +230 -0
  202. package/extensions/plugins/panel/state.ts +333 -0
  203. package/extensions/recap/index.ts +215 -0
  204. package/extensions/recap/prompt.ts +44 -0
  205. package/extensions/recap/scheduler.ts +87 -0
  206. package/extensions/search-tools/index.ts +0 -0
  207. package/extensions/search-tools/tools.ts +7 -5
  208. package/extensions/skill/index.ts +360 -34
  209. package/extensions/skill/invoke.ts +185 -0
  210. package/extensions/skill/listing.ts +21 -0
  211. package/extensions/skill/panel/keys.ts +41 -0
  212. package/extensions/skill/panel/render.ts +91 -0
  213. package/extensions/skill/panel/state.ts +115 -0
  214. package/extensions/spinner/index.ts +110 -0
  215. package/extensions/spinner/line.ts +51 -0
  216. package/extensions/spinner/verbs.ts +201 -0
  217. package/extensions/subagents/agents.ts +88 -44
  218. package/extensions/subagents/default-model.ts +62 -32
  219. package/extensions/subagents/delegation-steer.ts +23 -0
  220. package/extensions/subagents/hand-back-review.ts +67 -0
  221. package/extensions/subagents/index.ts +1545 -346
  222. package/extensions/subagents/live-runs.ts +293 -0
  223. package/extensions/subagents/model-select.ts +252 -82
  224. package/extensions/subagents/outcome.ts +90 -0
  225. package/extensions/subagents/panel-keys.ts +41 -0
  226. package/extensions/subagents/panel-render.ts +357 -0
  227. package/extensions/subagents/panel-widget.ts +330 -0
  228. package/extensions/subagents/pending-claim.ts +45 -0
  229. package/extensions/subagents/prose.ts +105 -0
  230. package/extensions/subagents/runner.ts +674 -0
  231. package/extensions/subagents/runs.ts +68 -1
  232. package/extensions/subagents/send-to-main-tool.ts +55 -0
  233. package/extensions/subagents/session-turns.ts +138 -0
  234. package/extensions/subagents/shell-panel.ts +198 -0
  235. package/extensions/subagents/usage.ts +16 -4
  236. package/extensions/subagents/worktree.ts +13 -0
  237. package/extensions/system-prompt/environment.ts +5 -3
  238. package/extensions/system-prompt/git-status.ts +93 -0
  239. package/extensions/system-prompt/index.ts +23 -1
  240. package/extensions/system-prompt/template.ts +34 -4
  241. package/extensions/system-prompt/tiers/common.ts +31 -6
  242. package/extensions/system-prompt/tiers/low.ts +13 -3
  243. package/extensions/system-prompt/tiers/mid.ts +21 -8
  244. package/extensions/system-reminder/index.ts +53 -9
  245. package/extensions/tasks/index.ts +65 -16
  246. package/extensions/tasks/store.ts +76 -3
  247. package/extensions/tool-search/announce.ts +42 -0
  248. package/extensions/tool-search/index.ts +136 -13
  249. package/extensions/turn-duration/index.ts +60 -0
  250. package/extensions/turn-duration/line.ts +22 -0
  251. package/extensions/turn-duration/span.ts +41 -0
  252. package/extensions/turn-duration/verbs.ts +24 -0
  253. package/extensions/web/backends.ts +421 -0
  254. package/extensions/web/index.ts +178 -21
  255. package/extensions/web-fetch/index.ts +74 -18
  256. package/extensions/web-fetch/summarize.ts +1 -1
  257. package/extensions/workflow/agent-session.ts +182 -112
  258. package/extensions/workflow/globals.ts +129 -106
  259. package/extensions/workflow/index.ts +343 -28
  260. package/extensions/workflow/records.ts +118 -0
  261. package/extensions/workflow/run-manager.ts +80 -31
  262. package/extensions/workflow/saved-workflows.ts +2 -1
  263. package/extensions/workflow/script-worker.mjs +110 -0
  264. package/extensions/workflow/types.ts +74 -8
  265. package/extensions/workflow/viewer.ts +562 -0
  266. package/extensions/workflow/vm-runtime.ts +118 -22
  267. package/extensions/workflow/widget.ts +126 -18
  268. package/extensions/worktree/guards.ts +240 -0
  269. package/extensions/worktree/index.ts +30 -5
  270. package/extensions/worktree/rewrite.ts +32 -16
  271. package/package.json +20 -10
  272. package/skills/code-review/SKILL.md +165 -0
  273. package/skills/fewer-permission-prompts/SKILL.md +61 -0
  274. package/skills/security-review/SKILL.md +178 -0
  275. package/skills/simplify/SKILL.md +70 -0
  276. package/themes/{one-code-light.json → onecode-light.json} +6 -1
  277. package/themes/{one-code.json → onecode.json} +6 -1
  278. package/types/app.d.ts +8 -1
  279. package/types/community-packages.d.ts +45 -2
  280. package/docs/decisions/auto-mode.md +0 -526
  281. package/docs/decisions/branding.md +0 -127
  282. package/docs/decisions/code-review-remediation.md +0 -106
  283. package/docs/decisions/compaction.md +0 -140
  284. package/docs/decisions/distribution.md +0 -168
  285. package/docs/decisions/hooks.md +0 -64
  286. package/docs/decisions/lsp.md +0 -58
  287. package/docs/decisions/mcp.md +0 -104
  288. package/docs/decisions/memory-state.md +0 -170
  289. package/docs/decisions/model-policy.md +0 -126
  290. package/docs/decisions/modes.md +0 -178
  291. package/docs/decisions/skills-plugins.md +0 -56
  292. package/docs/decisions/subagents-workflows.md +0 -225
  293. package/docs/decisions/system-prompt.md +0 -98
  294. package/docs/decisions/tools.md +0 -407
  295. package/docs/decisions/tui.md +0 -202
  296. package/docs/decisions.md +0 -148
  297. package/extensions/subagents/child.ts +0 -414
  298. package/extensions/subagents/rpc-turns.ts +0 -123
  299. package/extensions/todo/index.ts +0 -133
  300. package/extensions/workflow/permission-gate.ts +0 -71
@@ -1,407 +0,0 @@
1
- # Tools
2
-
3
- Part of [Decisions](../decisions.md).
4
-
5
- ## Tool names stay pi-idiomatic (snake_case)
6
-
7
- pi keys its built-in overrides and typed `tool_call` events on `read`, `bash`,
8
- `edit`, `write`, `grep`, `find`, `ls`. We register custom tools in the same
9
- style (`todo_write`, `enter_plan_mode`, `subagent`). Users' Claude Code
10
- permission rules still work: `matcher.ts` maps `Bash`, `Glob`, `WebFetch`,
11
- `Task`, … onto our names. pi's Anthropic OAuth mode separately renames tools to
12
- Claude Code's casing on the wire, so nothing is lost there either.
13
-
14
- ## Deferred tools (ToolSearch)
15
-
16
- `extensions/lib/deferred.ts` holds a registry; any extension defers its own
17
- tools by emitting `pi-claude-code:defer-tool` on the event bus. `tool-search`
18
- deactivates them at session start, announces the names in an every-turn
19
- `<system-reminder>` (as Claude Code does), and activates matches additively so
20
- pi can use native deferred loading.
21
-
22
- **Load order is load-bearing:** `tool-search` must appear before any extension
23
- that defers a tool, because those emit during extension loading and pi's event
24
- bus only delivers to already-registered listeners. Deferred tools also omit
25
- `promptSnippet`/`promptGuidelines` — activating a tool that has them rebuilds
26
- the system prompt and invalidates the cached prefix, defeating the purpose.
27
-
28
- Verified on OpenAI (gpt-5.5): after `tool_search`, pi injected native
29
- `tool_search_call` / `tool_search_output` items at the load point rather than
30
- appending the schema to the request's tool array, and the model then called the
31
- tool successfully.
32
-
33
- Verified on Anthropic (2026-08-05, API key): both pi-owned paths, with
34
- request-payload capture. Native (`claude-sonnet-5`, and any
35
- `supportsToolReferences` model — first-party opus/sonnet/fable ≥5.4-ish, never
36
- haiku): before activation the deactivated tools are simply absent from
37
- `tools`; after `tool_search` activates `web_fetch`, the next request carries it
38
- in `tools` with `defer_loading: true`. Fallback (`claude-haiku-4-5`): the
39
- activated tool is appended as a full definition, complete `input_schema`, no
40
- `defer_loading` flag. Both runs completed normally.
41
-
42
- ## Web tools
43
-
44
- **Search — kept as a dependency.** `web_search` comes from **`pi-web-search`**:
45
- it calls the *current model provider's own* search API (OpenAI/Codex, Anthropic,
46
- Gemini), so no extra API key and no scraping. At 1,610 lines with **zero runtime
47
- dependencies**, what it encodes is knowledge — three providers' search API shapes
48
- — rather than machinery, and tracking those ourselves would be a worse trade.
49
- It also registers Gemini-only `url_context`. Both tools are deferred.
50
-
51
- One coupling to preserve: pi-web-search drives `setActiveTools` to hide
52
- `url_context` on non-Gemini models. That composes with our deferral **only
53
- because `tool-search` loads first**, so deferred tools are already deactivated
54
- when pi-web-search snapshots the active set.
55
-
56
- **Fetch — replaced with our own.** `extensions/web-fetch/` (~250 lines) replaces
57
- `pi-web-access`, which was the heaviest thing in the tree: 21,719 lines, 7.5 MB,
58
- seven runtime dependencies, and **114 of our then-208 installed packages** — to
59
- supply one tool we used. Its other three tools were unused, its `web_search` was
60
- deliberately overridden, and it spawned processes for GitHub cloning, video
61
- extraction, and reading the browser's cookie store. That is a lot of unreviewed
62
- surface and startup weight for an HTTP GET.
63
-
64
- Ours keeps three focused dependencies (`@mozilla/readability`, `linkedom`,
65
- `turndown`) because HTML-to-markdown quality genuinely affects what the model
66
- reads; a regex stripper produces poor input on real documentation. Total install
67
- went from 208 packages to 109.
68
-
69
- Claude Code parity notes for `web_fetch`: http is upgraded to https,
70
- non-web schemes are refused, responses are cached 15 minutes, and cross-host
71
- redirects are reported rather than followed (so a redirect cannot quietly take
72
- the agent elsewhere; same-host ones are followed).
73
-
74
- **Deliberate deviation:** Claude Code answers a `prompt` against the page using a
75
- small fast model. pi exposes no clean in-process completion helper, and a
76
- summarisation call that fails silently would degrade quality invisibly — the
77
- exact failure shape we have been removing. Instead the tool returns extracted
78
- markdown windowed to 30k characters and reports `nextOffset`, so the model reads
79
- the page itself and can page through a long one.
80
-
81
- ## AskUserQuestion — our own
82
-
83
- `extensions/ask-user/` replaces the community `pi-ask-user`. That package worked
84
- and had a nicer overlay UI, but it carried two problems:
85
-
86
- 1. It pulled **`@sinclair/typebox@0.34`** — a second, different schema library,
87
- since pi itself uses `typebox@1.x` — and built its tool schema with it. It
88
- worked, but two schema implementations in one process is a silent-divergence
89
- risk, not a visible one.
90
- 2. It asked **one question per call**, while Claude Code's AskUserQuestion takes
91
- up to four questions, each with `header`, optional `multiSelect`, and 2–4
92
- labelled options with descriptions.
93
-
94
- Ours matches Claude Code's schema and drops the duplicate dependency, at the cost
95
- of a plainer presentation: pi has no native multi-question widget, so questions
96
- are asked one dialog at a time via `ctx.ui.select`, with a `✓` marker
97
- accumulating multi-select choices, a "Done selecting" entry, and an automatic
98
- "Other (type your own answer)" escape hatch routed to `ctx.ui.input`. Cancelling
99
- any question cancels the batch, since later answers are meaningless without the
100
- earlier ones. Non-interactive sessions get an instruction to ask in the reply
101
- instead.
102
-
103
- Verified over RPC: a two-question call produced three dialogs — single-select,
104
- then multi-select showing `✓ Caching` on the second pass — and returned both
105
- answers in one structured result.
106
-
107
- ## web_fetch answers a `prompt` with a reader model
108
-
109
- The original deviation ("no clean in-process completion helper; a summariser
110
- that fails silently degrades quality invisibly") went stale on both halves: the
111
- auto-mode classifier has been making exactly this call via pi-ai's
112
- `completeSimple` for months, and the silent-failure objection argues for a
113
- *loud fallback*, not for omitting the feature. So `web_fetch` now takes Claude
114
- Code's optional `prompt`, and a reader model answers it against up to 120k
115
- chars of the page — four times the window the main model would get — returning
116
- just the answer with a header naming the reader.
117
-
118
- The reader reuses the **classifier role profile** rather than adding a third
119
- curated inventory: summarisation wants the same small-but-capable floor, and
120
- two lists drifting apart helps nobody. Same containment (the page and the
121
- query go to the reader, so it never leaves the session's provider/family) and
122
- the same cost ceiling (never pricier than the session model); with no vetted
123
- smaller model the session model reads the page itself, which still wins by
124
- keeping the full page out of the conversation. `reasoning` and `temperature`
125
- are not sent, for the same fail-closed reasons recorded for the classifier.
126
- Any reader failure returns the raw windowed markdown with a note naming the
127
- error — the fetch is never wasted and the degradation is never silent. Page
128
- content rides in the user message tagged as untrusted data, and the system
129
- prompt pins the reader to extraction.
130
-
131
- Verified live on an OpenAI Codex Sol session (one-shot `--mode json` run with
132
- `--dangerously-skip-permissions`; the tool's permission surface is unchanged by
133
- this feature): the reader resolved to `openai-codex/gpt-5.6-luna` via the
134
- classifier profile, and the tool result read "Answered by
135
- openai-codex/gpt-5.6-luna from the full page" with a correct answer for the
136
- fetched page. Typecheck and all 698 unit tests pass.
137
-
138
- ## Tool errors fail loud, never soft-fallback
139
-
140
- A tool's `execute()` handler must, on a malformed / ambiguous / missing-required
141
- call, return `isError: true` with the **corrective action named** — never a
142
- plausible-but-wrong success. Reserve a non-error empty result for a genuine
143
- no-op (an unsupported filetype, an intentionally cleared list), not for "the
144
- operation failed" or "you called me wrong."
145
-
146
- The archetype: `subagent` with run options but no `agent` silently returned the
147
- agent catalog. A capable model self-corrects from that; a weaker one
148
- (**observed on deepseek-v4-flash**, 2026-08-07) reads the catalog as a
149
- non-sequitur, gets stuck, and — worse — invents a wrong cause (it blamed the
150
- auto-mode gate). The failure mode is general: one response shape serving two
151
- intents, a real failure reported as success (`isError` unset), or an error
152
- message that states no next step. All three send a weak model chasing the wrong
153
- fix.
154
-
155
- A read-only audit of every `pi.registerTool` `execute()` (four parallel Sonnet
156
- agents, one per tool group) found the same family across twelve tools; the
157
- high/medium set was fixed in one pass:
158
-
159
- - **Ambiguous overload** (like the archetype): `skill` (`args` without `skill`),
160
- `list_mcp_resources` (bad `server` name read as "no resources"),
161
- `enter_worktree` (empty `path` string fell through to *create*).
162
- - **Silent failure / `isError` unset on a real failure**: `workflow`'s `agent()`
163
- swallowed a `WorkflowScriptError` (script-config mistake) into a `null` the way
164
- a genuine agent failure resolves — now it rethrows, matching `parallel()` /
165
- `pipeline()`; `tool_search` dropped typo'd `select:` names and reported the rest
166
- "Loaded", and its zero-match branch was a non-error — now unmatched names are
167
- surfaced and a total miss is `isError: true`; `lsp_diagnostics` after a
168
- mid-session server *crash* fell through to "install `<command>`" for an
169
- already-installed server, and a start failure was reported as a clean "no
170
- diagnostics" — now the crash reason is recorded and a recognised-but-unavailable
171
- language is an error (only a truly unsupported filetype stays non-error).
172
- - **Destructive-before-validate**: `schedule_wakeup` cancelled the pending wakeup
173
- *before* validating params, so a malformed reschedule silently killed a running
174
- `/loop` — validation now runs first and a rejected call leaves the loop intact.
175
- - **Misleading cause**: `exit_plan_mode` called cold blamed "the plan file is
176
- empty" instead of "you're not in plan mode"; `task_update` reported a
177
- self-reference as "Unknown task id"; `send_message` to an unstarted run said
178
- "No agent named …" in the same words as the catalog, hiding that the fix is to
179
- spawn the run first.
180
-
181
- Deliberately left for later (logged, not silently dropped): the **lows**
182
- (`send_message`'s `""`-prefix matching the only run; `monitor`'s unwrapped bad
183
- `ws.url`; `notebook_edit`'s raw `ENOENT`; `schedule_wakeup`'s silent clamp) and
184
- two **vendor** gaps outside our code — `pi-web-search` never sets `isError`
185
- (would need a wrapper in `web/index.ts`), and `mcp/client.ts` swallows
186
- `listTools`/`listResources` failures into empty arrays.
187
-
188
- ## Harness discipline (divergences found by self-comparison)
189
-
190
- Comparing our implementation against the real Claude Code harness *as observed
191
- from inside a session* surfaced behaviours that reading a request payload would
192
- never reveal, because they are enforcement, not prompting.
193
-
194
- **File freshness — `extensions/file-tracker/`.** Claude Code refuses to edit a
195
- file the model has not read, refuses to write over an unread existing file,
196
- rejects an edit when the file changed after the read, and reports out-of-band
197
- changes with a line-numbered excerpt. This exists to prevent lost updates, and it
198
- is why Claude Code can tell the model "do not re-read a file you just edited — the
199
- harness tracks file state for you". We now do the same.
200
-
201
- Tracking is **by content, not by tool call**, which is the load-bearing detail:
202
- it catches writes made through bash that no tool hook can see. (In the session
203
- that prompted this, the reminders I received were triggered by my own `python3`
204
- edits.)
205
-
206
- One conflict worth recording, because the first implementation had it backwards:
207
- announcing an external change must **not** mark the file as read. If the pre-turn
208
- scan records the new content, the file becomes "fresh" and the stale-edit guard
209
- stops firing — so the harness would helpfully report the change and then permit
210
- the clobbering edit. Notifications are tracked separately from reads, purely to
211
- suppress repeat warnings.
212
-
213
- **Denial feedback carries the user's words.** The prompt option says "No, tell the
214
- agent what to do differently", so it now actually asks, and the typed reason is
215
- appended to the block reason (`The user said: …`). Previously the option promised
216
- something the code did not deliver.
217
-
218
- **State-driven nudges.** Claude Code injects a reminder when the task tools have
219
- gone unused. `todo_write` now does the same after eight quiet turns, and also
220
- flags a list with no `in_progress` item or several of them. A todo tool nobody
221
- remembers to call is decoration.
222
-
223
- **CLAUDE.md framing.** Context files are now introduced with Claude Code's own
224
- wording — "IMPORTANT: These instructions OVERRIDE any default behavior and you
225
- MUST follow them exactly as written" — rather than a neutral "project-specific
226
- instructions" header. Placement still differs deliberately: ours sits in the
227
- system prompt (via pi), Claude Code's arrives in a first-user-message reminder.
228
- Ours caches better.
229
-
230
- **`context_management` — on by default for first-party Anthropic only.**
231
- Claude Code sends `clear_thinking_20251015` on every request so long sessions
232
- stop carrying old reasoning blocks. `extensions/context-management/` does this
233
- via `before_provider_request`. Default: enabled when the model's provider is
234
- `anthropic` on `api.anthropic.com` (verified there, and it is what Claude Code
235
- does); disabled for every other `anthropic-messages` endpoint.
236
- `CC_CLEAR_THINKING=0` forces it off; `=1` forces it on for an endpoint you
237
- have confirmed accepts it.
238
-
239
- **Enabling for other providers later** — the default is scoped narrowly only
240
- because these are untested, not because they can't work. What each needs
241
- before flipping it on:
242
-
243
- - **Bedrock**: takes beta flags as `anthropic_beta` (array) *in the request
244
- body*, not the `anthropic-beta` header — needs its own request shaping plus
245
- a live check that it accepts `context_management` at all.
246
- - **Vertex**: `anthropic_version` in the body; same question about
247
- `context_management` support.
248
- - **Proxies/gateways (LiteLLM etc.)**: pass-through varies per deployment;
249
- users can already opt in per-endpoint with `CC_CLEAR_THINKING=1` once they
250
- have confirmed theirs forwards the header and body param.
251
-
252
- To extend: broaden `clearThinkingEnabled()` per provider and add the
253
- endpoint's beta representation next to `anthropicBetas()`; verify with a live
254
- run using `debug-capture.ts` (remember it logs pre-mutation payloads — confirm
255
- via absence of the 400, not via the capture).
256
-
257
- Verified against the live API (2026-08-05, API key) — the body param alone is
258
- not enough, found via a curl A/B:
259
-
260
- - It requires `anthropic-beta: context-management-2025-06-27`, else 400
261
- "context_management: Extra inputs are not permitted".
262
- - The edit requires thinking enabled or an adaptive-thinking model, else 400
263
- "`clear_thinking_20251015` strategy requires `thinking` to be enabled or
264
- adaptive" — so the payload hook gates on `payload.thinking` /
265
- `compat.forceAdaptiveThinking`.
266
- - **Header clobber hazard**: an extension's `before_provider_headers` value
267
- merges *after* pi's computed headers, replacing pi's own `anthropic-beta`
268
- (OAuth identity betas, interleaved thinking for non-adaptive models,
269
- fine-grained tool streaming). `anthropicBetas()` therefore rebuilds pi's
270
- list (pinned v0.83 logic) and appends ours; OAuth is detected from
271
- `~/.pi/agent/auth.json` entry `type`. Re-check on pi upgrades.
272
-
273
- Confirmed working end-to-end on `claude-haiku-4-5` (non-adaptive, thinking
274
- gated) and `claude-sonnet-5` (adaptive): no API errors, normal completions.
275
-
276
- Verified end-to-end: an unread-file edit was blocked and the file left untouched;
277
- after an external edit the stale guard blocked the edit and the change reminder
278
- reached the model (both visible in the request payload); a declined write
279
- delivered `The user said: Use append mode instead…`; and the todo nudge appeared
280
- after ten quiet turns.
281
-
282
- **Deliberately not copied:** OS sandboxing (pi's stance is to containerize);
283
- harness-level command shaping such as blocking foreground `sleep`; background
284
- agents with task notifications (still Phase 8); and mid-conversation
285
- `role: "system"` messages, which pi's message types cannot express — our
286
- user-message reminders are the closest equivalent. (The LLM safety classifier
287
- that gates bash, once listed here as not copied because it needs a model call
288
- per command, is now built — see [`auto-mode.md`](auto-mode.md).)
289
-
290
- ## Tier-aware tool surface: search built-ins for mid/low only (unreviewed)
291
-
292
- **Decision.** pi registers `grep`/`find`/`ls` but never activates them (only
293
- `read`/`bash`/`edit`/`write` are active by default — findings §2). One Code
294
- activates the three search tools at session start (and on model change) for
295
- the **mid/low** prompt tiers only; **frontier keeps pi's lean default**
296
- (`extensions/search-tools/`).
297
-
298
- **Why.** Every captured One Code payload carried no search tools while the
299
- mid/low tier prompts explicitly steer to "the search tools" — instructions
300
- pointing at tools that did not exist in the request, a weak-model trap.
301
- Frontier is deliberately different: Claude Code v2.1.81 ships no
302
- Grep/Glob/LS to frontier models (verified in `tools/eager-tools.json`) —
303
- bash covers search there, and CC's own frontier prompt keeps only the generic
304
- "prefer dedicated tools" line. The tier classifier from the prompt-tier
305
- decision is reused so prompt text and tool surface always agree.
306
-
307
- **Rejected.** Activating unconditionally (anti-parity for frontier, wasted
308
- schema tokens); editing the tier prompts to say "use bash grep" (weaker
309
- models measurably do better with structured tools, which is why the tiers
310
- exist).
311
-
312
- ## Subagent steering: injected catalog, fork hardening (unreviewed)
313
-
314
- **Decision.** (a) The agent catalog is injected as an every-turn keyed system
315
- reminder (`subagent-agents`), and the `subagent` schema descriptions point at
316
- it; (b) a fork child's task is wrapped in a framing preamble
317
- (`forkTaskMessage` in `subagents/child.ts`): inherited context is reference
318
- only, do ONLY the task, the parent's background task ids are not addressable;
319
- (c) `model`/`thinking` overrides on a fork run are rejected with a corrective
320
- error.
321
-
322
- **Why.** A captured weak-model payload showed the model had no way to know
323
- agent names without a discovery call (Claude Code injects the agent-type
324
- list in a system reminder). The fork preamble and override rejection close
325
- the fork-confabulation incident (docs/handoff-tool-ambiguity.md): a fork on
326
- `thinking:"minimal"` abandoned its task and continued the inherited topic,
327
- and the parent read the returned text as independent confirmation. Claude
328
- Code silently *ignores* `model` for forks; rejection was chosen over silent
329
- ignoring per the fail-loud convention.
330
-
331
- **Rejected.** Renaming `agent` to CC's `subagent_type` (the tool is named
332
- `subagent`, byte-parity is unreachable, and the pi-idiomatic naming decision
333
- stands); silently coercing fork overrides (hides the caller's mistake).
334
-
335
- ## Background bash: override the built-in, gate before detaching (unreviewed)
336
-
337
- **Decision.** `extensions/bash/` registers a tool named `bash`, overriding
338
- pi's built-in (same-name registration — findings §2, the pattern pi's own
339
- sandbox example uses). Foreground calls delegate to pi's real executor
340
- (`createBashToolDefinition`, re-created per cwd). `run_in_background: true`
341
- spawns detached in its own process group, returns a task id, spools to
342
- `<sessionDir>/bash/<id>/output.log`, and registers a `kind:"bash"`
343
- `BackgroundTask` — `task_output`/`task_stop` are kind-agnostic and work
344
- unchanged. Completion notifications carry byte-identical text to
345
- `task_output`, and legitimately-empty output is explicitly marked
346
- `(no output — …)`. The permission gate / auto-mode classifier runs before
347
- execute like any bash call — background bash is **not** auto-allowed, and the
348
- gate fires before anything detaches (verified live).
349
-
350
- **Why / rejected.** Full rationale and the rejected shapes (separate
351
- `bash_background` tool; generalising `monitor`) in
352
- [`../plan/background-bash.md`](../plan/background-bash.md), now implemented.
353
- A separate tool name would diverge from Claude Code's single-Bash shape and
354
- lose the existing `bash` permission rules for free.
355
-
356
- ## Deferral runs on all tiers; steering follows CC's channels (2026-08-10)
357
-
358
- **Decision.** (a) Tool deferral (ToolSearch) applies on **every** tier, not just
359
- frontier — the deferred-registry tools are deactivated and listed in the
360
- deferred-tools reminder regardless of model; core tools (read/edit/write/bash/…)
361
- never register as deferrable and stay eager. (b) Every harness-injected
362
- notification shares `lib/notifications.ts`'s anti-confabulation preamble
363
- (adapted from CC's). (c) MCP servers' `instructions` are injected as an
364
- every-turn reminder in CC's format instead of being dropped.
365
-
366
- **Why (a) reversed.** The original "frontier-only" was justified by a false
367
- premise — findings §14 wrongly claimed CC ships Haiku all tools eagerly. The raw
368
- captures (`haiku-4-6.json`, `latest-haiku.json`) show CC defers the long tail on
369
- Haiku too. And it costs nothing to do everywhere: `pi.setActiveTools` edits
370
- `agent.state.tools`, which *is* the request tool list, so deactivation omits a
371
- tool from the wire on **any** provider (OpenRouter/deepseek included) — verified
372
- live, 35→15 tools on `deepseek-v4-flash-free`. Provider-native deferral
373
- (Anthropic `defer_loading`, OpenAI `tool_search_call`) is only a cache
374
- optimization on top; without it, loading a tool mid-session invalidates the
375
- prompt cache from the tools block down — an accepted tradeoff for cheap models.
376
-
377
- **Rejected.** Keeping frontier-only as a deliberate divergence (weak models
378
- fumble the indirection) — the user chose CC fidelity; the risk is documented and
379
- revisitable. A One Code-specific notification format (CC's wording is
380
- field-tested against exactly this hazard).
381
-
382
- ## CLAUDE.md, memory, and skills injected as the CC reminder stack (2026-08-10)
383
-
384
- **Decision.** CLAUDE.md contents, the `MEMORY.md` index, and the skills listing
385
- are no longer in the system prompt. They ride the first-user-message
386
- `<system-reminder>` stack, prepended before the user's text in CC's fixed order
387
- (deferred tools → subagent-models → agents → MCP → skills → `# claudeMd`), via
388
- placement/order/suffix on the shared reminder queue (`lib/reminders.ts`,
389
- `CONTEXT_ORDER`). CLAUDE.md + memory + `# userEmail` + `# currentDate` are bundled
390
- into ONE byte-identical `# claudeMd` block (`extensions/claude-context` +
391
- `lib/claude-context.ts`); discovery mirrors CC (global + project ancestors, not
392
- pi's AGENTS.md-first loader). Tier-independent: the block is identical across
393
- models — only the `# Memory` *spec* in the system prompt varies by tier (already
394
- handled by `lib/memory.ts`).
395
-
396
- **Why (reversed from "stay in the system prompt").** The goal is byte-identity
397
- with CC's payload, which the user made explicit. CC puts this content in the user
398
- turn (trust boundary: project/user-authored content isn't in the system role),
399
- and the diff was visible and real. Injection is transient (pi's `context` event
400
- runs on a `structuredClone`; the session keeps the clean user message), so
401
- re-injecting every turn is safe on resume. The claudeMd block reproduces CC's
402
- exact bytes including the trailing `\n\n` (findings §14).
403
-
404
- **Rejected.** Keeping it in the system prompt (the earlier call — abandoned once
405
- byte-identity became the requirement); a first-message idempotent-prepend without
406
- the shared queue (the queue already models transient per-turn injection and
407
- placement generalizes to all five reminders).
@@ -1,202 +0,0 @@
1
- # TUI
2
-
3
- Part of [decisions](../decisions.md).
4
-
5
- ## Compact tool rendering — the Claude Code transcript look (unreviewed)
6
-
7
- *2026-08-08.* The transcript was cluttered: every custom tool rendered through
8
- pi's fallback (bold tool name + full JSON args + full output) inside a heavy
9
- colored box, harness notifications printed their whole anti-confabulation
10
- framing to the user, and thinking text was barely distinguishable from answer
11
- text. Claude Code's transcript shows a one-line call (`● Tool(args)`), a
12
- collapsed indented result (`⎿ …` + `+N lines (ctrl+o to expand)`), and hides
13
- model-facing boilerplate.
14
-
15
- **What was done:**
16
-
17
- - **`extensions/lib/tui-render.ts`** (pure, unit-tested) provides
18
- `ccToolRenderers(label, spec?)` — `renderShell: "self"` +
19
- `renderCall`/`renderResult` producing the `●`/`⎿` layout with pi's native
20
- ctrl+o expansion (`options.expanded`), status-colored bullet
21
- (muted → success/error), a primary-arg heuristic for the call line
22
- (per-tool `title`/`result` overrides where the heuristic is wrong), and
23
- ANSI-aware width truncation on every line (pi-tui crashes on overwide
24
- lines — findings §3). Every One Code-registered tool spreads it in; pi's
25
- built-ins (read/bash/edit/…) keep their own upstream renderers.
26
- - **Notifications render compact.** `task-notification`, `wakeup`,
27
- `subagent-message`, `subagent-result`, `workflow-result` get
28
- `registerMessageRenderer` components: one dim `✳ headline` line collapsed,
29
- full body on ctrl+o. The `systemNotification` framing stays in the model
30
- payload but is stripped from display (`notificationBody`) — it exists for
31
- the model, not the user.
32
- - **Bash background starts** render `⎿ Running in background (task <id> ·
33
- log: …)` instead of the model-facing instruction paragraph; foreground
34
- bash delegates to pi's own renderer untouched.
35
- - **Themes:** `toolPendingBg`/`toolSuccessBg` set to `""` (terminal default —
36
- pi maps the empty string to "no background"), which removes the box look
37
- for *all* tools including pi built-ins; `toolErrorBg` kept as the one
38
- remaining background signal. `thinkingText` moved from `gray` to `grayDim`
39
- (240 dark / 245 light) so thinking reads clearly dimmer than answer text —
40
- pi already italicizes it, and ctrl+t (`hideThinkingBlock`) remains the
41
- user's collapse toggle.
42
-
43
- **Rejected:** overriding pi's built-in tool renderers for a bullet-style call
44
- line on read/grep/etc. — re-registering seven built-ins to restyle them is
45
- high-risk surgery for marginal polish, and the theme change already de-boxes
46
- them. Also rejected: hiding notifications entirely (CC-like) — a dim one-liner
47
- keeps the transcript honest about turn triggers.
48
-
49
- ## Built-in tools joined the ● language; banner cut to four lines (unreviewed)
50
-
51
- *2026-08-08, same session.* With custom tools on `●`/`⎿` and pi's built-ins on
52
- their own style (`$ cmd`, `edit path`), the transcript spoke two languages —
53
- the user asked for one (`● Bash(command)`, like Claude Code).
54
-
55
- - **`extensions/tool-style/`** overrides read/write/edit/grep/find/ls under
56
- their built-in names (the bash-extension mechanism, findings §2). Every
57
- model-facing field is passed through byte-identical (system prompt
58
- unchanged); execute delegates to a per-cwd instance of pi's real definition
59
- (worktree moves). Rendering wraps rather than reimplements —
60
- `ccWrapBuiltinRenderers` in the shared lib: `● Label(primary arg)` call
61
- line; the **base result component indented under a `⎿` elbow**, so pi's
62
- streaming, truncation, and ctrl+o expansion behavior stays upstream's.
63
- Edit keeps its own call component (the diff preview a permission decision
64
- depends on) with only the header line swapped (`● Update(path)`).
65
- Inner components live on `context.state`, never `lastComponent` — base
66
- renderers cast `lastComponent` to their concrete classes and would throw.
67
- grep/find/ls stay tier-gated: search-tools reconciles the active list on
68
- session_start/model_select either way. Bash reuses the same wrapper (in
69
- `extensions/bash/`), which also dropped the "Took Ns" timer that counted
70
- permission-prompt wait time as command time.
71
- - **Trap that cost a debugging loop:** a padded Box's "blank" lines carry
72
- ANSI paint (`\x1b[49m…`), so `line.trim() === ""` is false — blank-line
73
- detection in renderers must strip ANSI first (`isBlankLine`). The stale
74
- render looked exactly like a stale jiti cache and wasn't.
75
- - **Banner** (`extensions/branding/`): three keymap-dump hint lines became
76
- one curated line — only the controls a user can't discover (renamed dials,
77
- collapsed-state toggles, input prefixes, the ultracode keyword), led by
78
- `/hotkeys all keys` so the pointer to everything else survives narrow
79
- terminals (`fitItems` drops whole trailing hints instead of cutting
80
- mid-word). Sections collapse to one line: names when ≤3 items, counts
81
- beyond (a truncated name dump carries no information). Startup warnings
82
- point at their commands (`(/lsp for status)`, `(/mcp for status)`), and the
83
- LSP one is capped at one line — the full server error stays in `/lsp`.
84
-
85
- Verified live: `● Read/Grep/Update/Bash/LS` with `⎿` results, edit's diff
86
- preview intact before approval, `seq 1 30` showing pi's "(25 earlier lines,
87
- ctrl+o to expand)" under the elbow, banner at 170 and 80 columns.
88
-
89
- ## Thinking collapsed by default, expandable with ctrl+t (unreviewed)
90
-
91
- *2026-08-08, follow-up to the same complaint.* Even dimmed and italic, visible
92
- thinking still read as answer narration. pi already has the exact mechanism
93
- Claude Code uses — `hideThinkingBlock` (persisted global setting, toggled by
94
- ctrl+t and /settings) collapses each thinking run to a one-line label, and
95
- `ctx.ui.setHiddenThinkingLabel()` is a public extension API for the label
96
- text — so One Code adopts it instead of building rendering of its own (assistant
97
- message rendering is not extension-replaceable anyway).
98
-
99
- - **`branding` defaults `hideThinkingBlock: true` exactly once**: only when
100
- the key is *absent* from `~/.pi/agent/settings.json` (pure decision helper
101
- `shouldDefaultHideThinking` — a present key, either value, is the user's
102
- choice and is never overridden; an unparseable file is left alone). Write
103
- goes through pi's exported `SettingsManager` (locked merge-write).
104
- - **Label**: `✻ Thinking… (ctrl+t to expand)`, set on every `session_start`
105
- (pi resets it to "Thinking..." on /reload via `resetExtensionUI`).
106
- - **Timing caveat (finding)**: pi creates its runtime `SettingsManager` — and
107
- caches the file in memory — *before* extensions load, so the written default
108
- lands from the **next** session; the install session still shows thinking
109
- expanded until the user toggles.
110
-
111
- Verified live across a restart: first launch writes the key, second session
112
- renders `✻ Thinking… (ctrl+t to expand)`, ctrl+t expands to the dim italic
113
- text and back, choice persists.
114
-
115
- **Verified live** (tmux capture, deepseek-v4-flash-free): boxless transcript,
116
- `● Todos(2 items)` / `● Task Output(<id>)` call lines, collapsed results,
117
- `✳ Background bash … completed. (+2 lines, ctrl+o to expand)` notification,
118
- ctrl+o expanding/collapsing both, thinking at `38;5;240` italic, no crash at
119
- 170 cols; 789 unit tests green.
120
-
121
- ## Fullscreen (alt-screen) for Claude Code's clean exit — a setting, not extension code (unreviewed)
122
-
123
- Claude Code runs its whole session in the terminal's **alternate screen
124
- buffer** (`ESC[?1049h` on start, `ESC[?1049l` on exit), so quitting restores
125
- the terminal to whatever preceded it and CC prints only a one-line resume hint.
126
- One Code's default looked different: pi's `TuiMainScreen` (inline, main buffer)
127
- leaves the banner and whole conversation in scrollback after exit.
128
-
129
- pi already has the machinery — `TuiAltScreen` (mode `"fullscreen"`), whose
130
- `.stop()` writes `ESC[?1049l` and then pi prints `To resume this session:
131
- pi --resume …` (`interactive-mode.ts`). It is gated behind the `tuiMode`
132
- setting (`"regular"` default vs `"fullscreen"`), marked *experimental* by pi.
133
-
134
- **Decision: recommend `tuiMode: "fullscreen"` as the One Code experience, but
135
- deliver it as a documented user/global setting — not extension code.** The
136
- renderer is a pi-core startup choice: it is built once from
137
- `--tui-mode <mode>` (CLI) or `settingsManager.getUiMode()` (settings.json), and
138
- the live switch (`switchUiMode`) is **private** to pi's `InteractiveMode` with
139
- no extension-facing hook. A One Code extension therefore cannot set or flip it.
140
-
141
- - **Rejected — flip it from an extension:** impossible; no API reaches the
142
- renderer, and it is read before/independent of which package loads.
143
- - **Rejected — One Code writes the user's global settings on load:** invasive
144
- (mutating user config the package doesn't own) and it would change plain `pi`
145
- everywhere, not just One Code sessions — the setting cannot be scoped to
146
- "this package is active."
147
- - **Chosen — document the toggle.** Set `"tuiMode": "fullscreen"` in
148
- `~/.pi/agent/settings.json` (global, per-user; right for a user who always
149
- runs One Code), or a project's `.pi/settings.json` (project settings override
150
- global — `deepMergeSettings`), or `pi --tui-mode fullscreen` ad-hoc. `/settings
151
- → Interface layout → regular` reverts.
152
-
153
- **Trade-off (accepted):** alt-screen means the conversation does not persist in
154
- terminal scrollback after quit — that is the whole point of the clean exit, and
155
- `pi --resume` covers re-entry.
156
-
157
- **Trap this surfaced (finding §10.16):** the setting key was `uiMode` in the
158
- pinned reference clone (v0.83.0) but was renamed `tuiMode` in installed pi
159
- (v0.84.1); a `uiMode` key is silently ignored on 0.84.1. Clone re-pinned to
160
- `v0.84.1` to match.
161
-
162
- Set in `~/.pi/agent/settings.json` on 2026-08-10; live clean-exit behavior to be
163
- confirmed by the user.
164
-
165
- ## Render memoization: components cache by width (2026-08-10)
166
-
167
- pi-tui calls `render(width)` on **every mounted component on every frame**
168
- (~60fps during streaming, every keystroke; fullscreen mode additionally
169
- re-measures the full unclipped scrollback to compute scroll height) — findings
170
- §15. Our `linesComponent` recomputed `build(width)` plus `truncateLine`'s
171
- per-character ANSI scan on every call, so every historical `●`/`⎿` row taxed
172
- every frame for the life of the session. Profiled on a 750-entry thread:
173
- `lib/tui-render.ts` was the largest app-level CPU consumer during typing
174
- (~0.5s self time per 10s, plus ~1s induced in pi-tui width/wrap utils).
175
-
176
- **Decision:** memoize by width inside the component, and rely on the
177
- factory-per-state-change contract for correctness:
178
-
179
- - Any state change (spinner tick, args streaming in, result arriving, expand
180
- toggle) makes pi re-invoke `renderCall`/`renderResult`, minting a **fresh
181
- component with a fresh cache** — so within one component's lifetime `build`
182
- is pure and a `Map<width, lines>` (capped at 4 entries) cannot go stale.
183
- - `invalidate()` clears the cache — that is the path pi-tui uses for theme
184
- swaps and resizes.
185
- - The `ccWrapBuiltinRenderers` wrappers cache only when the call/result is
186
- settled (`!isPartial`): a live base component may stream internally without
187
- a factory re-run, so in-flight rows stay uncached.
188
- - `truncateLine` gained the structural fast path: raw length bounds visible
189
- width (escapes only add), so `line.length <= width` returns immediately —
190
- the hot path for nearly every line; the scan itself now uses a sticky
191
- module-level regex instead of per-character `slice().match()` allocations.
192
-
193
- Verified: same typing/scrolling profile after the change — `truncateLine` and
194
- `tui-render.ts` no longer appear in the top-30 functions at all; remaining
195
- per-frame cost is pi-tui's own (upstream candidates: `docs/upstream_prs.md`
196
- #2-#4). Unit tests pin the contract (build-once-per-width, invalidate clears).
197
-
198
- **Rule for future renderers:** a component returned from
199
- `renderCall`/`renderResult`/`registerMessageRenderer` must memoize its
200
- `render(width)` (use `linesComponent`, or replicate its cache) — an uncached
201
- component re-renders 60×/s for as long as it is mounted, and the cost
202
- compounds linearly with thread length.