one-code-extension 0.4.1 → 0.5.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 (419) 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 +32 -23
  7. package/docs/guide/doctor.md +20 -13
  8. package/docs/guide/hooks.md +18 -3
  9. package/docs/guide/installation.md +27 -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 +123 -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 +515 -0
  56. package/extensions/background/index.ts +514 -236
  57. package/extensions/background/loop-fire.ts +136 -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 +52 -0
  63. package/extensions/background/wakeup.ts +159 -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 +88 -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 +229 -71
  77. package/extensions/btw/panel.ts +228 -100
  78. package/extensions/btw/prompt.ts +41 -4
  79. package/extensions/claude-compat/index.ts +7 -11
  80. package/extensions/claude-context/index.ts +446 -58
  81. package/extensions/compaction/cut.ts +157 -0
  82. package/extensions/compaction/index.ts +216 -75
  83. package/extensions/compaction/kept-thinking.ts +62 -0
  84. package/extensions/compaction/prompt.ts +15 -12
  85. package/extensions/context-budget/index.ts +24 -5
  86. package/extensions/context-management/index.ts +25 -0
  87. package/extensions/doctor/build.ts +59 -8
  88. package/extensions/doctor/builtins.ts +84 -0
  89. package/extensions/doctor/cli.ts +7 -1
  90. package/extensions/doctor/compat.ts +88 -23
  91. package/extensions/doctor/dependencies.ts +41 -5
  92. package/extensions/doctor/fix-prompt.ts +9 -2
  93. package/extensions/doctor/index.ts +102 -58
  94. package/extensions/doctor/models.ts +102 -63
  95. package/extensions/doctor/presets.ts +42 -27
  96. package/extensions/doctor/report.ts +19 -2
  97. package/extensions/doctor/viewer.ts +49 -27
  98. package/extensions/effort/index.ts +72 -19
  99. package/extensions/effort/settings.ts +21 -0
  100. package/extensions/effort/slider.ts +24 -20
  101. package/extensions/exit/index.ts +32 -1
  102. package/extensions/file-tracker/index.ts +337 -26
  103. package/extensions/file-tracker/replay.ts +96 -10
  104. package/extensions/file-tracker/restore.ts +63 -0
  105. package/extensions/file-tracker/results.ts +66 -0
  106. package/extensions/file-tracker/shell-reads.ts +211 -0
  107. package/extensions/file-tracker/tracker.ts +32 -15
  108. package/extensions/footer/footer-line.ts +45 -24
  109. package/extensions/footer/index.ts +47 -2
  110. package/extensions/footer/pr.ts +34 -5
  111. package/extensions/hooks/compaction.ts +7 -0
  112. package/extensions/hooks/executor.ts +32 -30
  113. package/extensions/hooks/index.ts +198 -31
  114. package/extensions/hooks/matcher.ts +3 -0
  115. package/extensions/hooks/protocol.ts +41 -3
  116. package/extensions/hooks/settings.ts +50 -19
  117. package/extensions/hooks/subagent-bridge.ts +2 -0
  118. package/extensions/hooks/trust.ts +13 -5
  119. package/extensions/idle-compact/index.ts +119 -0
  120. package/extensions/idle-compact/policy.ts +143 -0
  121. package/extensions/init/index.ts +11 -10
  122. package/extensions/init/prompt.ts +25 -32
  123. package/extensions/interrupted/index.ts +13 -1
  124. package/extensions/kept-thinking/index.ts +27 -0
  125. package/extensions/lib/agent-cron.ts +130 -0
  126. package/extensions/lib/agent-loader.ts +15 -1
  127. package/extensions/lib/agent-view.ts +85 -0
  128. package/extensions/lib/anthropic-payload.ts +82 -1
  129. package/extensions/lib/anthropic-server-call.ts +230 -0
  130. package/extensions/lib/anthropic-server-tools.ts +485 -0
  131. package/extensions/lib/app-launch.mjs +94 -0
  132. package/extensions/lib/argument-hints.ts +190 -0
  133. package/extensions/lib/atomic-write.ts +6 -2
  134. package/extensions/lib/auto-memory.ts +75 -0
  135. package/extensions/lib/bash-parser.ts +84 -0
  136. package/extensions/lib/btw-fork.ts +93 -0
  137. package/extensions/lib/child-extensions.ts +42 -0
  138. package/extensions/lib/child-writes.ts +45 -0
  139. package/extensions/lib/claude-context.ts +515 -100
  140. package/extensions/lib/claude-external-includes.ts +68 -0
  141. package/extensions/lib/claude-id.ts +22 -0
  142. package/extensions/lib/claude-rules.ts +323 -0
  143. package/extensions/lib/claude-settings.ts +76 -4
  144. package/extensions/lib/compaction-boundary.ts +36 -0
  145. package/extensions/lib/compaction-keep.mjs +83 -0
  146. package/extensions/lib/config-mode.ts +112 -0
  147. package/extensions/lib/consent-dialogs.ts +63 -0
  148. package/extensions/lib/consent-preview.ts +5 -1
  149. package/extensions/lib/consent-stores.ts +48 -0
  150. package/extensions/lib/context-facts.ts +78 -0
  151. package/extensions/lib/context-stack.ts +81 -0
  152. package/extensions/lib/deferred.ts +219 -25
  153. package/extensions/lib/dock-rows.ts +60 -0
  154. package/extensions/lib/empty-tool-result.ts +27 -0
  155. package/extensions/lib/environment-block.ts +76 -0
  156. package/extensions/lib/fetch-timeout.ts +24 -6
  157. package/extensions/{system-prompt → lib}/git-status.ts +51 -13
  158. package/extensions/lib/git.ts +75 -13
  159. package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
  160. package/extensions/lib/grammars/README.md +2 -0
  161. package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
  162. package/extensions/lib/guide-docs.ts +45 -0
  163. package/extensions/lib/headless-output.ts +46 -0
  164. package/extensions/lib/hook-gate.ts +7 -7
  165. package/extensions/lib/interrupt.ts +20 -4
  166. package/extensions/lib/key-input.ts +455 -0
  167. package/extensions/lib/keys.ts +14 -1
  168. package/extensions/lib/local-command.ts +47 -13
  169. package/extensions/lib/mcp-overrides.ts +1 -1
  170. package/extensions/lib/mcp-share.ts +46 -0
  171. package/extensions/lib/memory-content.ts +146 -0
  172. package/extensions/lib/memory-index.ts +18 -0
  173. package/extensions/lib/memory.ts +134 -123
  174. package/extensions/lib/model-catalog/huggingface.json +174 -0
  175. package/extensions/lib/model-catalog/models-dev.json +18297 -0
  176. package/extensions/lib/model-catalog/openrouter.json +5252 -0
  177. package/extensions/lib/model-catalog-data.ts +513 -0
  178. package/extensions/lib/model-catalog.ts +462 -0
  179. package/extensions/lib/model-choice-warnings.ts +137 -0
  180. package/extensions/lib/model-pairings.ts +152 -0
  181. package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
  182. package/extensions/lib/model-policy.ts +112 -15
  183. package/extensions/lib/model-tier.ts +283 -355
  184. package/extensions/lib/model-unusable.ts +1 -1
  185. package/extensions/lib/newer-model.ts +100 -0
  186. package/extensions/lib/notices.ts +87 -0
  187. package/extensions/lib/notifications.ts +308 -64
  188. package/extensions/lib/one-code-settings.ts +74 -8
  189. package/extensions/lib/open-browser.ts +63 -0
  190. package/extensions/lib/openrouter-generation.ts +94 -0
  191. package/extensions/lib/operation-deadline.ts +30 -0
  192. package/extensions/lib/original-command.ts +4 -4
  193. package/extensions/lib/package-version.ts +1 -1
  194. package/extensions/lib/panel-focus.ts +134 -0
  195. package/extensions/lib/paths.ts +17 -2
  196. package/extensions/lib/permission-gate.ts +72 -6
  197. package/extensions/lib/persisted-output.ts +60 -8
  198. package/extensions/lib/pi-ai-estimate.ts +19 -3
  199. package/extensions/lib/pi-version.ts +21 -2
  200. package/extensions/lib/plan-mode-channels.ts +20 -5
  201. package/extensions/lib/plugins.ts +26 -17
  202. package/extensions/lib/powershell-parse-script.ts +208 -0
  203. package/extensions/lib/powershell-parser.ts +344 -0
  204. package/extensions/lib/process-tree.ts +131 -8
  205. package/extensions/lib/prompt-options.ts +82 -0
  206. package/extensions/lib/queued-delivery.ts +44 -0
  207. package/extensions/lib/reminders.ts +364 -58
  208. package/extensions/lib/replaced-builtins.mjs +199 -0
  209. package/extensions/lib/replay-call.ts +70 -0
  210. package/extensions/lib/request-replay.ts +439 -0
  211. package/extensions/lib/scratchpad.ts +81 -14
  212. package/extensions/lib/send-now-channel.ts +7 -0
  213. package/extensions/lib/session-model-tier.ts +51 -0
  214. package/extensions/lib/session-work.ts +64 -0
  215. package/extensions/lib/settings-channels.ts +4 -4
  216. package/extensions/lib/shell-exit-semantics.ts +170 -0
  217. package/extensions/lib/shell-quote.ts +11 -0
  218. package/extensions/lib/shell-result.ts +70 -0
  219. package/extensions/lib/shell-spawn.ts +70 -6
  220. package/extensions/lib/shell-tool.ts +75 -25
  221. package/extensions/lib/side-call-cache.ts +124 -0
  222. package/extensions/lib/side-call-run.ts +96 -0
  223. package/extensions/lib/side-call-usage.ts +38 -0
  224. package/extensions/lib/side-call.ts +13 -1
  225. package/extensions/lib/single-flight.ts +18 -0
  226. package/extensions/lib/skill-body.ts +42 -0
  227. package/extensions/lib/skill-scan.ts +63 -24
  228. package/extensions/lib/spill-file.ts +140 -0
  229. package/extensions/lib/subagent-handback.ts +113 -0
  230. package/extensions/lib/system-role.ts +156 -0
  231. package/extensions/lib/terminal-text.ts +114 -0
  232. package/extensions/lib/text-width.ts +47 -16
  233. package/extensions/lib/timer-ops.ts +20 -0
  234. package/extensions/lib/tool-additions.ts +149 -0
  235. package/extensions/lib/tool-call-corruption.ts +134 -0
  236. package/extensions/lib/tool-path.ts +66 -0
  237. package/extensions/lib/tool-variants.ts +69 -0
  238. package/extensions/lib/tui-render.ts +255 -27
  239. package/extensions/lib/turn-budget-layout.ts +143 -0
  240. package/extensions/lib/update-check.mjs +15 -2
  241. package/extensions/lib/usage-bus.ts +1 -1
  242. package/extensions/lib/user-denial.ts +38 -0
  243. package/extensions/lib/workspace-channel.ts +16 -0
  244. package/extensions/lib/worktree-channel.ts +31 -3
  245. package/extensions/lib/worktree-isolation.ts +40 -10
  246. package/extensions/lsp/client.ts +8 -9
  247. package/extensions/lsp/index.ts +70 -4
  248. package/extensions/lsp/keep-alive.ts +1 -1
  249. package/extensions/lsp/trust.ts +119 -0
  250. package/extensions/lsp/watcher.ts +1 -1
  251. package/extensions/mcp/announce.ts +87 -5
  252. package/extensions/mcp/client.ts +161 -48
  253. package/extensions/mcp/config.ts +132 -8
  254. package/extensions/mcp/discovery.ts +19 -0
  255. package/extensions/mcp/index.ts +209 -71
  256. package/extensions/mcp/oauth/browser.ts +38 -17
  257. package/extensions/mcp/oauth/flow.ts +25 -17
  258. package/extensions/mcp/oauth/provider.ts +14 -8
  259. package/extensions/mcp/oauth/store.ts +34 -16
  260. package/extensions/mcp/panel/keys.ts +12 -11
  261. package/extensions/mcp/panel/render.ts +9 -6
  262. package/extensions/mcp/schema.ts +104 -5
  263. package/extensions/mcp/trust.ts +151 -67
  264. package/extensions/memory/entries.ts +23 -14
  265. package/extensions/memory/index.ts +134 -28
  266. package/extensions/memory/open-external.ts +33 -18
  267. package/extensions/memory/panel.ts +135 -26
  268. package/extensions/newer-model/index.ts +31 -0
  269. package/extensions/notebook/description.ts +18 -0
  270. package/extensions/notebook/index.ts +6 -5
  271. package/extensions/notebook/notebook.ts +1 -1
  272. package/extensions/permissions/index.ts +1300 -381
  273. package/extensions/permissions/matcher.ts +425 -74
  274. package/extensions/permissions/modes.ts +6 -4
  275. package/extensions/permissions/panel/host.ts +130 -0
  276. package/extensions/permissions/panel/keys.ts +48 -0
  277. package/extensions/permissions/panel/render.ts +450 -0
  278. package/extensions/permissions/panel/state.ts +550 -0
  279. package/extensions/permissions/powershell-cmdlets.ts +198 -0
  280. package/extensions/permissions/powershell-paths.ts +197 -0
  281. package/extensions/permissions/powershell-rules.ts +207 -280
  282. package/extensions/permissions/powershell-tree.ts +557 -0
  283. package/extensions/permissions/project-trust.ts +27 -11
  284. package/extensions/permissions/protected-paths.ts +14 -2
  285. package/extensions/permissions/session-grant.ts +6 -0
  286. package/extensions/permissions/settings.ts +166 -31
  287. package/extensions/permissions/subagent-gate.ts +25 -0
  288. package/extensions/permissions/workspace.ts +78 -0
  289. package/extensions/plan-mode/index.ts +156 -69
  290. package/extensions/plan-mode/reminder.ts +89 -21
  291. package/extensions/plan-mode/texts.ts +177 -0
  292. package/extensions/plan-mode/viewer.ts +92 -27
  293. package/extensions/plugins/counts.ts +3 -3
  294. package/extensions/plugins/index.ts +61 -46
  295. package/extensions/plugins/install/install.ts +16 -0
  296. package/extensions/plugins/marketplace/sync.ts +9 -3
  297. package/extensions/plugins/marketplace/types.ts +3 -1
  298. package/extensions/plugins/panel/keys.ts +21 -24
  299. package/extensions/plugins/panel/render.ts +12 -6
  300. package/extensions/plugins/shell-expand.ts +107 -0
  301. package/extensions/plugins/template.ts +32 -15
  302. package/extensions/powershell/child.ts +7 -0
  303. package/extensions/powershell/description.ts +38 -6
  304. package/extensions/powershell/guards.ts +1 -1
  305. package/extensions/powershell/index.ts +12 -7
  306. package/extensions/recap/index.ts +65 -60
  307. package/extensions/recap/prompt.ts +2 -2
  308. package/extensions/recap/scheduler.ts +3 -5
  309. package/extensions/search-tools/grep-merge.ts +39 -0
  310. package/extensions/search-tools/index.ts +0 -0
  311. package/extensions/send-now/chord.ts +62 -0
  312. package/extensions/send-now/index.ts +133 -0
  313. package/extensions/session-title/index.ts +3 -0
  314. package/extensions/session-title/title.ts +4 -1
  315. package/extensions/skill/code-review.ts +97 -0
  316. package/extensions/skill/index.ts +421 -101
  317. package/extensions/skill/invoke.ts +128 -18
  318. package/extensions/skill/listing.ts +133 -9
  319. package/extensions/skill/panel/keys.ts +15 -16
  320. package/extensions/skill/panel/render.ts +5 -2
  321. package/extensions/skill/panel/state.ts +12 -1
  322. package/extensions/subagents/agent-args.ts +39 -0
  323. package/extensions/subagents/agent-description.ts +132 -0
  324. package/extensions/subagents/agents.ts +116 -23
  325. package/extensions/subagents/announce.ts +69 -0
  326. package/extensions/subagents/default-model.ts +2 -3
  327. package/extensions/subagents/delegation-steer.ts +4 -4
  328. package/extensions/subagents/fork-cache.ts +76 -0
  329. package/extensions/subagents/guide-agent.ts +189 -0
  330. package/extensions/subagents/hand-back-review.ts +3 -0
  331. package/extensions/subagents/index.ts +1493 -824
  332. package/extensions/subagents/live-runs.ts +26 -6
  333. package/extensions/subagents/model-select.ts +38 -25
  334. package/extensions/subagents/outcome.ts +2 -2
  335. package/extensions/subagents/panel-keys.ts +73 -31
  336. package/extensions/subagents/panel-render.ts +120 -96
  337. package/extensions/subagents/panel-widget.ts +60 -89
  338. package/extensions/subagents/runner.ts +108 -80
  339. package/extensions/subagents/runs.ts +39 -5
  340. package/extensions/subagents/send-to-main-tool.ts +1 -1
  341. package/extensions/subagents/session-turns.ts +32 -8
  342. package/extensions/subagents/shell-panel.ts +36 -178
  343. package/extensions/subagents/tasks-dialog.ts +170 -0
  344. package/extensions/subagents/viewport-mirror.ts +95 -0
  345. package/extensions/subagents/worktree.ts +57 -13
  346. package/extensions/system-prompt/environment.ts +19 -19
  347. package/extensions/system-prompt/idle-turn.ts +65 -0
  348. package/extensions/system-prompt/index.ts +131 -47
  349. package/extensions/system-prompt/template.ts +24 -50
  350. package/extensions/system-prompt/tiers/common.ts +23 -64
  351. package/extensions/system-prompt/tiers/frontier.ts +32 -11
  352. package/extensions/system-prompt/tiers/low.ts +67 -45
  353. package/extensions/system-prompt/tiers/mid.ts +96 -38
  354. package/extensions/system-reminder/index.ts +175 -13
  355. package/extensions/tasks/descriptions.ts +190 -0
  356. package/extensions/tasks/index.ts +120 -41
  357. package/extensions/tasks/store.ts +83 -16
  358. package/extensions/tool-call-corruption/child.ts +7 -0
  359. package/extensions/tool-call-corruption/index.ts +237 -0
  360. package/extensions/tool-search/announce.ts +16 -0
  361. package/extensions/tool-search/index.ts +189 -41
  362. package/extensions/tool-style/index.ts +68 -10
  363. package/extensions/tool-style/write-description.ts +40 -0
  364. package/extensions/tool-style/write-render.ts +237 -0
  365. package/extensions/turn-duration/index.ts +7 -3
  366. package/extensions/turn-duration/line.ts +47 -4
  367. package/extensions/web/backends.ts +6 -9
  368. package/extensions/web/description.ts +50 -0
  369. package/extensions/web/index.ts +118 -53
  370. package/extensions/web/native-call.ts +15 -0
  371. package/extensions/web/native-results.ts +41 -0
  372. package/extensions/web-fetch/description.ts +42 -0
  373. package/extensions/web-fetch/extract.ts +53 -6
  374. package/extensions/web-fetch/index.ts +125 -76
  375. package/extensions/web-fetch/page-content.ts +19 -0
  376. package/extensions/web-fetch/response.ts +49 -0
  377. package/extensions/web-fetch/summarize.ts +9 -10
  378. package/extensions/workflow/agent-session.ts +105 -18
  379. package/extensions/workflow/args.ts +24 -0
  380. package/extensions/workflow/description.ts +67 -0
  381. package/extensions/workflow/globals.ts +17 -3
  382. package/extensions/workflow/index.ts +145 -162
  383. package/extensions/workflow/records.ts +1 -0
  384. package/extensions/workflow/run-manager.ts +29 -6
  385. package/extensions/workflow/saved-workflows.ts +3 -2
  386. package/extensions/workflow/types.ts +22 -1
  387. package/extensions/workflow/viewer.ts +54 -24
  388. package/extensions/workflow/widget.ts +20 -0
  389. package/extensions/worktree/descriptions.ts +91 -0
  390. package/extensions/worktree/guards.ts +316 -54
  391. package/extensions/worktree/index.ts +121 -39
  392. package/extensions/worktree/powershell-guards.ts +167 -0
  393. package/extensions/worktree/rewrite.ts +14 -16
  394. package/package.json +18 -7
  395. package/skills/artifact-design/SKILL.md +85 -0
  396. package/skills/code-review/SKILL.md +20 -20
  397. package/skills/code-review/cells/arguments-and-flags.md +19 -0
  398. package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
  399. package/skills/code-review/cells/high-8-inline.md +122 -0
  400. package/skills/code-review/cells/low-1-pass-max4.md +27 -0
  401. package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
  402. package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
  403. package/skills/code-review/cells/medium-3x5-verify.md +132 -0
  404. package/skills/code-review/cells/minimal-prompt.md +32 -0
  405. package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
  406. package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
  407. package/skills/fewer-permission-prompts/SKILL.md +1 -1
  408. package/skills/loop/SKILL.md +11 -0
  409. package/skills/security-review/SKILL.md +2 -5
  410. package/skills/simplify/SKILL.md +7 -5
  411. package/skills/workflow-authoring/SKILL.md +143 -0
  412. package/themes/onecode-light.json +1 -1
  413. package/themes/onecode.json +1 -1
  414. package/types/app.d.ts +58 -0
  415. package/types/community-packages.d.ts +26 -1
  416. package/extensions/auto-mode/recoverability.ts +0 -229
  417. package/extensions/lib/capability-index.ts +0 -466
  418. package/extensions/lib/model-facts.generated.json +0 -2675
  419. package/extensions/lib/model-facts.ts +0 -162
@@ -0,0 +1,344 @@
1
+ /**
2
+ * PowerShell's own parser as a service: one long-lived parse server per
3
+ * process, run on the PowerShell the tool itself runs, so the gate reads a
4
+ * line exactly as it will execute (decisions/windows.md "The PowerShell
5
+ * pre-gate reads PowerShell's own parse"; the tree-sitter grammars it replaces
6
+ * are measured in findings §41).
7
+ *
8
+ * {@link PowerShellParser.parse} never rejects. Anything short of a clean
9
+ * answer, whether the server could not start, timed out, crashed, runs in
10
+ * Constrained Language Mode or stopped for good after too many crashes,
11
+ * resolves to `{ ok: false, reason }`, and the caller treats the line as
12
+ * unparsed (the classifier decides). A parse with errors is still `ok: true`:
13
+ * the tree and its `errors` are PowerShell's answer; judging them is the
14
+ * caller's job.
15
+ *
16
+ * Lifecycle. The server starts on the first request and stops after
17
+ * `idleMs` without one; the next request starts it again. A crash, or a
18
+ * request that times out (the server is then killed), counts toward
19
+ * `maxRestarts`; past it the parser stays unavailable for the life of the
20
+ * process. The child, its pipes and every timer are unref'd, so an idle
21
+ * server never holds a finished one-shot run open; a caller that awaits a
22
+ * parse in one holds its own ref (`withKeepAlive`). The server also exits on
23
+ * its own when this process ends, because its stdin closes.
24
+ *
25
+ * Pure: no pi imports. The caller supplies `spawnServer`, which starts the
26
+ * right executable with the bootstrap after `-Command`, so the BI fork can use
27
+ * its own launcher.
28
+ */
29
+
30
+ import type { ChildProcess } from "node:child_process";
31
+ import { POWERSHELL_PARSE_BOOTSTRAP, POWERSHELL_PARSE_WALKER } from "./powershell-parse-script.ts";
32
+ import { unrefHandle } from "./process-tree.ts";
33
+
34
+ /** One syntax-tree node, as the walker reports it (powershell-parse-script.ts). */
35
+ export interface PowerShellAstNode {
36
+ /** The .NET type name: `CommandAst`, `StringConstantExpressionAst`, … */
37
+ type: string;
38
+ /** Index of the parent node in `nodes`; -1 for the root. */
39
+ parent: number;
40
+ /** Extent as UTF-16 offsets into the command (JS string indices). */
41
+ start: number;
42
+ end: number;
43
+ /** CommandAst: the static command name (null when not static). CommandParameterAst: the parameter. VariableExpressionAst: the variable path. Type nodes: the type's full name. */
44
+ name?: string | null;
45
+ /** String and constant nodes: the value PowerShell resolved (quotes and escapes applied). */
46
+ value?: string | null;
47
+ /** String nodes: `BareWord`, `SingleQuoted`, `DoubleQuoted`, `SingleQuotedHereString`, `DoubleQuotedHereString`. */
48
+ kind?: string;
49
+ /** CommandAst: the invocation operator (`Unknown`, `Ampersand`, `Dot`). Operator nodes: the operator. */
50
+ operator?: string;
51
+ /** VariableExpressionAst: `@name` splatting. */
52
+ splatted?: boolean;
53
+ /** Redirections: the stream redirected (`Output`, `Error`, `All`, …) and, for a merge, where to. */
54
+ from?: string;
55
+ to?: string;
56
+ /** FileRedirectionAst: `>>`. */
57
+ append?: boolean;
58
+ /** Pipelines and pipeline chains: a trailing `&` (PowerShell 7). */
59
+ background?: boolean;
60
+ /** Member access: `::`. */
61
+ static?: boolean;
62
+ /** CommandAst with a static name that resolves in this PowerShell: the resolved command's type (`Cmdlet`, `Application`, `Function`, …), after an alias. */
63
+ commandType?: string;
64
+ /** CommandAst: the name was an alias. */
65
+ alias?: boolean;
66
+ /** CommandAst: the resolved cmdlet's name, or an application's full path. */
67
+ resolvedName?: string;
68
+ /** CommandAst: the resolved command's module (empty for an application). */
69
+ module?: string;
70
+ /**
71
+ * CommandAst of a cmdlet: PowerShell's static parameter binding. `value` is
72
+ * the bound argument's node index, -1 for a switch given alone, and -2 for a
73
+ * value the binder built itself: the arguments a remaining-arguments
74
+ * parameter collects (`Write-Output a b`), whose node indices are
75
+ * `elements` (-1 for one that matched no argument).
76
+ */
77
+ bindings?: Array<{ parameter: string; value: number; elements?: number[] }>;
78
+ /** CommandAst of a cmdlet: the arguments or parameters the binder could not bind (an unknown or ambiguous name, a surplus positional, a repeat). */
79
+ bindingErrors?: string[];
80
+ /** CommandAst of a cmdlet: the static binder is missing or threw. */
81
+ bindingUnavailable?: boolean;
82
+ }
83
+
84
+ export interface PowerShellParse {
85
+ /** The tree in pre-order, root first. */
86
+ nodes: PowerShellAstNode[];
87
+ /** Each parse error's ErrorId. PowerShell runs nothing from a line that has one. */
88
+ errors: string[];
89
+ }
90
+
91
+ export type PowerShellParseOutcome = { ok: true; parse: PowerShellParse } | { ok: false; reason: string };
92
+
93
+ export interface PowerShellParserOptions {
94
+ /** Start the server: the PowerShell executable with `bootstrap` as the argument after `-Command`, stdio all piped. */
95
+ spawnServer: (bootstrap: string) => ChildProcess;
96
+ /** How long the first request waits while the server starts. */
97
+ startTimeoutMs?: number;
98
+ /** How long a request waits once the server is ready. */
99
+ requestTimeoutMs?: number;
100
+ /** Crashes and timeouts tolerated before the parser stays unavailable. */
101
+ maxRestarts?: number;
102
+ /** Stop the server after this long without a request. */
103
+ idleMs?: number;
104
+ }
105
+
106
+ export const POWERSHELL_PARSE_START_TIMEOUT_MS = 5_000;
107
+ export const POWERSHELL_PARSE_REQUEST_TIMEOUT_MS = 2_000;
108
+ export const POWERSHELL_PARSE_MAX_RESTARTS = 3;
109
+ export const POWERSHELL_PARSE_IDLE_MS = 10 * 60_000;
110
+
111
+ interface Pending {
112
+ resolve: (outcome: PowerShellParseOutcome) => void;
113
+ timer: ReturnType<typeof setTimeout>;
114
+ }
115
+
116
+ interface Server {
117
+ child: ChildProcess;
118
+ ready: boolean;
119
+ /** Set once the server is gone or being stopped; its late output and exit are ignored. */
120
+ stopped: boolean;
121
+ /** Why it stopped. */
122
+ reason?: string;
123
+ buffer: string;
124
+ pending: Map<number, Pending>;
125
+ }
126
+
127
+ const encode = (text: string) => Buffer.from(text, "utf8").toString("base64");
128
+
129
+ export class PowerShellParser {
130
+ private readonly options: Required<PowerShellParserOptions>;
131
+ private server: Server | undefined;
132
+ private nextId = 1;
133
+ private failures = 0;
134
+ /** Why the parser is unavailable for good; set past `maxRestarts`, or when the server runs in a restricted language mode. */
135
+ private disabled: string | undefined;
136
+ private idleTimer: ReturnType<typeof setTimeout> | undefined;
137
+
138
+ constructor(options: PowerShellParserOptions) {
139
+ this.options = {
140
+ startTimeoutMs: POWERSHELL_PARSE_START_TIMEOUT_MS,
141
+ requestTimeoutMs: POWERSHELL_PARSE_REQUEST_TIMEOUT_MS,
142
+ maxRestarts: POWERSHELL_PARSE_MAX_RESTARTS,
143
+ idleMs: POWERSHELL_PARSE_IDLE_MS,
144
+ ...options,
145
+ };
146
+ }
147
+
148
+ /** Why the parser cannot answer at all, or undefined while it can (or can still start). */
149
+ unavailable(): string | undefined {
150
+ return this.disabled;
151
+ }
152
+
153
+ /** Parse `command` with PowerShell's own parser. Never rejects. */
154
+ parse(command: string): Promise<PowerShellParseOutcome> {
155
+ if (this.disabled) return Promise.resolve({ ok: false, reason: this.disabled });
156
+ let server: Server;
157
+ try {
158
+ server = this.server ?? this.start();
159
+ } catch (error) {
160
+ return Promise.resolve({ ok: false, reason: this.fail(`PowerShell could not start: ${message(error)}`) });
161
+ }
162
+ // A server can fail while it starts (a spawn error, a restricted language
163
+ // mode) before this request is registered with it.
164
+ if (server.stopped) return Promise.resolve({ ok: false, reason: this.disabled ?? server.reason ?? "the PowerShell parser stopped" });
165
+ this.touch();
166
+ const id = this.nextId++;
167
+ return new Promise((resolve) => {
168
+ const timeoutMs = server.ready ? this.options.requestTimeoutMs : this.options.startTimeoutMs;
169
+ const timer = setTimeout(() => {
170
+ server.pending.delete(id);
171
+ resolve({ ok: false, reason: `the PowerShell parser did not answer within ${timeoutMs} ms` });
172
+ this.crashed(server, `the PowerShell parser timed out after ${timeoutMs} ms`);
173
+ }, timeoutMs);
174
+ unrefHandle(timer);
175
+ server.pending.set(id, { resolve, timer });
176
+ server.child.stdin?.write(`${id} ${encode(command)}\n`);
177
+ });
178
+ }
179
+
180
+ /** Stop the server. The next request starts a new one. */
181
+ stop(): void {
182
+ const server = this.server;
183
+ if (!server) return;
184
+ this.server = undefined;
185
+ this.clearIdle();
186
+ this.settleAll(server, "the PowerShell parser was stopped");
187
+ server.stopped = true;
188
+ server.child.stdin?.end();
189
+ server.child.kill();
190
+ }
191
+
192
+ private start(): Server {
193
+ const child = this.options.spawnServer(POWERSHELL_PARSE_BOOTSTRAP);
194
+ const server: Server = { child, ready: false, stopped: false, buffer: "", pending: new Map() };
195
+ this.server = server;
196
+ child.stdin?.on("error", () => {});
197
+ child.stdout?.setEncoding("ascii");
198
+ child.stdout?.on("data", (chunk: string) => this.receive(server, chunk));
199
+ // PowerShell's own error output is not part of the protocol; drain it so
200
+ // the pipe never fills.
201
+ child.stderr?.on("data", () => {});
202
+ child.on("error", (error) => this.crashed(server, `PowerShell could not start: ${message(error)}`));
203
+ child.on("exit", (code, signal) => this.crashed(server, `the PowerShell parser exited (${signal ?? `code ${code}`})`));
204
+ unrefHandle(child);
205
+ unrefHandle(child.stdin);
206
+ unrefHandle(child.stdout);
207
+ unrefHandle(child.stderr);
208
+ child.stdin?.write(`${encode(POWERSHELL_PARSE_WALKER)}\n`);
209
+ return server;
210
+ }
211
+
212
+ private receive(server: Server, chunk: string): void {
213
+ if (server.stopped) return;
214
+ server.buffer += chunk;
215
+ for (let newline = server.buffer.indexOf("\n"); newline >= 0; newline = server.buffer.indexOf("\n")) {
216
+ const line = server.buffer.slice(0, newline).trim();
217
+ server.buffer = server.buffer.slice(newline + 1);
218
+ if (line) this.record(server, line);
219
+ if (server.stopped) return;
220
+ }
221
+ }
222
+
223
+ private record(server: Server, line: string): void {
224
+ let record: Record<string, unknown>;
225
+ try {
226
+ record = JSON.parse(Buffer.from(line, "base64").toString("utf8")) as Record<string, unknown>;
227
+ } catch {
228
+ this.crashed(server, "the PowerShell parser wrote a line that is not part of its protocol");
229
+ return;
230
+ }
231
+ if (record.id === 0) {
232
+ if (record.language !== "FullLanguage") {
233
+ // Constrained Language Mode (or stricter) is a machine policy, so a
234
+ // restart cannot change it.
235
+ this.disabled = `PowerShell runs in ${String(record.language)} mode, where the parser is not available`;
236
+ this.crashed(server, this.disabled);
237
+ return;
238
+ }
239
+ // Failures are not reset here: a line that hangs the walker would
240
+ // otherwise restart it forever.
241
+ server.ready = true;
242
+ return;
243
+ }
244
+ const pending = typeof record.id === "number" ? server.pending.get(record.id) : undefined;
245
+ if (!pending) return;
246
+ server.pending.delete(record.id as number);
247
+ clearTimeout(pending.timer);
248
+ if (typeof record.failure === "string") {
249
+ pending.resolve({ ok: false, reason: `the PowerShell parser failed: ${record.failure}` });
250
+ return;
251
+ }
252
+ pending.resolve({ ok: true, parse: { nodes: asArray(record.nodes).map(normalizeNode), errors: asArray(record.errors).map(String) } });
253
+ }
254
+
255
+ /** The server is gone or unusable: settle what waits on it and count the failure. */
256
+ private crashed(server: Server, reason: string): void {
257
+ if (server.stopped) return;
258
+ server.stopped = true;
259
+ server.reason = reason;
260
+ if (this.server === server) this.server = undefined;
261
+ this.clearIdle();
262
+ this.settleAll(server, reason);
263
+ server.child.stdin?.end();
264
+ server.child.kill();
265
+ this.fail(reason);
266
+ }
267
+
268
+ private fail(reason: string): string {
269
+ this.failures++;
270
+ if (!this.disabled && this.failures > this.options.maxRestarts) {
271
+ this.disabled = `the PowerShell parser is off after ${this.failures} failures (last: ${reason})`;
272
+ }
273
+ return reason;
274
+ }
275
+
276
+ private settleAll(server: Server, reason: string): void {
277
+ for (const pending of server.pending.values()) {
278
+ clearTimeout(pending.timer);
279
+ pending.resolve({ ok: false, reason });
280
+ }
281
+ server.pending.clear();
282
+ }
283
+
284
+ private touch(): void {
285
+ this.clearIdle();
286
+ this.idleTimer = setTimeout(() => this.stop(), this.options.idleMs);
287
+ unrefHandle(this.idleTimer);
288
+ }
289
+
290
+ private clearIdle(): void {
291
+ if (this.idleTimer) clearTimeout(this.idleTimer);
292
+ this.idleTimer = undefined;
293
+ }
294
+ }
295
+
296
+ /**
297
+ * A node with its list fields made lists: ConvertTo-Json may write a
298
+ * one-element list as the element. A binding list that is not one after
299
+ * that marks the binding unavailable, which the gate refuses.
300
+ */
301
+ function normalizeNode(value: unknown): PowerShellAstNode {
302
+ const node = { ...(value as PowerShellAstNode) };
303
+ if (node.bindingErrors !== undefined) node.bindingErrors = asArray(node.bindingErrors).map(String);
304
+ if (node.bindings !== undefined) {
305
+ const bindings = asArray(node.bindings) as Array<{ parameter?: unknown; value?: unknown; elements?: unknown }>;
306
+ if (bindings.every((b) => b !== null && typeof b === "object" && typeof b.parameter === "string" && typeof b.value === "number")) {
307
+ node.bindings = bindings.map((b) => ({ parameter: b.parameter as string, value: b.value as number, ...(b.elements === undefined ? {} : { elements: asArray(b.elements).map(Number) }) }));
308
+ } else {
309
+ delete node.bindings;
310
+ node.bindingUnavailable = true;
311
+ }
312
+ }
313
+ return node;
314
+ }
315
+
316
+ function asArray(value: unknown): unknown[] {
317
+ if (Array.isArray(value)) return value;
318
+ // ConvertTo-Json writes a one-element array as that element on some
319
+ // PowerShell versions, and an empty one as null.
320
+ return value === undefined || value === null ? [] : [value];
321
+ }
322
+
323
+ function message(error: unknown): string {
324
+ return error instanceof Error ? error.message : String(error);
325
+ }
326
+
327
+ const STATE_KEY = Symbol.for("one-code.powershell-parser");
328
+
329
+ /**
330
+ * The process-wide parser for one PowerShell executable. It lives on
331
+ * `globalThis`, not in module state: jiti gives each extension, and each
332
+ * in-process child session, its own instance of this module (findings §3), and
333
+ * one server per process is the point.
334
+ */
335
+ export function sharedPowerShellParser(executable: string, create: () => PowerShellParser): PowerShellParser {
336
+ const store = globalThis as { [STATE_KEY]?: Map<string, PowerShellParser> };
337
+ const parsers = (store[STATE_KEY] ??= new Map());
338
+ let parser = parsers.get(executable);
339
+ if (!parser) {
340
+ parser = create();
341
+ parsers.set(executable, parser);
342
+ }
343
+ return parser;
344
+ }
@@ -14,9 +14,22 @@
14
14
  * The producer spawns `detached` (own process group; `detachedSpawnOptions`)
15
15
  * and stops it with `killProcessTree`, which signals the negative pid — the
16
16
  * whole group — falling back to the child alone when the group is already gone.
17
- * `stopProcessTree` adds the grace period: SIGTERM now, SIGKILL if the leader
18
- * has not exited by then (a command that ignores SIGTERM still closes late,
19
- * which is exactly the late callback the shutdown paths guard against).
17
+ * `stopProcessTree` adds the grace period: SIGTERM now, SIGKILL to the group
18
+ * when it ends, whether or not the leader is still alive. The leader is often
19
+ * only the `bash -c` wrapper, which dies on TERM at once while the command it
20
+ * forked (a dev server in its graceful-shutdown handler) keeps the group and
21
+ * its port; a leader-only check skipped the SIGKILL exactly then
22
+ * (LIFECYCLE-BACKGROUND-REVIEW-2026-09-26 M1, measured). A group whose SIGKILL
23
+ * is still pending when the process exits (pi's quit calls `process.exit`,
24
+ * and the grace timer is unref'd) is SIGKILLed from a `process.on("exit")`
25
+ * hook, so nothing One Code stopped outlives it.
26
+ *
27
+ * `rememberProcessGroup` owns a background shell's group for the lifetime of
28
+ * this process, including descendants left by a successfully exited leader.
29
+ * Session replacement must not kill those intentionally launched daemons.
30
+ * Each module instance keeps its own groups through process listeners (never
31
+ * through a session context or cross-extension module state), reaps empty
32
+ * groups, and sends TERM synchronously on process exit or termination signals.
20
33
  *
21
34
  * `waitForChildExit` is the counterpart on the waiting side: it settles on
22
35
  * `exit` plus a short stdio grace, never on `close` alone — a descendant the
@@ -37,6 +50,16 @@ export const EXIT_STDIO_GRACE_MS = 200;
37
50
  /** ...and the most it may keep arriving in total: a straggler writing continuously must not keep re-arming the grace. */
38
51
  export const EXIT_STDIO_MAX_MS = 2_000;
39
52
 
53
+ /**
54
+ * Unref a child process, one of its stdio streams or a timer, so it never
55
+ * holds the event loop open on its own (a finished one-shot run can exit);
56
+ * a caller awaiting it holds its own ref. Child pipes are sockets at run
57
+ * time and carry `unref()` even though their stream types do not declare it.
58
+ */
59
+ export function unrefHandle(handle: unknown): void {
60
+ (handle as { unref?: () => void } | null | undefined)?.unref?.();
61
+ }
62
+
40
63
  /**
41
64
  * `detached` everywhere but Windows, where process groups do not exist — except
42
65
  * for a child that must outlive an exiting parent (a fire-and-forget hook),
@@ -95,20 +118,120 @@ export function killProcessTree(child: ChildProcess, signal: NodeJS.Signals = "S
95
118
  }
96
119
  }
97
120
 
121
+ /** Groups owned by this producer, retained by process listeners across session replacement. */
122
+ const rememberedGroups = new Set<ChildProcess>();
123
+ let groupReaper: NodeJS.Timeout | undefined;
124
+ const terminationSignals = ["SIGINT", "SIGTERM", "SIGHUP"] as const;
125
+ const groupSignalHandlers = new Map<NodeJS.Signals, () => void>();
126
+
127
+ /**
128
+ * Whether a remembered tree still needs killing at exit. Windows has no
129
+ * process groups, so only a live leader is worth a `taskkill /T`; it is judged
130
+ * by the child's own exit state, because once the leader exits Windows may
131
+ * hand its pid to an unrelated process that a pid probe would then find. On
132
+ * POSIX a group id stays reserved while any member lives, and the reaper
133
+ * forgets an emptied group within a second, far sooner than sequential pid
134
+ * allocation wraps around to it.
135
+ */
136
+ export function rememberedGroupAlive(child: ChildProcess): boolean {
137
+ if (child.pid == null) return false;
138
+ if (process.platform === "win32") return child.exitCode === null && child.signalCode === null;
139
+ try {
140
+ process.kill(-child.pid, 0);
141
+ return true;
142
+ } catch (error) {
143
+ return (error as NodeJS.ErrnoException).code !== "ESRCH";
144
+ }
145
+ }
146
+
147
+ function releaseGroupListeners(): void {
148
+ if (groupReaper) clearInterval(groupReaper);
149
+ groupReaper = undefined;
150
+ process.removeListener("exit", terminateRememberedGroups);
151
+ for (const [signal, handler] of groupSignalHandlers) process.removeListener(signal, handler);
152
+ groupSignalHandlers.clear();
153
+ }
154
+
155
+ /** Exit handlers cannot await a grace period. Existing explicit stops retain their TERM/KILL escalation below. */
156
+ function terminateRememberedGroups(): void {
157
+ for (const child of rememberedGroups) {
158
+ if (rememberedGroupAlive(child)) killProcessTree(child, "SIGTERM");
159
+ }
160
+ rememberedGroups.clear();
161
+ releaseGroupListeners();
162
+ }
163
+
98
164
  /**
99
- * SIGTERM the tree, then SIGKILL it after `graceMs` unless the leader exited.
100
- * The timer is unref'd: it must never be what keeps a one-shot process alive.
165
+ * Remember a detached background tree from spawn until its group becomes empty.
166
+ * Register before awaiting its leader, so even immediate process exit covers
167
+ * running work. The unref'd reaper cannot keep a print run alive. No session
168
+ * shutdown handler touches this set; process listeners keep its module instance
169
+ * alive even when jiti builds a new extension instance after /clear.
170
+ */
171
+ export function rememberProcessGroup(child: ChildProcess): void {
172
+ if (child.pid == null || rememberedGroups.has(child)) return;
173
+ rememberedGroups.add(child);
174
+ if (groupReaper) return;
175
+ process.on("exit", terminateRememberedGroups);
176
+ for (const signal of terminationSignals) {
177
+ const handler = () => {
178
+ terminateRememberedGroups();
179
+ // Do not swallow Node's default signal termination when there is no
180
+ // pi handler. With isolated producers, the last listener re-raises it.
181
+ if (process.listenerCount(signal) === 0) process.kill(process.pid, signal);
182
+ };
183
+ groupSignalHandlers.set(signal, handler);
184
+ // Run before signal-exit observers: after we remove ourselves they must
185
+ // still see the original listener count and preserve default termination.
186
+ process.prependOnceListener(signal, handler);
187
+ }
188
+ groupReaper = setInterval(() => {
189
+ for (const child of rememberedGroups) {
190
+ if (!rememberedGroupAlive(child)) rememberedGroups.delete(child);
191
+ }
192
+ if (rememberedGroups.size === 0) releaseGroupListeners();
193
+ }, 1_000);
194
+ groupReaper.unref?.();
195
+ }
196
+
197
+ /**
198
+ * Trees sent SIGTERM whose SIGKILL has not run yet. Keyed by the child, not
199
+ * its pid, so a pid the OS hands to a new child during the grace does not
200
+ * make that child look already scheduled. Per module instance on purpose
201
+ * (each extension force-kills its own children); one `exit` listener per
202
+ * instance, installed on first use.
203
+ */
204
+ const pendingKills = new Set<ChildProcess>();
205
+ let exitHookInstalled = false;
206
+
207
+ /** The process is exiting: the grace timers will never fire, so force-kill every tree still pending. */
208
+ function killPendingAtExit(): void {
209
+ for (const child of pendingKills) killProcessTree(child, "SIGKILL");
210
+ pendingKills.clear();
211
+ }
212
+
213
+ /**
214
+ * SIGTERM the tree, then SIGKILL the whole group after `graceMs`, whatever
215
+ * the leader's state: a group already gone makes that an ESRCH, and a live
216
+ * group keeps its id reserved, so the id cannot name anyone else. The timer
217
+ * is unref'd (it must never be what keeps a one-shot process alive); a
218
+ * process exit before it fires runs the SIGKILL from the `exit` hook.
101
219
  */
102
220
  export function stopProcessTree(child: ChildProcess, graceMs: number): void {
103
221
  killProcessTree(child, "SIGTERM");
104
222
  // taskkill /F is already forceful; there is no gentler first signal to grace.
105
223
  if (process.platform === "win32") return;
106
- if (child.exitCode !== null || child.signalCode !== null) return;
224
+ if (child.pid == null || pendingKills.has(child)) return;
225
+ pendingKills.add(child);
226
+ if (!exitHookInstalled) {
227
+ exitHookInstalled = true;
228
+ process.on("exit", killPendingAtExit);
229
+ }
107
230
  const timer = setTimeout(() => {
108
- if (child.exitCode === null && child.signalCode === null) killProcessTree(child, "SIGKILL");
231
+ if (!pendingKills.delete(child)) return;
232
+ killProcessTree(child, "SIGKILL");
109
233
  }, graceMs);
110
234
  timer.unref?.();
111
- child.once("exit", () => clearTimeout(timer));
112
235
  }
113
236
 
114
237
  export interface ChildExit {
@@ -0,0 +1,82 @@
1
+ /**
2
+ * pi's system-prompt options for a session no `prompt()` has run in yet.
3
+ *
4
+ * A turn an extension opens from idle skips `before_agent_start`, the one
5
+ * event that carries the options (upstream ask #17). After the first typed
6
+ * prompt the extensions rebuild from what that event saw (system-prompt's
7
+ * idle-turn.ts). Before it they have nothing, so a background completion that
8
+ * opens a session's first turn used to go out as a user message, which takes
9
+ * the prompt path and renders as a user bubble.
10
+ *
11
+ * A command handler's context does carry the options
12
+ * (`getSystemPromptOptions`, the same base object `before_agent_start`
13
+ * receives). So the command that starts background work announces them here:
14
+ * system-prompt builds the prompt from them, the skill extension lists its
15
+ * skills, and the notifier opens the turn with the notification itself as a
16
+ * custom message, rendered like every later one. Pure: no pi imports.
17
+ *
18
+ * The same module carries the fork's system-prompt request (below).
19
+ */
20
+
21
+ export const PROMPT_OPTIONS_CHANNEL = "one-code:prompt-options";
22
+
23
+ /**
24
+ * Whether a `before_agent_start` event opens a run the user typed. pi 1.2.0
25
+ * also fires the event for a run an extension opens from idle with
26
+ * `sendMessage(…, { triggerTurn: true })` (a notification, the Stop hook's
27
+ * continuation, a scheduled fire), marked `messageType: "custom"` and without
28
+ * `prompt`. Older pi fires it only for prompts and sends no `messageType`.
29
+ */
30
+ export function isUserRun<E>(event: E): event is Exclude<E, { messageType: "custom" }> {
31
+ return (event as { messageType?: unknown } | undefined)?.messageType !== "custom";
32
+ }
33
+
34
+ /** The payload: pi's base options, and the session cwd they were read in. */
35
+ export interface PromptOptionsAnnouncement {
36
+ options: unknown;
37
+ cwd: string;
38
+ }
39
+
40
+ /**
41
+ * Emit the options of a command context. A context without
42
+ * `getSystemPromptOptions` (an event handler's, or an older pi) announces
43
+ * nothing, and the listeners keep their first-turn fallbacks.
44
+ */
45
+ export function announcePromptOptions(events: { emit(channel: string, data: unknown): void }, ctx: unknown): void {
46
+ const context = ctx as { getSystemPromptOptions?: () => unknown; cwd?: string } | undefined;
47
+ if (typeof context?.getSystemPromptOptions !== "function") return;
48
+ let options: unknown;
49
+ try {
50
+ options = context.getSystemPromptOptions();
51
+ } catch {
52
+ return; // A torn-down session: nothing to announce.
53
+ }
54
+ if (!options || typeof options !== "object") return;
55
+ events.emit(PROMPT_OPTIONS_CHANNEL, { options, cwd: context.cwd ?? "" } satisfies PromptOptionsAnnouncement);
56
+ }
57
+
58
+ /**
59
+ * The system prompt a fork inherits. `ctx.getSystemPrompt()` is pi's own
60
+ * prompt whenever One Code's is not installed on pi's run: between turns (pi
61
+ * drops a run's options when it settles), before the first prompt, and in a
62
+ * turn opened from idle (installed on the wire only, by context_with_system).
63
+ * A fork started then ran on pi's stock prompt. The system-prompt extension
64
+ * answers this request synchronously with the prompt the session's next
65
+ * request carries; no answer (not loaded, a named agent's own prompt, nothing
66
+ * to build from) leaves the caller's fallback.
67
+ */
68
+ export const SYSTEM_PROMPT_REQUEST_CHANNEL = "one-code:system-prompt-request";
69
+
70
+ export interface SystemPromptRequest {
71
+ /** The requester's context: cwd and model shape the prompt. */
72
+ ctx: unknown;
73
+ /** Set by the listener before it returns. */
74
+ prompt?: string;
75
+ }
76
+
77
+ /** Ask for One Code's system prompt; `undefined` when nobody answered. */
78
+ export function requestSystemPrompt(events: { emit(channel: string, data: unknown): void }, ctx: unknown): string | undefined {
79
+ const request: SystemPromptRequest = { ctx };
80
+ events.emit(SYSTEM_PROMPT_REQUEST_CHANNEL, request);
81
+ return typeof request.prompt === "string" ? request.prompt : undefined;
82
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Hold something for a message the user queued mid-turn until pi delivers it.
3
+ *
4
+ * Since pi 0.86 the `input` event also fires for a message queued while a turn
5
+ * runs (`streamingBehavior` set; interactive queue, RPC steer/follow_up). A
6
+ * one-shot reminder emitted there is taken by whatever the model reads next,
7
+ * usually the running tool's result, so it lands before the message it is
8
+ * about, a request or more early for a follow-up. A context message of its
9
+ * own is no better: pi drains one queued message per request, so it arrives a
10
+ * request late. Instead the caller holds the payload here, keyed by the typed
11
+ * text, and releases it from `message_start` when pi delivers the user message
12
+ * with that text (compare with pi-ai's `contentText(content, "")`, the
13
+ * comparison pi uses for its own queue). pi awaits `message_start` before the
14
+ * request that carries the message, so a `last-append` one-shot emitted then
15
+ * is pinned to that message.
16
+ *
17
+ * A queued message pi expands first (a prompt template) never matches, nor
18
+ * does one an extension replaces, unless its replacement carries the typed
19
+ * text (the skill extension's `details.input`, which hooks matches). Clear on `agent_settled` so its payload
20
+ * never rides a later, unrelated prompt, and on `session_start`.
21
+ *
22
+ * Pure (no pi imports); each extension keeps its own instance.
23
+ */
24
+ export class QueuedDelivery<T> {
25
+ private entries: Array<{ text: string; payload: T }> = [];
26
+
27
+ hold(text: string, payload: T): void {
28
+ this.entries.push({ text, payload });
29
+ }
30
+
31
+ /** The payload held for the delivered message with this text, oldest first; removed once released. */
32
+ release(text: string): T | undefined {
33
+ const index = this.entries.findIndex((entry) => entry.text === text);
34
+ return index === -1 ? undefined : this.entries.splice(index, 1)[0].payload;
35
+ }
36
+
37
+ get isEmpty(): boolean {
38
+ return this.entries.length === 0;
39
+ }
40
+
41
+ clear(): void {
42
+ this.entries = [];
43
+ }
44
+ }