@phuetz/code-buddy 2.0.0 → 2.1.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 (403) hide show
  1. package/README.fr.md +56 -0
  2. package/README.md +73 -16
  3. package/codebuddy-runtime.json +5 -5
  4. package/dist/agent/codebuddy-agent.d.ts +18 -0
  5. package/dist/agent/codebuddy-agent.js +45 -8
  6. package/dist/agent/execution/agent-executor.d.ts +0 -9
  7. package/dist/agent/execution/agent-executor.js +165 -12
  8. package/dist/agent/execution/context-pipeline.d.ts +9 -0
  9. package/dist/agent/execution/context-pipeline.js +64 -2
  10. package/dist/agent/execution/tool-loop-guard.d.ts +76 -0
  11. package/dist/agent/execution/tool-loop-guard.js +165 -0
  12. package/dist/agent/execution/tool-selection-strategy.js +19 -2
  13. package/dist/agent/facades/session-facade.d.ts +0 -6
  14. package/dist/agent/facades/session-facade.js +0 -45
  15. package/dist/agent/hermes-claw-migrate.d.ts +0 -21
  16. package/dist/agent/hermes-claw-migrate.js +9 -4
  17. package/dist/agent/hermes-protocol-gateways.js +8 -1
  18. package/dist/agent/repo-profiler.js +17 -4
  19. package/dist/agent/self-improvement/authored-tool-runtime.js +10 -2
  20. package/dist/agent/self-improvement/evolution/ast-novelty.d.ts +5 -0
  21. package/dist/agent/self-improvement/evolution/ast-novelty.js +58 -23
  22. package/dist/agent/self-improvement/evolution/code-variant-store.d.ts +3 -0
  23. package/dist/agent/self-improvement/evolution/evolution-engine.js +2 -0
  24. package/dist/agent/self-improvement/evolution/protected-paths.js +6 -0
  25. package/dist/agent/self-improvement/evolution/variant-fitness.d.ts +13 -2
  26. package/dist/agent/self-improvement/evolution/variant-fitness.js +184 -58
  27. package/dist/agent/self-improvement/evolution/worktree-scorer.d.ts +2 -0
  28. package/dist/agent/self-improvement/evolution/worktree-scorer.js +15 -0
  29. package/dist/agent/self-improvement/evolutionary-archive.d.ts +2 -1
  30. package/dist/agent/self-improvement/evolutionary-archive.js +34 -5
  31. package/dist/agent/self-improvement/proposal-store.d.ts +1 -0
  32. package/dist/agent/self-improvement/proposal-store.js +79 -17
  33. package/dist/agent/self-improvement/skill-apply-journal.d.ts +44 -0
  34. package/dist/agent/self-improvement/skill-apply-journal.js +178 -0
  35. package/dist/agent/self-improvement/skill-behavior-benchmark.d.ts +3 -0
  36. package/dist/agent/self-improvement/skill-behavior-benchmark.js +19 -0
  37. package/dist/agent/self-improvement/skill-behavior-gate.d.ts +34 -0
  38. package/dist/agent/self-improvement/skill-behavior-gate.js +96 -0
  39. package/dist/agent/self-improvement/skill-engine.d.ts +22 -0
  40. package/dist/agent/self-improvement/skill-engine.js +385 -13
  41. package/dist/agent/self-improvement/skill-gate.d.ts +2 -0
  42. package/dist/agent/self-improvement/skill-gate.js +17 -2
  43. package/dist/agent/self-improvement/skill-mutator.d.ts +6 -1
  44. package/dist/agent/self-improvement/skill-mutator.js +51 -15
  45. package/dist/agent/self-improvement/skill-types.d.ts +2 -1
  46. package/dist/agent/self-improvement/types.d.ts +11 -0
  47. package/dist/agent/tool-handler.d.ts +1 -1
  48. package/dist/agent/tool-handler.js +28 -9
  49. package/dist/auth/profile-manager.js +1 -1
  50. package/dist/channels/companion-channel-turn.d.ts +53 -4
  51. package/dist/channels/companion-channel-turn.js +218 -16
  52. package/dist/channels/slash-parity.d.ts +4 -6
  53. package/dist/channels/slash-parity.js +9 -44
  54. package/dist/cli/session-commands.d.ts +9 -0
  55. package/dist/cli/session-commands.js +45 -5
  56. package/dist/cli/session-picker.d.ts +43 -0
  57. package/dist/cli/session-picker.js +128 -0
  58. package/dist/codebuddy/a2a-call-tool-defs.d.ts +26 -0
  59. package/dist/codebuddy/a2a-call-tool-defs.js +12 -0
  60. package/dist/codebuddy/fleet-tool-defs.d.ts +1 -0
  61. package/dist/codebuddy/fleet-tool-defs.js +24 -0
  62. package/dist/codebuddy/providers/chatgpt-headers.d.ts +18 -0
  63. package/dist/codebuddy/providers/chatgpt-headers.js +37 -0
  64. package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +2 -1
  65. package/dist/codebuddy/providers/provider-chatgpt-responses.js +10 -23
  66. package/dist/codebuddy/providers/provider-openai-compat.js +1 -1
  67. package/dist/codebuddy/ragchat-tool-defs.d.ts +31 -0
  68. package/dist/codebuddy/ragchat-tool-defs.js +19 -0
  69. package/dist/codebuddy/resource-catalog-tool-defs.d.ts +28 -0
  70. package/dist/codebuddy/resource-catalog-tool-defs.js +18 -0
  71. package/dist/codebuddy/tool-definitions/agent-tools.js +2 -2
  72. package/dist/codebuddy/tool-definitions/code-exec-tools.js +5 -1
  73. package/dist/codebuddy/tool-definitions/cron-tools.js +6 -0
  74. package/dist/codebuddy/tool-definitions/self-describe-tools.js +6 -1
  75. package/dist/codebuddy/tools.d.ts +0 -6
  76. package/dist/codebuddy/tools.js +25 -3
  77. package/dist/commands/cli/daemon-commands.js +3 -1
  78. package/dist/commands/cli/evolve-command.js +21 -5
  79. package/dist/commands/cli/fleet-collaboration-commands.d.ts +2 -0
  80. package/dist/commands/cli/fleet-collaboration-commands.js +57 -0
  81. package/dist/commands/cli/fleet-commands.js +29 -0
  82. package/dist/commands/cli/fleet-mission-commands.d.ts +3 -0
  83. package/dist/commands/cli/fleet-mission-commands.js +72 -0
  84. package/dist/commands/cli/fleet-rooms-commands.d.ts +10 -0
  85. package/dist/commands/cli/fleet-rooms-commands.js +289 -0
  86. package/dist/commands/cli/tools-commands.js +2 -1
  87. package/dist/commands/cli/triage-command.d.ts +8 -0
  88. package/dist/commands/cli/triage-command.js +72 -0
  89. package/dist/commands/cli/utility-commands.js +25 -1
  90. package/dist/commands/client-dispatcher.d.ts +7 -0
  91. package/dist/commands/client-dispatcher.js +73 -21
  92. package/dist/commands/cron-cli/index.d.ts +3 -1
  93. package/dist/commands/cron-cli/index.js +25 -0
  94. package/dist/commands/device-auth.d.ts +10 -0
  95. package/dist/commands/device-auth.js +91 -0
  96. package/dist/commands/enhanced-command-handler.d.ts +9 -0
  97. package/dist/commands/enhanced-command-handler.js +28 -6
  98. package/dist/commands/handlers/agent-handlers.d.ts +1 -1
  99. package/dist/commands/handlers/agent-handlers.js +4 -4
  100. package/dist/commands/handlers/channel-handlers.js +53 -2
  101. package/dist/commands/handlers/export-handlers.d.ts +3 -2
  102. package/dist/commands/handlers/export-handlers.js +55 -28
  103. package/dist/commands/handlers/extra-handlers.js +5 -1
  104. package/dist/commands/handlers/grill-me-handler.js +26 -18
  105. package/dist/commands/handlers/memory-handlers.d.ts +6 -2
  106. package/dist/commands/handlers/memory-handlers.js +35 -15
  107. package/dist/commands/handlers/missing-handlers.d.ts +2 -2
  108. package/dist/commands/handlers/missing-handlers.js +4 -4
  109. package/dist/commands/handlers/resources-handler.d.ts +12 -0
  110. package/dist/commands/handlers/resources-handler.js +52 -0
  111. package/dist/commands/handlers/starter-handlers.d.ts +1 -1
  112. package/dist/commands/handlers/starter-handlers.js +32 -2
  113. package/dist/commands/handlers/switch-handler.d.ts +1 -1
  114. package/dist/commands/handlers/switch-handler.js +15 -0
  115. package/dist/commands/handlers/test-handlers.js +4 -86
  116. package/dist/commands/handlers/ui-handlers.js +11 -5
  117. package/dist/commands/login-chatgpt.d.ts +8 -0
  118. package/dist/commands/login-chatgpt.js +17 -0
  119. package/dist/commands/mcp-import.d.ts +11 -0
  120. package/dist/commands/mcp-import.js +67 -0
  121. package/dist/commands/mcp-invoke.d.ts +2 -0
  122. package/dist/commands/mcp-invoke.js +68 -0
  123. package/dist/commands/mcp.js +4 -0
  124. package/dist/commands/pipeline.d.ts +7 -1
  125. package/dist/commands/pipeline.js +104 -7
  126. package/dist/commands/resources.d.ts +2 -0
  127. package/dist/commands/resources.js +64 -0
  128. package/dist/commands/skills-cli/index.js +35 -12
  129. package/dist/commands/slash/builtin-commands.js +16 -3
  130. package/dist/commands/slash/index.d.ts +3 -1
  131. package/dist/commands/slash/index.js +2 -0
  132. package/dist/commands/slash/surfaces.d.ts +53 -0
  133. package/dist/commands/slash/surfaces.js +177 -0
  134. package/dist/commands/slash/types.d.ts +17 -0
  135. package/dist/commands/try.d.ts +1 -1
  136. package/dist/commands/try.js +1 -1
  137. package/dist/companion/companion-identity.d.ts +43 -0
  138. package/dist/companion/companion-identity.js +136 -0
  139. package/dist/companion/companion-toolset.d.ts +66 -0
  140. package/dist/companion/companion-toolset.js +368 -0
  141. package/dist/companion/companion-turn.d.ts +10 -2
  142. package/dist/companion/companion-turn.js +38 -1
  143. package/dist/config/code-exec-policy.d.ts +25 -0
  144. package/dist/config/code-exec-policy.js +39 -0
  145. package/dist/config/model-tools.d.ts +9 -0
  146. package/dist/config/model-tools.js +4 -0
  147. package/dist/context/context-manager-v2.js +2 -2
  148. package/dist/context/lm-resizer-compressor.js +3 -2
  149. package/dist/context/lm-resizer-diagnostics.d.ts +9 -0
  150. package/dist/context/lm-resizer-diagnostics.js +30 -0
  151. package/dist/context/tool-output-masking.js +3 -4
  152. package/dist/curator/curator.d.ts +6 -0
  153. package/dist/curator/curator.js +53 -3
  154. package/dist/daemon/cron-agent-bridge.d.ts +6 -0
  155. package/dist/daemon/cron-agent-bridge.js +31 -4
  156. package/dist/doctor/index.d.ts +11 -2
  157. package/dist/doctor/index.js +84 -21
  158. package/dist/doctor/integrations.d.ts +179 -0
  159. package/dist/doctor/integrations.js +227 -0
  160. package/dist/doctor/triage.d.ts +108 -0
  161. package/dist/doctor/triage.js +304 -0
  162. package/dist/fleet/collaboration.d.ts +44 -0
  163. package/dist/fleet/collaboration.js +170 -0
  164. package/dist/fleet/fleet-listener.d.ts +1 -0
  165. package/dist/fleet/fleet-listener.js +87 -46
  166. package/dist/fleet/fleet-registry.d.ts +2 -0
  167. package/dist/fleet/resource-catalog.d.ts +245 -0
  168. package/dist/fleet/resource-catalog.js +211 -0
  169. package/dist/fleet/rooms/room-access.d.ts +138 -0
  170. package/dist/fleet/rooms/room-access.js +196 -0
  171. package/dist/fleet/rooms/room-client.d.ts +158 -0
  172. package/dist/fleet/rooms/room-client.js +685 -0
  173. package/dist/fleet/rooms/room-event.d.ts +139 -0
  174. package/dist/fleet/rooms/room-event.js +330 -0
  175. package/dist/fleet/rooms/room-filter.d.ts +48 -0
  176. package/dist/fleet/rooms/room-filter.js +152 -0
  177. package/dist/fleet/rooms/room-hub.d.ts +154 -0
  178. package/dist/fleet/rooms/room-hub.js +363 -0
  179. package/dist/fleet/rooms/room-identity.d.ts +26 -0
  180. package/dist/fleet/rooms/room-identity.js +82 -0
  181. package/dist/fleet/rooms/room-lock.d.ts +34 -0
  182. package/dist/fleet/rooms/room-lock.js +180 -0
  183. package/dist/fleet/rooms/room-observations.d.ts +7 -0
  184. package/dist/fleet/rooms/room-observations.js +61 -0
  185. package/dist/fleet/rooms/room-server.d.ts +28 -0
  186. package/dist/fleet/rooms/room-server.js +75 -0
  187. package/dist/fleet/rooms/room-store.d.ts +119 -0
  188. package/dist/fleet/rooms/room-store.js +448 -0
  189. package/dist/fleet/rooms/room-ws-bridge.d.ts +27 -0
  190. package/dist/fleet/rooms/room-ws-bridge.js +137 -0
  191. package/dist/fleet/sensory-mission-bridge.d.ts +42 -0
  192. package/dist/fleet/sensory-mission-bridge.js +136 -0
  193. package/dist/git/worktree-sessions.js +1 -0
  194. package/dist/harness/contract.d.ts +4 -4
  195. package/dist/harness/fleet-supervisor.d.ts +16 -0
  196. package/dist/harness/fleet-supervisor.js +64 -0
  197. package/dist/harness/index.d.ts +4 -0
  198. package/dist/harness/index.js +4 -0
  199. package/dist/harness/mission-runner.d.ts +3 -0
  200. package/dist/harness/mission-runner.js +53 -0
  201. package/dist/harness/mission-store.d.ts +93 -0
  202. package/dist/harness/mission-store.js +297 -0
  203. package/dist/harness/tool-harness.d.ts +56 -0
  204. package/dist/harness/tool-harness.js +153 -0
  205. package/dist/hooks/use-enhanced-input.js +48 -4
  206. package/dist/hooks/use-input-handler.d.ts +1 -0
  207. package/dist/hooks/use-input-handler.js +82 -22
  208. package/dist/hooks/use-input-history.js +1 -0
  209. package/dist/identity/lisa-introspection.js +5 -2
  210. package/dist/identity/operational-self-model.d.ts +2 -0
  211. package/dist/identity/operational-self-model.js +4 -0
  212. package/dist/index.js +52 -6
  213. package/dist/mcp/client.js +6 -0
  214. package/dist/mcp/config.d.ts +20 -0
  215. package/dist/mcp/config.js +121 -8
  216. package/dist/mcp/import-normalize.d.ts +12 -0
  217. package/dist/mcp/import-normalize.js +114 -0
  218. package/dist/mcp/transports.d.ts +10 -5
  219. package/dist/mcp/transports.js +61 -99
  220. package/dist/mcp/types.d.ts +5 -0
  221. package/dist/media/comfyui-recipe-contract.d.ts +7 -7
  222. package/dist/memory/facts-memory.d.ts +11 -1
  223. package/dist/memory/facts-memory.js +47 -8
  224. package/dist/memory/memory-candidate-queue.d.ts +2 -2
  225. package/dist/memory/memory-candidate-queue.js +6 -6
  226. package/dist/memory/persistent-memory.d.ts +13 -8
  227. package/dist/memory/persistent-memory.js +77 -30
  228. package/dist/nodes/device-node.js +5 -9
  229. package/dist/observability/mobile-supervision-gateway-contract.js +20 -7
  230. package/dist/observability/run-event-writer.d.ts +21 -0
  231. package/dist/observability/run-event-writer.js +81 -0
  232. package/dist/observability/run-store.d.ts +5 -0
  233. package/dist/observability/run-store.js +18 -13
  234. package/dist/observability/run-trajectory.js +1 -1
  235. package/dist/optimization/prompt-cache.d.ts +19 -2
  236. package/dist/optimization/prompt-cache.js +41 -6
  237. package/dist/orchestration/orchestrator.d.ts +21 -0
  238. package/dist/orchestration/orchestrator.js +122 -15
  239. package/dist/persistence/session-content.d.ts +8 -0
  240. package/dist/persistence/session-content.js +76 -0
  241. package/dist/persistence/session-handoff.d.ts +64 -0
  242. package/dist/persistence/session-handoff.js +80 -0
  243. package/dist/persistence/session-history.d.ts +4 -0
  244. package/dist/persistence/session-history.js +38 -0
  245. package/dist/persistence/session-store.d.ts +9 -0
  246. package/dist/persistence/session-store.js +48 -20
  247. package/dist/personas/persona-manager.js +17 -1
  248. package/dist/plugins/code-explorer/CodeExplorerManager.d.ts +10 -4
  249. package/dist/plugins/code-explorer/CodeExplorerManager.js +69 -16
  250. package/dist/plugins/plugin-manager.js +10 -1
  251. package/dist/prompts/prompt-manager.js +11 -4
  252. package/dist/prompts/system-base.js +6 -2
  253. package/dist/protocols/a2a/codebuddy-executor.d.ts +11 -2
  254. package/dist/protocols/a2a/codebuddy-executor.js +9 -8
  255. package/dist/protocols/a2a/index.d.ts +1 -1
  256. package/dist/protocols/a2a/jsonrpc-v1.d.ts +41 -0
  257. package/dist/protocols/a2a/jsonrpc-v1.js +145 -0
  258. package/dist/protocols/a2a/peer-config.d.ts +20 -0
  259. package/dist/protocols/a2a/peer-config.js +55 -0
  260. package/dist/providers/codex-oauth.js +30 -6
  261. package/dist/providers/model-provider-compat.js +2 -1
  262. package/dist/sandbox/os-sandbox.d.ts +1 -1
  263. package/dist/scheduler/cron-scheduler.d.ts +15 -2
  264. package/dist/scheduler/cron-scheduler.js +35 -4
  265. package/dist/scheduler/job-notepad.d.ts +69 -0
  266. package/dist/scheduler/job-notepad.js +339 -0
  267. package/dist/security/audit-logger.d.ts +1 -1
  268. package/dist/security/compute-confinement.d.ts +4 -0
  269. package/dist/security/compute-confinement.js +89 -0
  270. package/dist/security/session-encryption.d.ts +4 -0
  271. package/dist/security/session-encryption.js +31 -0
  272. package/dist/sensory/voice-loop.d.ts +17 -0
  273. package/dist/sensory/voice-loop.js +72 -15
  274. package/dist/server/auth/device-session-context.d.ts +9 -0
  275. package/dist/server/auth/device-session-context.js +13 -0
  276. package/dist/server/auth/device-store.d.ts +89 -0
  277. package/dist/server/auth/device-store.js +261 -0
  278. package/dist/server/auth/device-token.d.ts +3 -0
  279. package/dist/server/auth/device-token.js +6 -0
  280. package/dist/server/auth/jwt.js +4 -0
  281. package/dist/server/index.d.ts +0 -10
  282. package/dist/server/index.js +28 -0
  283. package/dist/server/middleware/auth.js +3 -2
  284. package/dist/server/routes/a2a-jsonrpc.d.ts +9 -0
  285. package/dist/server/routes/a2a-jsonrpc.js +66 -0
  286. package/dist/server/routes/a2a-protocol.d.ts +12 -0
  287. package/dist/server/routes/a2a-protocol.js +1 -1
  288. package/dist/server/routes/device-auth.d.ts +4 -0
  289. package/dist/server/routes/device-auth.js +40 -0
  290. package/dist/server/types.d.ts +4 -0
  291. package/dist/server/websocket/handler.d.ts +13 -0
  292. package/dist/server/websocket/handler.js +102 -17
  293. package/dist/services/prompt-builder.d.ts +2 -0
  294. package/dist/services/prompt-builder.js +13 -5
  295. package/dist/services/runtime-settings-context.d.ts +73 -0
  296. package/dist/services/runtime-settings-context.js +69 -0
  297. package/dist/skills/executor.js +2 -0
  298. package/dist/skills/hub.js +16 -14
  299. package/dist/skills/local-inventory.d.ts +21 -0
  300. package/dist/skills/local-inventory.js +72 -0
  301. package/dist/skills/skill-loader.js +2 -2
  302. package/dist/skills/skill-usage-store.d.ts +49 -0
  303. package/dist/skills/skill-usage-store.js +121 -0
  304. package/dist/testing/ai-integration-tests.js +8 -5
  305. package/dist/themes/theme-manager.d.ts +12 -0
  306. package/dist/themes/theme-manager.js +29 -0
  307. package/dist/themes/theme-schema.d.ts +24 -24
  308. package/dist/tools/a2a-call-tool.d.ts +34 -0
  309. package/dist/tools/a2a-call-tool.js +75 -0
  310. package/dist/tools/bash/streaming-executor.d.ts +1 -0
  311. package/dist/tools/bash/streaming-executor.js +78 -90
  312. package/dist/tools/code-exec-preflight-runner.d.ts +41 -0
  313. package/dist/tools/code-exec-preflight-runner.js +254 -0
  314. package/dist/tools/code-exec-preflight.d.ts +44 -0
  315. package/dist/tools/code-exec-preflight.js +501 -0
  316. package/dist/tools/code-exec-tool.d.ts +48 -1
  317. package/dist/tools/code-exec-tool.js +191 -47
  318. package/dist/tools/comment-watcher.js +29 -12
  319. package/dist/tools/core-code-inspection.d.ts +56 -0
  320. package/dist/tools/core-code-inspection.js +86 -0
  321. package/dist/tools/create-skill-tool.d.ts +2 -3
  322. package/dist/tools/create-skill-tool.js +16 -75
  323. package/dist/tools/cronjob-tool.d.ts +1 -0
  324. package/dist/tools/cronjob-tool.js +1 -0
  325. package/dist/tools/env-doctor-tool.d.ts +2 -0
  326. package/dist/tools/env-doctor-tool.js +4 -3
  327. package/dist/tools/execute-code-rpc-invoker.d.ts +1 -1
  328. package/dist/tools/execute-code-rpc-invoker.js +35 -19
  329. package/dist/tools/execute-code-runner.d.ts +2 -0
  330. package/dist/tools/execute-code-runner.js +52 -17
  331. package/dist/tools/fleet-room-tool.d.ts +104 -0
  332. package/dist/tools/fleet-room-tool.js +124 -0
  333. package/dist/tools/integration-tool-hints.d.ts +4 -0
  334. package/dist/tools/integration-tool-hints.js +19 -0
  335. package/dist/tools/list-peers-tool.d.ts +3 -0
  336. package/dist/tools/list-peers-tool.js +11 -4
  337. package/dist/tools/media-generation-tool.d.ts +16 -1
  338. package/dist/tools/media-generation-tool.js +341 -9
  339. package/dist/tools/merge-conflict-tool.js +53 -25
  340. package/dist/tools/metadata.d.ts +1 -5
  341. package/dist/tools/metadata.js +21 -28
  342. package/dist/tools/ragchat-tool.d.ts +39 -0
  343. package/dist/tools/ragchat-tool.js +105 -0
  344. package/dist/tools/registry/fleet-tools.d.ts +9 -0
  345. package/dist/tools/registry/fleet-tools.js +22 -1
  346. package/dist/tools/registry/knowledge-tools.d.ts +2 -2
  347. package/dist/tools/registry/knowledge-tools.js +2 -2
  348. package/dist/tools/registry/lessons-tools.d.ts +7 -0
  349. package/dist/tools/registry/lessons-tools.js +43 -3
  350. package/dist/tools/registry/memory-tools.d.ts +1 -1
  351. package/dist/tools/registry/memory-tools.js +8 -8
  352. package/dist/tools/registry/research-tools.d.ts +1 -0
  353. package/dist/tools/registry/research-tools.js +4 -13
  354. package/dist/tools/registry/self-describe-tools.d.ts +1 -1
  355. package/dist/tools/registry/self-describe-tools.js +43 -30
  356. package/dist/tools/resource-catalog-tool.d.ts +38 -0
  357. package/dist/tools/resource-catalog-tool.js +46 -0
  358. package/dist/tools/skills-inspection-tool.js +30 -1
  359. package/dist/tools/stream-tool-output.d.ts +3 -0
  360. package/dist/tools/stream-tool-output.js +33 -0
  361. package/dist/tools/tool-call-scheduler.d.ts +10 -0
  362. package/dist/tools/tool-call-scheduler.js +58 -0
  363. package/dist/tools/tool-effect.d.ts +5 -0
  364. package/dist/tools/tool-effect.js +26 -0
  365. package/dist/tools/tool-search.d.ts +12 -11
  366. package/dist/tools/tool-search.js +67 -26
  367. package/dist/tools/types.d.ts +6 -0
  368. package/dist/ui/components/ChatHistory.d.ts +1 -1
  369. package/dist/ui/components/ChatHistory.js +2 -2
  370. package/dist/ui/components/ChatInput.d.ts +2 -1
  371. package/dist/ui/components/ChatInput.js +42 -88
  372. package/dist/ui/components/ChatInterface.d.ts +4 -2
  373. package/dist/ui/components/ChatInterface.js +27 -63
  374. package/dist/ui/components/CommandSuggestions.d.ts +0 -1
  375. package/dist/ui/components/CommandSuggestions.js +4 -6
  376. package/dist/ui/components/FileAutocomplete.js +1 -1
  377. package/dist/ui/components/KeyboardHelp.js +8 -6
  378. package/dist/ui/components/ModelSelection.js +6 -5
  379. package/dist/ui/components/StatusBar.d.ts +2 -1
  380. package/dist/ui/components/StatusBar.js +8 -7
  381. package/dist/ui/context/theme-context.js +3 -2
  382. package/dist/ui/index.d.ts +1 -1
  383. package/dist/ui/index.js +1 -1
  384. package/dist/ui/utils/markdown-renderer.js +4 -13
  385. package/dist/utils/atomic-write.d.ts +22 -0
  386. package/dist/utils/atomic-write.js +29 -0
  387. package/dist/utils/bounded-output.d.ts +16 -0
  388. package/dist/utils/bounded-output.js +63 -0
  389. package/dist/utils/device-store-file.d.ts +4 -0
  390. package/dist/utils/device-store-file.js +40 -0
  391. package/dist/utils/export-manager.d.ts +9 -0
  392. package/dist/utils/export-manager.js +19 -0
  393. package/dist/utils/first-use-hints.d.ts +25 -0
  394. package/dist/utils/first-use-hints.js +58 -0
  395. package/dist/utils/history-manager.js +1 -1
  396. package/dist/utils/update-notifier.js +16 -18
  397. package/dist/wizard/environment-detection.d.ts +5 -1
  398. package/dist/wizard/environment-detection.js +49 -7
  399. package/dist/wizard/onboarding.d.ts +5 -1
  400. package/dist/wizard/onboarding.js +90 -30
  401. package/dist/wizard/provider-onboarding.d.ts +2 -0
  402. package/dist/wizard/provider-onboarding.js +55 -17
  403. package/package.json +7 -3
@@ -0,0 +1,145 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { z } from 'zod';
3
+ import { TaskStatus } from './index.js';
4
+ const identifier = z.string().min(1).max(128).regex(/^[A-Za-z0-9_.:-]+$/);
5
+ const requestSchema = z.object({ jsonrpc: z.literal('2.0'), id: z.union([z.string().max(128), z.number().finite(), z.null()]), method: z.string().max(64), params: z.record(z.string(), z.unknown()).default({}) }).strict();
6
+ const messageSchema = z.object({ messageId: identifier, contextId: identifier.optional(), role: z.enum(['ROLE_USER', 'user']).optional(),
7
+ parts: z.array(z.object({ text: z.string(), type: z.literal('text').optional(), kind: z.literal('text').optional() }).strict()).min(1).max(64) }).strict();
8
+ const MAX_TEXT = 64 * 1024;
9
+ const timestamp = () => new Date().toISOString();
10
+ function truncateUtf8(text, limit) {
11
+ const buffer = Buffer.from(text);
12
+ let end = Math.min(buffer.length, limit);
13
+ while (end > 0 && end < buffer.length && (buffer[end] & 0xc0) === 0x80)
14
+ end--;
15
+ return buffer.subarray(0, end).toString('utf8');
16
+ }
17
+ export const rpcError = (id, code, message) => ({ jsonrpc: '2.0', id: id ?? null, error: { code, message } });
18
+ /** Bounded text-only A2A 1.0 JSON-RPC subset; no automatic retries, streaming or pretend cancellation. */
19
+ export class A2AJsonRpcAdapter {
20
+ executor;
21
+ timeoutMs;
22
+ peerCount;
23
+ tasks = new Map();
24
+ messages = new Map();
25
+ contexts = new Map();
26
+ constructor(executor, timeoutMs = 120000, peerCount = 1) {
27
+ this.executor = executor;
28
+ this.timeoutMs = timeoutMs;
29
+ this.peerCount = peerCount;
30
+ }
31
+ async handle(peer, input, version) {
32
+ const parsed = requestSchema.safeParse(input);
33
+ if (!parsed.success)
34
+ return rpcError(null, -32600, 'Invalid JSON-RPC request');
35
+ const { id, method, params } = parsed.data;
36
+ if (version && !['1.0', '1.0.0'].includes(version))
37
+ return rpcError(id, -32602, 'Only A2A 1.0 is supported');
38
+ this.prune();
39
+ if (method === 'GetTask' || method === 'tasks/get') {
40
+ const key = typeof params.id === 'string' ? params.id : typeof params.name === 'string' ? params.name.replace(/^tasks\//, '') : '';
41
+ const stored = this.tasks.get(key);
42
+ return stored?.peer === peer ? { jsonrpc: '2.0', id, result: structuredClone(stored.task) } : rpcError(id, -32001, 'Task not found');
43
+ }
44
+ if (method === 'CancelTask' || method === 'tasks/cancel')
45
+ return rpcError(id, -32004, 'Cancellation is not supported; no cancellation was performed');
46
+ if (!['SendMessage', 'message/send'].includes(method))
47
+ return rpcError(id, -32601, 'Method not supported (including streaming)');
48
+ const message = messageSchema.safeParse(params.message);
49
+ if (!message.success)
50
+ return rpcError(id, -32602, 'A text message with messageId is required');
51
+ const text = message.data.parts.map(p => p.text).join('\n').trim();
52
+ if (!text || Buffer.byteLength(text) > MAX_TEXT)
53
+ return rpcError(id, -32602, 'Message must contain 1–65536 UTF-8 bytes');
54
+ if (params.contextId !== undefined && !identifier.safeParse(params.contextId).success)
55
+ return rpcError(id, -32602, 'Invalid contextId');
56
+ if (params.configuration !== undefined && (!params.configuration || typeof params.configuration !== 'object' || params.configuration.blocking !== true))
57
+ return rpcError(id, -32602, 'Only blocking SendMessage is supported');
58
+ const contextId = message.data.contextId ?? (typeof params.contextId === 'string' && identifier.safeParse(params.contextId).success ? params.contextId : randomUUID());
59
+ const fingerprint = createHash('sha256').update(JSON.stringify([message.data.contextId ?? params.contextId ?? null, text])).digest('hex');
60
+ const messageKey = `${peer}:${message.data.messageId}`;
61
+ const previousId = this.messages.get(messageKey);
62
+ const previous = previousId ? this.tasks.get(previousId) : undefined;
63
+ if (previous) {
64
+ if (previous.fingerprint !== fingerprint)
65
+ return rpcError(id, -32602, 'messageId already used with different content');
66
+ return { jsonrpc: '2.0', id, result: { task: await (previous.promise ?? Promise.resolve(structuredClone(previous.task))) } };
67
+ }
68
+ const contextKey = `${peer}:${contextId}`;
69
+ const context = this.contexts.get(contextKey) ?? { messages: [], turns: 0, busy: false, at: Date.now() };
70
+ if (context.messages.reduce((bytes, msg) => bytes + Buffer.byteLength(JSON.stringify(msg)), Buffer.byteLength(text)) > MAX_TEXT)
71
+ return rpcError(id, -32602, 'Context text limit reached');
72
+ if (context.busy)
73
+ return rpcError(id, -32005, 'Context is busy; no second task was started');
74
+ if (context.turns >= 5)
75
+ return rpcError(id, -32005, 'Context turn limit reached');
76
+ const shares = Math.max(1, Math.min(32, Math.floor(this.peerCount)));
77
+ const peerTasks = [...this.tasks.values()].filter(value => value.peer === peer).length;
78
+ const peerContexts = [...this.contexts.keys()].filter(key => key.startsWith(`${peer}:`)).length;
79
+ if (peerTasks >= Math.floor(256 / shares) || (!this.contexts.has(contextKey) && peerContexts >= Math.floor(128 / shares)))
80
+ return rpcError(id, -32005, 'Peer task capacity reached');
81
+ if (this.tasks.size >= 256 || (!this.contexts.has(contextKey) && this.contexts.size >= 128))
82
+ return rpcError(id, -32005, 'Task capacity reached');
83
+ const task = { id: randomUUID(), contextId, status: { state: 'TASK_STATE_WORKING', timestamp: timestamp() }, artifacts: [] };
84
+ const stored = { peer, contextId, fingerprint, task, at: Date.now() };
85
+ this.tasks.set(task.id, stored);
86
+ this.messages.set(messageKey, task.id);
87
+ if (text.startsWith('/')) {
88
+ task.status = { state: 'TASK_STATE_REJECTED', timestamp: timestamp(), message: { role: 'ROLE_AGENT', parts: [{ text: 'Slash commands are not allowed over A2A' }] } };
89
+ return { jsonrpc: '2.0', id, result: { task: structuredClone(task) } };
90
+ }
91
+ context.busy = true;
92
+ context.turns++;
93
+ context.at = Date.now();
94
+ this.contexts.set(contextKey, context);
95
+ const inputTask = { id: task.id, sessionId: `a2a:${peer}:${contextId}`, metadata: { peerId: peer },
96
+ messages: [...structuredClone(context.messages), { role: 'user', parts: [{ type: 'text', text }] }], artifacts: [], history: [], status: { status: TaskStatus.WORKING, timestamp: Date.now() } };
97
+ const controller = new AbortController();
98
+ let timer;
99
+ stored.running = true;
100
+ const execution = Promise.resolve().then(() => this.executor(inputTask, controller.signal)).finally(() => { context.busy = false; stored.running = false; });
101
+ stored.promise = (async () => {
102
+ try {
103
+ const completed = await Promise.race([execution, new Promise((_, reject) => {
104
+ timer = setTimeout(() => { controller.abort(); reject(new Error('TIMEOUT')); }, this.timeoutMs);
105
+ timer.unref?.();
106
+ })]);
107
+ const failed = completed.status.status !== TaskStatus.COMPLETED;
108
+ const output = completed.messages.filter(m => m.role === 'agent').at(-1)?.parts.filter(p => p.type === 'text').map(p => p.text).join('\n') ?? '';
109
+ task.status = { state: failed ? 'TASK_STATE_FAILED' : 'TASK_STATE_COMPLETED', timestamp: timestamp() };
110
+ if (!failed) {
111
+ const returnedText = Buffer.byteLength(output) > MAX_TEXT ? truncateUtf8(output, MAX_TEXT - 32) + '\n[Output truncated]' : output;
112
+ task.artifacts = [{ artifactId: randomUUID(), parts: [{ text: returnedText }] }];
113
+ context.messages = [...structuredClone(completed.messages).slice(0, -1), { role: 'agent', parts: [{ type: 'text', text: returnedText }] }].slice(-10);
114
+ while (context.messages.length > 1 && Buffer.byteLength(JSON.stringify(context.messages)) > MAX_TEXT)
115
+ context.messages.shift();
116
+ }
117
+ else
118
+ task.status.message = { role: 'ROLE_AGENT', parts: [{ text: 'Executor failed; inspect local diagnostics' }] };
119
+ }
120
+ catch {
121
+ task.status = { state: 'TASK_STATE_FAILED', timestamp: timestamp(), message: { role: 'ROLE_AGENT', parts: [{ text: 'Execution failed or timed out; no automatic retry' }] } };
122
+ }
123
+ finally {
124
+ clearTimeout(timer);
125
+ stored.promise = undefined;
126
+ stored.at = Date.now();
127
+ }
128
+ return structuredClone(task);
129
+ })();
130
+ return { jsonrpc: '2.0', id, result: { task: await stored.promise } };
131
+ }
132
+ prune() {
133
+ const cutoff = Date.now() - 30 * 60 * 1000;
134
+ for (const [id, value] of this.tasks)
135
+ if (!value.running && !value.promise && value.at < cutoff)
136
+ this.tasks.delete(id);
137
+ for (const [id, taskId] of this.messages)
138
+ if (!this.tasks.has(taskId))
139
+ this.messages.delete(id);
140
+ for (const [id, context] of this.contexts)
141
+ if (!context.busy && context.at < cutoff)
142
+ this.contexts.delete(id);
143
+ }
144
+ }
145
+ //# sourceMappingURL=jsonrpc-v1.js.map
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ declare const schema: z.ZodRecord<z.ZodString, z.ZodObject<{
3
+ url: z.ZodOptional<z.ZodString>;
4
+ inboundTokenEnv: z.ZodOptional<z.ZodString>;
5
+ outboundTokenEnv: z.ZodOptional<z.ZodString>;
6
+ }, "strict", z.ZodTypeAny, {
7
+ url?: string | undefined;
8
+ inboundTokenEnv?: string | undefined;
9
+ outboundTokenEnv?: string | undefined;
10
+ }, {
11
+ url?: string | undefined;
12
+ inboundTokenEnv?: string | undefined;
13
+ outboundTokenEnv?: string | undefined;
14
+ }>>;
15
+ export type A2APeers = z.infer<typeof schema>;
16
+ export declare function readA2APeers(raw?: string | undefined): A2APeers;
17
+ export declare function validateA2AUrl(raw: string): URL;
18
+ export declare function a2aToken(reference?: string): string | undefined;
19
+ export declare function authenticateA2APeer(peers: A2APeers, authorization?: string): string | undefined;
20
+ export {};
@@ -0,0 +1,55 @@
1
+ import { createHash, timingSafeEqual } from 'node:crypto';
2
+ import { z } from 'zod';
3
+ const envRef = z.string().regex(/^[A-Z][A-Z0-9_]{0,95}$/);
4
+ const schema = z.record(z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$/), z.object({
5
+ url: z.string().optional(), inboundTokenEnv: envRef.optional(), outboundTokenEnv: envRef.optional(),
6
+ }).strict());
7
+ export function readA2APeers(raw = process.env.CODEBUDDY_A2A_PEERS) {
8
+ if (!raw)
9
+ return {};
10
+ try {
11
+ if (raw.length > 16384)
12
+ throw new Error();
13
+ const result = schema.parse(JSON.parse(raw));
14
+ if (Object.keys(result).length > 32)
15
+ throw new Error();
16
+ for (const peer of Object.values(result))
17
+ if (peer.url)
18
+ validateA2AUrl(peer.url);
19
+ return result;
20
+ }
21
+ catch {
22
+ throw new Error('Invalid CODEBUDDY_A2A_PEERS configuration (URLs and environment references only)');
23
+ }
24
+ }
25
+ export function validateA2AUrl(raw) {
26
+ let url;
27
+ try {
28
+ url = new URL(raw);
29
+ }
30
+ catch {
31
+ throw new Error('Invalid configured A2A URL');
32
+ }
33
+ if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password || url.search || url.hash
34
+ || (url.protocol === 'http:' && !['localhost', '127.0.0.1', '[::1]'].includes(url.hostname))) {
35
+ throw new Error('A2A requires HTTPS or loopback HTTP, without URL credentials/query/fragment');
36
+ }
37
+ return url;
38
+ }
39
+ export function a2aToken(reference) {
40
+ const value = reference ? process.env[reference] : undefined;
41
+ return value && value.length >= 16 && value.length <= 16384 && !/[\r\n]/.test(value) ? value : undefined;
42
+ }
43
+ export function authenticateA2APeer(peers, authorization) {
44
+ if (!authorization?.startsWith('Bearer ') || authorization.length > 16400)
45
+ return undefined;
46
+ const hash = (value) => createHash('sha256').update(value).digest();
47
+ const presented = hash(authorization.slice(7));
48
+ const matches = Object.entries(peers).filter(([, config]) => {
49
+ const token = a2aToken(config.inboundTokenEnv);
50
+ return token && timingSafeEqual(hash(token), presented);
51
+ });
52
+ // Duplicate credentials are ambiguous identities; fail closed.
53
+ return matches.length === 1 ? matches[0]?.[0] : undefined;
54
+ }
55
+ //# sourceMappingURL=peer-config.js.map
@@ -56,12 +56,21 @@ const TOKEN_REFRESH_AGE_MS = 60 * 60 * 1000; // 1 hour
56
56
  * the same rotating refresh token more than once. */
57
57
  let refreshAuthInFlight = null;
58
58
  const AUTH_FILE_PATH = path.join(os.homedir(), '.codebuddy', 'codex-auth.json');
59
+ const CODEX_CLI_AUTH_PATH = path.join(os.homedir(), '.codex', 'auth.json');
59
60
  // ─────────────────────────────────────────────────────────────────────
60
61
  // Storage
61
62
  // ─────────────────────────────────────────────────────────────────────
62
63
  function loadAuthFile() {
63
64
  try {
64
- return readJsonAtomicSync(AUTH_FILE_PATH, null);
65
+ const primary = readJsonAtomicSync(AUTH_FILE_PATH, null);
66
+ if (primary?.tokens?.access_token)
67
+ return primary;
68
+ if (fs.existsSync(CODEX_CLI_AUTH_PATH)) {
69
+ const fallback = readJsonAtomicSync(CODEX_CLI_AUTH_PATH, null);
70
+ if (fallback?.tokens?.access_token)
71
+ return fallback;
72
+ }
73
+ return primary;
65
74
  }
66
75
  catch (err) {
67
76
  logger.error('Error reading codex-auth.json', err instanceof Error ? err : { error: String(err) });
@@ -72,8 +81,10 @@ function saveAuthFile(auth) {
72
81
  try {
73
82
  writeJsonAtomicSync(AUTH_FILE_PATH, auth, { mode: 0o600 });
74
83
  }
75
- catch (err) {
76
- logger.error('Error writing codex-auth.json', err instanceof Error ? err : { error: String(err) });
84
+ catch {
85
+ // A successful exchange is not a successful login until tokens survive
86
+ // a restart. Never return freshly rotated tokens as if they were saved.
87
+ throw new Error('Could not save ChatGPT credentials. Check file permissions and available disk space, then run `buddy login` again.');
77
88
  }
78
89
  }
79
90
  /** Remove cached tokens. `/logout chatgpt` calls this. */
@@ -92,9 +103,13 @@ export function clearCodexCredentials() {
92
103
  export function hasCodexCredentials() {
93
104
  try {
94
105
  const parsed = readJsonAtomicSync(AUTH_FILE_PATH, null);
95
- if (!parsed)
96
- return false;
97
- return Boolean(parsed.tokens?.access_token);
106
+ if (parsed?.tokens?.access_token)
107
+ return true;
108
+ if (fs.existsSync(CODEX_CLI_AUTH_PATH)) {
109
+ const fallback = readJsonAtomicSync(CODEX_CLI_AUTH_PATH, null);
110
+ return Boolean(fallback?.tokens?.access_token);
111
+ }
112
+ return false;
98
113
  }
99
114
  catch {
100
115
  return false;
@@ -343,7 +358,9 @@ export function loginInteractive(openUrl) {
343
358
  let serverInstance = null;
344
359
  let timeoutHandle = null;
345
360
  let actualPort = CALLBACK_PORT;
361
+ let finished = false;
346
362
  const cleanup = () => {
363
+ finished = true;
347
364
  if (timeoutHandle)
348
365
  clearTimeout(timeoutHandle);
349
366
  if (serverInstance) {
@@ -392,6 +409,13 @@ export function loginInteractive(openUrl) {
392
409
  }
393
410
  const redirectUri = `http://localhost:${actualPort}/auth/callback`;
394
411
  const tokens = await exchangeCodeForTokens(code, pkce.code_verifier, redirectUri);
412
+ if (finished) {
413
+ // Cancellation/timeout may happen while the issuer is responding.
414
+ // A late response must not replace credentials after the UI has ended.
415
+ res.writeHead(410, { 'Content-Type': 'text/plain' });
416
+ res.end('Login is no longer active. Run buddy login again.');
417
+ return;
418
+ }
395
419
  const authFile = {
396
420
  tokens,
397
421
  last_refresh: new Date().toISOString(),
@@ -1,3 +1,4 @@
1
+ import { isChatGptSubscriptionModel } from './chatgpt-models.js';
1
2
  /**
2
3
  * Model ↔ provider compatibility for the CLI model resolver (`loadModel`).
3
4
  *
@@ -35,7 +36,7 @@ export function isModelCompatibleWithProvider(model, provider) {
35
36
  if (provider === 'grok')
36
37
  return looksGrok;
37
38
  if (provider === 'chatgpt')
38
- return /^(gpt-|o[1-9]|codex)/i.test(model) && !looksGrok;
39
+ return isChatGptSubscriptionModel(model) && !looksGrok;
39
40
  if (LOCAL_RUNTIME_PROVIDERS.has(provider))
40
41
  return !looksLikeCloudModel(model);
41
42
  if (FIRST_PARTY_PROVIDERS.has(provider)) {
@@ -240,4 +240,4 @@ export declare function createSandboxConfigForMode(mode: SandboxMode, cwd?: stri
240
240
  export declare function createSandboxForMode(mode: SandboxMode, cwd?: string, extraReadOnly?: string[], extraReadWrite?: string[]): Promise<OSSandbox>;
241
241
  export declare function getOSSandbox(config?: Partial<OSSandboxConfig>): OSSandbox;
242
242
  export declare function resetOSSandbox(): void;
243
- export { OSSandboxConfig as OSConfig };
243
+ export type { OSSandboxConfig as OSConfig };
@@ -11,6 +11,7 @@
11
11
  import { EventEmitter } from 'events';
12
12
  import type { CronPreCheck } from './pre-check-runner.js';
13
13
  import type { CronWatchdog } from './watchdog-handlers.js';
14
+ import { type ContinuityFlag } from './job-notepad.js';
14
15
  export type ScheduleType = 'at' | 'every' | 'cron';
15
16
  export type JobStatus = 'active' | 'paused' | 'completed' | 'error';
16
17
  export interface CronJob {
@@ -78,9 +79,17 @@ export interface CronJob {
78
79
  /**
79
80
  * Optional non-LLM pre-check. When present, it is evaluated before the task;
80
81
  * if it decides nothing changed, the expensive task is skipped with evidence.
81
- * `lastFingerprint` is updated and persisted across runs by the bridge.
82
+ * `lastFingerprint` is updated and persisted across runs by the bridge,
83
+ * but only after a successful evaluation (skip or completed task) — a failed
84
+ * run must not consume the fingerprint, so an unchanged source can retry.
82
85
  */
83
86
  preCheck?: CronPreCheck;
87
+ /**
88
+ * Opt-in per-job bounded continuity (scratchpad + last successful output).
89
+ * Off by default: agent prompts stay byte-identical. Notes passed at
90
+ * create/update are stored in the isolated notepad file, not in jobs.json.
91
+ */
92
+ continuity?: ContinuityFlag;
84
93
  /** Delivery options */
85
94
  delivery?: {
86
95
  /** Delivery mode: 'channel' (default), 'webhook', or 'none' (silent, no notification) */
@@ -175,7 +184,10 @@ export declare class CronScheduler extends EventEmitter {
175
184
  private tickInFlight;
176
185
  private running;
177
186
  private taskExecutor?;
187
+ private notepad;
178
188
  constructor(config?: Partial<CronSchedulerConfig>);
189
+ /** Directory of isolated per-job notepad files (`<cron-root>/notepads`). */
190
+ get notepadDir(): string;
179
191
  /**
180
192
  * Set the task executor (for CronAgentBridge integration)
181
193
  */
@@ -205,11 +217,12 @@ export declare class CronScheduler extends EventEmitter {
205
217
  sessionTarget?: CronJob['sessionTarget'];
206
218
  preCheck?: CronJob['preCheck'];
207
219
  then?: string;
220
+ continuity?: CronJob['continuity'];
208
221
  }): Promise<CronJob>;
209
222
  /**
210
223
  * Update a job
211
224
  */
212
- updateJob(jobId: string, updates: Partial<Pick<CronJob, 'name' | 'description' | 'type' | 'schedule' | 'task' | 'delivery' | 'maxRuns' | 'enabled' | 'preCheck' | 'then'>>): Promise<CronJob | null>;
225
+ updateJob(jobId: string, updates: Partial<Pick<CronJob, 'name' | 'description' | 'type' | 'schedule' | 'task' | 'delivery' | 'maxRuns' | 'enabled' | 'preCheck' | 'then' | 'continuity'>>): Promise<CronJob | null>;
213
226
  /**
214
227
  * Remove a job
215
228
  */
@@ -17,6 +17,7 @@ import { logger } from '../utils/logger.js';
17
17
  import { canonicalizeTimeZone } from '../life-rhythm/day-context.js';
18
18
  import { findNextZonedMinute } from '../life-rhythm/zoned-minute.js';
19
19
  import { readJsonAtomic, readTextAtomic, writeFileAtomic, writeJsonAtomic } from '../utils/atomic-write.js';
20
+ import { JobNotepadStore, isContinuityEnabled, persistableContinuity, validateContinuity, } from './job-notepad.js';
20
21
  /** Exponential backoff delays in ms: 30s, 1m, 5m, 15m, 60m */
21
22
  const BACKOFF_DELAYS_MS = [30_000, 60_000, 300_000, 900_000, 3_600_000];
22
23
  /** Maximum chain depth to guard against cyclic `then` references (A→B→A). */
@@ -194,9 +195,15 @@ export class CronScheduler extends EventEmitter {
194
195
  tickInFlight = false;
195
196
  running = false;
196
197
  taskExecutor;
198
+ notepad;
197
199
  constructor(config = {}) {
198
200
  super();
199
201
  this.config = { ...DEFAULT_CRON_SCHEDULER_CONFIG, ...config };
202
+ this.notepad = new JobNotepadStore(path.join(path.dirname(this.config.persistPath), 'notepads'));
203
+ }
204
+ /** Directory of isolated per-job notepad files (`<cron-root>/notepads`). */
205
+ get notepadDir() {
206
+ return path.join(path.dirname(this.config.persistPath), 'notepads');
200
207
  }
201
208
  // ==========================================================================
202
209
  // Lifecycle
@@ -267,6 +274,8 @@ export class CronScheduler extends EventEmitter {
267
274
  async addJob(params) {
268
275
  const id = crypto.randomUUID();
269
276
  const now = new Date();
277
+ const continuity = params.continuity === undefined ? undefined : validateContinuity(params.continuity);
278
+ const seededNotes = typeof continuity === 'object' ? continuity.notes : undefined;
270
279
  const job = {
271
280
  id,
272
281
  name: params.name,
@@ -285,6 +294,7 @@ export class CronScheduler extends EventEmitter {
285
294
  sessionTarget: params.sessionTarget,
286
295
  preCheck: params.preCheck,
287
296
  then: params.then,
297
+ continuity: persistableContinuity(continuity),
288
298
  };
289
299
  // Resolve 'current' session target to concrete session ID at creation time
290
300
  if (job.sessionTarget === 'current') {
@@ -292,6 +302,9 @@ export class CronScheduler extends EventEmitter {
292
302
  }
293
303
  // Calculate next run
294
304
  job.nextRunAt = this.calculateNextRun(job);
305
+ if (seededNotes && isContinuityEnabled(job.continuity)) {
306
+ await this.notepad.replaceNotes(id, seededNotes);
307
+ }
295
308
  this.jobs.set(id, job);
296
309
  await this.persistJobs();
297
310
  if (job.enabled && this.running) {
@@ -307,11 +320,23 @@ export class CronScheduler extends EventEmitter {
307
320
  const job = this.jobs.get(jobId);
308
321
  if (!job)
309
322
  return null;
310
- // Cancel existing timer
323
+ const { continuity: inputContinuity, ...rest } = updates;
324
+ const continuity = inputContinuity === undefined ? undefined : validateContinuity(inputContinuity);
325
+ const next = { ...job, ...rest };
326
+ if (continuity !== undefined)
327
+ next.continuity = persistableContinuity(continuity);
328
+ // Validate scheduling before writing notes; retain the live job/timer on failure.
329
+ this.calculateNextRun(next);
330
+ if (continuity !== undefined) {
331
+ const seededNotes = typeof continuity === 'object' ? continuity.notes : undefined;
332
+ if (seededNotes && isContinuityEnabled(next.continuity)) {
333
+ await this.notepad.replaceNotes(jobId, seededNotes);
334
+ }
335
+ }
311
336
  this.cancelJobTimer(jobId);
312
- // Apply updates
313
- Object.assign(job, updates);
314
- // Recalculate next run
337
+ Object.assign(job, rest);
338
+ if (continuity !== undefined)
339
+ job.continuity = next.continuity;
315
340
  job.nextRunAt = this.calculateNextRun(job);
316
341
  // Reschedule if enabled
317
342
  if (job.enabled && this.running) {
@@ -329,6 +354,12 @@ export class CronScheduler extends EventEmitter {
329
354
  return false;
330
355
  this.cancelJobTimer(jobId);
331
356
  this.jobs.delete(jobId);
357
+ try {
358
+ await this.notepad.clear(jobId);
359
+ }
360
+ catch (error) {
361
+ logger.warn('Cron job removed but its notepad could not be cleared', { jobId, error: String(error) });
362
+ }
332
363
  await this.persistJobs();
333
364
  this.emit('job:deleted', jobId);
334
365
  return true;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Per-job bounded continuity store (Hermes-inspired cron notepad).
3
+ *
4
+ * Isolated JSON files under `<cron-root>/notepads/<jobId>.json`, written
5
+ * atomically (0600): 16 KiB per value, 128-char keys, 64 KiB per serialized
6
+ * record. Mutations use an exclusive per-job lock across local processes.
7
+ * Contention requires retry; abandoned locks require operator inspection and
8
+ * are never stolen. Invalid/corrupt records are preserved for inspection.
9
+ */
10
+ export declare const MAX_VALUE_BYTES: number;
11
+ export declare const MAX_KEY_CHARS = 128;
12
+ export declare const MAX_JOB_TOTAL_BYTES: number;
13
+ export declare const MAX_LAST_OUTPUT_BYTES: number;
14
+ export type ContinuityFlag = boolean | {
15
+ enabled?: boolean;
16
+ notes?: Record<string, string>;
17
+ };
18
+ export interface JobNotepadRecord {
19
+ notes: Record<string, string>;
20
+ /** Most recent non-empty successful output that was saved. */
21
+ lastSuccessfulOutput?: string;
22
+ updatedAt: string;
23
+ }
24
+ export declare class JobNotepadBusyError extends Error {
25
+ constructor();
26
+ }
27
+ /** Validate operator/tool input before changing jobs or writing any notes. */
28
+ export declare function validateContinuity(value: unknown): ContinuityFlag;
29
+ export declare function isContinuityEnabled(continuity: ContinuityFlag | undefined): boolean;
30
+ /** Persist the opt-in flag only — notes live in the notepad file, not jobs.json. */
31
+ export declare function persistableContinuity(continuity: ContinuityFlag | undefined): boolean | {
32
+ enabled: boolean;
33
+ } | undefined;
34
+ export declare function defaultNotepadDir(): string;
35
+ export declare function utf8ByteLength(value: string): number;
36
+ export declare function sanitizeJobId(jobId: string): string;
37
+ export declare function truncateUtf8(value: string, maxBytes: number): string;
38
+ export declare function notesByteTotal(notes: Record<string, string>): number;
39
+ export declare function assertNotesWithinCaps(notes: unknown): asserts notes is Record<string, string>;
40
+ export declare class JobNotepadStore {
41
+ private readonly dir;
42
+ constructor(dir?: string);
43
+ fileFor(jobId: string): string;
44
+ load(jobId: string): Promise<JobNotepadRecord>;
45
+ setNote(jobId: string, key: string, value: string): Promise<JobNotepadRecord>;
46
+ replaceNotes(jobId: string, notes: Record<string, string>): Promise<JobNotepadRecord>;
47
+ getNote(jobId: string, key: string): Promise<string | undefined>;
48
+ deleteNote(jobId: string, key: string): Promise<boolean>;
49
+ listNotes(jobId: string): Promise<Array<{
50
+ key: string;
51
+ value: string;
52
+ }>>;
53
+ /**
54
+ * Record the last *successful* job output. Truncates to the per-value and
55
+ * serialized per-job caps. Storage errors are reported to the caller.
56
+ * Empty output leaves the previous non-empty successful output in place.
57
+ */
58
+ saveLastSuccessfulOutput(jobId: string, output: string): Promise<void>;
59
+ /** Missing files are a no-op; existing files share writer exclusion. */
60
+ clear(jobId: string): Promise<void>;
61
+ /**
62
+ * Prompt section for this job. Empty notepad MUST return '' so jobs that
63
+ * never use continuity keep a byte-identical agent prompt.
64
+ */
65
+ renderSection(jobId: string): Promise<string>;
66
+ private commitNotes;
67
+ private write;
68
+ private withMutationLock;
69
+ }