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
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Control characters in text that reaches the terminal (pure).
3
+ *
4
+ * Tool results, tool arguments, notification bodies, MCP results, fetched
5
+ * pages and the session title all carry text One Code did not write. Sent to
6
+ * the terminal raw, an escape sequence in it acts: OSC 0 retitles the
7
+ * terminal, OSC 52 rewrites the clipboard in kitty, WezTerm, Alacritty and
8
+ * Windows Terminal, cursor movement rewrites the rows above, SGR conceal hides
9
+ * text, and a carriage return overwrites the start of the row.
10
+ *
11
+ * `sanitizeDisplayText` applies pi's own rule for a tool result
12
+ * (`render-utils.js getTextOutput`: `stripAnsi`, then `sanitizeBinaryOutput`,
13
+ * then drop `\r`), and also drops C1 controls, which some terminals act on.
14
+ * It keeps `\n` and `\t` and never touches styling One Code adds afterwards,
15
+ * because it runs on the text before painting. `escapeControlText` shows each
16
+ * control character as a visible `\xHH` escape instead, for consent dialogs,
17
+ * where the user must see exactly what would run. `sanitizeTitle` is for the
18
+ * one-line terminal title.
19
+ *
20
+ * Display only: the model-facing text is never passed through these.
21
+ */
22
+
23
+ // pi's `ansiRegex` (utils/ansi.js, from chalk's ansi-regex) matches an OSC up
24
+ // to the first ST, else CSI and the related ESC forms. Its OSC branch is a lazy
25
+ // scan to the end of the text from every `ESC ]`, quadratic on many
26
+ // unterminated ones (50,000 took 1.8 s), so `stripAnsi` walks the text once
27
+ // with the same alternation and remembers where no terminator is left.
28
+ const CSI_AT = /[\u001B\u009B][[\]()#;?]*(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]/y;
29
+
30
+ /** The index just past the first string terminator (BEL, `ESC \`, or C1 ST) at or after `from`, else -1. */
31
+ function stringTerminatorEnd(text: string, from: number): number {
32
+ for (let i = from; i < text.length; i++) {
33
+ const ch = text.charCodeAt(i);
34
+ if (ch === 0x07 || ch === 0x9c) return i + 1;
35
+ if (ch === 0x1b && text.charCodeAt(i + 1) === 0x5c) return i + 2;
36
+ }
37
+ return -1;
38
+ }
39
+
40
+ /** `text.replace(ansiRegex, "")` in linear time: the same matches, left to right. */
41
+ function stripAnsi(text: string): string {
42
+ let out = "";
43
+ let from = 0;
44
+ // No string terminator exists at or after this index (a failed search holds for every later start).
45
+ let noTerminatorFrom = Number.POSITIVE_INFINITY;
46
+ let i = 0;
47
+ while (i < text.length) {
48
+ const ch = text.charCodeAt(i);
49
+ if (ch !== 0x1b && ch !== 0x9b) {
50
+ i++;
51
+ continue;
52
+ }
53
+ if (ch === 0x1b && text.charCodeAt(i + 1) === 0x5d && i + 2 < noTerminatorFrom) {
54
+ const end = stringTerminatorEnd(text, i + 2);
55
+ if (end !== -1) {
56
+ out += text.slice(from, i);
57
+ from = i = end;
58
+ continue;
59
+ }
60
+ noTerminatorFrom = i + 2;
61
+ }
62
+ CSI_AT.lastIndex = i;
63
+ const match = CSI_AT.exec(text);
64
+ if (match) {
65
+ out += text.slice(from, i);
66
+ from = i = i + match[0].length;
67
+ continue;
68
+ }
69
+ i++;
70
+ }
71
+ return out + text.slice(from);
72
+ }
73
+
74
+ /** C0 controls other than tab and newline, DEL, C1 controls, and U+FFF9 to U+FFFB (interlinear annotation). */
75
+ const UNSAFE = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f-]/g;
76
+ const HAS_UNSAFE = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f-]/;
77
+
78
+ /**
79
+ * Text safe to write to the terminal: escape sequences removed, then every
80
+ * control character other than `\n` and `\t` (carriage returns included, as
81
+ * pi does). Plain text comes back unchanged (same string).
82
+ */
83
+ export function sanitizeDisplayText(text: string): string {
84
+ if (!HAS_UNSAFE.test(text)) return text;
85
+ return stripAnsi(text).replace(UNSAFE, "");
86
+ }
87
+
88
+ /**
89
+ * `label` as an OSC 8 hyperlink to `url` (pi-tui's `hyperlink`). Terminals
90
+ * without OSC 8, and tmux without its `hyperlinks` feature, show the label
91
+ * alone, so the label must read on its own. Both parts are sanitized, so
92
+ * neither can end the sequence early.
93
+ */
94
+ export function terminalLink(label: string, url: string): string {
95
+ return `\x1b]8;;${sanitizeDisplayText(url)}\x1b\\${sanitizeDisplayText(label)}\x1b]8;;\x1b\\`;
96
+ }
97
+
98
+ /**
99
+ * Every control character other than `\n` and `\t` shown as a visible
100
+ * `\xHH` escape (`\x1b[8m` for an SGR conceal), so nothing in the text can
101
+ * act on the terminal or hide part of itself.
102
+ */
103
+ export function escapeControlText(text: string): string {
104
+ if (!HAS_UNSAFE.test(text)) return text;
105
+ return text.replace(UNSAFE, (ch) => {
106
+ const code = ch.charCodeAt(0);
107
+ return code > 0xff ? `\\u${code.toString(16).padStart(4, "0")}` : `\\x${code.toString(16).padStart(2, "0")}`;
108
+ });
109
+ }
110
+
111
+ /** One line for the terminal title: escape sequences and every control character removed, whitespace collapsed. */
112
+ export function sanitizeTitle(text: string): string {
113
+ return sanitizeDisplayText(text).replace(/\s+/g, " ").trim();
114
+ }
@@ -17,6 +17,10 @@
17
17
  * 2-vs-1 decision is byte-identical to pi's without importing pi or adding a
18
18
  * package. Grapheme segmentation and the mark/emoji classification use V8's
19
19
  * built-in `Intl.Segmenter` and Unicode property regexes.
20
+ *
21
+ * Synced with pi-tui 0.99.2. `text-width.test.ts` compares this measure with
22
+ * pi-tui's over every code point to U+3FFFF and over random strings with
23
+ * escapes, so a pi bump that changes pi-tui's measure fails the suite.
20
24
  */
21
25
 
22
26
  // --- East Asian wide/fullwidth lookup (get-east-asian-width v1.6.0, Unicode 16) ---
@@ -127,6 +131,7 @@ export function graphemeWidth(segment: string): number {
127
131
  } else if (!nonPrintingCharRegex.test(char)) {
128
132
  const c = char.codePointAt(0)!;
129
133
  if (followsMark || (c >= 0xff00 && c <= 0xffef)) width += eastAsianWidth(c);
134
+ else if (c === 0x0e33 || c === 0x0eb3) width += 1; // Thai SARA AM, Lao AM
130
135
  followsMark = false;
131
136
  }
132
137
  }
@@ -134,36 +139,35 @@ export function graphemeWidth(segment: string): number {
134
139
  }
135
140
 
136
141
  // --- ANSI / OSC / APC escape handling ---
137
- // A single terminal control sequence starting at index 0 of `s`, or null.
138
- // Covers CSI (styling/cursor), OSC (hyperlinks, prompt markers, terminated by
139
- // BEL or ST) and APC (terminated by ST) — the same families pi-tui strips.
142
+ // The length of the terminal control sequence starting at index `i`, or 0.
143
+ // Mirrors pi-tui's `extractAnsiCode` exactly, because pi-tui validates a line
144
+ // with it: a CSI runs to the first `m`, `G`, `K`, `H` or `J`; an OSC or APC
145
+ // runs to BEL or ST; an unterminated sequence, or any other ESC form, is not
146
+ // a sequence, so its bytes count as text. A broader rule here measured such
147
+ // text narrower than pi-tui and could emit a line pi-tui rejects. Display text
148
+ // never carries those forms once `sanitizeDisplayText` has run.
140
149
  function escapeAt(s: string, i: number): number {
141
150
  if (s.charCodeAt(i) !== 0x1b) return 0;
142
151
  const next = s[i + 1];
143
152
  if (next === "[") {
144
- // CSI: ESC [ ... final byte 0x40–0x7E
145
153
  let j = i + 2;
146
- while (j < s.length) {
147
- const c = s.charCodeAt(j);
148
- j++;
149
- if (c >= 0x40 && c <= 0x7e) return j - i;
150
- }
151
- return s.length - i;
154
+ while (j < s.length && !CSI_END.test(s[j])) j++;
155
+ return j < s.length ? j + 1 - i : 0;
152
156
  }
153
- if (next === "]" || next === "_" || next === "P" || next === "^") {
154
- // OSC / APC / DCS / PM: ESC <intro> ... terminated by BEL or ST (ESC \)
157
+ if (next === "]" || next === "_") {
155
158
  let j = i + 2;
156
159
  while (j < s.length) {
157
160
  if (s.charCodeAt(j) === 0x07) return j + 1 - i; // BEL
158
161
  if (s.charCodeAt(j) === 0x1b && s[j + 1] === "\\") return j + 2 - i; // ST
159
162
  j++;
160
163
  }
161
- return s.length - i;
164
+ return 0;
162
165
  }
163
- // Other two-byte escapes (ESC c, ESC =, …)
164
- return next ? 2 : 1;
166
+ return 0;
165
167
  }
166
168
 
169
+ const CSI_END = /[mGKHJ]/;
170
+
167
171
  const PRINTABLE_ASCII = /^[\x20-\x7e]*$/;
168
172
 
169
173
  /**
@@ -174,6 +178,9 @@ const PRINTABLE_ASCII = /^[\x20-\x7e]*$/;
174
178
  export function visibleWidth(text: string): number {
175
179
  if (text.length === 0) return 0;
176
180
  if (PRINTABLE_ASCII.test(text)) return text.length;
181
+ // pi-tui expands tabs before segmenting, so a mark after a tab joins the
182
+ // last space's cluster; doing the same keeps the two measures identical.
183
+ if (text.includes("\t")) text = text.replace(/\t/g, " ");
177
184
  let clean = "";
178
185
  for (let i = 0; i < text.length; ) {
179
186
  const esc = escapeAt(text, i);
@@ -204,6 +211,29 @@ export function sliceColumns(text: string, maxCols: number): { text: string; wid
204
211
  return fitPainted(text, maxCols);
205
212
  }
206
213
 
214
+ /**
215
+ * Longest suffix of PLAIN (escape-free) text whose visible width is ≤
216
+ * `maxCols`, cut on a grapheme boundary: the counterpart of `sliceColumns` for
217
+ * text that keeps its tail, such as a path whose deepest folders matter most.
218
+ */
219
+ export function tailColumns(text: string, maxCols: number): { text: string; width: number } {
220
+ if (maxCols <= 0) return { text: "", width: 0 };
221
+ if (PRINTABLE_ASCII.test(text)) {
222
+ const tail = text.slice(Math.max(0, text.length - maxCols));
223
+ return { text: tail, width: tail.length };
224
+ }
225
+ const graphemes = [...graphemeSegmenter.segment(text)].map((s) => s.segment);
226
+ let width = 0;
227
+ let start = graphemes.length;
228
+ while (start > 0) {
229
+ const w = graphemeWidth(graphemes[start - 1]);
230
+ if (width + w > maxCols) break;
231
+ width += w;
232
+ start--;
233
+ }
234
+ return { text: graphemes.slice(start).join(""), width };
235
+ }
236
+
207
237
  /**
208
238
  * Hard-wrap text into chunks each ≤ `columns` visible columns, on grapheme
209
239
  * boundaries. A single glyph wider than the whole budget occupies its own chunk
@@ -253,7 +283,8 @@ export function fitPainted(line: string, maxCols: number): { text: string; width
253
283
  continue;
254
284
  }
255
285
  // Consume the visible run up to the next escape, segment it into graphemes.
256
- let end = i;
286
+ // An ESC that starts no sequence is text (width 0), so the run starts past it.
287
+ let end = line.charCodeAt(i) === 0x1b ? i + 1 : i;
257
288
  while (end < line.length && line.charCodeAt(end) !== 0x1b) end++;
258
289
  for (const { segment } of graphemeSegmenter.segment(line.slice(i, end))) {
259
290
  const w = graphemeWidth(segment);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * An injectable timer (pure), so an idle-timer state machine is unit-tested
3
+ * with fake timers, and the real adapter both recap and idle-compact run on.
4
+ */
5
+
6
+ export interface TimerOps {
7
+ /** Schedule `cb` after `ms`; return a handle for `clear`. */
8
+ set(cb: () => void, ms: number): unknown;
9
+ clear(handle: unknown): void;
10
+ }
11
+
12
+ /** `setTimeout` with the handle unref'd, so a pending idle timer never keeps a one-shot process alive. */
13
+ export const unrefTimers: TimerOps = {
14
+ set: (cb, ms) => {
15
+ const handle = setTimeout(cb, ms);
16
+ handle.unref?.();
17
+ return handle;
18
+ },
19
+ clear: (handle) => clearTimeout(handle as ReturnType<typeof setTimeout>),
20
+ };
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Claude Code's `tool_addition` for tools that arrive mid-session (pure). One
3
+ * Code renders the arrival as the deferred-tool addendum on the request's
4
+ * tail (lib/deferred.ts deferredAddendumText), which reads correctly on any model.
5
+ * On a first-party model that takes mid-conversation tool changes, the
6
+ * addendum leaves the message that carries it for a system message right
7
+ * after it: Claude Code's sentence and names, then one reference-form
8
+ * `tool_addition` block per tool, each declared deferred in `tools`. The model
9
+ * can then call the tool without a `tool_search` round trip (findings §55
10
+ * P1). Elsewhere the addendum stays as it is.
11
+ *
12
+ * An addendum is matched as a whole block, never inside other text: in a tool
13
+ * result at `context` time, while the result's blocks are still separate and
14
+ * keyed by its call id (`liftAddenda`), and on a user message as a whole part
15
+ * (a one-shot pinned there).
16
+ */
17
+
18
+ import { MID_CONVERSATION_SYSTEM_BETA, MID_CONVERSATION_TOOL_CHANGES_BETA, reseatMessageMark } from "./anthropic-payload.ts";
19
+ import { byWireCallId, systemRoleLayout, type WireMessage, type WirePart, withoutParts } from "./system-role.ts";
20
+
21
+ /** The first line of the stored addendum; the names follow, one per line. */
22
+ export const ADDENDUM_HEAD =
23
+ "Additional deferred tools became available via tool_search since this conversation started (they registered after the first request). Same rules: load with tool_search \"select:<name>\" before calling.";
24
+
25
+ /** Claude Code's sentence before the names, in its tool-addition message. */
26
+ export const TOOLS_AVAILABLE = "The following tools just became available and are ready to use:";
27
+
28
+ /** Claude Code's betas for a reference-form tool addition in a system message. */
29
+ export const TOOL_ADDITION_BETAS = [MID_CONVERSATION_SYSTEM_BETA, MID_CONVERSATION_TOOL_CHANGES_BETA];
30
+
31
+ /**
32
+ * Whether the model takes Claude Code's reference-form `tool_addition`:
33
+ * first-party Anthropic on the system-role layout, with pi's catalog flag for
34
+ * mid-conversation tool changes (Opus 5.5, Sonnet 5.5 and Fable 5.1 among them).
35
+ */
36
+ export function supportsToolAdditions(model: { id?: string; provider?: string; api?: string; compat?: unknown } | undefined): boolean {
37
+ if (!model || model.provider !== "anthropic" || systemRoleLayout({ id: model.id ?? "", api: model.api ?? "", compat: model.compat }) !== "anthropic") return false;
38
+ return (model.compat as { supportsMidConvoToolChanges?: boolean } | undefined)?.supportsMidConvoToolChanges === true;
39
+ }
40
+
41
+ const OPEN = `<system-reminder>\n${ADDENDUM_HEAD}\n`;
42
+ const CLOSE = "\n</system-reminder>";
43
+
44
+ /** The names a whole framed addendum block lists; undefined for any other text. */
45
+ export function addendumNames(text: string): string[] | undefined {
46
+ if (!text.startsWith(OPEN) || !text.endsWith(CLOSE)) return undefined;
47
+ return text
48
+ .slice(OPEN.length, -CLOSE.length)
49
+ .split("\n")
50
+ .filter((line) => line.length > 0);
51
+ }
52
+
53
+ type StoredBlock = { type?: string; text?: string };
54
+ type StoredMessage = { role: string; toolCallId?: string; content?: unknown };
55
+
56
+ /**
57
+ * The context-time step: each tool result's addendum block leaves the result,
58
+ * its names kept under the call id, for the payload step to send as an
59
+ * addition after that result. Returns the input when nothing changed.
60
+ */
61
+ export function liftAddenda<T extends StoredMessage>(messages: T[]): { messages: T[]; byCall: Map<string, string[]> } {
62
+ const byCall = new Map<string, string[]>();
63
+ let changed = false;
64
+ const out = messages.map((message) => {
65
+ if (message.role !== "toolResult" || !Array.isArray(message.content) || !message.toolCallId) return message;
66
+ const names: string[] = [];
67
+ const kept = (message.content as StoredBlock[]).filter((block) => {
68
+ const found = block?.type === "text" && typeof block.text === "string" ? addendumNames(block.text) : undefined;
69
+ if (found) names.push(...found);
70
+ return !found;
71
+ });
72
+ if (names.length === 0) return message;
73
+ changed = true;
74
+ byCall.set(message.toolCallId, names);
75
+ return { ...message, content: kept };
76
+ });
77
+ return { messages: changed ? out : messages, byCall };
78
+ }
79
+
80
+ /**
81
+ * The Anthropic payload with a tool-addition system message after each user
82
+ * message that answers a call `liftAddenda` lifted an addendum from, or that
83
+ * carries a pinned addendum part (removed). Also returns every name added: they
84
+ * must be declared deferred in `tools`. With `references: false` the message
85
+ * is Claude Code's sentence and names alone, for a request whose tools cannot
86
+ * be referenced (OAuth renames them): the tools are active, so the model can
87
+ * still call them. Undefined when there is none.
88
+ */
89
+ export function withToolAdditions(
90
+ payload: Record<string, unknown>,
91
+ byCall: ReadonlyMap<string, readonly string[]>,
92
+ references = true,
93
+ ): { payload: Record<string, unknown>; names: string[] } | undefined {
94
+ const messages = payload.messages;
95
+ if (!Array.isArray(messages)) return undefined;
96
+ const out: WireMessage[] = [];
97
+ const all = new Set<string>();
98
+ for (const message of messages as WireMessage[]) {
99
+ if (message?.role !== "user" || !Array.isArray(message.content)) {
100
+ out.push(message);
101
+ continue;
102
+ }
103
+ const names: string[] = [];
104
+ const parts = message.content as WirePart[];
105
+ for (const part of parts) {
106
+ const called = part?.type === "tool_result" && typeof part.tool_use_id === "string" ? byWireCallId(byCall, part.tool_use_id) : undefined;
107
+ if (called) names.push(...called);
108
+ }
109
+ const pinnedNames = (part: WirePart) => (part?.type === "text" && typeof part.text === "string" ? addendumNames(part.text) : undefined);
110
+ // A pinned addendum part goes, keeping pi's cache mark; a message it would empty keeps it.
111
+ const kept = withoutParts(parts, (part) => pinnedNames(part) !== undefined) ?? parts;
112
+ if (kept !== parts) for (const part of parts) names.push(...(pinnedNames(part) ?? []));
113
+ if (names.length === 0) {
114
+ out.push(message);
115
+ continue;
116
+ }
117
+ const unique = [...new Set(names)];
118
+ for (const name of unique) all.add(name);
119
+ out.push(kept.length === parts.length ? message : { ...message, content: kept }, {
120
+ role: "system",
121
+ content: [
122
+ { type: "text", text: [TOOLS_AVAILABLE, ...unique].join("\n") },
123
+ ...(references ? unique.map((name) => ({ type: "tool_addition", tool: { type: "tool_reference", name } })) : []),
124
+ ],
125
+ });
126
+ }
127
+ if (all.size === 0) return undefined;
128
+ return { payload: { ...payload, messages: reseatMessageMark(out) }, names: [...all] };
129
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * A streamed tool call's damage. `corrupt` is certain (not one JSON value, a
3
+ * shell command ending inside an open double quote): the call is blocked and
4
+ * the turn stopped. `suspect` has the shape of a cut but also of a valid
5
+ * command (an option followed by one trailing blank): the call goes back to the
6
+ * model once, and a repeat in the same run counts as corrupt.
7
+ */
8
+ export interface ToolCallDamage {
9
+ kind: "corrupt" | "suspect";
10
+ reason: string;
11
+ }
12
+
13
+ // The last word is an option followed by one blank, or a long option with an
14
+ // empty `=` value: `grep -rn "x"` and `--include="*.py"` cut at the escaped
15
+ // quote (findings §61, Morph). A bare `FOO=` is a valid assignment.
16
+ const CUT_AFTER_OPTION = /(?:^|\s)(-{1,2}[A-Za-z][\w-]*)[ \t]$|(?:^|\s)(--[A-Za-z][\w-]*=)$/;
17
+ // Heredocs and here-strings (bash `<<`, `<<<`; PowerShell `@"…"@`, `@'…'@`)
18
+ // carry quotes the scan below cannot pair, so it does not judge them.
19
+ const HERE_DOCUMENT = /<<|@["']/;
20
+
21
+ /** Inspect one tool call's raw streamed arguments before pi's tolerant parsing hides damage. */
22
+ export function toolCallCorruptionReason(toolName: string, rawArguments: string): ToolCallDamage | undefined {
23
+ // A tool with no parameters may stream no argument text at all; pi reads it as {}.
24
+ if (rawArguments.trim() === "") return;
25
+ let args: unknown;
26
+ try {
27
+ args = JSON.parse(rawArguments);
28
+ } catch {
29
+ return { kind: "corrupt", reason: "The raw tool-call arguments are not exactly one JSON value." };
30
+ }
31
+ if (toolName !== "bash" && toolName !== "powershell" && toolName !== "monitor") return;
32
+ if (!args || typeof args !== "object" || !("command" in args) || typeof args.command !== "string") return;
33
+ const cut = CUT_AFTER_OPTION.exec(args.command);
34
+ if (cut) {
35
+ return {
36
+ kind: "suspect",
37
+ reason: `The shell command ends with "${cut[1] ?? cut[2]}" and nothing after it, the shape of arguments cut off before a quoted string.`,
38
+ };
39
+ }
40
+ const command = args.command.trimEnd();
41
+ if (!command.endsWith('"') || HERE_DOCUMENT.test(command)) return;
42
+
43
+ let singleQuoted = false;
44
+ let doubleQuoted = false;
45
+ const escape = toolName === "powershell" ? "`" : "\\";
46
+ for (let i = 0; i < command.length; i++) {
47
+ const char = command[i];
48
+ if (singleQuoted) {
49
+ if (char === "'") singleQuoted = false;
50
+ } else if (char === escape) {
51
+ i++;
52
+ } else if (char === "'" && !doubleQuoted) {
53
+ singleQuoted = true;
54
+ } else if (char === '"') {
55
+ doubleQuoted = !doubleQuoted;
56
+ } else if (char === "#" && !doubleQuoted && (i === 0 || /[\s;&|(]/.test(command[i - 1]))) {
57
+ // A comment runs to the end of its line.
58
+ const newline = command.indexOf("\n", i);
59
+ if (newline === -1) break;
60
+ i = newline;
61
+ }
62
+ }
63
+ if (doubleQuoted) return { kind: "corrupt", reason: "The shell command ends with an unbalanced double quote, indicating truncated tool-call arguments." };
64
+ }
65
+
66
+ // GLM's native tool-call tags, which a broken upstream parser leaks into the JSON arguments.
67
+ const TOOL_CALL_MARKUP = /<\/arg_value>\s{0,8}<arg_key>|<\/arg_key>\s{0,8}<arg_value>|<\/tool_call>\s{0,8}<tool_call>/g;
68
+ const LONGEST_MARKUP = 40;
69
+
70
+ /** The number of markup matches that marks a streaming call as leaked. */
71
+ export const TOOL_CALL_MARKUP_LIMIT = 2;
72
+ /**
73
+ * The same inside file content (write, edit, notebook_edit), which can quote
74
+ * the markup legitimately (this repo's tests do); a leak there repeats without
75
+ * end and passes it within a few thousand characters.
76
+ */
77
+ export const CONTENT_MARKUP_LIMIT = 50;
78
+
79
+ // A string value under one of these keys is file content.
80
+ const CONTENT_KEY = /"(?:content|oldText|newText|new_source)"\s*:\s*$/;
81
+
82
+ /** Whether `index` sits in a string value under a content key: the nearest unescaped quote before it opens that value. */
83
+ function inContentValue(text: string, index: number): boolean {
84
+ // lastIndexOf clamps a negative start to 0, so stop before it can repeat a quote at 0.
85
+ for (let quote = index; quote > 0; ) {
86
+ quote = text.lastIndexOf('"', quote - 1);
87
+ if (quote < 0) break;
88
+ let slashes = 0;
89
+ while (text[quote - 1 - slashes] === "\\") slashes++;
90
+ if (slashes % 2 === 0) return CONTENT_KEY.test(text.slice(Math.max(0, quote - 32), quote));
91
+ }
92
+ return false;
93
+ }
94
+
95
+ /**
96
+ * Count native tool-call markup in `text` from `from` on, for a call whose
97
+ * arguments are still streaming, apart (`contentCount`) inside file content;
98
+ * `next` is where the following scan resumes, so each delta is scanned once.
99
+ */
100
+ export function scanToolCallMarkup(text: string, from: number): { count: number; contentCount: number; next: number } {
101
+ TOOL_CALL_MARKUP.lastIndex = from;
102
+ let count = 0;
103
+ let contentCount = 0;
104
+ let next = from;
105
+ for (let match = TOOL_CALL_MARKUP.exec(text); match; match = TOOL_CALL_MARKUP.exec(text)) {
106
+ if (inContentValue(text, match.index)) contentCount++;
107
+ else count++;
108
+ next = TOOL_CALL_MARKUP.lastIndex;
109
+ }
110
+ return { count, contentCount, next: Math.max(next, text.length - LONGEST_MARKUP) };
111
+ }
112
+
113
+ /**
114
+ * Blank space a tool call may stream after its last argument character before
115
+ * the stream counts as runaway. Only a run holding a raw tab or newline
116
+ * counts: inside a JSON string those are escaped, so such a run is padding
117
+ * between tokens, never argument content (a string may hold any run of spaces).
118
+ */
119
+ export const RUNAWAY_PADDING_LIMIT = 10_000;
120
+
121
+ export interface TrailingPadding {
122
+ run: number;
123
+ structural: boolean;
124
+ }
125
+
126
+ /** The trailing blank run after one more streamed delta. */
127
+ export function trailingPadding(previous: TrailingPadding | undefined, delta: string): TrailingPadding {
128
+ const last = delta.search(/\S\s*$/);
129
+ if (last < 0) {
130
+ return { run: (previous?.run ?? 0) + delta.length, structural: (previous?.structural ?? false) || /[\t\n\r]/.test(delta) };
131
+ }
132
+ const tail = delta.slice(last + 1);
133
+ return { run: tail.length, structural: /[\t\n\r]/.test(tail) };
134
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Resolve a file tool's `path` argument exactly as pi's own `read`, `edit` and
3
+ * `write` do (`resolveToCwd` in pi-coding-agent's `core/tools/path-utils.js`,
4
+ * over `utils/paths.js normalizePath`), vendored because the package does not
5
+ * export it. Anything that reasons about the file a pi tool will touch (the
6
+ * file tracker's read-before-write and stale-edit guards, our notebook tool,
7
+ * the worktree rewrite of relative paths) must land on the same file, or a spelling pi accepts (`~/x`, `@x`,
8
+ * `file:///…`, a non-breaking space, Git Bash's `/c/…` on Windows) slips past
9
+ * the guard (TOOLS-REVIEW-2026-09-26 M1). Pure: no pi imports.
10
+ *
11
+ * Mirrors pi 0.87.1 to 0.99.2: Unicode spaces fold to a plain space, one
12
+ * leading `@` is dropped, on Windows a Git Bash/MSYS/Cygwin/WSL drive path
13
+ * becomes `C:\…`, `~` and `~/` expand to the home directory (and `~\` on
14
+ * Windows), a `file://` URL becomes its path, and a relative result resolves
15
+ * against `cwd`.
16
+ */
17
+
18
+ import { homedir } from "node:os";
19
+ import { isAbsolute, join, resolve } from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+
22
+ const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
23
+
24
+ /** pi's `normalizeWindowsShellPath`: `/c/x`, `/mnt/c/x`, `/cygdrive/c/x` to `C:\x`. */
25
+ function windowsShellPath(path: string): string {
26
+ if (!path.startsWith("/") || path.startsWith("//") || path.includes("\\")) return path;
27
+ const match = path.match(/^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/i);
28
+ if (!match) return path;
29
+ return `${match[1].toUpperCase()}:\\${match[2]?.replaceAll("/", "\\") ?? ""}`;
30
+ }
31
+
32
+ export interface ToolPathOptions {
33
+ home?: string;
34
+ platform?: NodeJS.Platform;
35
+ }
36
+
37
+ /**
38
+ * `raw` as pi's file tools read it before resolving it against a cwd: the
39
+ * steps above except the last. Callers that must tell a relative path from an
40
+ * absolute one the way pi will (the worktree rewrite) judge this, not `raw`.
41
+ */
42
+ export function normalizeToolPath(raw: string, options: ToolPathOptions = {}): string {
43
+ const platform = options.platform ?? process.platform;
44
+ const home = options.home ?? homedir();
45
+ let path = raw.replace(UNICODE_SPACES, " ");
46
+ if (path.startsWith("@")) path = path.slice(1);
47
+ if (platform === "win32") path = windowsShellPath(path);
48
+ if (path === "~") return home;
49
+ if (path.startsWith("~/") || (platform === "win32" && path.startsWith("~\\"))) return join(home, path.slice(2));
50
+ if (/^file:\/\//.test(path)) {
51
+ // A URL this platform cannot map (`file:///x` on Windows has no drive)
52
+ // stays as written: pi's tool then fails on it, and a guard must not throw.
53
+ try {
54
+ return fileURLToPath(path);
55
+ } catch {
56
+ return path;
57
+ }
58
+ }
59
+ return path;
60
+ }
61
+
62
+ /** The absolute path a pi file tool resolves `raw` to from `cwd`. */
63
+ export function resolveToolPath(raw: string, cwd: string, options: ToolPathOptions = {}): string {
64
+ const path = normalizeToolPath(raw, options);
65
+ return isAbsolute(path) ? resolve(path) : resolve(cwd, path);
66
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Tool descriptions that follow the session, as Claude Code's do.
3
+ *
4
+ * Claude Code sends its strongest models short tool descriptions and its
5
+ * weakest the long ones it wrote for Haiku 4.5; its Bash text also changes
6
+ * with the permission mode. One Code keys the same split on the model's tier
7
+ * (`working-docs/decisions/system-prompt.md`, "Four registers"): frontier and
8
+ * workhorse get the short forms, cheap and tiny the long ones, on every
9
+ * provider.
10
+ *
11
+ * pi copies a tool's description when the tool is registered, so a tool whose
12
+ * text depends on the session is registered again when that text changes
13
+ * (`registerVariantTool`). This can also move a tool in pi's active tool
14
+ * order, so only a model-tier change may re-register after the first request.
15
+ * The tier changes with the model, which starts a new provider cache anyway.
16
+ * Permission-mode variants freeze at the first request (bash/index.ts); live
17
+ * changes ride reminders and the permission gate, never the cached tool list.
18
+ */
19
+
20
+ import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
21
+ import type { PromptTier } from "./model-tier.ts";
22
+ import { sessionModelTier } from "./session-model-tier.ts";
23
+
24
+ /** Which of Claude Code's description forms a tool carries. */
25
+ export type DescriptionForm = "short" | "long";
26
+
27
+ /** Claude Code's short forms on frontier and workhorse; its long forms on cheap and tiny. */
28
+ export function descriptionForm(tier: PromptTier): DescriptionForm {
29
+ return tier === "cheap" || tier === "tiny" ? "long" : "short";
30
+ }
31
+
32
+ // Every tool definition shape registerTool accepts; the concrete generics differ per tool.
33
+ type AnyToolDefinition = ToolDefinition<any, any, any>;
34
+
35
+ /**
36
+ * Register `define(initial)` now, and return a setter that registers the tool
37
+ * again with `define(next)` when `next` differs from the variant in force.
38
+ * Repeated calls with the same variant do nothing, so callers can apply the
39
+ * session's facts on every event that might change them.
40
+ */
41
+ export function registerVariantTool<V>(pi: ExtensionAPI, initial: V, define: (variant: V) => AnyToolDefinition): (variant: V) => void {
42
+ let current = initial;
43
+ pi.registerTool(define(initial));
44
+ return (next: V) => {
45
+ if (next === current) return;
46
+ current = next;
47
+ pi.registerTool(define(next));
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Register `tool` with the description `describe` gives the session model's
53
+ * form, following it through the session: the one call each tool with a
54
+ * short and a long form makes.
55
+ */
56
+ export function registerFormTool(pi: ExtensionAPI, tool: AnyToolDefinition, describe: (form: DescriptionForm) => string): void {
57
+ followDescriptionForm(pi, registerVariantTool<DescriptionForm>(pi, "short", (form) => ({ ...tool, description: describe(form) })));
58
+ }
59
+
60
+ /** Apply the frozen session tier's form (and the tier it came from) only at session and model boundaries. */
61
+ export function followDescriptionForm(pi: ExtensionAPI, apply: (form: DescriptionForm, tier: PromptTier) => void): void {
62
+ const requestTier = sessionModelTier(pi);
63
+ const follow = () => {
64
+ const tier = requestTier();
65
+ apply(descriptionForm(tier), tier);
66
+ };
67
+ pi.on("session_start", follow);
68
+ pi.on("model_select", follow);
69
+ }