one-code-extension 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (439) hide show
  1. package/README.md +16 -5
  2. package/agents/general-purpose.md +2 -2
  3. package/docs/guide/README.md +2 -1
  4. package/docs/guide/bring-your-claude-code-setup.md +60 -9
  5. package/docs/guide/configuration.md +48 -14
  6. package/docs/guide/differences-from-claude-code.md +40 -28
  7. package/docs/guide/doctor.md +20 -13
  8. package/docs/guide/hooks.md +18 -3
  9. package/docs/guide/installation.md +26 -4
  10. package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
  11. package/docs/guide/providers-and-models.md +137 -54
  12. package/docs/guide/reference.md +27 -15
  13. package/docs/guide/sessions-and-context.md +69 -16
  14. package/docs/guide/skills-plugins-and-mcp.md +57 -20
  15. package/docs/guide/subagents-and-workflows.md +95 -29
  16. package/docs/guide/tasks-and-background-work.md +101 -26
  17. package/docs/guide/terminal-interface.md +24 -12
  18. package/docs/guide/tools.md +46 -5
  19. package/docs/guide/troubleshooting.md +13 -9
  20. package/docs/guide/windows.md +58 -31
  21. package/extensions/artifacts/gallery.ts +227 -0
  22. package/extensions/artifacts/index.ts +288 -0
  23. package/extensions/artifacts/store.ts +570 -0
  24. package/extensions/artifacts/viewer.ts +199 -0
  25. package/extensions/ask-user/description.ts +67 -0
  26. package/extensions/ask-user/dialogs.ts +80 -0
  27. package/extensions/ask-user/index.ts +37 -33
  28. package/extensions/ask-user/questions.ts +32 -7
  29. package/extensions/ask-user/widget.ts +45 -25
  30. package/extensions/auto-mode/actions.ts +6 -0
  31. package/extensions/auto-mode/cache.ts +88 -0
  32. package/extensions/auto-mode/classifier-prompt.ts +5 -4
  33. package/extensions/auto-mode/classifier.ts +290 -65
  34. package/extensions/auto-mode/config.ts +72 -20
  35. package/extensions/auto-mode/decision-log.ts +2 -0
  36. package/extensions/auto-mode/defaults.ts +1 -1
  37. package/extensions/auto-mode/denials.ts +124 -0
  38. package/extensions/auto-mode/git-checkout-programs.ts +187 -0
  39. package/extensions/auto-mode/git-status-meta.ts +159 -0
  40. package/extensions/auto-mode/history.ts +189 -0
  41. package/extensions/auto-mode/instructions.ts +58 -52
  42. package/extensions/auto-mode/model-select.ts +159 -134
  43. package/extensions/auto-mode/paths.ts +57 -5
  44. package/extensions/auto-mode/pause.ts +57 -2
  45. package/extensions/auto-mode/prompt.ts +37 -21
  46. package/extensions/auto-mode/read-only-options.ts +724 -0
  47. package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
  48. package/extensions/auto-mode/rules.ts +18 -1
  49. package/extensions/auto-mode/safety-floor.ts +361 -18
  50. package/extensions/auto-mode/setup-run.ts +20 -14
  51. package/extensions/auto-mode/shell-analysis.ts +764 -490
  52. package/extensions/auto-mode/shell-parse.ts +885 -0
  53. package/extensions/auto-mode/system-note.ts +33 -0
  54. package/extensions/auto-mode/transcript.ts +80 -51
  55. package/extensions/background/cron.ts +642 -0
  56. package/extensions/background/index.ts +614 -248
  57. package/extensions/background/loop-fire.ts +173 -0
  58. package/extensions/background/loop-preamble.ts +59 -0
  59. package/extensions/background/loop-skill.ts +163 -0
  60. package/extensions/background/monitor-batch.ts +53 -0
  61. package/extensions/background/noop-fold.ts +128 -0
  62. package/extensions/background/switch-guard.ts +56 -0
  63. package/extensions/background/wakeup.ts +178 -55
  64. package/extensions/bash/background.ts +53 -17
  65. package/extensions/bash/child.ts +7 -0
  66. package/extensions/bash/description.ts +188 -0
  67. package/extensions/bash/guards.ts +15 -67
  68. package/extensions/bash/index.ts +66 -35
  69. package/extensions/branding/compaction-keep.ts +63 -0
  70. package/extensions/branding/declined.ts +22 -0
  71. package/extensions/branding/index.ts +133 -9
  72. package/extensions/branding/prompt-editor.ts +53 -6
  73. package/extensions/branding/queued-edit.ts +31 -0
  74. package/extensions/branding/replaced-builtins.ts +68 -0
  75. package/extensions/branding/startup.ts +33 -71
  76. package/extensions/btw/index.ts +250 -78
  77. package/extensions/btw/panel.ts +228 -100
  78. package/extensions/btw/prompt.ts +41 -4
  79. package/extensions/cache-marks/index.ts +42 -0
  80. package/extensions/claude-compat/index.ts +7 -11
  81. package/extensions/claude-context/index.ts +436 -63
  82. package/extensions/compaction/cut.ts +157 -0
  83. package/extensions/compaction/index.ts +219 -75
  84. package/extensions/compaction/kept-thinking.ts +62 -0
  85. package/extensions/compaction/prompt.ts +15 -12
  86. package/extensions/context-budget/budget.ts +16 -2
  87. package/extensions/context-budget/index.ts +35 -15
  88. package/extensions/context-management/index.ts +36 -28
  89. package/extensions/doctor/build.ts +59 -8
  90. package/extensions/doctor/builtins.ts +84 -0
  91. package/extensions/doctor/cli.ts +7 -1
  92. package/extensions/doctor/compat.ts +88 -23
  93. package/extensions/doctor/dependencies.ts +41 -5
  94. package/extensions/doctor/fix-prompt.ts +9 -2
  95. package/extensions/doctor/index.ts +102 -58
  96. package/extensions/doctor/models.ts +102 -63
  97. package/extensions/doctor/presets.ts +42 -27
  98. package/extensions/doctor/report.ts +19 -2
  99. package/extensions/doctor/viewer.ts +49 -27
  100. package/extensions/effort/index.ts +141 -25
  101. package/extensions/effort/settings.ts +21 -0
  102. package/extensions/effort/slider.ts +24 -20
  103. package/extensions/exit/index.ts +32 -1
  104. package/extensions/file-tracker/index.ts +420 -42
  105. package/extensions/file-tracker/replay.ts +137 -24
  106. package/extensions/file-tracker/restore.ts +66 -0
  107. package/extensions/file-tracker/results.ts +66 -0
  108. package/extensions/file-tracker/shell-reads.ts +211 -0
  109. package/extensions/file-tracker/tracker.ts +45 -15
  110. package/extensions/footer/footer-line.ts +45 -24
  111. package/extensions/footer/index.ts +47 -2
  112. package/extensions/footer/pr.ts +34 -5
  113. package/extensions/hooks/compaction.ts +7 -0
  114. package/extensions/hooks/executor.ts +32 -30
  115. package/extensions/hooks/index.ts +209 -58
  116. package/extensions/hooks/matcher.ts +3 -0
  117. package/extensions/hooks/prompt-context.ts +59 -0
  118. package/extensions/hooks/protocol.ts +41 -3
  119. package/extensions/hooks/settings.ts +50 -19
  120. package/extensions/hooks/subagent-bridge.ts +2 -0
  121. package/extensions/hooks/trust.ts +26 -12
  122. package/extensions/idle-compact/index.ts +119 -0
  123. package/extensions/idle-compact/policy.ts +143 -0
  124. package/extensions/init/index.ts +11 -10
  125. package/extensions/init/prompt.ts +25 -32
  126. package/extensions/interrupted/index.ts +13 -1
  127. package/extensions/kept-thinking/index.ts +27 -0
  128. package/extensions/ledger/index.ts +46 -0
  129. package/extensions/ledger/wire.ts +154 -0
  130. package/extensions/lib/agent-cron.ts +146 -0
  131. package/extensions/lib/agent-loader.ts +18 -3
  132. package/extensions/lib/agent-view.ts +85 -0
  133. package/extensions/lib/anthropic-auth.ts +98 -0
  134. package/extensions/lib/anthropic-payload.ts +82 -1
  135. package/extensions/lib/anthropic-server-call.ts +230 -0
  136. package/extensions/lib/anthropic-server-tools.ts +485 -0
  137. package/extensions/lib/app-launch.mjs +94 -0
  138. package/extensions/lib/argument-hints.ts +190 -0
  139. package/extensions/lib/atomic-write.ts +6 -2
  140. package/extensions/lib/auto-memory.ts +75 -0
  141. package/extensions/lib/bash-parser.ts +84 -0
  142. package/extensions/lib/btw-fork.ts +93 -0
  143. package/extensions/lib/child-extensions.ts +52 -0
  144. package/extensions/lib/child-writes.ts +45 -0
  145. package/extensions/lib/claude-context.ts +515 -100
  146. package/extensions/lib/claude-external-includes.ts +68 -0
  147. package/extensions/lib/claude-id.ts +22 -0
  148. package/extensions/lib/claude-rules.ts +323 -0
  149. package/extensions/lib/claude-settings.ts +76 -4
  150. package/extensions/lib/compaction-boundary.ts +36 -0
  151. package/extensions/lib/compaction-keep.mjs +83 -0
  152. package/extensions/lib/config-mode.ts +112 -0
  153. package/extensions/lib/consent-dialogs.ts +63 -0
  154. package/extensions/lib/consent-preview.ts +5 -1
  155. package/extensions/lib/consent-stores.ts +48 -0
  156. package/extensions/lib/context-facts.ts +97 -0
  157. package/extensions/lib/covered-messages.ts +23 -0
  158. package/extensions/lib/deferred.ts +359 -25
  159. package/extensions/lib/dock-rows.ts +60 -0
  160. package/extensions/lib/empty-tool-result.ts +27 -0
  161. package/extensions/lib/environment-block.ts +76 -0
  162. package/extensions/lib/fetch-timeout.ts +24 -6
  163. package/extensions/{system-prompt → lib}/git-status.ts +51 -13
  164. package/extensions/lib/git.ts +75 -13
  165. package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
  166. package/extensions/lib/grammars/README.md +2 -0
  167. package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
  168. package/extensions/lib/guide-docs.ts +45 -0
  169. package/extensions/lib/headless-output.ts +46 -0
  170. package/extensions/lib/hook-gate.ts +7 -7
  171. package/extensions/lib/interrupt.ts +20 -4
  172. package/extensions/lib/key-input.ts +455 -0
  173. package/extensions/lib/keys.ts +14 -1
  174. package/extensions/lib/ledger/fold-memo.ts +49 -0
  175. package/extensions/lib/ledger/fold.ts +265 -0
  176. package/extensions/lib/ledger/layout.ts +72 -0
  177. package/extensions/lib/ledger/plan.ts +102 -0
  178. package/extensions/lib/ledger/records.ts +276 -0
  179. package/extensions/lib/ledger/state.ts +47 -0
  180. package/extensions/lib/ledger/validate.ts +141 -0
  181. package/extensions/lib/ledger/wire.ts +632 -0
  182. package/extensions/lib/local-command.ts +71 -19
  183. package/extensions/lib/mcp-overrides.ts +1 -1
  184. package/extensions/lib/mcp-share.ts +46 -0
  185. package/extensions/lib/memory-content.ts +146 -0
  186. package/extensions/lib/memory-index.ts +18 -0
  187. package/extensions/lib/memory.ts +134 -123
  188. package/extensions/lib/model-catalog/huggingface.json +178 -0
  189. package/extensions/lib/model-catalog/models-dev.json +18533 -0
  190. package/extensions/lib/model-catalog/openrouter.json +5050 -0
  191. package/extensions/lib/model-catalog-data.ts +513 -0
  192. package/extensions/lib/model-catalog.ts +462 -0
  193. package/extensions/lib/model-choice-warnings.ts +137 -0
  194. package/extensions/lib/model-pairings.ts +152 -0
  195. package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
  196. package/extensions/lib/model-policy.ts +112 -15
  197. package/extensions/lib/model-tier.ts +283 -355
  198. package/extensions/lib/model-unusable.ts +1 -1
  199. package/extensions/lib/newer-model.ts +100 -0
  200. package/extensions/lib/notices.ts +87 -0
  201. package/extensions/lib/notifications.ts +357 -73
  202. package/extensions/lib/one-code-settings.ts +74 -8
  203. package/extensions/lib/open-browser.ts +63 -0
  204. package/extensions/lib/openrouter-generation.ts +94 -0
  205. package/extensions/lib/operation-deadline.ts +30 -0
  206. package/extensions/lib/original-command.ts +4 -4
  207. package/extensions/lib/package-version.ts +1 -1
  208. package/extensions/lib/panel-focus.ts +134 -0
  209. package/extensions/lib/paths.ts +17 -2
  210. package/extensions/lib/permission-gate.ts +72 -6
  211. package/extensions/lib/persisted-output.ts +60 -8
  212. package/extensions/lib/pi-ai-estimate.ts +19 -3
  213. package/extensions/lib/pi-version.ts +38 -2
  214. package/extensions/lib/plan-mode-channels.ts +42 -5
  215. package/extensions/lib/plugins.ts +26 -17
  216. package/extensions/lib/powershell-parse-script.ts +208 -0
  217. package/extensions/lib/powershell-parser.ts +344 -0
  218. package/extensions/lib/previous-mark.ts +101 -0
  219. package/extensions/lib/process-state.ts +48 -0
  220. package/extensions/lib/process-tree.ts +132 -8
  221. package/extensions/lib/prompt-options.ts +82 -0
  222. package/extensions/lib/queued-delivery.ts +44 -0
  223. package/extensions/lib/reminders.ts +370 -358
  224. package/extensions/lib/replaced-builtins.mjs +199 -0
  225. package/extensions/lib/replay-call.ts +70 -0
  226. package/extensions/lib/request-replay.ts +547 -0
  227. package/extensions/lib/scratchpad.ts +81 -14
  228. package/extensions/lib/send-now-channel.ts +7 -0
  229. package/extensions/lib/session-model-tier.ts +51 -0
  230. package/extensions/lib/session-work.ts +64 -0
  231. package/extensions/lib/settings-channels.ts +4 -4
  232. package/extensions/lib/shell-exit-semantics.ts +170 -0
  233. package/extensions/lib/shell-quote.ts +11 -0
  234. package/extensions/lib/shell-result.ts +70 -0
  235. package/extensions/lib/shell-spawn.ts +70 -6
  236. package/extensions/lib/shell-tool.ts +75 -25
  237. package/extensions/lib/side-call-cache.ts +124 -0
  238. package/extensions/lib/side-call-run.ts +96 -0
  239. package/extensions/lib/side-call-usage.ts +38 -0
  240. package/extensions/lib/side-call.ts +24 -1
  241. package/extensions/lib/single-flight.ts +18 -0
  242. package/extensions/lib/skill-body.ts +42 -0
  243. package/extensions/lib/skill-scan.ts +63 -24
  244. package/extensions/lib/spill-file.ts +140 -0
  245. package/extensions/lib/subagent-handback.ts +112 -0
  246. package/extensions/lib/system-role.ts +156 -0
  247. package/extensions/lib/terminal-text.ts +114 -0
  248. package/extensions/lib/text-width.ts +47 -16
  249. package/extensions/lib/timer-ops.ts +20 -0
  250. package/extensions/lib/tool-additions.ts +129 -0
  251. package/extensions/lib/tool-call-corruption.ts +134 -0
  252. package/extensions/lib/tool-path.ts +66 -0
  253. package/extensions/lib/tool-variants.ts +69 -0
  254. package/extensions/lib/tui-render.ts +255 -27
  255. package/extensions/lib/turn-budget-layout.ts +159 -0
  256. package/extensions/lib/update-check.mjs +15 -2
  257. package/extensions/lib/usage-bus.ts +1 -1
  258. package/extensions/lib/user-denial.ts +38 -0
  259. package/extensions/lib/workspace-channel.ts +16 -0
  260. package/extensions/lib/worktree-channel.ts +31 -3
  261. package/extensions/lib/worktree-isolation.ts +40 -10
  262. package/extensions/lsp/client.ts +8 -9
  263. package/extensions/lsp/index.ts +76 -12
  264. package/extensions/lsp/keep-alive.ts +1 -1
  265. package/extensions/lsp/trust.ts +119 -0
  266. package/extensions/lsp/watcher.ts +1 -1
  267. package/extensions/mcp/announce.ts +90 -43
  268. package/extensions/mcp/client.ts +161 -48
  269. package/extensions/mcp/config.ts +132 -8
  270. package/extensions/mcp/discovery.ts +19 -0
  271. package/extensions/mcp/index.ts +220 -121
  272. package/extensions/mcp/oauth/browser.ts +38 -17
  273. package/extensions/mcp/oauth/flow.ts +25 -17
  274. package/extensions/mcp/oauth/provider.ts +14 -8
  275. package/extensions/mcp/oauth/store.ts +34 -16
  276. package/extensions/mcp/panel/keys.ts +12 -11
  277. package/extensions/mcp/panel/render.ts +9 -6
  278. package/extensions/mcp/schema.ts +104 -5
  279. package/extensions/mcp/trust.ts +164 -73
  280. package/extensions/memory/entries.ts +23 -14
  281. package/extensions/memory/index.ts +134 -28
  282. package/extensions/memory/open-external.ts +33 -18
  283. package/extensions/memory/panel.ts +135 -26
  284. package/extensions/model-default/index.ts +86 -19
  285. package/extensions/newer-model/index.ts +31 -0
  286. package/extensions/notebook/description.ts +18 -0
  287. package/extensions/notebook/index.ts +6 -5
  288. package/extensions/notebook/notebook.ts +1 -1
  289. package/extensions/permissions/index.ts +1426 -398
  290. package/extensions/permissions/matcher.ts +425 -74
  291. package/extensions/permissions/modes.ts +11 -4
  292. package/extensions/permissions/panel/host.ts +130 -0
  293. package/extensions/permissions/panel/keys.ts +48 -0
  294. package/extensions/permissions/panel/render.ts +450 -0
  295. package/extensions/permissions/panel/state.ts +550 -0
  296. package/extensions/permissions/powershell-cmdlets.ts +198 -0
  297. package/extensions/permissions/powershell-paths.ts +197 -0
  298. package/extensions/permissions/powershell-rules.ts +207 -280
  299. package/extensions/permissions/powershell-tree.ts +557 -0
  300. package/extensions/permissions/project-trust.ts +27 -11
  301. package/extensions/permissions/protected-paths.ts +14 -2
  302. package/extensions/permissions/session-grant.ts +6 -0
  303. package/extensions/permissions/settings.ts +166 -31
  304. package/extensions/permissions/subagent-gate.ts +25 -0
  305. package/extensions/permissions/workspace.ts +78 -0
  306. package/extensions/plan-mode/index.ts +276 -92
  307. package/extensions/plan-mode/reminder.ts +89 -21
  308. package/extensions/plan-mode/run-state.ts +26 -0
  309. package/extensions/plan-mode/texts.ts +177 -0
  310. package/extensions/plan-mode/viewer.ts +92 -27
  311. package/extensions/plugins/counts.ts +3 -3
  312. package/extensions/plugins/index.ts +64 -46
  313. package/extensions/plugins/install/install.ts +16 -0
  314. package/extensions/plugins/marketplace/sync.ts +9 -3
  315. package/extensions/plugins/marketplace/types.ts +3 -1
  316. package/extensions/plugins/panel/keys.ts +21 -24
  317. package/extensions/plugins/panel/render.ts +12 -6
  318. package/extensions/plugins/shell-expand.ts +107 -0
  319. package/extensions/plugins/template.ts +32 -15
  320. package/extensions/powershell/child.ts +7 -0
  321. package/extensions/powershell/description.ts +38 -6
  322. package/extensions/powershell/guards.ts +1 -1
  323. package/extensions/powershell/index.ts +14 -7
  324. package/extensions/recap/index.ts +73 -71
  325. package/extensions/recap/prompt.ts +2 -2
  326. package/extensions/recap/scheduler.ts +3 -5
  327. package/extensions/search-tools/grep-merge.ts +39 -0
  328. package/extensions/search-tools/index.ts +0 -0
  329. package/extensions/send-now/chord.ts +62 -0
  330. package/extensions/send-now/index.ts +133 -0
  331. package/extensions/session-title/index.ts +3 -0
  332. package/extensions/session-title/title.ts +4 -1
  333. package/extensions/skill/announce.ts +30 -0
  334. package/extensions/skill/code-review.ts +97 -0
  335. package/extensions/skill/index.ts +469 -132
  336. package/extensions/skill/invoke.ts +118 -75
  337. package/extensions/skill/listing.ts +133 -9
  338. package/extensions/skill/panel/keys.ts +15 -16
  339. package/extensions/skill/panel/render.ts +5 -2
  340. package/extensions/skill/panel/state.ts +12 -1
  341. package/extensions/subagents/agent-args.ts +39 -0
  342. package/extensions/subagents/agent-description.ts +132 -0
  343. package/extensions/subagents/agents.ts +146 -31
  344. package/extensions/subagents/default-model.ts +2 -3
  345. package/extensions/subagents/delegation-steer.ts +4 -4
  346. package/extensions/subagents/fork-cache.ts +82 -0
  347. package/extensions/subagents/guide-agent.ts +189 -0
  348. package/extensions/subagents/hand-back-review.ts +3 -0
  349. package/extensions/subagents/index.ts +1594 -852
  350. package/extensions/subagents/live-runs.ts +26 -6
  351. package/extensions/subagents/model-select.ts +38 -25
  352. package/extensions/subagents/outcome.ts +5 -2
  353. package/extensions/subagents/panel-keys.ts +73 -31
  354. package/extensions/subagents/panel-render.ts +120 -96
  355. package/extensions/subagents/panel-widget.ts +60 -89
  356. package/extensions/subagents/parent-mode.ts +50 -0
  357. package/extensions/subagents/pending-claim.ts +18 -1
  358. package/extensions/subagents/runner.ts +135 -80
  359. package/extensions/subagents/runs.ts +44 -5
  360. package/extensions/subagents/send-to-main-tool.ts +1 -1
  361. package/extensions/subagents/session-turns.ts +32 -8
  362. package/extensions/subagents/shell-panel.ts +36 -178
  363. package/extensions/subagents/tasks-dialog.ts +170 -0
  364. package/extensions/subagents/viewport-mirror.ts +95 -0
  365. package/extensions/subagents/worktree.ts +57 -13
  366. package/extensions/system-prompt/environment.ts +19 -19
  367. package/extensions/system-prompt/idle-turn.ts +65 -0
  368. package/extensions/system-prompt/index.ts +165 -47
  369. package/extensions/system-prompt/template.ts +34 -51
  370. package/extensions/system-prompt/tiers/common.ts +23 -64
  371. package/extensions/system-prompt/tiers/frontier.ts +32 -11
  372. package/extensions/system-prompt/tiers/low.ts +67 -45
  373. package/extensions/system-prompt/tiers/mid.ts +96 -38
  374. package/extensions/system-reminder/index.ts +204 -70
  375. package/extensions/tasks/descriptions.ts +190 -0
  376. package/extensions/tasks/index.ts +120 -41
  377. package/extensions/tasks/store.ts +83 -16
  378. package/extensions/tool-call-corruption/child.ts +7 -0
  379. package/extensions/tool-call-corruption/index.ts +237 -0
  380. package/extensions/tool-search/index.ts +366 -144
  381. package/extensions/tool-style/index.ts +68 -10
  382. package/extensions/tool-style/write-description.ts +40 -0
  383. package/extensions/tool-style/write-render.ts +237 -0
  384. package/extensions/turn-duration/index.ts +7 -3
  385. package/extensions/turn-duration/line.ts +47 -4
  386. package/extensions/web/backends.ts +6 -9
  387. package/extensions/web/description.ts +50 -0
  388. package/extensions/web/index.ts +118 -53
  389. package/extensions/web/native-call.ts +15 -0
  390. package/extensions/web/native-results.ts +41 -0
  391. package/extensions/web-fetch/description.ts +42 -0
  392. package/extensions/web-fetch/extract.ts +53 -6
  393. package/extensions/web-fetch/index.ts +125 -76
  394. package/extensions/web-fetch/page-content.ts +19 -0
  395. package/extensions/web-fetch/response.ts +49 -0
  396. package/extensions/web-fetch/summarize.ts +9 -10
  397. package/extensions/workflow/agent-session.ts +108 -18
  398. package/extensions/workflow/args.ts +24 -0
  399. package/extensions/workflow/description.ts +67 -0
  400. package/extensions/workflow/globals.ts +17 -3
  401. package/extensions/workflow/index.ts +157 -162
  402. package/extensions/workflow/records.ts +1 -0
  403. package/extensions/workflow/run-manager.ts +33 -6
  404. package/extensions/workflow/saved-workflows.ts +3 -2
  405. package/extensions/workflow/types.ts +22 -1
  406. package/extensions/workflow/viewer.ts +54 -24
  407. package/extensions/workflow/widget.ts +20 -0
  408. package/extensions/worktree/descriptions.ts +91 -0
  409. package/extensions/worktree/guards.ts +316 -54
  410. package/extensions/worktree/index.ts +162 -44
  411. package/extensions/worktree/powershell-guards.ts +167 -0
  412. package/extensions/worktree/rewrite.ts +14 -16
  413. package/package.json +21 -7
  414. package/skills/artifact-design/SKILL.md +85 -0
  415. package/skills/code-review/SKILL.md +20 -20
  416. package/skills/code-review/cells/arguments-and-flags.md +19 -0
  417. package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
  418. package/skills/code-review/cells/high-8-inline.md +122 -0
  419. package/skills/code-review/cells/low-1-pass-max4.md +27 -0
  420. package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
  421. package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
  422. package/skills/code-review/cells/medium-3x5-verify.md +132 -0
  423. package/skills/code-review/cells/minimal-prompt.md +32 -0
  424. package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
  425. package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
  426. package/skills/fewer-permission-prompts/SKILL.md +1 -1
  427. package/skills/loop/SKILL.md +11 -0
  428. package/skills/security-review/SKILL.md +2 -5
  429. package/skills/simplify/SKILL.md +7 -5
  430. package/skills/workflow-authoring/SKILL.md +143 -0
  431. package/themes/onecode-light.json +1 -1
  432. package/themes/onecode.json +1 -1
  433. package/types/app.d.ts +58 -0
  434. package/types/community-packages.d.ts +26 -1
  435. package/extensions/auto-mode/recoverability.ts +0 -229
  436. package/extensions/lib/capability-index.ts +0 -466
  437. package/extensions/lib/model-facts.generated.json +0 -2675
  438. package/extensions/lib/model-facts.ts +0 -162
  439. package/extensions/tool-search/announce.ts +0 -42
@@ -0,0 +1,642 @@
1
+ /**
2
+ * Session cron (pure) — the store behind cron_create / cron_list / cron_delete
3
+ * and fixed-interval /loop.
4
+ *
5
+ * Claude Code's cron is a session-only, in-memory schedule of prompts, each
6
+ * re-invoking the session verbatim when its 5-field local-time expression
7
+ * matches (findings §21). This module holds the parts with no timers: the
8
+ * expression parser, the next-match search, the human cadence text, and the
9
+ * store's due/expiry bookkeeping. The extension owns the single timer and decides WHEN to call
10
+ * `takeDue` (only while idle), so a job due mid-turn fires once at settle.
11
+ *
12
+ * Parsing, matching and cadence text follow Claude Code 2.1.281 exactly:
13
+ * numeric fields only (`*`, `* /n`, `a`, `a-b`, `a-b/n`, lists), day-of-week 7
14
+ * is Sunday, day-of-month and day-of-week OR together when both are
15
+ * restricted, and a job whose expression matches nothing within a year is
16
+ * rejected. Fire times carry Claude Code's deterministic jitter (`JITTER`).
17
+ */
18
+
19
+ import { randomUUID } from "node:crypto";
20
+ import { Type } from "typebox";
21
+ import { sliceColumns, visibleWidth } from "../lib/text-width.ts";
22
+
23
+ /** Claude Code's per-session job cap. */
24
+ export const MAX_JOBS = 50;
25
+ /** Recurring jobs expire after 7 days: they fire one final time, then are deleted. */
26
+ export const RECURRING_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
27
+ export const RECURRING_MAX_AGE_DAYS = RECURRING_MAX_AGE_MS / 86_400_000;
28
+ /**
29
+ * Claude Code's jitter constants (2.1.282; findings §21). The tool
30
+ * description tells the model "up to 10% of their period late (max 15 min)";
31
+ * the code fires up to 50% late, capped at 30 minutes. We match the code.
32
+ */
33
+ export const JITTER = {
34
+ recurringFrac: 0.5,
35
+ recurringCapMs: 1_800_000,
36
+ oneShotMaxMs: 90_000,
37
+ oneShotFloorMs: 0,
38
+ oneShotMinuteMod: 30,
39
+ /** A plain every-5-minutes job fires this much before the period ends, as Claude Code's does. */
40
+ cacheLeadMs: 15_000,
41
+ } as const;
42
+ const FIVE_MINUTES_MS = 300_000;
43
+ const PLAIN_STEP_MINUTES = /^\*\/\d+ \* \* \* \*$/;
44
+
45
+ /** A job's fixed jitter fraction in [0, 1): its id's first 8 hex digits over 2^32. */
46
+ export function jitterFraction(id: string): number {
47
+ const value = Number.parseInt(id.slice(0, 8), 16) / 4_294_967_296;
48
+ return Number.isFinite(value) ? value : 0;
49
+ }
50
+
51
+ /**
52
+ * A recurring job's next fire after `from`, as Claude Code computes it: the next
53
+ * match plus up to half the period (capped), by the job's fraction. A plain
54
+ * `*\/N` job whose period is exactly 5 minutes (to within the lead) fires 15 s
55
+ * before the period ends instead, measured from `from`.
56
+ */
57
+ export function nextRecurringFire(cron: string, fields: CronFields, from: number, id: string): number | null {
58
+ const first = nextMatch(fields, new Date(from));
59
+ if (!first) return null;
60
+ const second = nextMatch(fields, first);
61
+ if (!second) return first.getTime();
62
+ const period = second.getTime() - first.getTime();
63
+ const lead = JITTER.cacheLeadMs;
64
+ if (PLAIN_STEP_MINUTES.test(cron.trim()) && lead > 0 && lead < period && period >= FIVE_MINUTES_MS && period - lead < FIVE_MINUTES_MS) {
65
+ return from + period - lead;
66
+ }
67
+ return first.getTime() + Math.min(jitterFraction(id) * JITTER.recurringFrac * period, JITTER.recurringCapMs);
68
+ }
69
+
70
+ /**
71
+ * A one-shot job's fire, as Claude Code computes it: its match, or up to 90 s
72
+ * earlier when the match falls on :00 or :30, never before it was created.
73
+ */
74
+ export function oneShotFire(fields: CronFields, createdAt: number, id: string): number | null {
75
+ const match = nextMatch(fields, new Date(createdAt));
76
+ if (!match) return null;
77
+ if (match.getMinutes() % JITTER.oneShotMinuteMod !== 0) return match.getTime();
78
+ const early = JITTER.oneShotFloorMs + jitterFraction(id) * (JITTER.oneShotMaxMs - JITTER.oneShotFloorMs);
79
+ return Math.max(match.getTime() - early, createdAt);
80
+ }
81
+
82
+ /** The next-match search walks at most one leap year of minutes. */
83
+ const SEARCH_LIMIT_MINUTES = 366 * 24 * 60;
84
+
85
+ export interface CronFields {
86
+ minute: number[];
87
+ hour: number[];
88
+ dayOfMonth: number[];
89
+ month: number[];
90
+ dayOfWeek: number[];
91
+ }
92
+
93
+ const FIELD_RANGES = [
94
+ { min: 0, max: 59 },
95
+ { min: 0, max: 23 },
96
+ { min: 1, max: 31 },
97
+ { min: 1, max: 12 },
98
+ { min: 0, max: 6 },
99
+ ] as const;
100
+
101
+ function parseField(text: string, range: { min: number; max: number }): number[] | null {
102
+ const { min, max } = range;
103
+ const isDayOfWeek = min === 0 && max === 6;
104
+ const values = new Set<number>();
105
+ for (const part of text.split(",")) {
106
+ const star = part.match(/^\*(?:\/(\d+))?$/);
107
+ if (star) {
108
+ const step = star[1] ? Number.parseInt(star[1], 10) : 1;
109
+ if (step < 1) return null;
110
+ for (let v = min; v <= max; v += step) values.add(v);
111
+ continue;
112
+ }
113
+ const span = part.match(/^(\d+)-(\d+)(?:\/(\d+))?$/);
114
+ if (span) {
115
+ const lo = Number.parseInt(span[1], 10);
116
+ const hi = Number.parseInt(span[2], 10);
117
+ const step = span[3] ? Number.parseInt(span[3], 10) : 1;
118
+ // Day-of-week accepts 7 as Sunday at the top of a range.
119
+ const top = isDayOfWeek ? 7 : max;
120
+ if (lo > hi || step < 1 || lo < min || hi > top) return null;
121
+ for (let v = lo; v <= hi; v += step) values.add(isDayOfWeek && v === 7 ? 0 : v);
122
+ continue;
123
+ }
124
+ if (/^\d+$/.test(part)) {
125
+ let v = Number.parseInt(part, 10);
126
+ if (isDayOfWeek && v === 7) v = 0;
127
+ if (v < min || v > max) return null;
128
+ values.add(v);
129
+ continue;
130
+ }
131
+ return null;
132
+ }
133
+ if (values.size === 0) return null;
134
+ return [...values].sort((a, b) => a - b);
135
+ }
136
+
137
+ /** Parse a 5-field cron expression; null when it is malformed. */
138
+ export function parseCron(expr: string): CronFields | null {
139
+ const parts = expr.trim().split(/\s+/);
140
+ if (parts.length !== 5) return null;
141
+ const fields: number[][] = [];
142
+ for (let i = 0; i < 5; i++) {
143
+ const values = parseField(parts[i], FIELD_RANGES[i]);
144
+ if (!values) return null;
145
+ fields.push(values);
146
+ }
147
+ return { minute: fields[0], hour: fields[1], dayOfMonth: fields[2], month: fields[3], dayOfWeek: fields[4] };
148
+ }
149
+
150
+ /**
151
+ * The first local-time minute strictly after `from` that matches, or null when
152
+ * nothing matches within a year. Walks forward in local time, skipping whole
153
+ * months, days and hours that cannot match, so DST gaps and repeats follow the
154
+ * platform's `Date` arithmetic the way Claude Code's scheduler does.
155
+ */
156
+ export function nextMatch(fields: CronFields, from: Date): Date | null {
157
+ const minutes = new Set(fields.minute);
158
+ const hours = new Set(fields.hour);
159
+ const doms = new Set(fields.dayOfMonth);
160
+ const months = new Set(fields.month);
161
+ const dows = new Set(fields.dayOfWeek);
162
+ const anyDom = fields.dayOfMonth.length === 31;
163
+ const anyDow = fields.dayOfWeek.length === 7;
164
+ const t = new Date(from.getTime());
165
+ t.setSeconds(0, 0);
166
+ t.setMinutes(t.getMinutes() + 1);
167
+ for (let i = 0; i < SEARCH_LIMIT_MINUTES; i++) {
168
+ if (!months.has(t.getMonth() + 1)) {
169
+ t.setMonth(t.getMonth() + 1, 1);
170
+ t.setHours(0, 0, 0, 0);
171
+ continue;
172
+ }
173
+ const dom = t.getDate();
174
+ const dow = t.getDay();
175
+ const dayMatches = anyDom && anyDow ? true : anyDom ? dows.has(dow) : anyDow ? doms.has(dom) : doms.has(dom) || dows.has(dow);
176
+ if (!dayMatches) {
177
+ t.setDate(t.getDate() + 1);
178
+ t.setHours(0, 0, 0, 0);
179
+ continue;
180
+ }
181
+ if (!hours.has(t.getHours())) {
182
+ t.setHours(t.getHours() + 1, 0, 0, 0);
183
+ continue;
184
+ }
185
+ if (!minutes.has(t.getMinutes())) {
186
+ t.setMinutes(t.getMinutes() + 1);
187
+ continue;
188
+ }
189
+ return t;
190
+ }
191
+ return null;
192
+ }
193
+
194
+ const WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
195
+
196
+ function clockTime(minute: number, hour: number): string {
197
+ return new Date(2000, 0, 1, hour, minute).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit" });
198
+ }
199
+
200
+ /** Claude Code's human-readable cadence ("Every 5 minutes", "Weekdays at 9:00 AM"); the raw expression otherwise. */
201
+ export function describeCadence(expr: string): string {
202
+ const parts = expr.trim().split(/\s+/);
203
+ if (parts.length !== 5) return expr;
204
+ const [min, hour, dom, month, dow] = parts;
205
+ const restAny = dom === "*" && month === "*" && dow === "*";
206
+ if (hour === "*" && restAny) {
207
+ if (min === "*") return "Every minute";
208
+ const step = min.match(/^\*\/(\d+)$/);
209
+ if (step) {
210
+ const n = Number.parseInt(step[1], 10);
211
+ return n === 1 ? "Every minute" : `Every ${n} minutes`;
212
+ }
213
+ }
214
+ if (/^\d+$/.test(min) && hour === "*" && restAny) {
215
+ const m = Number.parseInt(min, 10);
216
+ return m === 0 ? "Every hour" : `Every hour at :${String(m).padStart(2, "0")}`;
217
+ }
218
+ const hourStep = hour.match(/^\*\/(\d+)$/);
219
+ if (/^\d+$/.test(min) && hourStep && restAny) {
220
+ const n = Number.parseInt(hourStep[1], 10);
221
+ const m = Number.parseInt(min, 10);
222
+ const at = m === 0 ? "" : ` at :${String(m).padStart(2, "0")}`;
223
+ return n === 1 ? `Every hour${at}` : `Every ${n} hours${at}`;
224
+ }
225
+ if (!/^\d+$/.test(min) || !/^\d+$/.test(hour)) return expr;
226
+ const time = clockTime(Number.parseInt(min, 10), Number.parseInt(hour, 10));
227
+ if (restAny) return `Every day at ${time}`;
228
+ if (dom === "*" && month === "*" && /^\d$/.test(dow)) {
229
+ const day = WEEKDAYS[Number.parseInt(dow, 10) % 7];
230
+ if (day) return `Every ${day} at ${time}`;
231
+ }
232
+ if (dom === "*" && month === "*" && dow === "1-5") return `Weekdays at ${time}`;
233
+ return expr;
234
+ }
235
+
236
+ /** Who made the job: the model (cron_create, /loop's fixed interval included) or schedule_wakeup (a one-shot, as in Claude Code). */
237
+ export type CronSource = "model" | "wakeup";
238
+
239
+ export interface CronJob {
240
+ id: string;
241
+ cron: string;
242
+ prompt: string;
243
+ recurring: boolean;
244
+ source: CronSource;
245
+ /** A subagent's task id when the job is that agent's (lib/agent-cron.ts). */
246
+ agentId?: string;
247
+ /** A subagent job's working directory, where its fire resolves loop.md and skills. */
248
+ cwd?: string;
249
+ createdAt: number;
250
+ /** Epoch ms of the next fire. */
251
+ nextFireAt: number;
252
+ }
253
+
254
+ export interface CronFire {
255
+ job: CronJob;
256
+ /** The job is gone after this fire: a one-shot, or a recurring job past its 7-day age. */
257
+ final: boolean;
258
+ }
259
+
260
+ export type CreateResult = { ok: true; job: CronJob } | { ok: false; error: string };
261
+
262
+ export interface CronStoreOptions {
263
+ maxJobs?: number;
264
+ maxAgeMs?: number;
265
+ newId?: () => string;
266
+ /** false fires on the exact match (tests); Claude Code's jitter otherwise. */
267
+ jitter?: boolean;
268
+ }
269
+
270
+ /** The session's jobs, keyed by id, with no timers of its own. */
271
+ export class CronStore {
272
+ private readonly jobs = new Map<string, CronJob & { fields: CronFields }>();
273
+ private readonly maxJobs: number;
274
+ private readonly maxAgeMs: number;
275
+ private readonly newId: () => string;
276
+ private readonly jitter: boolean;
277
+
278
+ constructor(options: CronStoreOptions = {}) {
279
+ this.maxJobs = options.maxJobs ?? MAX_JOBS;
280
+ this.maxAgeMs = options.maxAgeMs ?? RECURRING_MAX_AGE_MS;
281
+ this.newId = options.newId ?? (() => randomUUID().slice(0, 8));
282
+ this.jitter = options.jitter ?? true;
283
+ }
284
+
285
+ /** The fire after `from`: the exact next match without jitter, Claude Code's jittered time with it. */
286
+ private fireAfter(job: { id: string; cron: string; recurring: boolean; fields: CronFields }, from: number): number | null {
287
+ if (!this.jitter) return nextMatch(job.fields, new Date(from))?.getTime() ?? null;
288
+ return job.recurring ? nextRecurringFire(job.cron, job.fields, from, job.id) : oneShotFire(job.fields, from, job.id);
289
+ }
290
+
291
+ /** Validate and add a job, with Claude Code's error wording. */
292
+ /**
293
+ * `fireAt` pins the first fire to an exact time instead of the next match: a
294
+ * wakeup fires `delaySeconds` from now, its cron (`M H * * *`) only naming
295
+ * that minute for cron_list, as in Claude Code.
296
+ */
297
+ create(input: { cron: string; prompt: string; recurring?: boolean; source?: CronSource; fireAt?: number; agentId?: string; cwd?: string }, now: number): CreateResult {
298
+ const fields = parseCron(input.cron);
299
+ if (!fields) return { ok: false, error: `Invalid cron expression '${input.cron}'. Expected 5 fields: M H DoM Mon DoW.` };
300
+ if (!nextMatch(fields, new Date(now))) return { ok: false, error: `Cron expression '${input.cron}' does not match any calendar date in the next year.` };
301
+ // The cap is cron_create's check, as in Claude Code; a wakeup is never refused.
302
+ if (input.source !== "wakeup" && this.jobs.size >= this.maxJobs) return { ok: false, error: `Too many scheduled jobs (max ${this.maxJobs}). Cancel one first.` };
303
+ let id = this.newId();
304
+ while (this.jobs.has(id)) id = this.newId();
305
+ const base = { id, cron: input.cron.trim(), recurring: input.recurring ?? true, fields };
306
+ const job = {
307
+ ...base,
308
+ prompt: input.prompt,
309
+ source: input.source ?? "model",
310
+ ...(input.agentId !== undefined && { agentId: input.agentId }),
311
+ ...(input.cwd !== undefined && { cwd: input.cwd }),
312
+ createdAt: now,
313
+ nextFireAt: input.fireAt ?? this.fireAfter(base, now)!,
314
+ };
315
+ this.jobs.set(id, job);
316
+ return { ok: true, job: publicJob(job) };
317
+ }
318
+
319
+ delete(id: string): boolean {
320
+ return this.jobs.delete(id);
321
+ }
322
+
323
+ get(id: string): CronJob | undefined {
324
+ const job = this.jobs.get(id);
325
+ return job && publicJob(job);
326
+ }
327
+
328
+ /** Every job, in creation order. */
329
+ list(): CronJob[] {
330
+ return [...this.jobs.values()].map(publicJob);
331
+ }
332
+
333
+ get size(): number {
334
+ return this.jobs.size;
335
+ }
336
+
337
+ /** Epoch ms of the earliest pending fire, for arming the one timer. */
338
+ nextFireAt(): number | undefined {
339
+ let earliest: number | undefined;
340
+ for (const job of this.jobs.values()) if (earliest === undefined || job.nextFireAt < earliest) earliest = job.nextFireAt;
341
+ return earliest;
342
+ }
343
+
344
+ /**
345
+ * Claim every job due at `now`. Each fires once however many of its matches
346
+ * passed (a turn that ran through three ticks yields one fire, not three);
347
+ * a recurring job re-arms to its next match after `now`, and a one-shot or
348
+ * an expired recurring job is deleted.
349
+ */
350
+ takeDue(now: number): CronFire[] {
351
+ const fires: CronFire[] = [];
352
+ for (const job of this.jobs.values()) {
353
+ if (job.nextFireAt > now) continue;
354
+ const next = job.recurring ? this.fireAfter(job, now) : null;
355
+ const expired = job.recurring && now - job.createdAt >= this.maxAgeMs;
356
+ const final = !job.recurring || expired || next === null;
357
+ fires.push({ job: publicJob(job), final });
358
+ if (final) this.jobs.delete(job.id);
359
+ else job.nextFireAt = next!;
360
+ }
361
+ return fires;
362
+ }
363
+
364
+ /** Remove every job matching `predicate`; returns the removed jobs. */
365
+ deleteWhere(predicate: (job: CronJob) => boolean): CronJob[] {
366
+ const removed: CronJob[] = [];
367
+ for (const job of this.jobs.values()) {
368
+ const view = publicJob(job);
369
+ if (predicate(view)) {
370
+ this.jobs.delete(job.id);
371
+ removed.push(view);
372
+ }
373
+ }
374
+ return removed;
375
+ }
376
+
377
+ clear(): void {
378
+ this.jobs.clear();
379
+ }
380
+
381
+ /**
382
+ * Put back a job the branch recorded (`scheduledJobsOnBranch`), keeping its
383
+ * id: a /reload re-arms the session's jobs. A recurring job past its age is
384
+ * not restored; a fire missed during the reload comes at once.
385
+ */
386
+ restore(job: RecordedJob, now: number): boolean {
387
+ const fields = parseCron(job.cron);
388
+ if (!fields || this.jobs.has(job.id)) return false;
389
+ if (job.recurring && now - job.createdAt >= this.maxAgeMs) return false;
390
+ const base = { id: job.id, cron: job.cron, recurring: job.recurring, fields };
391
+ // Due after the job's last fire on the branch (or its creation), so one missed during the reload is due now.
392
+ const nextFireAt = job.fireAt ?? this.fireAfter(base, Math.max(job.lastFiredAt ?? 0, job.createdAt));
393
+ if (nextFireAt === null) return false;
394
+ this.jobs.set(job.id, { ...base, prompt: job.prompt, source: job.source, createdAt: job.createdAt, nextFireAt });
395
+ return true;
396
+ }
397
+ }
398
+
399
+ /** A job as the branch records it: what `CronStore.restore` takes. */
400
+ export interface RecordedJob {
401
+ id: string;
402
+ cron: string;
403
+ prompt: string;
404
+ recurring: boolean;
405
+ source: CronSource;
406
+ createdAt: number;
407
+ /** A wakeup's exact fire time. */
408
+ fireAt?: number;
409
+ /** A recurring job's last fire on the branch: the next one is due after it. */
410
+ lastFiredAt?: number;
411
+ /** A wakeup's loop start, and whether the keepalive armed it (DynamicLoop.restoreChain). */
412
+ chainStartedAt?: number;
413
+ keepalive?: boolean;
414
+ }
415
+
416
+ type BranchEntry = { type?: string; timestamp?: string; customType?: string; details?: unknown; data?: unknown; message?: unknown };
417
+
418
+ /**
419
+ * The custom entry a main-session job's fire writes (`{ jobId, final, source }`),
420
+ * whatever way its notification is then delivered: a fire sent as a plain user
421
+ * message (no turn prompted since a /reload) carries no details.
422
+ */
423
+ export const JOB_FIRED_ENTRY = "one-code:job-fired";
424
+
425
+ /**
426
+ * The custom entry the loop's keepalive writes when it arms a wakeup itself
427
+ * (`{ id, prompt, fireAt, startedAt }`): no `schedule_wakeup` result records that one.
428
+ */
429
+ export const WAKEUP_ARMED_ENTRY = "one-code:wakeup-armed";
430
+ type Message = { role?: string; toolName?: string; toolCallId?: string; isError?: boolean; details?: unknown; content?: unknown; timestamp?: number };
431
+
432
+ /**
433
+ * The main session's scheduled jobs a branch still holds: cron_create and
434
+ * schedule_wakeup results (the prompt read from their calls), minus
435
+ * cron_delete results, a wakeup stop, and final fires. A wakeup supersedes the
436
+ * one before it, as `DynamicLoop.schedule` does. Pure.
437
+ */
438
+ export function scheduledJobsOnBranch(branch: readonly BranchEntry[]): RecordedJob[] {
439
+ const calls = new Map<string, Record<string, unknown>>();
440
+ const jobs = new Map<string, RecordedJob>();
441
+ const dropWakeups = () => {
442
+ for (const [id, job] of jobs) if (job.source === "wakeup") jobs.delete(id);
443
+ };
444
+ for (const entry of branch) {
445
+ if (entry.type === "custom" && entry.customType === WAKEUP_ARMED_ENTRY) {
446
+ const armed = entry.data as { id?: unknown; prompt?: unknown; fireAt?: unknown; startedAt?: unknown } | undefined;
447
+ if (typeof armed?.id !== "string" || typeof armed.prompt !== "string" || typeof armed.fireAt !== "number") continue;
448
+ dropWakeups();
449
+ const target = new Date(armed.fireAt);
450
+ const createdAt = Date.parse(entry.timestamp ?? "") || 0;
451
+ jobs.set(armed.id, {
452
+ id: armed.id,
453
+ cron: `${target.getMinutes()} ${target.getHours()} * * *`,
454
+ prompt: armed.prompt,
455
+ recurring: false,
456
+ source: "wakeup",
457
+ createdAt,
458
+ fireAt: armed.fireAt,
459
+ chainStartedAt: typeof armed.startedAt === "number" ? armed.startedAt : createdAt,
460
+ keepalive: true,
461
+ });
462
+ continue;
463
+ }
464
+ const fired = entry.type === "custom" && entry.customType === JOB_FIRED_ENTRY;
465
+ if (fired || (entry.type === "custom_message" && (entry.customType === "wakeup" || entry.customType === "cron"))) {
466
+ const details = (fired ? entry.data : entry.details) as { jobId?: unknown; final?: unknown; source?: unknown } | undefined;
467
+ const wakeup = fired ? details?.source === "wakeup" : entry.customType === "wakeup";
468
+ if (details?.final !== true) {
469
+ const job = typeof details?.jobId === "string" ? jobs.get(details.jobId) : undefined;
470
+ const at = Date.parse(entry.timestamp ?? "");
471
+ if (job && !Number.isNaN(at)) job.lastFiredAt = at;
472
+ continue;
473
+ }
474
+ if (wakeup) dropWakeups();
475
+ else if (typeof details.jobId === "string") jobs.delete(details.jobId);
476
+ continue;
477
+ }
478
+ if (entry.type !== "message") continue;
479
+ const message = entry.message as Message | undefined;
480
+ if (message?.role === "assistant" && Array.isArray(message.content)) {
481
+ for (const block of message.content as Array<{ type?: string; id?: string; arguments?: unknown }>) {
482
+ if (block?.type === "toolCall" && typeof block.id === "string" && block.arguments && typeof block.arguments === "object") calls.set(block.id, block.arguments as Record<string, unknown>);
483
+ }
484
+ continue;
485
+ }
486
+ if (message?.role !== "toolResult" || message.isError || !message.toolCallId) continue;
487
+ const args = calls.get(message.toolCallId) ?? {};
488
+ const details = (message.details ?? {}) as Record<string, unknown>;
489
+ const at = message.timestamp ?? (Date.parse(entry.timestamp ?? "") || 0);
490
+ if (message.toolName === "cron_create" && typeof details.jobId === "string" && typeof details.cron === "string" && typeof args.prompt === "string") {
491
+ jobs.set(details.jobId, { id: details.jobId, cron: details.cron, prompt: args.prompt, recurring: details.recurring !== false, source: "model", createdAt: at });
492
+ } else if (message.toolName === "cron_delete" && typeof details.jobId === "string") {
493
+ jobs.delete(details.jobId);
494
+ } else if (message.toolName === "schedule_wakeup") {
495
+ // A schedule replaces the pending wakeup; one past the loop's age (scheduledFor 0) cancels it.
496
+ if (details.stopped === true || typeof details.scheduledFor === "number") dropWakeups();
497
+ if (typeof details.scheduledFor === "number" && details.scheduledFor > 0 && typeof args.prompt === "string") {
498
+ const target = new Date(details.scheduledFor);
499
+ // The store's own id, so a cron_delete of the wakeup removes it here too.
500
+ const id = typeof details.jobId === "string" ? details.jobId : `w-${message.toolCallId}`;
501
+ const chainStartedAt = typeof details.startedAt === "number" ? details.startedAt : at;
502
+ jobs.set(id, { id, cron: `${target.getMinutes()} ${target.getHours()} * * *`, prompt: args.prompt, recurring: false, source: "wakeup", createdAt: at, fireAt: details.scheduledFor, chainStartedAt });
503
+ }
504
+ }
505
+ }
506
+ return [...jobs.values()];
507
+ }
508
+
509
+ function publicJob(job: CronJob & { fields?: CronFields }): CronJob {
510
+ const { fields: _fields, ...rest } = job;
511
+ return rest;
512
+ }
513
+
514
+ // ── Model-facing text (Claude Code's wording, our tool names) ───────────────
515
+
516
+ const SESSION_ONLY = "Session-only (not written to disk, dies when this session ends)";
517
+
518
+ export function formatCreateResult(job: CronJob): string {
519
+ const cadence = describeCadence(job.cron);
520
+ return job.recurring
521
+ ? `Scheduled recurring job ${job.id} (${cadence}). ${SESSION_ONLY}. Auto-expires after ${RECURRING_MAX_AGE_DAYS} days. Use cron_delete to cancel sooner.`
522
+ : `Scheduled one-shot task ${job.id} (${cadence}). ${SESSION_ONLY}. It will fire once then auto-delete.`;
523
+ }
524
+
525
+ /**
526
+ * Claude Code's `CronList` clip of a prompt (its `truncate(text, 80, true)`):
527
+ * a multi-line prompt shows its first line plus "…", and a line wider than
528
+ * 80 columns is cut to 79 plus "…". Loosened no-truncation rule:
529
+ * decisions/tools.md, "Model-facing text is persisted past its cap".
530
+ */
531
+ export function clipPrompt(prompt: string, max = 80): string {
532
+ const newline = prompt.indexOf("\n");
533
+ if (newline !== -1) {
534
+ const first = prompt.slice(0, newline);
535
+ return visibleWidth(first) + 1 > max ? cutToWidth(`${first}…`, max) : `${first}…`;
536
+ }
537
+ return visibleWidth(prompt) <= max ? prompt : cutToWidth(prompt, max);
538
+ }
539
+
540
+ /** Claude Code's cut: the text to `max - 1` columns plus "…". */
541
+ function cutToWidth(text: string, max: number): string {
542
+ if (visibleWidth(text) <= max) return text;
543
+ return max <= 1 ? "…" : `${sliceColumns(text, max - 1).text}…`;
544
+ }
545
+
546
+ export function formatJobLine(job: CronJob): string {
547
+ return `${job.id} — ${describeCadence(job.cron)} (${job.recurring ? "recurring" : "one-shot"}) [session-only]: ${clipPrompt(job.prompt)}`;
548
+ }
549
+
550
+ export function formatJobList(jobs: CronJob[]): string {
551
+ return jobs.length ? jobs.map(formatJobLine).join("\n") : "No scheduled jobs.";
552
+ }
553
+
554
+ export function formatDeleteResult(id: string): string {
555
+ return `Cancelled job ${id}.`;
556
+ }
557
+
558
+ export function formatUnknownJob(id: string): string {
559
+ return `No scheduled job with id '${id}'`;
560
+ }
561
+
562
+ export function formatCannotFire(): string {
563
+ return "This is a one-shot session: it ends when this turn does, so the job will never fire.";
564
+ }
565
+
566
+ /**
567
+ * Claude Code's descriptions with the durable feature off, the names swapped
568
+ * to ours and "Claude" read as "this session" (the model behind One Code may
569
+ * be any). The jitter sentence is Claude Code's own, understating its code.
570
+ */
571
+ export const CRON_CREATE_DESCRIPTION = `Schedule a prompt to be enqueued at a future time. Use for both recurring schedules and one-shot reminders.
572
+
573
+ Uses standard 5-field cron in the user's local timezone: minute hour day-of-month month day-of-week. "0 9 * * *" means 9am local — no timezone conversion needed.
574
+
575
+ ## One-shot tasks (recurring: false)
576
+
577
+ For "remind me at X" or "at <time>, do Y" requests — fire once then auto-delete.
578
+ Pin minute/hour/day-of-month/month to specific values:
579
+ "remind me at 2:30pm today to check the deploy" → cron: "30 14 <today_dom> <today_month> *", recurring: false
580
+ "tomorrow morning, run the smoke test" → cron: "57 8 <tomorrow_dom> <tomorrow_month> *", recurring: false
581
+
582
+ ## Recurring jobs (recurring: true, the default)
583
+
584
+ For "every N minutes" / "every hour" / "weekdays at 9am" requests:
585
+ "*/5 * * * *" (every 5 min), "0 * * * *" (hourly), "0 9 * * 1-5" (weekdays at 9am local)
586
+
587
+ ## Avoid the :00 and :30 minute marks when the task allows it
588
+
589
+ Every user who asks for "9am" gets \`0 9\`, and every user who asks for "hourly" gets \`0 *\` — which means requests from across the planet land on the API at the same instant. When the user's request is approximate, pick a minute that is NOT 0 or 30:
590
+ "every morning around 9" → "57 8 * * *" or "3 9 * * *" (not "0 9 * * *")
591
+ "hourly" → "7 * * * *" (not "0 * * * *")
592
+ "in an hour or so, remind me to..." → pick whatever minute you land on, don't round
593
+
594
+ Only use minute 0 or 30 when the user names that exact time and clearly means it ("at 9:00 sharp", "at half past", coordinating with a meeting). When in doubt, nudge a few minutes early or late — the user will not notice, and the fleet will.
595
+
596
+ ## Session-only
597
+
598
+ Jobs live only in this session — nothing is written to disk, and the job is gone when the session ends.
599
+
600
+ ## Not for live watching
601
+
602
+ cron_create re-runs a prompt at fixed wall-clock intervals. To watch a log file, process, or command output and be notified the moment something changes, use the monitor tool instead — monitor streams events as they happen; cron polls on a schedule.
603
+
604
+ ## Runtime behavior
605
+
606
+ Jobs only fire while the REPL is idle (not mid-query). The scheduler adds a small deterministic jitter on top of whatever you pick: recurring tasks fire up to 10% of their period late (max 15 min); one-shot tasks landing on :00 or :30 fire up to 90 s early. Picking an off-minute is still the bigger lever.
607
+
608
+ Recurring tasks auto-expire after ${RECURRING_MAX_AGE_DAYS} days — they fire one final time, then are deleted. This bounds session lifetime. Tell the user about the ${RECURRING_MAX_AGE_DAYS}-day limit when scheduling recurring jobs.
609
+
610
+ Returns a job ID you can pass to cron_delete.`;
611
+
612
+ export const CRON_CREATE_PARAMS = {
613
+ cron: 'Standard 5-field cron expression in local time: "M H DoM Mon DoW" (e.g. "*/5 * * * *" = every 5 minutes, "30 14 28 2 *" = Feb 28 at 2:30pm local once).',
614
+ prompt: "The prompt to enqueue at each fire time.",
615
+ /** Claude Code's text with its durable mode off, as on the account One Code matches (decisions/tools.md, "Session cron"). */
616
+ durable: "Has no effect — durable persistence is not available. All jobs are session-only (in-memory, gone when this session ends).",
617
+ recurring: `true (default) = fire on every cron match until deleted or auto-expired after ${RECURRING_MAX_AGE_DAYS} days. false = fire once at the next match, then auto-delete. Use false for "remind me at X" one-shot requests with pinned minute/hour/dom/month.`,
618
+ };
619
+
620
+ export const CRON_LIST_DESCRIPTION = "List all cron jobs scheduled via cron_create in this session.";
621
+
622
+ /**
623
+ * The three tools' parameters, shared by the main session's tools and a
624
+ * subagent's proxies (lib/agent-cron.ts). `durable` is accepted and ignored,
625
+ * as in Claude Code with its durable mode off; cron_list and cron_delete stay
626
+ * open to stray fields like our other tools (deepseek sends `query` to an
627
+ * empty schema; decisions/tools.md, "Session cron").
628
+ */
629
+ export const CRON_CREATE_PARAMETERS = Type.Object(
630
+ {
631
+ cron: Type.String({ description: CRON_CREATE_PARAMS.cron }),
632
+ prompt: Type.String({ description: CRON_CREATE_PARAMS.prompt }),
633
+ recurring: Type.Optional(Type.Boolean({ description: CRON_CREATE_PARAMS.recurring })),
634
+ durable: Type.Optional(Type.Boolean({ description: CRON_CREATE_PARAMS.durable })),
635
+ },
636
+ { additionalProperties: false },
637
+ );
638
+ export const CRON_LIST_PARAMETERS = Type.Object({});
639
+
640
+ export const CRON_DELETE_DESCRIPTION = "Cancel a cron job previously scheduled with cron_create. Removes it from the in-memory session store.";
641
+ export const CRON_DELETE_ID_DESCRIPTION = "Job ID returned by cron_create.";
642
+ export const CRON_DELETE_PARAMETERS = Type.Object({ id: Type.String({ description: CRON_DELETE_ID_DESCRIPTION }) });