one-code-extension 0.4.1 → 0.6.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 (439) hide show
  1. package/README.md +16 -5
  2. package/agents/general-purpose.md +2 -2
  3. package/docs/guide/README.md +2 -1
  4. package/docs/guide/bring-your-claude-code-setup.md +60 -9
  5. package/docs/guide/configuration.md +48 -14
  6. package/docs/guide/differences-from-claude-code.md +40 -28
  7. package/docs/guide/doctor.md +20 -13
  8. package/docs/guide/hooks.md +18 -3
  9. package/docs/guide/installation.md +26 -4
  10. package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
  11. package/docs/guide/providers-and-models.md +137 -54
  12. package/docs/guide/reference.md +27 -15
  13. package/docs/guide/sessions-and-context.md +69 -16
  14. package/docs/guide/skills-plugins-and-mcp.md +57 -20
  15. package/docs/guide/subagents-and-workflows.md +95 -29
  16. package/docs/guide/tasks-and-background-work.md +101 -26
  17. package/docs/guide/terminal-interface.md +24 -12
  18. package/docs/guide/tools.md +46 -5
  19. package/docs/guide/troubleshooting.md +13 -9
  20. package/docs/guide/windows.md +58 -31
  21. package/extensions/artifacts/gallery.ts +227 -0
  22. package/extensions/artifacts/index.ts +288 -0
  23. package/extensions/artifacts/store.ts +570 -0
  24. package/extensions/artifacts/viewer.ts +199 -0
  25. package/extensions/ask-user/description.ts +67 -0
  26. package/extensions/ask-user/dialogs.ts +80 -0
  27. package/extensions/ask-user/index.ts +37 -33
  28. package/extensions/ask-user/questions.ts +32 -7
  29. package/extensions/ask-user/widget.ts +45 -25
  30. package/extensions/auto-mode/actions.ts +6 -0
  31. package/extensions/auto-mode/cache.ts +88 -0
  32. package/extensions/auto-mode/classifier-prompt.ts +5 -4
  33. package/extensions/auto-mode/classifier.ts +290 -65
  34. package/extensions/auto-mode/config.ts +72 -20
  35. package/extensions/auto-mode/decision-log.ts +2 -0
  36. package/extensions/auto-mode/defaults.ts +1 -1
  37. package/extensions/auto-mode/denials.ts +124 -0
  38. package/extensions/auto-mode/git-checkout-programs.ts +187 -0
  39. package/extensions/auto-mode/git-status-meta.ts +159 -0
  40. package/extensions/auto-mode/history.ts +189 -0
  41. package/extensions/auto-mode/instructions.ts +58 -52
  42. package/extensions/auto-mode/model-select.ts +159 -134
  43. package/extensions/auto-mode/paths.ts +57 -5
  44. package/extensions/auto-mode/pause.ts +57 -2
  45. package/extensions/auto-mode/prompt.ts +37 -21
  46. package/extensions/auto-mode/read-only-options.ts +724 -0
  47. package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
  48. package/extensions/auto-mode/rules.ts +18 -1
  49. package/extensions/auto-mode/safety-floor.ts +361 -18
  50. package/extensions/auto-mode/setup-run.ts +20 -14
  51. package/extensions/auto-mode/shell-analysis.ts +764 -490
  52. package/extensions/auto-mode/shell-parse.ts +885 -0
  53. package/extensions/auto-mode/system-note.ts +33 -0
  54. package/extensions/auto-mode/transcript.ts +80 -51
  55. package/extensions/background/cron.ts +642 -0
  56. package/extensions/background/index.ts +614 -248
  57. package/extensions/background/loop-fire.ts +173 -0
  58. package/extensions/background/loop-preamble.ts +59 -0
  59. package/extensions/background/loop-skill.ts +163 -0
  60. package/extensions/background/monitor-batch.ts +53 -0
  61. package/extensions/background/noop-fold.ts +128 -0
  62. package/extensions/background/switch-guard.ts +56 -0
  63. package/extensions/background/wakeup.ts +178 -55
  64. package/extensions/bash/background.ts +53 -17
  65. package/extensions/bash/child.ts +7 -0
  66. package/extensions/bash/description.ts +188 -0
  67. package/extensions/bash/guards.ts +15 -67
  68. package/extensions/bash/index.ts +66 -35
  69. package/extensions/branding/compaction-keep.ts +63 -0
  70. package/extensions/branding/declined.ts +22 -0
  71. package/extensions/branding/index.ts +133 -9
  72. package/extensions/branding/prompt-editor.ts +53 -6
  73. package/extensions/branding/queued-edit.ts +31 -0
  74. package/extensions/branding/replaced-builtins.ts +68 -0
  75. package/extensions/branding/startup.ts +33 -71
  76. package/extensions/btw/index.ts +250 -78
  77. package/extensions/btw/panel.ts +228 -100
  78. package/extensions/btw/prompt.ts +41 -4
  79. package/extensions/cache-marks/index.ts +42 -0
  80. package/extensions/claude-compat/index.ts +7 -11
  81. package/extensions/claude-context/index.ts +436 -63
  82. package/extensions/compaction/cut.ts +157 -0
  83. package/extensions/compaction/index.ts +219 -75
  84. package/extensions/compaction/kept-thinking.ts +62 -0
  85. package/extensions/compaction/prompt.ts +15 -12
  86. package/extensions/context-budget/budget.ts +16 -2
  87. package/extensions/context-budget/index.ts +35 -15
  88. package/extensions/context-management/index.ts +36 -28
  89. package/extensions/doctor/build.ts +59 -8
  90. package/extensions/doctor/builtins.ts +84 -0
  91. package/extensions/doctor/cli.ts +7 -1
  92. package/extensions/doctor/compat.ts +88 -23
  93. package/extensions/doctor/dependencies.ts +41 -5
  94. package/extensions/doctor/fix-prompt.ts +9 -2
  95. package/extensions/doctor/index.ts +102 -58
  96. package/extensions/doctor/models.ts +102 -63
  97. package/extensions/doctor/presets.ts +42 -27
  98. package/extensions/doctor/report.ts +19 -2
  99. package/extensions/doctor/viewer.ts +49 -27
  100. package/extensions/effort/index.ts +141 -25
  101. package/extensions/effort/settings.ts +21 -0
  102. package/extensions/effort/slider.ts +24 -20
  103. package/extensions/exit/index.ts +32 -1
  104. package/extensions/file-tracker/index.ts +420 -42
  105. package/extensions/file-tracker/replay.ts +137 -24
  106. package/extensions/file-tracker/restore.ts +66 -0
  107. package/extensions/file-tracker/results.ts +66 -0
  108. package/extensions/file-tracker/shell-reads.ts +211 -0
  109. package/extensions/file-tracker/tracker.ts +45 -15
  110. package/extensions/footer/footer-line.ts +45 -24
  111. package/extensions/footer/index.ts +47 -2
  112. package/extensions/footer/pr.ts +34 -5
  113. package/extensions/hooks/compaction.ts +7 -0
  114. package/extensions/hooks/executor.ts +32 -30
  115. package/extensions/hooks/index.ts +209 -58
  116. package/extensions/hooks/matcher.ts +3 -0
  117. package/extensions/hooks/prompt-context.ts +59 -0
  118. package/extensions/hooks/protocol.ts +41 -3
  119. package/extensions/hooks/settings.ts +50 -19
  120. package/extensions/hooks/subagent-bridge.ts +2 -0
  121. package/extensions/hooks/trust.ts +26 -12
  122. package/extensions/idle-compact/index.ts +119 -0
  123. package/extensions/idle-compact/policy.ts +143 -0
  124. package/extensions/init/index.ts +11 -10
  125. package/extensions/init/prompt.ts +25 -32
  126. package/extensions/interrupted/index.ts +13 -1
  127. package/extensions/kept-thinking/index.ts +27 -0
  128. package/extensions/ledger/index.ts +46 -0
  129. package/extensions/ledger/wire.ts +154 -0
  130. package/extensions/lib/agent-cron.ts +146 -0
  131. package/extensions/lib/agent-loader.ts +18 -3
  132. package/extensions/lib/agent-view.ts +85 -0
  133. package/extensions/lib/anthropic-auth.ts +98 -0
  134. package/extensions/lib/anthropic-payload.ts +82 -1
  135. package/extensions/lib/anthropic-server-call.ts +230 -0
  136. package/extensions/lib/anthropic-server-tools.ts +485 -0
  137. package/extensions/lib/app-launch.mjs +94 -0
  138. package/extensions/lib/argument-hints.ts +190 -0
  139. package/extensions/lib/atomic-write.ts +6 -2
  140. package/extensions/lib/auto-memory.ts +75 -0
  141. package/extensions/lib/bash-parser.ts +84 -0
  142. package/extensions/lib/btw-fork.ts +93 -0
  143. package/extensions/lib/child-extensions.ts +52 -0
  144. package/extensions/lib/child-writes.ts +45 -0
  145. package/extensions/lib/claude-context.ts +515 -100
  146. package/extensions/lib/claude-external-includes.ts +68 -0
  147. package/extensions/lib/claude-id.ts +22 -0
  148. package/extensions/lib/claude-rules.ts +323 -0
  149. package/extensions/lib/claude-settings.ts +76 -4
  150. package/extensions/lib/compaction-boundary.ts +36 -0
  151. package/extensions/lib/compaction-keep.mjs +83 -0
  152. package/extensions/lib/config-mode.ts +112 -0
  153. package/extensions/lib/consent-dialogs.ts +63 -0
  154. package/extensions/lib/consent-preview.ts +5 -1
  155. package/extensions/lib/consent-stores.ts +48 -0
  156. package/extensions/lib/context-facts.ts +97 -0
  157. package/extensions/lib/covered-messages.ts +23 -0
  158. package/extensions/lib/deferred.ts +359 -25
  159. package/extensions/lib/dock-rows.ts +60 -0
  160. package/extensions/lib/empty-tool-result.ts +27 -0
  161. package/extensions/lib/environment-block.ts +76 -0
  162. package/extensions/lib/fetch-timeout.ts +24 -6
  163. package/extensions/{system-prompt → lib}/git-status.ts +51 -13
  164. package/extensions/lib/git.ts +75 -13
  165. package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
  166. package/extensions/lib/grammars/README.md +2 -0
  167. package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
  168. package/extensions/lib/guide-docs.ts +45 -0
  169. package/extensions/lib/headless-output.ts +46 -0
  170. package/extensions/lib/hook-gate.ts +7 -7
  171. package/extensions/lib/interrupt.ts +20 -4
  172. package/extensions/lib/key-input.ts +455 -0
  173. package/extensions/lib/keys.ts +14 -1
  174. package/extensions/lib/ledger/fold-memo.ts +49 -0
  175. package/extensions/lib/ledger/fold.ts +265 -0
  176. package/extensions/lib/ledger/layout.ts +72 -0
  177. package/extensions/lib/ledger/plan.ts +102 -0
  178. package/extensions/lib/ledger/records.ts +276 -0
  179. package/extensions/lib/ledger/state.ts +47 -0
  180. package/extensions/lib/ledger/validate.ts +141 -0
  181. package/extensions/lib/ledger/wire.ts +632 -0
  182. package/extensions/lib/local-command.ts +71 -19
  183. package/extensions/lib/mcp-overrides.ts +1 -1
  184. package/extensions/lib/mcp-share.ts +46 -0
  185. package/extensions/lib/memory-content.ts +146 -0
  186. package/extensions/lib/memory-index.ts +18 -0
  187. package/extensions/lib/memory.ts +134 -123
  188. package/extensions/lib/model-catalog/huggingface.json +178 -0
  189. package/extensions/lib/model-catalog/models-dev.json +18533 -0
  190. package/extensions/lib/model-catalog/openrouter.json +5050 -0
  191. package/extensions/lib/model-catalog-data.ts +513 -0
  192. package/extensions/lib/model-catalog.ts +462 -0
  193. package/extensions/lib/model-choice-warnings.ts +137 -0
  194. package/extensions/lib/model-pairings.ts +152 -0
  195. package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
  196. package/extensions/lib/model-policy.ts +112 -15
  197. package/extensions/lib/model-tier.ts +283 -355
  198. package/extensions/lib/model-unusable.ts +1 -1
  199. package/extensions/lib/newer-model.ts +100 -0
  200. package/extensions/lib/notices.ts +87 -0
  201. package/extensions/lib/notifications.ts +357 -73
  202. package/extensions/lib/one-code-settings.ts +74 -8
  203. package/extensions/lib/open-browser.ts +63 -0
  204. package/extensions/lib/openrouter-generation.ts +94 -0
  205. package/extensions/lib/operation-deadline.ts +30 -0
  206. package/extensions/lib/original-command.ts +4 -4
  207. package/extensions/lib/package-version.ts +1 -1
  208. package/extensions/lib/panel-focus.ts +134 -0
  209. package/extensions/lib/paths.ts +17 -2
  210. package/extensions/lib/permission-gate.ts +72 -6
  211. package/extensions/lib/persisted-output.ts +60 -8
  212. package/extensions/lib/pi-ai-estimate.ts +19 -3
  213. package/extensions/lib/pi-version.ts +38 -2
  214. package/extensions/lib/plan-mode-channels.ts +42 -5
  215. package/extensions/lib/plugins.ts +26 -17
  216. package/extensions/lib/powershell-parse-script.ts +208 -0
  217. package/extensions/lib/powershell-parser.ts +344 -0
  218. package/extensions/lib/previous-mark.ts +101 -0
  219. package/extensions/lib/process-state.ts +48 -0
  220. package/extensions/lib/process-tree.ts +132 -8
  221. package/extensions/lib/prompt-options.ts +82 -0
  222. package/extensions/lib/queued-delivery.ts +44 -0
  223. package/extensions/lib/reminders.ts +370 -358
  224. package/extensions/lib/replaced-builtins.mjs +199 -0
  225. package/extensions/lib/replay-call.ts +70 -0
  226. package/extensions/lib/request-replay.ts +547 -0
  227. package/extensions/lib/scratchpad.ts +81 -14
  228. package/extensions/lib/send-now-channel.ts +7 -0
  229. package/extensions/lib/session-model-tier.ts +51 -0
  230. package/extensions/lib/session-work.ts +64 -0
  231. package/extensions/lib/settings-channels.ts +4 -4
  232. package/extensions/lib/shell-exit-semantics.ts +170 -0
  233. package/extensions/lib/shell-quote.ts +11 -0
  234. package/extensions/lib/shell-result.ts +70 -0
  235. package/extensions/lib/shell-spawn.ts +70 -6
  236. package/extensions/lib/shell-tool.ts +75 -25
  237. package/extensions/lib/side-call-cache.ts +124 -0
  238. package/extensions/lib/side-call-run.ts +96 -0
  239. package/extensions/lib/side-call-usage.ts +38 -0
  240. package/extensions/lib/side-call.ts +24 -1
  241. package/extensions/lib/single-flight.ts +18 -0
  242. package/extensions/lib/skill-body.ts +42 -0
  243. package/extensions/lib/skill-scan.ts +63 -24
  244. package/extensions/lib/spill-file.ts +140 -0
  245. package/extensions/lib/subagent-handback.ts +112 -0
  246. package/extensions/lib/system-role.ts +156 -0
  247. package/extensions/lib/terminal-text.ts +114 -0
  248. package/extensions/lib/text-width.ts +47 -16
  249. package/extensions/lib/timer-ops.ts +20 -0
  250. package/extensions/lib/tool-additions.ts +129 -0
  251. package/extensions/lib/tool-call-corruption.ts +134 -0
  252. package/extensions/lib/tool-path.ts +66 -0
  253. package/extensions/lib/tool-variants.ts +69 -0
  254. package/extensions/lib/tui-render.ts +255 -27
  255. package/extensions/lib/turn-budget-layout.ts +159 -0
  256. package/extensions/lib/update-check.mjs +15 -2
  257. package/extensions/lib/usage-bus.ts +1 -1
  258. package/extensions/lib/user-denial.ts +38 -0
  259. package/extensions/lib/workspace-channel.ts +16 -0
  260. package/extensions/lib/worktree-channel.ts +31 -3
  261. package/extensions/lib/worktree-isolation.ts +40 -10
  262. package/extensions/lsp/client.ts +8 -9
  263. package/extensions/lsp/index.ts +76 -12
  264. package/extensions/lsp/keep-alive.ts +1 -1
  265. package/extensions/lsp/trust.ts +119 -0
  266. package/extensions/lsp/watcher.ts +1 -1
  267. package/extensions/mcp/announce.ts +90 -43
  268. package/extensions/mcp/client.ts +161 -48
  269. package/extensions/mcp/config.ts +132 -8
  270. package/extensions/mcp/discovery.ts +19 -0
  271. package/extensions/mcp/index.ts +220 -121
  272. package/extensions/mcp/oauth/browser.ts +38 -17
  273. package/extensions/mcp/oauth/flow.ts +25 -17
  274. package/extensions/mcp/oauth/provider.ts +14 -8
  275. package/extensions/mcp/oauth/store.ts +34 -16
  276. package/extensions/mcp/panel/keys.ts +12 -11
  277. package/extensions/mcp/panel/render.ts +9 -6
  278. package/extensions/mcp/schema.ts +104 -5
  279. package/extensions/mcp/trust.ts +164 -73
  280. package/extensions/memory/entries.ts +23 -14
  281. package/extensions/memory/index.ts +134 -28
  282. package/extensions/memory/open-external.ts +33 -18
  283. package/extensions/memory/panel.ts +135 -26
  284. package/extensions/model-default/index.ts +86 -19
  285. package/extensions/newer-model/index.ts +31 -0
  286. package/extensions/notebook/description.ts +18 -0
  287. package/extensions/notebook/index.ts +6 -5
  288. package/extensions/notebook/notebook.ts +1 -1
  289. package/extensions/permissions/index.ts +1426 -398
  290. package/extensions/permissions/matcher.ts +425 -74
  291. package/extensions/permissions/modes.ts +11 -4
  292. package/extensions/permissions/panel/host.ts +130 -0
  293. package/extensions/permissions/panel/keys.ts +48 -0
  294. package/extensions/permissions/panel/render.ts +450 -0
  295. package/extensions/permissions/panel/state.ts +550 -0
  296. package/extensions/permissions/powershell-cmdlets.ts +198 -0
  297. package/extensions/permissions/powershell-paths.ts +197 -0
  298. package/extensions/permissions/powershell-rules.ts +207 -280
  299. package/extensions/permissions/powershell-tree.ts +557 -0
  300. package/extensions/permissions/project-trust.ts +27 -11
  301. package/extensions/permissions/protected-paths.ts +14 -2
  302. package/extensions/permissions/session-grant.ts +6 -0
  303. package/extensions/permissions/settings.ts +166 -31
  304. package/extensions/permissions/subagent-gate.ts +25 -0
  305. package/extensions/permissions/workspace.ts +78 -0
  306. package/extensions/plan-mode/index.ts +276 -92
  307. package/extensions/plan-mode/reminder.ts +89 -21
  308. package/extensions/plan-mode/run-state.ts +26 -0
  309. package/extensions/plan-mode/texts.ts +177 -0
  310. package/extensions/plan-mode/viewer.ts +92 -27
  311. package/extensions/plugins/counts.ts +3 -3
  312. package/extensions/plugins/index.ts +64 -46
  313. package/extensions/plugins/install/install.ts +16 -0
  314. package/extensions/plugins/marketplace/sync.ts +9 -3
  315. package/extensions/plugins/marketplace/types.ts +3 -1
  316. package/extensions/plugins/panel/keys.ts +21 -24
  317. package/extensions/plugins/panel/render.ts +12 -6
  318. package/extensions/plugins/shell-expand.ts +107 -0
  319. package/extensions/plugins/template.ts +32 -15
  320. package/extensions/powershell/child.ts +7 -0
  321. package/extensions/powershell/description.ts +38 -6
  322. package/extensions/powershell/guards.ts +1 -1
  323. package/extensions/powershell/index.ts +14 -7
  324. package/extensions/recap/index.ts +73 -71
  325. package/extensions/recap/prompt.ts +2 -2
  326. package/extensions/recap/scheduler.ts +3 -5
  327. package/extensions/search-tools/grep-merge.ts +39 -0
  328. package/extensions/search-tools/index.ts +0 -0
  329. package/extensions/send-now/chord.ts +62 -0
  330. package/extensions/send-now/index.ts +133 -0
  331. package/extensions/session-title/index.ts +3 -0
  332. package/extensions/session-title/title.ts +4 -1
  333. package/extensions/skill/announce.ts +30 -0
  334. package/extensions/skill/code-review.ts +97 -0
  335. package/extensions/skill/index.ts +469 -132
  336. package/extensions/skill/invoke.ts +118 -75
  337. package/extensions/skill/listing.ts +133 -9
  338. package/extensions/skill/panel/keys.ts +15 -16
  339. package/extensions/skill/panel/render.ts +5 -2
  340. package/extensions/skill/panel/state.ts +12 -1
  341. package/extensions/subagents/agent-args.ts +39 -0
  342. package/extensions/subagents/agent-description.ts +132 -0
  343. package/extensions/subagents/agents.ts +146 -31
  344. package/extensions/subagents/default-model.ts +2 -3
  345. package/extensions/subagents/delegation-steer.ts +4 -4
  346. package/extensions/subagents/fork-cache.ts +82 -0
  347. package/extensions/subagents/guide-agent.ts +189 -0
  348. package/extensions/subagents/hand-back-review.ts +3 -0
  349. package/extensions/subagents/index.ts +1594 -852
  350. package/extensions/subagents/live-runs.ts +26 -6
  351. package/extensions/subagents/model-select.ts +38 -25
  352. package/extensions/subagents/outcome.ts +5 -2
  353. package/extensions/subagents/panel-keys.ts +73 -31
  354. package/extensions/subagents/panel-render.ts +120 -96
  355. package/extensions/subagents/panel-widget.ts +60 -89
  356. package/extensions/subagents/parent-mode.ts +50 -0
  357. package/extensions/subagents/pending-claim.ts +18 -1
  358. package/extensions/subagents/runner.ts +135 -80
  359. package/extensions/subagents/runs.ts +44 -5
  360. package/extensions/subagents/send-to-main-tool.ts +1 -1
  361. package/extensions/subagents/session-turns.ts +32 -8
  362. package/extensions/subagents/shell-panel.ts +36 -178
  363. package/extensions/subagents/tasks-dialog.ts +170 -0
  364. package/extensions/subagents/viewport-mirror.ts +95 -0
  365. package/extensions/subagents/worktree.ts +57 -13
  366. package/extensions/system-prompt/environment.ts +19 -19
  367. package/extensions/system-prompt/idle-turn.ts +65 -0
  368. package/extensions/system-prompt/index.ts +165 -47
  369. package/extensions/system-prompt/template.ts +34 -51
  370. package/extensions/system-prompt/tiers/common.ts +23 -64
  371. package/extensions/system-prompt/tiers/frontier.ts +32 -11
  372. package/extensions/system-prompt/tiers/low.ts +67 -45
  373. package/extensions/system-prompt/tiers/mid.ts +96 -38
  374. package/extensions/system-reminder/index.ts +204 -70
  375. package/extensions/tasks/descriptions.ts +190 -0
  376. package/extensions/tasks/index.ts +120 -41
  377. package/extensions/tasks/store.ts +83 -16
  378. package/extensions/tool-call-corruption/child.ts +7 -0
  379. package/extensions/tool-call-corruption/index.ts +237 -0
  380. package/extensions/tool-search/index.ts +366 -144
  381. package/extensions/tool-style/index.ts +68 -10
  382. package/extensions/tool-style/write-description.ts +40 -0
  383. package/extensions/tool-style/write-render.ts +237 -0
  384. package/extensions/turn-duration/index.ts +7 -3
  385. package/extensions/turn-duration/line.ts +47 -4
  386. package/extensions/web/backends.ts +6 -9
  387. package/extensions/web/description.ts +50 -0
  388. package/extensions/web/index.ts +118 -53
  389. package/extensions/web/native-call.ts +15 -0
  390. package/extensions/web/native-results.ts +41 -0
  391. package/extensions/web-fetch/description.ts +42 -0
  392. package/extensions/web-fetch/extract.ts +53 -6
  393. package/extensions/web-fetch/index.ts +125 -76
  394. package/extensions/web-fetch/page-content.ts +19 -0
  395. package/extensions/web-fetch/response.ts +49 -0
  396. package/extensions/web-fetch/summarize.ts +9 -10
  397. package/extensions/workflow/agent-session.ts +108 -18
  398. package/extensions/workflow/args.ts +24 -0
  399. package/extensions/workflow/description.ts +67 -0
  400. package/extensions/workflow/globals.ts +17 -3
  401. package/extensions/workflow/index.ts +157 -162
  402. package/extensions/workflow/records.ts +1 -0
  403. package/extensions/workflow/run-manager.ts +33 -6
  404. package/extensions/workflow/saved-workflows.ts +3 -2
  405. package/extensions/workflow/types.ts +22 -1
  406. package/extensions/workflow/viewer.ts +54 -24
  407. package/extensions/workflow/widget.ts +20 -0
  408. package/extensions/worktree/descriptions.ts +91 -0
  409. package/extensions/worktree/guards.ts +316 -54
  410. package/extensions/worktree/index.ts +162 -44
  411. package/extensions/worktree/powershell-guards.ts +167 -0
  412. package/extensions/worktree/rewrite.ts +14 -16
  413. package/package.json +21 -7
  414. package/skills/artifact-design/SKILL.md +85 -0
  415. package/skills/code-review/SKILL.md +20 -20
  416. package/skills/code-review/cells/arguments-and-flags.md +19 -0
  417. package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
  418. package/skills/code-review/cells/high-8-inline.md +122 -0
  419. package/skills/code-review/cells/low-1-pass-max4.md +27 -0
  420. package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
  421. package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
  422. package/skills/code-review/cells/medium-3x5-verify.md +132 -0
  423. package/skills/code-review/cells/minimal-prompt.md +32 -0
  424. package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
  425. package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
  426. package/skills/fewer-permission-prompts/SKILL.md +1 -1
  427. package/skills/loop/SKILL.md +11 -0
  428. package/skills/security-review/SKILL.md +2 -5
  429. package/skills/simplify/SKILL.md +7 -5
  430. package/skills/workflow-authoring/SKILL.md +143 -0
  431. package/themes/onecode-light.json +1 -1
  432. package/themes/onecode.json +1 -1
  433. package/types/app.d.ts +58 -0
  434. package/types/community-packages.d.ts +26 -1
  435. package/extensions/auto-mode/recoverability.ts +0 -229
  436. package/extensions/lib/capability-index.ts +0 -466
  437. package/extensions/lib/model-facts.generated.json +0 -2675
  438. package/extensions/lib/model-facts.ts +0 -162
  439. package/extensions/tool-search/announce.ts +0 -42
@@ -13,31 +13,64 @@
13
13
  * turn puts the block on the queue before the turn's next request — until
14
14
  * 2026-09-05 the mid-turn requests ran with no plan reminder while every edit
15
15
  * was denied "see the plan-mode reminder" (STEERING-REVIEW-2026-09-05 M3) —
16
- * and on before_agent_start, which covers a session that STARTS in plan mode
17
- * before this extension has seen a context (defaultMode: "plan"; permissions'
18
- * session_start runs first) and runs after every extension's session_start,
19
- * so restoring a previous path from the session branch never races a fresh
20
- * allocation.
16
+ * and on session_start, after restoring a previous path and its baseline.
17
+ * A session that STARTS in plan mode must install the block before the first
18
+ * prompt is stamped: before_agent_start is already too late to anchor it to
19
+ * that prompt. before_agent_start also re-announces the existing state.
21
20
  */
22
21
 
23
- import { existsSync, readFileSync } from "node:fs";
24
- import { join } from "node:path";
25
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
22
+ import { copyFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
23
+ import { dirname, join } from "node:path";
24
+ import { defineTool, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
25
+ import { registerFormTool } from "../lib/tool-variants.ts";
26
26
  import { Type } from "typebox";
27
27
  import { DEFER_CHANNEL } from "../lib/deferred.ts";
28
28
  import { oneCodeStateDir } from "../lib/paths.ts";
29
29
  import { REMINDER_CHANNEL } from "../lib/reminders.ts";
30
+ import { canShowCustomUi } from "../lib/headless-output.ts";
30
31
  import { ccToolRenderers, safeThemePaint } from "../lib/tui-render.ts";
32
+ import { userDenialText } from "../lib/user-denial.ts";
31
33
  import type { PermissionMode } from "../permissions/matcher.ts";
32
34
  import { PERMISSION_STATUS_CHANNEL, type PermissionStatus } from "../permissions/modes.ts";
33
35
  import { buildPlanModeReminder } from "./reminder.ts";
34
36
  import { randomSlug } from "./slug.ts";
35
- import { clampOffset, decodeViewerKey, initialPlanChoice, type PlanChoice, renderPlanViewer, wrapPlanText } from "./viewer.ts";
37
+ import { readState, recordState } from "../lib/ledger/state.ts";
36
38
 
37
- import { MODE_CHANNEL, PLAN_FILE_CHANNEL } from "../lib/plan-mode-channels.ts";
39
+ /** A plan file larger than this is named after a compaction, not inlined: the model reads it when it needs it. */
40
+ const PLAN_REFERENCE_CHARS = 40_000;
41
+
42
+ /** The plan the summary replaced, re-attached after a compaction. */
43
+ function planFileReferenceText(path: string, content: string): string {
44
+ if (content.length > PLAN_REFERENCE_CHARS) return `A plan file exists from plan mode at: ${path}\n\nIt is too large to include here (${content.length.toLocaleString("en-US")} characters); read it before continuing.\n\nIf this plan is relevant to the current work and not already complete, continue working on it.`;
45
+ return `A plan file exists from plan mode at: ${path}\n\nPlan contents:\n\n${content}\n\nIf this plan is relevant to the current work and not already complete, continue working on it.`;
46
+ }
47
+
48
+ import { PLAN_ENTRY_STATE, parsePlanRun, type PlanRun } from "./run-state.ts";
49
+ import {
50
+ approvedPlanText,
51
+ ENTER_PLAN_MODE_DESCRIPTION,
52
+ enteredPlanModeText,
53
+ enterPlanModeDescription,
54
+ EXIT_PLAN_MODE_DESCRIPTION,
55
+ exitedPlanModeText,
56
+ } from "./texts.ts";
57
+ import { createMarkdownProse } from "../subagents/prose.ts";
58
+ import type { ProseRenderer } from "../subagents/panel-render.ts";
59
+ import {
60
+ clampOffset,
61
+ decodeViewerKey,
62
+ initialPlanChoice,
63
+ KEEP_PLANNING_QUESTION,
64
+ type PlanChoice,
65
+ planLines,
66
+ renderPlanViewer,
67
+ runPlanApproval,
68
+ selectPlanChoice,
69
+ } from "./viewer.ts";
70
+
71
+ import { MODE_CHANNEL, PLAN_FILE_CHANNEL, PLAN_FILE_ENTRY, planFileOnBranch, planFileRecords, type PlanFileRecord } from "../lib/plan-mode-channels.ts";
72
+ import { panelUi } from "../lib/panel-focus.ts";
38
73
  export { MODE_CHANNEL, PLAN_FILE_CHANNEL };
39
- /** Session entry type persisting the allocated path across resume/branch. */
40
- const PLAN_FILE_ENTRY = "one-code:plan-mode-file";
41
74
 
42
75
  const PLANS_DIR = () => join(oneCodeStateDir(), "plans");
43
76
 
@@ -55,48 +88,126 @@ export default function planModeExtension(pi: ExtensionAPI) {
55
88
  /** The last mode broadcast by permissions; undefined until the first status arrives. */
56
89
  let currentMode: string | undefined;
57
90
  /**
58
- * The mode the session was in when it entered plan mode, offered first on
59
- * exit (Claude Code's ExitPlanMode restores `prePlanMode`). Unknown for a
60
- * session that STARTED in plan mode — the first status seen is plan itself —
61
- * so that session gets the fixed list. Until 2026-09-05 the exit could only
62
- * escalate: a default-mode session that planned was offered auto
63
- * (PERMISSIONS-REVIEW-2026-09-05 M4).
91
+ * The live mode the last switch into plan mode came from. It is recorded on
92
+ * the branch only when that switch begins a run (`refresh` finds no run
93
+ * recorded for the branch's plan file). A switch that merely restores a run,
94
+ * permissions following a /tree to a branch in plan mode, keeps the branch's
95
+ * own value, since the live mode before it belongs to the abandoned branch.
64
96
  */
65
- let modeBeforePlan: PermissionMode | undefined;
66
- let planFilePath: string | undefined;
97
+ let enteredFrom: PermissionMode | undefined;
98
+ /** Whether the session can enter bypassPermissions, as permissions last said: the exit offers it back only then. */
99
+ let bypassAvailable = false;
67
100
 
68
- /** Restore the branch's plan file, else allocate a fresh slug. */
69
- const ensurePlanFile = (ctx: ExtensionContext): string => {
70
- if (planFilePath) return planFilePath;
71
-
72
- for (const entry of ctx.sessionManager.getBranch()) {
73
- if (entry.type !== "custom" || entry.customType !== PLAN_FILE_ENTRY) continue;
74
- const path = (entry.data as { path?: unknown } | undefined)?.path;
75
- if (typeof path === "string") planFilePath = path;
76
- }
77
- if (planFilePath) return planFilePath;
101
+ /**
102
+ * The current run as the branch recorded it when plan mode came on: its plan
103
+ * file, whether that file existed then (which picks Claude Code's "Plan File
104
+ * Info" line, fixed for the run so the sticky block stays byte-identical turn
105
+ * after turn, plan-mode/reminder.ts), and the mode it came from, offered
106
+ * first on exit (Claude Code's ExitPlanMode restores `prePlanMode`). Read
107
+ * from the branch every time, never kept here, so a /reload, a resume and a
108
+ * /tree each see the run of the branch they land on. Until 2026-09-05 the
109
+ * exit could only escalate: a default-mode session that planned was offered
110
+ * auto (PERMISSIONS-REVIEW-2026-09-05 M4).
111
+ */
112
+ const runOnBranch = (ctx: ExtensionContext, path: string): PlanRun | undefined => parsePlanRun(readState(ctx, PLAN_ENTRY_STATE), path);
113
+ /** Record the current run on the branch, for `runOnBranch` to read back. */
114
+ const recordRun = (path: string, run: PlanRun) => recordState(pi, PLAN_ENTRY_STATE, { path, ...run });
78
115
 
116
+ const freshPlanPath = (): string => {
79
117
  let path = join(PLANS_DIR(), `${randomSlug()}.md`);
80
118
  for (let attempt = 0; attempt < 5 && existsSync(path); attempt++) {
81
119
  path = join(PLANS_DIR(), `${randomSlug()}.md`);
82
120
  }
83
- planFilePath = path;
84
- pi.appendEntry(PLAN_FILE_ENTRY, { path });
85
121
  return path;
86
122
  };
87
123
 
88
- /** Re-announce path + reminder; every-turn re-emits replace by key. */
89
- const refresh = (ctx: ExtensionContext) => {
124
+ /**
125
+ * A fork's own copy of the plan another session recorded, as Claude Code
126
+ * gives a forked session, so the two sessions never overwrite each other's plan
127
+ * (decisions/modes.md). The copy this session already made of it, on any
128
+ * of its branches, is reused. The run moves to the copy with the mode it
129
+ * came from, and says whether the copy exists (the parent's run may have
130
+ * begun before its plan did).
131
+ */
132
+ const forkPlanFile = (ctx: ExtensionContext, parent: PlanFileRecord, sessionId: string): string => {
133
+ const made = planFileRecords(ctx.sessionManager.getEntries()).find((r) => r.sessionId === sessionId && r.copiedFrom === parent.path);
134
+ const path = made?.path ?? freshPlanPath();
135
+ if (!made && existsSync(parent.path)) {
136
+ try {
137
+ mkdirSync(dirname(path), { recursive: true });
138
+ copyFileSync(parent.path, path);
139
+ } catch (error) {
140
+ if (ctx.hasUI) ctx.ui.notify(`Could not copy the plan at ${parent.path} for this fork (${(error as Error).message}); its plan starts empty at ${path}.`, "warning");
141
+ }
142
+ }
143
+ const run = runOnBranch(ctx, parent.path);
144
+ pi.appendEntry(PLAN_FILE_ENTRY, { path, sessionId, copiedFrom: parent.path } satisfies PlanFileRecord);
145
+ // The standing block names the new path anyway, so it also says whether the copy exists.
146
+ if (run) recordRun(path, { ...run, existed: existsSync(path) });
147
+ return path;
148
+ };
149
+
150
+ /**
151
+ * The branch's plan file, else a fresh slug recorded on the branch. A
152
+ * record another session made (a fork copies its parent's entries) gets
153
+ * this session's own copy, and so does a record that names no session (an
154
+ * older session's): only a record this session made is its own.
155
+ * Never cached: on /tree, permissions' session_tree switches the mode (and
156
+ * this extension refreshes) before this extension's own session_tree runs,
157
+ * so a cached path would be the abandoned branch's.
158
+ */
159
+ const ensurePlanFile = (ctx: ExtensionContext, onBranch = planFileRecords(ctx.sessionManager.getBranch()).at(-1)): string => {
160
+ const sessionId = ctx.sessionManager.getSessionId();
161
+ if (onBranch?.sessionId === sessionId) return onBranch.path;
162
+ if (onBranch) return forkPlanFile(ctx, onBranch, sessionId);
163
+
164
+ const path = freshPlanPath();
165
+ pi.appendEntry(PLAN_FILE_ENTRY, { path, sessionId } satisfies PlanFileRecord);
166
+ return path;
167
+ };
168
+
169
+ /** A branch that names a plan file owns it before anything reads it: a fork copies its parent's now. */
170
+ const ownPlanFile = (ctx: ExtensionContext) => {
171
+ const onBranch = planFileRecords(ctx.sessionManager.getBranch()).at(-1);
172
+ if (onBranch?.path) ensurePlanFile(ctx, onBranch);
173
+ };
174
+
175
+ /** Re-announce path + reminder; a re-emit replaces the sticky state by key. */
176
+ const refresh = (ctx: ExtensionContext, toolCallId?: string) => {
90
177
  const path = ensurePlanFile(ctx);
178
+ let run = runOnBranch(ctx, path);
179
+ if (!run) {
180
+ // A run begins: record what it began with.
181
+ run = { existed: existsSync(path), ...(enteredFrom ? { modeBeforePlan: enteredFrom } : {}) };
182
+ recordRun(path, run);
183
+ }
91
184
  pi.events.emit(PLAN_FILE_CHANNEL, { path });
92
185
  pi.events.emit(REMINDER_CHANNEL, {
93
- text: buildPlanModeReminder(path),
94
- scope: "every-turn",
186
+ text: buildPlanModeReminder(path, run.existed),
95
187
  key: "permission-mode",
96
188
  placement: "sticky-append",
189
+ toolCallId,
97
190
  });
98
191
  };
99
192
 
193
+ /**
194
+ * pi-tui's Markdown renderer for the approval dialog, loaded on first use.
195
+ * A failed load is not cached (the theme may not be ready yet); the dialog
196
+ * then wraps the plan as plain text.
197
+ */
198
+ let markdownLoad: Promise<ProseRenderer | undefined> | undefined;
199
+ const planMarkdown = (): Promise<ProseRenderer | undefined> =>
200
+ (markdownLoad ??= createMarkdownProse().catch(() => {
201
+ markdownLoad = undefined;
202
+ return undefined;
203
+ }));
204
+
205
+ /** Plan approvals the user dismissed without words: the turn stops once pi records their result. */
206
+ const abortAfterResult = new Set<string>();
207
+ pi.on("tool_execution_end", (event, ctx) => {
208
+ if (abortAfterResult.delete(event.toolCallId)) ctx.abort();
209
+ });
210
+
100
211
  /** The last context seen, for a refresh triggered off the bus (no ctx of its own). */
101
212
  let lastCtx: ExtensionContext | undefined;
102
213
 
@@ -105,18 +216,32 @@ export default function planModeExtension(pi: ExtensionAPI) {
105
216
  if (typeof status?.mode !== "string") return;
106
217
  const previous = currentMode;
107
218
  currentMode = status.mode;
108
- if (currentMode === "plan" && previous !== "plan") modeBeforePlan = previous as PermissionMode | undefined;
109
- // Entering plan mode mid-turn: permissions' setMode has just dropped the
219
+ bypassAvailable = status.bypassAvailable === true;
220
+ // Unknown for a session that STARTED in plan mode (the first status seen
221
+ // is plan itself): a run it begins offers the fixed list on exit.
222
+ if (currentMode === "plan" && previous !== "plan") enteredFrom = previous as PermissionMode | undefined;
223
+ // The run ended: the next entry decides afresh.
224
+ if (previous === "plan" && currentMode !== "plan") recordState(pi, PLAN_ENTRY_STATE, null);
225
+ // Entering plan mode mid-turn: permissions' setMode has just closed the
110
226
  // shared "permission-mode" key (before broadcasting this status, so this
111
227
  // re-add is not undone) and will announce the change on the next tool
112
228
  // result; the standing block has to be back on the queue for that same
113
229
  // request. The path is announced over PLAN_FILE_CHANNEL inside refresh,
114
230
  // synchronously, so setMode's announcement can name it.
115
- if (currentMode === "plan" && previous !== "plan" && lastCtx) refresh(lastCtx);
231
+ if (currentMode === "plan" && previous !== "plan" && lastCtx) refresh(lastCtx, status.toolCallId);
116
232
  });
117
233
 
118
234
  pi.on("session_start", (_event, ctx) => {
119
235
  lastCtx = ctx;
236
+ ownPlanFile(ctx);
237
+ if (currentMode === "plan") refresh(ctx);
238
+ });
239
+ // The plan file and its run follow the branch (both are read from it): a
240
+ // /tree switch takes the selected branch's, or allocates one there.
241
+ pi.on("session_tree", (_event, ctx) => {
242
+ lastCtx = ctx;
243
+ ownPlanFile(ctx);
244
+ if (currentMode === "plan") refresh(ctx);
120
245
  });
121
246
 
122
247
  pi.on("before_agent_start", (_event, ctx) => {
@@ -124,42 +249,69 @@ export default function planModeExtension(pi: ExtensionAPI) {
124
249
  if (currentMode === "plan") refresh(ctx);
125
250
  });
126
251
 
127
- pi.registerTool({
128
- name: "enter_plan_mode",
129
- label: "Enter plan mode",
130
- ...ccToolRenderers("Enter plan mode"),
131
- description:
132
- "Enter plan mode: read-only investigation to design an approach before changing anything. Most tasks do not need it.\n" +
133
- "For a small or clearly-scoped change, act directly instead. Enter plan mode only for multi-file work whose design is genuinely unclear, or when the user asks for a plan. In plan mode only read-only tools are available, plus one writable file: the plan file whose path you are told, where you build the plan incrementally.",
134
- parameters: Type.Object({}),
135
- async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
136
- pi.events.emit(MODE_CHANNEL, { mode: "plan" });
137
- // setMode broadcasts synchronously over the status channel, so the
138
- // listener above has already installed the block and announced the
139
- // path (or the mode was plan already and both stand); name the file in
140
- // the tool result too so the model can start writing this same turn.
141
- const path = ensurePlanFile(ctx);
142
- return {
143
- content: [
144
- {
145
- type: "text",
146
- text: `Entered plan mode. Only read-only tools are available, except for your plan file at ${path} — build your plan there incrementally, then call exit_plan_mode to request approval.`,
147
- },
148
- ],
149
- details: { planFilePath: path },
150
- };
151
- },
252
+ // A compaction summarizes the plan away and opens an epoch, where nothing
253
+ // above has to stay byte-stable (R3-M20). The plan file is re-attached
254
+ // whenever one exists, plan mode or not, as Claude Code does; in plan mode
255
+ // the run is recorded again, so the new epoch's block says whether the file
256
+ // exists now.
257
+ pi.on("session_compact", (_event, ctx) => {
258
+ lastCtx = ctx;
259
+ const path = planFileOnBranch(ctx.sessionManager.getBranch());
260
+ if (!path) return;
261
+ if (currentMode === "plan") {
262
+ recordRun(path, { ...runOnBranch(ctx, path), existed: existsSync(path) });
263
+ refresh(ctx);
264
+ }
265
+ let content = "";
266
+ try {
267
+ content = existsSync(path) ? readFileSync(path, "utf8") : "";
268
+ } catch {
269
+ // The re-attach is best effort, like the other post-compaction restores:
270
+ // an unreadable path (a directory, no permission) attaches nothing.
271
+ }
272
+ if (!content.trim()) return;
273
+ pi.events.emit(REMINDER_CHANNEL, { text: planFileReferenceText(path, content), placement: "user-prepend", orTail: true, untilCompaction: true });
152
274
  });
153
275
 
276
+ /**
277
+ * enter_plan_mode's description follows the model's tier: Claude Code's,
278
+ * which prefers plan mode, on frontier and workhorse, and One Code's, which
279
+ * says most tasks do not need it, on cheap and tiny (`texts.ts`, through the
280
+ * shared lib/tool-variants.ts). A re-registration keeps the tool's active
281
+ * state, so the deferred tool stays deferred, and it sets no prompt snippet,
282
+ * so the system prompt never moves with it.
283
+ */
284
+ registerFormTool(
285
+ pi,
286
+ defineTool({
287
+ name: "enter_plan_mode",
288
+ label: "Enter plan mode",
289
+ ...ccToolRenderers("Enter plan mode"),
290
+ description: ENTER_PLAN_MODE_DESCRIPTION,
291
+ parameters: Type.Object({}),
292
+ async execute(toolCallId, _params, _signal, _onUpdate, ctx) {
293
+ pi.events.emit(MODE_CHANNEL, { mode: "plan", toolCallId });
294
+ // setMode broadcasts synchronously over the status channel, so the
295
+ // listener above has already installed the block and announced the
296
+ // path (or the mode was plan already and both stand); name the file in
297
+ // the tool result too so the model can start writing this same turn.
298
+ const path = ensurePlanFile(ctx);
299
+ return {
300
+ content: [{ type: "text", text: enteredPlanModeText(path) }],
301
+ details: { planFilePath: path },
302
+ };
303
+ },
304
+ }),
305
+ enterPlanModeDescription,
306
+ );
307
+
154
308
  pi.registerTool({
155
309
  name: "exit_plan_mode",
156
310
  label: "Exit plan mode",
157
311
  ...ccToolRenderers("Exit plan mode"),
158
- description:
159
- "Signal that planning is complete and ask the user to approve the plan.\n" +
160
- "Takes no parameters: the plan is read from the plan file named in the plan-mode reminder, which you must have written before calling this. The user reviews that file's contents.",
312
+ description: EXIT_PLAN_MODE_DESCRIPTION,
161
313
  parameters: Type.Object({}),
162
- async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
314
+ async execute(toolCallId, _params, signal, _onUpdate, ctx) {
163
315
  // Guard the out-of-sequence call: without this, a cold exit_plan_mode
164
316
  // (never entered plan mode) allocates a fresh empty plan file and blames
165
317
  // "the plan file is empty" — a plausible but wrong cause a weak model
@@ -213,11 +365,15 @@ export default function planModeExtension(pi: ExtensionAPI) {
213
365
  // The mode the session was in before planning leads the list (Claude
214
366
  // Code restores it), then auto mode when a classifier model is reachable —
215
367
  // the same gate the mode cycle uses (permissions/index.ts autoInCycle) —
216
- // and the fixed choices. Each approving choice carries the mode it
217
- // switches to; the final "Keep planning" choice has none and leaves the
218
- // session in plan mode.
368
+ // and the fixed choices. A bypass before planning is offered back only
369
+ // when this session can grant it (a resume without the bypass flag
370
+ // cannot, and the request would land in default). Each approving
371
+ // choice carries the mode it switches to; the final "Keep planning"
372
+ // choice has none and leaves the session in plan mode.
219
373
  const autoAvailable = ctx.modelRegistry.getAvailable().length > 0;
220
- const restore = modeBeforePlan && (modeBeforePlan !== "auto" || autoAvailable) ? modeBeforePlan : undefined;
374
+ const modeBeforePlan = runOnBranch(ctx, path)?.modeBeforePlan;
375
+ const offerable = (mode: PermissionMode) => (mode !== "auto" || autoAvailable) && (mode !== "bypassPermissions" || bypassAvailable);
376
+ const restore = modeBeforePlan && offerable(modeBeforePlan) ? modeBeforePlan : undefined;
221
377
  const fixedModes: PermissionMode[] = [...(autoAvailable ? (["auto"] as const) : []), "acceptEdits", "default"];
222
378
  const options: { label: string; mode?: PermissionMode; restore?: boolean }[] = [
223
379
  ...(restore ? [{ label: `Approve — back to ${MODE_NAMES[restore]} (the mode before planning)`, mode: restore, restore: true }] : []),
@@ -226,11 +382,30 @@ export default function planModeExtension(pi: ExtensionAPI) {
226
382
  ];
227
383
  const choices = options.map((o) => o.label);
228
384
 
229
- const choice = await ctx.ui.custom<PlanChoice | null>((tui, theme, _keybindings, done) => {
385
+ // The plan renders as Markdown, like assistant text in the transcript.
386
+ const prose = canShowCustomUi(ctx) ? await planMarkdown() : undefined;
387
+ const planRef = {};
388
+ const markdown = prose ? (text: string, width: number) => prose(planRef, text, width) : undefined;
389
+
390
+ // The scroll position and the highlighted choice outlive one showing: a
391
+ // blank keep-planning answer reopens the dialog where the user left it.
392
+ const maxVisible = 12;
393
+ let offset = 0;
394
+ let selected: PlanChoice = initialPlanChoice(options);
395
+ const showDialog = (): Promise<PlanChoice | null> => !canShowCustomUi(ctx) ? selectPlanChoice(ctx.ui, plan, path, choices, signal) : panelUi(pi.events, ctx.ui).custom<PlanChoice | null>((tui, theme, _keybindings, close) => {
396
+ // An aborted turn closes the dialog as a cancel; a choice or a cancel
397
+ // drops the abort listener. Either way the dialog settles once.
398
+ let settled = false;
399
+ const onAbort = () => done(null);
400
+ const done = (choice: PlanChoice | null) => {
401
+ if (settled) return;
402
+ settled = true;
403
+ signal?.removeEventListener("abort", onAbort);
404
+ close(choice);
405
+ };
406
+ if (signal?.aborted) queueMicrotask(onAbort);
407
+ else signal?.addEventListener("abort", onAbort, { once: true });
230
408
  const paint = safeThemePaint(theme);
231
- const maxVisible = 12;
232
- let offset = 0;
233
- let selected: PlanChoice = initialPlanChoice(options);
234
409
  let lineCount = 0;
235
410
  // The dialog stays open while exit_plan_mode runs, so pi's working
236
411
  // spinner drives ~8 frames a second — and re-wrapping the whole plan
@@ -243,7 +418,7 @@ export default function planModeExtension(pi: ExtensionAPI) {
243
418
  let wrapped: { width: number; lines: string[] } | undefined;
244
419
  let frame: { key: string; lines: string[] } | undefined;
245
420
  const wrapAt = (wrapWidth: number): string[] => {
246
- if (wrapped?.width !== wrapWidth) wrapped = { width: wrapWidth, lines: wrapPlanText(plan, wrapWidth) };
421
+ if (wrapped?.width !== wrapWidth) wrapped = { width: wrapWidth, lines: planLines(plan, wrapWidth, markdown) };
247
422
  return wrapped.lines;
248
423
  };
249
424
  return {
@@ -274,29 +449,38 @@ export default function planModeExtension(pi: ExtensionAPI) {
274
449
  },
275
450
  };
276
451
  });
452
+ const decision = await runPlanApproval(
453
+ options,
454
+ showDialog,
455
+ () => ctx.ui.input(KEEP_PLANNING_QUESTION, undefined, { signal }),
456
+ signal,
457
+ );
277
458
 
278
- const picked = choice != null ? options[choice] : undefined;
459
+ const picked = decision.kind === "approve" ? options[decision.index] : undefined;
279
460
  if (picked?.mode) {
280
- pi.events.emit(MODE_CHANNEL, { mode: picked.mode });
461
+ pi.events.emit(MODE_CHANNEL, { mode: picked.mode, toolCallId });
462
+ // Claude Code's exit reminder, queued from execute so it is written
463
+ // into this result after the mode notice.
464
+ pi.events.emit(REMINDER_CHANNEL, { text: exitedPlanModeText(path), toolCallId });
281
465
  return {
282
- content: [
283
- {
284
- type: "text",
285
- text: `Plan approved by the user. You may now implement it. The approved plan stays at ${path} for reference.`,
286
- },
287
- ],
466
+ content: [{ type: "text", text: approvedPlanText(path, plan) }],
288
467
  details: { plan, approved: true },
289
468
  };
290
469
  }
291
470
 
471
+ // Rejected: Claude Code's rejection text. With the user's words the model
472
+ // acts on them and keeps planning; without (Esc) the turn stops, as a
473
+ // reasonless permission rejection does (permissions/index.ts): `terminate`
474
+ // ends pi's loop after the batch, and the abort waits for the recorded
475
+ // result so the run ends as the user's interrupt.
476
+ const feedback = decision.kind === "reject" ? decision.feedback : undefined;
477
+ const stop = !feedback;
478
+ if (stop) abortAfterResult.add(toolCallId);
292
479
  return {
293
- content: [
294
- {
295
- type: "text",
296
- text: "The user did not approve the plan. Stay in plan mode; refine the plan file based on their feedback.",
297
- },
298
- ],
299
- details: { plan, approved: false },
480
+ content: [{ type: "text", text: userDenialText(feedback) }],
481
+ details: { plan, approved: false, ...(feedback ? { feedback } : {}) },
482
+ isError: true,
483
+ ...(stop ? { terminate: true } : {}),
300
484
  };
301
485
  },
302
486
  });
@@ -1,30 +1,98 @@
1
1
  /**
2
- * Plan-mode reminder text (pure). Byte-identical for a given plan-file path,
3
- * whether or not the file exists yet: the block is a sticky-append reminder
4
- * carried by every user message since plan mode came on, and the queue keeps
5
- * that anchor only while the re-emitted text is unchanged. A text that flipped
6
- * when the file appeared re-anchored the block and re-cached every message
7
- * between entering plan mode and the first write (CACHE-REVIEW-2026-09-04 M2).
2
+ * Plan-mode reminder text (pure): Claude Code's plan-mode instructions, the
3
+ * five-phase workflow, with One Code's tool and agent names. The block is a
4
+ * sticky-append reminder carried by every user message since plan mode came
5
+ * on, and the queue keeps that anchor only while the re-emitted text is
6
+ * unchanged, so the text must not flip while the mode lasts: a text that
7
+ * flipped when the file appeared re-anchored the block and re-cached every
8
+ * message between entering plan mode and the first write
9
+ * (CACHE-REVIEW-2026-09-04 M2). Claude Code's own "Plan File Info" line
10
+ * depends on whether the plan file exists; the caller decides that once, when
11
+ * plan mode comes on (`planExists`), and keeps it until the mode ends.
8
12
  *
9
- * Adapted from Claude Code's plan-mode system message: the plan lives in a
10
- * file the model builds incrementally — the one writable path in plan mode —
11
- * and exit_plan_mode reads that file rather than taking the plan as a
12
- * parameter. Claude Code's own text does vary with the file's existence; ours
13
- * folds both states into one sentence so the cached prefix survives.
13
+ * Claude Code puts this text in a system message on the models that take one
14
+ * and in a reminder elsewhere; One Code delivers it through the reminder
15
+ * queue's plan-mode placement either way.
14
16
  */
15
17
 
16
- export function buildPlanModeReminder(filePath: string): string {
17
- const fileLine = `Build your plan at ${filePath} with the write/edit tools — create it if it does not exist yet, then edit it incrementally rather than rewriting it from scratch. Do not present the plan as chat text.`;
18
+ /** Claude Code's "Plan File Info" line, for a plan file that exists or not yet. */
19
+ export function planFileInfo(filePath: string, planExists: boolean): string {
20
+ return planExists
21
+ ? `A plan file already exists at ${filePath}. You can read it and make incremental edits using the edit tool.`
22
+ : `No plan file exists yet. You should create your plan at ${filePath} using the write tool.`;
23
+ }
24
+
25
+ export function buildPlanModeReminder(filePath: string, planExists = false): string {
26
+ const fileInfo = planFileInfo(filePath, planExists);
18
27
  return [
19
- `Plan mode is active. You may only use read-only tools; edit and write are blocked everywhere except one file. ${fileLine}`,
28
+ "Plan mode is active. The user indicated that they do not want you to execute yet -- you MUST NOT make any edits (with the exception of the plan file mentioned below), run any non-readonly tools (including changing configs or making commits), or otherwise make any changes to the system. This supercedes any other instructions you have received.",
29
+ "",
30
+ "## Plan File Info:",
31
+ fileInfo,
32
+ "You should build your plan incrementally by writing to or editing this file. NOTE that this is the only file you are allowed to edit - other than this you are only allowed to take READ-ONLY actions.",
33
+ "",
34
+ "## Plan Workflow",
35
+ "",
36
+ "### Phase 1: Initial Understanding",
37
+ "Goal: Gain a comprehensive understanding of the user's request by reading through code and asking them questions. Critical: In this phase you should only use the explore subagent type.",
38
+ "",
39
+ "1. Focus on understanding the user's request and the code associated with their request. Actively search for existing functions, utilities, and patterns that can be reused — avoid proposing new code when suitable implementations already exist.",
40
+ "",
41
+ "2. **Launch up to 3 explore agents IN PARALLEL** (single message, multiple tool calls) to efficiently explore the codebase.",
42
+ " - Use 1 agent when the task is isolated to known files, the user provided specific file paths, or you're making a small targeted change.",
43
+ " - Use multiple agents when: the scope is uncertain, multiple areas of the codebase are involved, or you need to understand existing patterns before planning.",
44
+ " - Quality over quantity - 3 agents maximum, but you should try to use the minimum number of agents necessary (usually just 1)",
45
+ " - If using multiple agents: Provide each agent with a specific search focus or area to explore. Example: One agent searches for existing implementations, another explores related components, a third investigating testing patterns",
46
+ "",
47
+ "### Phase 2: Design",
48
+ "Goal: Design an implementation approach.",
49
+ "",
50
+ "Launch plan agent(s) to design the implementation based on the user's intent and your exploration results from Phase 1.",
51
+ "",
52
+ "You can launch up to 3 agent(s) in parallel.",
53
+ "",
54
+ "**Guidelines:**",
55
+ "- **Default**: Launch at least 1 plan agent for most tasks - it helps validate your understanding and consider alternatives",
56
+ "- **Skip agents**: Only for truly trivial tasks (typo fixes, single-line changes, simple renames)",
57
+ "- **Multiple agents**: Use up to 3 agents for complex tasks that benefit from different perspectives",
58
+ "",
59
+ "Examples of when to use multiple agents:",
60
+ "- The task touches multiple parts of the codebase",
61
+ "- It's a large refactor or architectural change",
62
+ "- There are many edge cases to consider",
63
+ "- You'd benefit from exploring different approaches",
64
+ "",
65
+ "Example perspectives by task type:",
66
+ "- New feature: simplicity vs performance vs maintainability",
67
+ "- Bug fix: root cause vs workaround vs prevention",
68
+ "- Refactoring: minimal change vs clean architecture",
69
+ "",
70
+ "In the agent prompt:",
71
+ "- Provide comprehensive background context from Phase 1 exploration including filenames and code path traces",
72
+ "- Describe requirements and constraints",
73
+ "- Request a detailed implementation plan",
74
+ "",
75
+ "### Phase 3: Review",
76
+ "Goal: Review the plan(s) from Phase 2 and ensure alignment with the user's intentions.",
77
+ "1. Read the critical files you identified during exploration to deepen your understanding",
78
+ "2. Ensure that the plans align with the user's original request",
79
+ "3. Use ask_user_question to clarify any remaining questions with the user",
80
+ "",
81
+ "### Phase 4: Final Plan",
82
+ "Goal: Write your final plan to the plan file (the only file you can edit).",
83
+ "- Begin with a **Context** section: explain why this change is being made — the problem or need it addresses, what prompted it, and the intended outcome",
84
+ "- Include only your recommended approach, not all alternatives",
85
+ "- Ensure that the plan file is concise enough to scan quickly, but detailed enough to execute effectively",
86
+ "- Name the critical files to be modified. For changes that repeat a pattern across many files, describe the pattern once and list a few representative paths — do not enumerate every file or line number",
87
+ "- Reference existing functions and utilities you found that should be reused, with their file paths",
88
+ "- Include a verification section describing how to test the changes end-to-end (run the code, use MCP tools, run tests)",
89
+ "",
90
+ "### Phase 5: Call exit_plan_mode",
91
+ "At the very end of your turn, once you have asked the user questions and are happy with your final plan file - you should always call exit_plan_mode to indicate to the user that you are done planning.",
92
+ "This is critical - your turn should only end with either using the ask_user_question tool OR calling exit_plan_mode. Do not stop unless it's for these 2 reasons",
20
93
  "",
21
- "Workflow:",
22
- '1. Explore: delegate broad reconnaissance to the `Agent` tool with `subagent_type: "explore"` (up to 3 in parallel) rather than reading everything yourself.',
23
- '2. Design: delegate the implementation strategy to the `Agent` tool with `subagent_type: "plan"` once exploration has mapped the ground.',
24
- "3. Review the critical files yourself, and call `ask_user_question` for any decision you cannot make on the user's behalf.",
25
- `4. Write the final plan to ${filePath}: a Context section explaining why, your recommended approach (not several options), the critical files named, existing utilities to reuse, and a verification section.`,
26
- "5. Call `exit_plan_mode` to ask the user to approve the plan. It reads the plan file directly — you do not pass the plan as a parameter. If `exit_plan_mode` is not in your active tools, load it first with tool_search (`select:exit_plan_mode`).",
94
+ "**Important:** Use ask_user_question ONLY to clarify requirements or choose between approaches. Use exit_plan_mode to request plan approval. Do NOT ask about plan approval in any other way - no text questions, no ask_user_question. Phrases like \"Is this plan okay?\", \"Should I proceed?\", \"How does this plan look?\", \"Any changes before we start?\", or similar MUST use exit_plan_mode.",
27
95
  "",
28
- "End every plan-mode turn with either `ask_user_question` or `exit_plan_mode`. Never ask for plan approval in chat text.",
96
+ "NOTE: At any point in time through this workflow you should feel free to ask the user questions or clarifications using the ask_user_question tool. Don't make large assumptions about user intent. The goal is to present a well researched plan to the user, and tie any loose ends before implementation begins.",
29
97
  ].join("\n");
30
98
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A plan-mode run as the branch records it (pure, no pi imports): its plan
3
+ * file's path, whether that file existed when the run began, and the mode the
4
+ * run came from. plan-mode/index.ts reads and writes the record.
5
+ */
6
+ import type { PermissionMode } from "../permissions/matcher.ts";
7
+ import { normalizePermissionMode } from "../permissions/settings.ts";
8
+
9
+ /** The `one-code:state` key of the current plan-mode run. */
10
+ export const PLAN_ENTRY_STATE = "plan-mode:entry";
11
+
12
+ /** A plan-mode run as the branch records it, beside its plan file's path. */
13
+ export type PlanRun = { existed: boolean; modeBeforePlan?: PermissionMode };
14
+
15
+ /**
16
+ * The run `stored` records for the plan file at `path`, or undefined when it
17
+ * records another file or is malformed. A mode it came from that is unknown,
18
+ * or plan itself, is dropped: the exit then offers its fixed list.
19
+ */
20
+ export function parsePlanRun(stored: unknown, path: string): PlanRun | undefined {
21
+ const record = stored as { path?: unknown; existed?: unknown; modeBeforePlan?: unknown } | null | undefined;
22
+ if (record?.path !== path || typeof record.existed !== "boolean") return undefined;
23
+ const mode = normalizePermissionMode(record.modeBeforePlan);
24
+ const before = mode !== "plan" ? mode : undefined;
25
+ return { existed: record.existed, ...(before ? { modeBeforePlan: before } : {}) };
26
+ }