one-code-extension 0.4.0 → 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 +219 -219
  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 +66 -34
  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 +19 -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
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * System-reminder queue — the steering mechanism shared by all One Code
3
3
  * extensions. Reminders are injected transiently into the outgoing request via
4
- * pi's `context` event; they are never written to the session file.
4
+ * pi's `context` event. Context and sticky snapshots are stored as hidden custom
5
+ * entries by the owner, never as rendered transcript messages.
5
6
  *
6
7
  * Cross-extension contract: emit on the event bus channel
7
8
  * `one-code:system-reminder` with `{ text, scope?, placement? }` to enqueue from
@@ -16,14 +17,18 @@
16
17
  * FIRST user message, before the user's text, ordered by `order`, exactly
17
18
  * Claude Code's first-message stack. Byte-stable, so the cached prefix holds.
18
19
  * - `sticky-append` — SESSION STATE that switches on and off (auto mode, plan
19
- * mode, a worktree session, ultracode). Appended to the user message that
20
- * opened the turn during which the state switched on (`since`) and to EVERY
21
- * user message after it, so message N carries the block on every later
22
- * request too: the prefix stays byte-stable while the state holds, and the
23
- * model still sees the reminder on its latest turn. Switching off drops the
24
- * blocks (one cache miss, once). The old `last-append` + every-turn
25
- * combination re-cached the previous turn on every turn — permanently, in
26
- * auto mode.
20
+ * mode, a worktree session, ultracode). Appended to EVERY user message
21
+ * stamped after the state switched on (`since`). Mid-turn it first rides the
22
+ * switching call's tool result (`toolCallId` → `tailPin`), or the first
23
+ * carrier after the switch, never the already-cached turn opener. That pin
24
+ * and every later user carrier keep identical bytes. Switching off CLOSES
25
+ * the lifetime (`until`) and freezes its actual user carriers (`userPins`),
26
+ * so queued input cannot acquire past state. It never removes blocks from
27
+ * history. Re-entry opens a separate lifetime. Closed lifetimes
28
+ * are persisted alongside open ones and dropped only when all their carriers
29
+ * leave the context (compaction/fork). They never migrate onto a compaction
30
+ * summary; retained messages still carry their historical blocks. Legacy
31
+ * `opener` anchors are honored on resume, but never created for new states.
27
32
  * - `last-append` — ONE-SHOT STEERING about what just happened (a deferred tool
28
33
  * miss, a file changed under the model, a mode change). Delivered where the
29
34
  * model reads next, and then KEPT there so the message never changes again:
@@ -33,8 +38,8 @@
33
38
  * request ever re-caches the result); a one-shot still pending when a request
34
39
  * goes out is `pin`ned to the request's tail (the trailing tool result by call
35
40
  * id, else the last user message by timestamp) and re-attached to that same
36
- * message on every later request, byte-identical. Pins are process memory
37
- * (a `--resume` drops them: one miss, once).
41
+ * message on every later request, byte-identical. Pins are persisted with
42
+ * sticky lifetimes so a resume also preserves the switch announcements.
38
43
  * - `user-prepend` — LOCAL-COMMAND BREADCRUMBS (`/clear`, `/model`, a panel
39
44
  * command): what Claude Code shows the model when the user ran a slash
40
45
  * command. Bare text blocks placed BEFORE the user's text on the next user
@@ -82,11 +87,18 @@ export type PinAnchor = { kind: "toolResult"; toolCallId: string } | { kind: "us
82
87
 
83
88
  /**
84
89
  * `order` values for the `first-prepend` context stack, matching Claude Code's
85
- * fixed sequence on the first user message: deferred tools → agent catalog → MCP
86
- * instructions → skills → the `# claudeMd` block (last, just before the user's
87
- * text). Gaps leave room for One Code-specific reminders (e.g. subagent models).
90
+ * fixed sequence on the first user message: environment → model line → deferred
91
+ * tools → agent catalog → MCP instructions → skills → the instructions block →
92
+ * the context block → the date (just before the user's text). Gaps leave room
93
+ * for One Code-specific reminders (e.g. subagent models). On a model that takes
94
+ * a mid-conversation system message, everything below `claudeMd`, and the date,
95
+ * moves into that message instead (`movesToSystemRole`, lib/system-role.ts).
88
96
  */
89
97
  export const CONTEXT_ORDER = {
98
+ environment: 1,
99
+ modelLine: 2,
100
+ /** One Code's note that a `-p`/json run ends with its turn (`lib/notifications.ts oneShotSessionNote`); not Claude Code's. */
101
+ oneShot: 3,
90
102
  deferredTools: 10,
91
103
  subagentModels: 20,
92
104
  agents: 21,
@@ -94,17 +106,37 @@ export const CONTEXT_ORDER = {
94
106
  delegation: 22,
95
107
  mcp: 30,
96
108
  skills: 40,
109
+ /** Claude Code's auto-mode note; only in the system message (`systemRoleOnly`). */
110
+ autoModeNote: 45,
111
+ /** The `<total_tokens>` line the first prompt carries (later prompts: lib/turn-budget-layout.ts). */
112
+ totalTokens: 46,
97
113
  // CLAUDE.md-family (with AGENTS.md as a per-directory fallback when a directory
98
114
  // has no CLAUDE.md) — CLAUDE.md > AGENTS.md.
99
115
  claudeMd: 50,
100
- // One Code's own instructions ride in their own block AFTER the # claudeMd
116
+ // One Code's own instructions ride in their own block AFTER the instructions
101
117
  // block (higher `order` = closer to the user text = higher precedence), so
102
118
  // ONECODE.md takes precedence over CLAUDE.md/AGENTS.md. Not part of CC.
103
- oneCodeMd: 60,
119
+ oneCodeMd: 52,
120
+ /** The user's email and the git snapshot. */
121
+ context: 54,
122
+ date: 56,
104
123
  } as const;
105
124
 
125
+ /**
126
+ * Whether a `first-prepend` block of this order leaves the first user message
127
+ * for the mid-conversation system message on a model that takes one: the
128
+ * session facts below the instructions block, and the date, which Claude Code
129
+ * puts last in that message. The instructions, One Code's own block and the
130
+ * context block stay in the user message.
131
+ */
132
+ export function movesToSystemRole(order: number): boolean {
133
+ return order < CONTEXT_ORDER.claudeMd || order === CONTEXT_ORDER.date;
134
+ }
135
+
106
136
  /** A drained reminder with everything the injector needs to place it. */
107
137
  export interface ReminderEntry {
138
+ /** Present in session snapshots; not rendered into the request. */
139
+ key?: string;
108
140
  text: string;
109
141
  placement: ReminderPlacement;
110
142
  order: number;
@@ -115,11 +147,28 @@ export interface ReminderEntry {
115
147
  */
116
148
  suffix?: string;
117
149
  /**
118
- * `sticky-append` only: when the state switched on. The user message that
119
- * opened the turn in progress at that moment, and every user message after
120
- * it, carry the block.
150
+ * `sticky-append` only: when the state switched on. Every user message
151
+ * stamped at or after it carries the block while open. Closing freezes the
152
+ * actual carriers in `userPins`; legacy snapshots use `until` as the end.
121
153
  */
122
154
  since?: number;
155
+ /** `sticky-append` only: exclusive end of a closed lifetime; absent while active. */
156
+ until?: number;
157
+ /** Actual user carriers of the last request; frozen on close, so queued input cannot acquire old state. */
158
+ userPins?: number[];
159
+ /** Pending sticky activation belongs to this call, not another result in its parallel batch. */
160
+ toolCallId?: string;
161
+ /**
162
+ * Legacy `sticky-append` anchor from older snapshots. Honored to keep their
163
+ * bytes stable on resume; new lifetimes use null, never an earlier opener.
164
+ */
165
+ opener?: number | null;
166
+ /**
167
+ * `sticky-append` only: its first mid-turn tool result, or the tail of a
168
+ * request without any user carrier (overflow compaction). Once chosen it
169
+ * stays on that message, even after later user turns arrive or state closes.
170
+ */
171
+ tailPin?: PinAnchor;
123
172
  /** Set on a pinned one-shot: the exact message it rides on every request. */
124
173
  pin?: PinAnchor;
125
174
  /**
@@ -128,6 +177,17 @@ export interface ReminderEntry {
128
177
  * result. Defaults to false.
129
178
  */
130
179
  raw?: boolean;
180
+ /**
181
+ * `first-prepend` only: sent only inside the mid-conversation system
182
+ * message, never on the user message of a model without one (Claude Code's
183
+ * auto-mode note).
184
+ */
185
+ systemRoleOnly?: boolean;
186
+ /**
187
+ * `sticky-append` only: never on the message that carries the context
188
+ * stack, which already says the same (the first prompt's `<total_tokens>`).
189
+ */
190
+ skipStackCarrier?: boolean;
131
191
  }
132
192
 
133
193
  export interface ReminderPayload {
@@ -135,7 +195,7 @@ export interface ReminderPayload {
135
195
  scope?: ReminderScope;
136
196
  /** Key so a reminder can be replaced (next-turn) or replaced/removed (every-turn). */
137
197
  key?: string;
138
- /** Remove the every-turn reminder registered under `key` instead of enqueueing. */
198
+ /** Close a sticky lifetime under `key`; remove other every-turn reminders. */
139
199
  remove?: boolean;
140
200
  /** Where in the message stack this reminder lands. Defaults to `last-append`. */
141
201
  placement?: ReminderPlacement;
@@ -145,6 +205,10 @@ export interface ReminderPayload {
145
205
  suffix?: string;
146
206
  /** Emit the text bare, with no `<system-reminder>` frame. */
147
207
  raw?: boolean;
208
+ /** `first-prepend` only: sent only inside the mid-conversation system message (see ReminderEntry). */
209
+ systemRoleOnly?: boolean;
210
+ /** `sticky-append` only: never on the message that carries the context stack (see ReminderEntry). */
211
+ skipStackCarrier?: boolean;
148
212
  /** `user-prepend` only: skip when the same text is already pending (the shared caveat). */
149
213
  once?: boolean;
150
214
  /**
@@ -153,6 +217,13 @@ export interface ReminderPayload {
153
217
  * brought back (the `<total_tokens>` line rides every user message in CC).
154
218
  */
155
219
  since?: number;
220
+ /**
221
+ * The tool call whose result this reminder belongs to. For `last-append`,
222
+ * only that result's `tool_result` hook takes the one-shot. For a new sticky
223
+ * lifetime, that result is its first pin. Both avoid attaching a switch or
224
+ * countdown to another call in a parallel batch.
225
+ */
226
+ toolCallId?: string;
156
227
  }
157
228
 
158
229
  interface StoredReminder extends ReminderEntry {
@@ -166,10 +237,14 @@ type EnqueueOptions = {
166
237
  order?: number;
167
238
  suffix?: string;
168
239
  raw?: boolean;
240
+ systemRoleOnly?: boolean;
241
+ skipStackCarrier?: boolean;
169
242
  /** Test seam / explicit anchor for `sticky-append`; defaults to now. */
170
243
  since?: number;
171
244
  /** `user-prepend` only: skip when the same text is already pending. */
172
245
  once?: boolean;
246
+ /** The switching tool result, or the hook that takes a one-shot (see ReminderPayload). */
247
+ toolCallId?: string;
173
248
  };
174
249
 
175
250
  export class ReminderQueue {
@@ -177,15 +252,34 @@ export class ReminderQueue {
177
252
  /** Delivered one-shots, each fixed to the message it first rode (see `pin`). */
178
253
  private pinned: StoredReminder[] = [];
179
254
  private everyTurn = new Map<string, StoredReminder>();
255
+ /** Open and closed lifetimes in original insertion order, independent of live keys. */
256
+ private sticky: StoredReminder[] = [];
180
257
  private readonly now: () => number;
258
+ /** Only restored keys are protected; fresh sessions retain their existing emitter behavior. */
259
+ private restoredKeys = new Set<string>();
260
+ private pendingCapabilities = new Set<string>();
261
+ private changes = 0;
181
262
 
182
263
  constructor(now: () => number = Date.now) {
183
264
  this.now = now;
184
265
  }
185
266
 
267
+ /**
268
+ * Moves on every change to what the queue holds (a drain only when it
269
+ * re-anchored, pinned or dropped something), so the owner can skip
270
+ * serializing a snapshot that cannot have changed.
271
+ */
272
+ get version(): number {
273
+ return this.changes;
274
+ }
275
+
186
276
  enqueue(text: string, opts?: EnqueueOptions): void {
187
277
  if (!text.trim()) return;
188
278
  const placement = opts?.placement ?? "last-append";
279
+ if (placement === "first-prepend" && opts?.key) {
280
+ if (this.restoredKeys.has(opts.key)) return;
281
+ this.pendingCapabilities.delete(opts.key);
282
+ }
189
283
  const entry: StoredReminder = {
190
284
  text,
191
285
  placement,
@@ -193,20 +287,30 @@ export class ReminderQueue {
193
287
  suffix: opts?.suffix,
194
288
  key: opts?.key,
195
289
  raw: opts?.raw,
290
+ toolCallId: opts?.toolCallId,
196
291
  };
292
+ if (opts?.systemRoleOnly) entry.systemRoleOnly = true;
293
+ if (opts?.skipStackCarrier) entry.skipStackCarrier = true;
294
+ const key = opts?.key ?? text;
295
+ const previous = this.everyTurn.get(key);
197
296
  if (placement === "sticky-append") {
198
- // A standing reminder re-emitted with the SAME text (plan mode re-emits
199
- // every turn) keeps its anchor, so the blocks on earlier messages do not
200
- // move. Different text under the same key is a new fact (plan → auto
201
- // under the shared "permission-mode" key) and anchors from now.
202
- const previous = opts?.key !== undefined ? this.everyTurn.get(opts.key) : undefined;
203
- entry.since =
204
- opts?.since ??
205
- (previous?.placement === "sticky-append" && previous.text === text ? previous.since : undefined) ??
206
- this.now();
297
+ // Re-emitting an unchanged standing fact keeps both its anchors and its
298
+ // position among other blocks. A changed fact opens a new lifetime.
299
+ const same = previous?.placement === placement && previous.text === text &&
300
+ previous.suffix === entry.suffix && previous.raw === entry.raw &&
301
+ previous.skipStackCarrier === entry.skipStackCarrier && previous.order === entry.order ? previous : undefined;
302
+ entry.since = opts?.since ?? same?.since ?? this.now();
303
+ entry.userPins = [];
304
+ if (same && same.since === entry.since && opts?.scope === "every-turn") return;
207
305
  }
306
+ this.changes++;
208
307
  if (opts?.scope === "every-turn") {
209
- this.everyTurn.set(opts.key ?? text, entry);
308
+ if (previous?.placement === "sticky-append") this.remove(key);
309
+ this.everyTurn.set(key, entry);
310
+ if (placement === "sticky-append") {
311
+ entry.key = key;
312
+ this.sticky.push(entry);
313
+ }
210
314
  return;
211
315
  }
212
316
  // A breadcrumb run shares one caveat block (`once`): the same text pending
@@ -222,17 +326,132 @@ export class ReminderQueue {
222
326
  }
223
327
 
224
328
  remove(key: string): void {
225
- this.everyTurn.delete(key);
329
+ this.changes++;
330
+ if (!this.restoredKeys.has(key)) {
331
+ const entry = this.everyTurn.get(key);
332
+ if (entry?.placement === "sticky-append") entry.until = this.now();
333
+ this.everyTurn.delete(key);
334
+ }
335
+ this.pendingCapabilities.delete(key);
336
+ }
337
+
338
+ /** Seed before startup emitters. Earlier hooks (context-budget) may already have emitted. */
339
+ restore(stack: readonly ReminderEntry[], sticky: readonly ReminderEntry[], frozenKeys: ReadonlySet<string>, liveKeys: ReadonlySet<string> = new Set(), pinned: readonly ReminderEntry[] = []): void {
340
+ this.changes++;
341
+ const early = [...this.everyTurn.values()];
342
+ this.everyTurn.clear();
343
+ this.sticky = [];
344
+ this.pinned = structuredClone([...pinned]);
345
+ this.restoredKeys = new Set(frozenKeys);
346
+ this.pendingCapabilities = new Set(stack.flatMap((entry) => entry.key && liveKeys.has(entry.key) ? [entry.key] : []));
347
+ for (const source of [...stack, ...sticky]) {
348
+ const entry = structuredClone(source);
349
+ if (entry.placement === "sticky-append") this.sticky.push(entry);
350
+ if (entry.until === undefined) this.everyTurn.set(entry.key ?? entry.text, entry);
351
+ }
352
+ for (const entry of early) this.enqueue(entry.text, { ...entry, scope: "every-turn" });
353
+ }
354
+
355
+ /**
356
+ * A session start with no snapshot (`/clear` after a resume): the previous
357
+ * session's resume locks must not keep the new session's emitters out.
358
+ */
359
+ releaseRestore(): void {
360
+ this.restoredKeys.clear();
361
+ this.pendingCapabilities.clear();
362
+ }
363
+
364
+ /** The first request has seen all startup/turn emitters. An absent capability is gone. */
365
+ finishRestore(): void {
366
+ if (this.pendingCapabilities.size > 0) this.changes++;
367
+ for (const key of this.pendingCapabilities) this.everyTurn.delete(key);
368
+ this.pendingCapabilities.clear();
369
+ }
370
+
371
+ /** A compaction replaces these facts atomically, bypassing only their resume locks. */
372
+ replaceFirstPrepend(keys: readonly string[], entries: readonly ReminderEntry[]): void {
373
+ this.changes++;
374
+ const replaced = new Set(keys);
375
+ for (const key of replaced) this.everyTurn.delete(key);
376
+ this.nextTurn = this.nextTurn.filter((entry) => !entry.key || !replaced.has(entry.key));
377
+ for (const entry of entries) {
378
+ if (entry.key && replaced.has(entry.key) && entry.placement === "first-prepend") {
379
+ this.everyTurn.set(entry.key, structuredClone(entry));
380
+ }
381
+ }
382
+ }
383
+
384
+ /**
385
+ * A `/tree` switch to a branch whose last snapshot is `snapshot`: its
386
+ * session facts (`factKeys`, an absent one included), its sticky lifetimes
387
+ * and its pins come back, so its messages keep the bytes they were sent
388
+ * with. Live state stays live: other first-prepend blocks keep their
389
+ * current text, and a lifetime open on the branch stays open only while its
390
+ * owner still holds it, else it closes on its recorded carriers. A live
391
+ * lifetime the branch shows closed goes on as a new one from now. What the
392
+ * branch left behind put here stays until a drain finds its carriers gone.
393
+ */
394
+ restoreBranch(snapshot: { stack: readonly ReminderEntry[]; sticky: readonly ReminderEntry[]; pinned?: readonly ReminderEntry[] }, factKeys: readonly string[]): void {
395
+ this.replaceFirstPrepend(factKeys, snapshot.stack);
396
+ const same = (a: ReminderEntry, b: ReminderEntry) => a.key === b.key && a.text === b.text && a.since === b.since;
397
+ const restored: StoredReminder[] = structuredClone([...snapshot.sticky]);
398
+ const now = this.now();
399
+ for (const entry of restored) {
400
+ if (entry.until !== undefined) continue;
401
+ const live = this.sticky.find((current) => current.until === undefined && same(current, entry));
402
+ if (live?.key !== undefined && this.everyTurn.get(live.key) === live) this.everyTurn.set(live.key, entry);
403
+ else entry.until = now;
404
+ }
405
+ this.sticky = [...restored, ...this.sticky.filter((current) => !restored.some((entry) => same(entry, current)))];
406
+ // The branch's carriers keep their closed copy of a lifetime still live here.
407
+ for (const [key, live] of this.everyTurn) {
408
+ if (live.placement !== "sticky-append" || live.until !== undefined || this.sticky.includes(live)) continue;
409
+ const { toolCallId: _call, tailPin: _pin, opener: _opener, ...state } = live;
410
+ const reopened: StoredReminder = { ...state, key, since: now, userPins: [] };
411
+ this.everyTurn.set(key, reopened);
412
+ this.sticky.push(reopened);
413
+ }
414
+ const pinned: StoredReminder[] = structuredClone([...(snapshot.pinned ?? [])]);
415
+ const known = new Set(pinned.map((entry) => JSON.stringify(strip(entry))));
416
+ this.pinned = [...pinned, ...this.pinned.filter((entry) => !known.has(JSON.stringify(strip(entry))))];
417
+ this.changes++;
418
+ }
419
+
420
+ /** A new snapshot supersedes undelivered notices about its old facts. Delivered pins stay history. */
421
+ cancelPending(keys: readonly string[]): void {
422
+ const cancelled = new Set(keys);
423
+ const before = this.nextTurn.length;
424
+ this.nextTurn = this.nextTurn.filter((entry) => !entry.key || !cancelled.has(entry.key));
425
+ if (this.nextTurn.length !== before) this.changes++;
426
+ }
427
+
428
+ /** Hidden session metadata, preserving keys and insertion order without changing drain's API. */
429
+ persistentEntries(placement: "first-prepend" | "sticky-append"): ReminderEntry[] {
430
+ return [
431
+ ...(placement === "sticky-append" ? this.sticky.map((entry) => ({ ...strip(entry), key: entry.key })) :
432
+ [...this.everyTurn.entries()].filter(([, entry]) => entry.placement === placement).map(([key, entry]) => ({ ...strip(entry), key }))),
433
+ ...this.nextTurn.filter((entry) => entry.placement === placement).map((entry) => ({ ...strip(entry), key: entry.key })),
434
+ ];
435
+ }
436
+
437
+ /** Delivered steering is history too, especially the notice that closes a sticky state. */
438
+ persistentPins(): ReminderEntry[] {
439
+ return this.pinned.map(strip);
226
440
  }
227
441
 
228
442
  /**
229
443
  * Take the pending `last-append` one-shots out of the queue — for the
230
- * `tool_result` hook, which writes them into the stored result. Other
444
+ * `tool_result` hook, which writes them into the stored result. A one-shot
445
+ * bound to another call's result stays queued for that result's hook. Other
231
446
  * placements stay queued for the next request.
232
447
  */
233
- takeOneShots(): ReminderEntry[] {
234
- const { matching, rest } = partition(this.nextTurn, (r) => r.placement === "last-append");
448
+ takeOneShots(toolCallId?: string): ReminderEntry[] {
449
+ const { matching, rest } = partition(
450
+ this.nextTurn,
451
+ (r) => r.placement === "last-append" && (r.toolCallId === undefined || r.toolCallId === toolCallId),
452
+ );
235
453
  this.nextTurn = rest;
454
+ if (matching.length > 0) this.changes++;
236
455
  return matching.map(strip);
237
456
  }
238
457
 
@@ -244,10 +463,16 @@ export class ReminderQueue {
244
463
  * on a request that ends in a user-like message (`openingUserAnchor`) —
245
464
  * mid-turn, breadcrumbs stay queued.
246
465
  */
247
- pin(anchor: PinAnchor, placement: "last-append" | "user-prepend" = "last-append"): void {
466
+ pin(anchor: PinAnchor, placement: "last-append" | "user-prepend" = "last-append", messages?: readonly AgentMessage[]): void {
248
467
  const { matching, rest } = partition(this.nextTurn, (r) => r.placement === placement);
249
- for (const entry of matching) this.pinned.push({ ...entry, pin: anchor });
468
+ for (const entry of matching) {
469
+ // A one-shot bound to a call whose result hook already ran (a read's nested
470
+ // instructions) rides that call's result, not whichever result ended the batch.
471
+ const own = entry.toolCallId !== undefined && messages ? ownResultAnchor(messages, entry.toolCallId) : undefined;
472
+ this.pinned.push({ ...entry, pin: own ?? anchor });
473
+ }
250
474
  this.nextTurn = rest;
475
+ if (matching.length > 0) this.changes++;
251
476
  }
252
477
 
253
478
  /**
@@ -263,17 +488,51 @@ export class ReminderQueue {
263
488
  * did exactly that (CACHE-REVIEW-2026-09-04 M3).
264
489
  */
265
490
  drain(messages: AgentMessage[]): ReminderEntry[] {
266
- if (this.pinned.length > 0) {
267
- const locate = pinLocator(messages);
268
- this.pinned = this.pinned.filter((entry) => locate(entry.pin as PinAnchor) !== -1);
491
+ const locate = pinLocator(messages);
492
+ const stackCarrier = messages.findIndex(isUserLike);
493
+ let changed = false;
494
+ for (const entry of this.sticky) {
495
+ if (entry.until !== undefined) continue;
496
+ if (entry.opener === undefined) {
497
+ entry.opener = null;
498
+ changed = true;
499
+ }
500
+ // Only live states may find a new carrier after compaction. A closed
501
+ // state's missing pin must never move to the summary or a later turn.
502
+ const tailPin = entry.tailPin;
503
+ if (entry.tailPin && locate(entry.tailPin) === -1) entry.tailPin = undefined;
504
+ if (entry.tailPin === undefined) entry.tailPin = firstStickyPin(messages, entry);
505
+ if (!samePin(tailPin, entry.tailPin)) changed = true;
506
+ // Timestamps alone cannot close a lifetime: a steer typed while it was
507
+ // active can remain queued until after the switch. Keep only messages
508
+ // that actually rode the block, including across a startup-time close.
509
+ const userPins = messages.flatMap((m, index) => carriesSticky(m, entry) &&
510
+ (!entry.skipStackCarrier || index !== stackCarrier) ? [m.timestamp] : []);
511
+ // The same array stays while its carriers do, so its lookup set (`carrierSet`) is reused.
512
+ if (!entry.userPins || entry.userPins.length !== userPins.length || entry.userPins.some((stamp, i) => stamp !== userPins[i])) {
513
+ entry.userPins = userPins;
514
+ changed = true;
515
+ }
516
+ if ((entry.tailPin || entry.userPins.length > 0) && entry.toolCallId !== undefined) {
517
+ delete entry.toolCallId;
518
+ changed = true;
519
+ }
269
520
  }
521
+ const sticky = this.sticky.length;
522
+ this.sticky = this.sticky.filter((entry) => entry.until === undefined ||
523
+ messages.some((m) => carriesSticky(m, entry)) || (entry.tailPin !== undefined && locate(entry.tailPin) !== -1));
524
+ const pinned = this.pinned.length;
525
+ if (this.pinned.length > 0) this.pinned = this.pinned.filter((entry) => locate(entry.pin as PinAnchor) !== -1);
270
526
  const { matching: held, rest: pending } = partition(this.nextTurn, (r) => r.placement === "user-prepend");
271
527
  this.nextTurn = held;
272
- return [...[...this.everyTurn.values()].map(strip), ...this.pinned.map(strip), ...pending.map(strip)];
528
+ if (changed || pending.length > 0 || this.sticky.length !== sticky || this.pinned.length !== pinned) this.changes++;
529
+ return [...[...this.everyTurn.values()].filter((entry) => entry.placement !== "sticky-append").map(strip),
530
+ ...this.sticky.map(strip), ...this.pinned.map(strip), ...pending.map(strip)];
273
531
  }
274
532
 
275
533
  get size(): number {
276
- return this.everyTurn.size + this.nextTurn.length + this.pinned.length;
534
+ return [...this.everyTurn.values()].filter((entry) => entry.placement !== "sticky-append").length +
535
+ this.sticky.length + this.nextTurn.length + this.pinned.length;
277
536
  }
278
537
 
279
538
  /** True when a one-shot of `placement` is waiting to be delivered (the owner decides where). */
@@ -298,7 +557,14 @@ function partition<T>(items: T[], test: (item: T) => boolean): { matching: T[];
298
557
  function strip(r: StoredReminder): ReminderEntry {
299
558
  const entry: ReminderEntry = { text: r.text, placement: r.placement, order: r.order, suffix: r.suffix };
300
559
  if (r.raw) entry.raw = true;
560
+ if (r.systemRoleOnly) entry.systemRoleOnly = true;
561
+ if (r.skipStackCarrier) entry.skipStackCarrier = true;
301
562
  if (r.since !== undefined) entry.since = r.since;
563
+ if (r.until !== undefined) entry.until = r.until;
564
+ if (r.userPins !== undefined) entry.userPins = [...r.userPins];
565
+ if (r.placement === "sticky-append" && r.toolCallId !== undefined) entry.toolCallId = r.toolCallId;
566
+ if (r.opener !== undefined) entry.opener = r.opener;
567
+ if (r.tailPin) entry.tailPin = r.tailPin;
302
568
  if (r.pin !== undefined) entry.pin = r.pin;
303
569
  return entry;
304
570
  }
@@ -324,6 +590,49 @@ function pinLocator(messages: AgentMessage[]): (pin: PinAnchor) => number {
324
590
  return (pin) => (pin.kind === "toolResult" ? byToolCall.get(pin.toolCallId) : byTimestamp.get(pin.timestamp)) ?? -1;
325
591
  }
326
592
 
593
+ function samePin(a: PinAnchor | undefined, b: PinAnchor | undefined): boolean {
594
+ if (!a || !b) return a === b;
595
+ return a.kind === "toolResult" ? b.kind === "toolResult" && a.toolCallId === b.toolCallId : b.kind === "user" && a.timestamp === b.timestamp;
596
+ }
597
+
598
+ /** Each carrier list's lookup set, built once per array: a check runs for every message against every lifetime. */
599
+ const carrierSets = new WeakMap<readonly number[], Set<number>>();
600
+
601
+ function carrierSet(userPins: readonly number[]): Set<number> {
602
+ let set = carrierSets.get(userPins);
603
+ if (!set) carrierSets.set(userPins, (set = new Set(userPins)));
604
+ return set;
605
+ }
606
+
607
+ /** Closed lifetimes keep only delivered carriers; legacy snapshots retain their timestamp interval. */
608
+ function carriesSticky(message: AgentMessage, entry: ReminderEntry): boolean {
609
+ if (!isStickyCarrier(message)) return false;
610
+ const stamp = (message as { timestamp?: number }).timestamp ?? 0;
611
+ if (entry.until !== undefined && entry.userPins !== undefined) return carrierSet(entry.userPins).has(stamp);
612
+ return (stamp >= (entry.since ?? 0) && (entry.until === undefined || stamp < entry.until)) ||
613
+ (entry.opener !== null && entry.opener !== undefined && stamp === entry.opener);
614
+ }
615
+
616
+ /** First carrier after activation; never reach back into an earlier user turn. */
617
+ function firstStickyPin(messages: AgentMessage[], entry: ReminderEntry): PinAnchor | undefined {
618
+ if (entry.until !== undefined) return undefined;
619
+ if (entry.toolCallId !== undefined) return ownResultAnchor(messages, entry.toolCallId);
620
+ for (const message of messages) {
621
+ if (carriesSticky(message, entry)) return undefined;
622
+ if (message.role === "toolResult" && message.timestamp >= (entry.since ?? 0)) {
623
+ return { kind: "toolResult", toolCallId: message.toolCallId };
624
+ }
625
+ }
626
+ // Overflow compaction may retain only an old reply/result. With no real
627
+ // user turn, a live state must still be visible and pinned to that tail.
628
+ return messages.some(isStickyCarrier) ? undefined : tailAnchor(messages);
629
+ }
630
+
631
+ /** `toolCallId`'s own result as an anchor, when this request holds it. */
632
+ function ownResultAnchor(messages: readonly AgentMessage[], toolCallId: string): PinAnchor | undefined {
633
+ return messages.some((m) => m.role === "toolResult" && m.toolCallId === toolCallId) ? { kind: "toolResult", toolCallId } : undefined;
634
+ }
635
+
327
636
  /** The anchor a one-shot lands on for this request: the trailing tool result, else the last user-like message. */
328
637
  export function tailAnchor(messages: AgentMessage[]): PinAnchor | undefined {
329
638
  const last = messages[messages.length - 1] as (AgentMessage & { toolCallId?: string }) | undefined;
@@ -387,8 +696,13 @@ function toBlocks(content: string | ContentBlock[]): ContentBlock[] {
387
696
  return typeof content === "string" ? [{ type: "text", text: content }] : [...content];
388
697
  }
389
698
 
699
+ /** A reminder's text exactly as it rides a message: framed (unless raw), then its suffix. */
700
+ export function framedReminderText(entry: Pick<ReminderEntry, "text" | "raw" | "suffix">): string {
701
+ return (entry.raw ? entry.text : wrapReminder(entry.text)) + (entry.suffix ?? "");
702
+ }
703
+
390
704
  function reminderBlock(entry: ReminderEntry): TextContent {
391
- return { type: "text", text: (entry.raw ? entry.text : wrapReminder(entry.text)) + (entry.suffix ?? "") };
705
+ return { type: "text", text: framedReminderText(entry) };
392
706
  }
393
707
 
394
708
  /**
@@ -473,28 +787,20 @@ export function injectReminders(messages: AgentMessage[], reminders: Array<strin
473
787
 
474
788
  push(before, firstUserIndex === -1 ? tailIndex : firstUserIndex, firstPrepend.map(reminderBlock));
475
789
 
790
+ const locate = pinLocator(messages);
476
791
  for (const entry of sticky) {
477
- const since = entry.since ?? 0;
478
- // The block rides the user message that opened the turn during which the
479
- // state switched on (the latest user message stamped before `since` — a
480
- // standing reminder emitted on before_agent_start is always stamped after
481
- // the turn's user message) and every user message after it. The set only
482
- // grows while the state holds, so earlier messages never change.
483
- let opener = -1;
484
792
  const carriers: number[] = [];
485
793
  messages.forEach((m, index) => {
486
- if (!isStickyCarrier(m)) return;
487
- const stamp = (m as { timestamp?: number }).timestamp ?? 0;
488
- if (stamp >= since) carriers.push(index);
489
- else opener = index;
794
+ if (carriesSticky(m, entry)) carriers.push(index);
490
795
  });
491
- if (opener !== -1) carriers.unshift(opener);
492
- if (carriers.length === 0) {
493
- // No user turn at all (overflow compaction mid-turn): ride the tail.
494
- push(after, tailIndex === -1 ? firstUserIndex : tailIndex, [reminderBlock(entry)]);
796
+ const pin = entry.tailPin ?? firstStickyPin(messages, entry);
797
+ const pinned = pin ? locate(pin) : -1;
798
+ if (pinned !== -1 && !carriers.includes(pinned)) carriers.unshift(pinned);
799
+ if (entry.skipStackCarrier && carriers.includes(firstUserIndex)) {
800
+ for (const index of carriers.filter((index) => index !== firstUserIndex).sort((a, b) => a - b)) push(after, index, [reminderBlock(entry)]);
495
801
  continue;
496
802
  }
497
- for (const index of carriers) push(after, index, [reminderBlock(entry)]);
803
+ for (const index of carriers.sort((a, b) => a - b)) push(after, index, [reminderBlock(entry)]);
498
804
  }
499
805
 
500
806
  push(after, tailIndex === -1 ? firstUserIndex : tailIndex, lastAppend.map(reminderBlock));