@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
@@ -20,6 +20,8 @@ export declare class Orchestrator extends EventEmitter {
20
20
  private failedTaskCount;
21
21
  private totalTaskDuration;
22
22
  private running;
23
+ private workflowTaskSequence;
24
+ private workflowTaskIdentity;
23
25
  constructor(config?: Partial<OrchestratorConfig>);
24
26
  /**
25
27
  * Register a new agent
@@ -81,6 +83,25 @@ export declare class Orchestrator extends EventEmitter {
81
83
  * Cancel a task
82
84
  */
83
85
  cancelTask(taskId: string): void;
86
+ private isTerminal;
87
+ /**
88
+ * A report (result, error, answered approval) for a task that is already
89
+ * terminal — typically abandoned when its workflow timed out — changes
90
+ * nothing: no status, output, retry, stat or event. The worker its executor
91
+ * kept busy is released, but only if it is still on that task.
92
+ */
93
+ private releaseWorkerAfterLateReport;
94
+ /**
95
+ * A failed workflow must stop dispatching: its queued tasks leave the queue and
96
+ * every unfinished task becomes cancelled. A task whose executor is still busy
97
+ * keeps its worker until that executor reports back (see
98
+ * `releaseWorkerAfterLateReport`); an effect it already started is not undone.
99
+ */
100
+ private abandonUnfinishedTasks;
101
+ private assertWorkflowRunning;
102
+ private createWorkflowTask;
103
+ private isDependencyComplete;
104
+ private pruneTerminalTasksForCapacity;
84
105
  /**
85
106
  * Process the task queue
86
107
  */
@@ -37,6 +37,8 @@ export class Orchestrator extends EventEmitter {
37
37
  failedTaskCount = 0;
38
38
  totalTaskDuration = 0;
39
39
  running = false;
40
+ workflowTaskSequence = 0;
41
+ workflowTaskIdentity = new WeakMap();
40
42
  constructor(config = {}) {
41
43
  super();
42
44
  this.config = { ...DEFAULT_CONFIG, ...config };
@@ -147,6 +149,7 @@ export class Orchestrator extends EventEmitter {
147
149
  * Create a new task
148
150
  */
149
151
  createTask(definition) {
152
+ this.pruneTerminalTasksForCapacity();
150
153
  if (this.tasks.size >= this.config.maxTasks) {
151
154
  throw new Error(`Maximum task limit (${this.config.maxTasks}) reached`);
152
155
  }
@@ -237,6 +240,10 @@ export class Orchestrator extends EventEmitter {
237
240
  const task = this.tasks.get(taskId);
238
241
  if (!task)
239
242
  throw new Error(`Task '${taskId}' not found`);
243
+ if (this.isTerminal(task)) {
244
+ this.releaseWorkerAfterLateReport(task);
245
+ return;
246
+ }
240
247
  task.status = 'completed';
241
248
  task.output = output;
242
249
  task.completedAt = new Date();
@@ -245,10 +252,10 @@ export class Orchestrator extends EventEmitter {
245
252
  if (task.startedAt) {
246
253
  this.totalTaskDuration += task.completedAt.getTime() - task.startedAt.getTime();
247
254
  }
248
- // Free up agent
255
+ // Free up agent — only if it is still on this task
249
256
  if (task.assignedAgent) {
250
257
  const agent = this.agents.get(task.assignedAgent);
251
- if (agent) {
258
+ if (agent && agent.currentTask === taskId) {
252
259
  agent.status = 'idle';
253
260
  agent.currentTask = undefined;
254
261
  agent.completedTasks++;
@@ -267,16 +274,20 @@ export class Orchestrator extends EventEmitter {
267
274
  const task = this.tasks.get(taskId);
268
275
  if (!task)
269
276
  throw new Error(`Task '${taskId}' not found`);
277
+ if (this.isTerminal(task)) {
278
+ this.releaseWorkerAfterLateReport(task);
279
+ return;
280
+ }
270
281
  // Check for retries
271
282
  if (task.retries < (task.definition.maxRetries || 0)) {
272
283
  task.retries++;
273
284
  task.status = 'queued';
274
285
  this.insertIntoQueue(task);
275
286
  this.log('warn', `Task ${taskId} failed, retrying (${task.retries}/${task.definition.maxRetries})`);
276
- // Free up agent
287
+ // Free up agent — only if it is still on this task
277
288
  if (task.assignedAgent) {
278
289
  const agent = this.agents.get(task.assignedAgent);
279
- if (agent) {
290
+ if (agent && agent.currentTask === taskId) {
280
291
  agent.status = 'idle';
281
292
  agent.currentTask = undefined;
282
293
  agent.lastActivity = new Date();
@@ -290,10 +301,10 @@ export class Orchestrator extends EventEmitter {
290
301
  task.error = error;
291
302
  task.completedAt = new Date();
292
303
  this.failedTaskCount++;
293
- // Free up agent
304
+ // Free up agent — only if it is still on this task
294
305
  if (task.assignedAgent) {
295
306
  const agent = this.agents.get(task.assignedAgent);
296
- if (agent) {
307
+ if (agent && agent.currentTask === taskId) {
297
308
  agent.status = 'idle';
298
309
  agent.currentTask = undefined;
299
310
  agent.failedTasks++;
@@ -318,6 +329,8 @@ export class Orchestrator extends EventEmitter {
318
329
  const task = this.tasks.get(taskId);
319
330
  if (!task)
320
331
  throw new Error(`Task '${taskId}' not found`);
332
+ if (this.isTerminal(task))
333
+ return;
321
334
  task.status = 'cancelled';
322
335
  task.completedAt = new Date();
323
336
  // Remove from queue
@@ -325,10 +338,10 @@ export class Orchestrator extends EventEmitter {
325
338
  if (queueIdx !== -1) {
326
339
  this.taskQueue.splice(queueIdx, 1);
327
340
  }
328
- // Free up agent
341
+ // Free up agent — only if it is still on this task
329
342
  if (task.assignedAgent) {
330
343
  const agent = this.agents.get(task.assignedAgent);
331
- if (agent) {
344
+ if (agent && agent.currentTask === taskId) {
332
345
  agent.status = 'idle';
333
346
  agent.currentTask = undefined;
334
347
  agent.lastActivity = new Date();
@@ -336,6 +349,95 @@ export class Orchestrator extends EventEmitter {
336
349
  }
337
350
  this.log('info', `Task cancelled: ${taskId}`);
338
351
  }
352
+ isTerminal(task) {
353
+ return task.status === 'completed' || task.status === 'failed' || task.status === 'cancelled';
354
+ }
355
+ /**
356
+ * A report (result, error, answered approval) for a task that is already
357
+ * terminal — typically abandoned when its workflow timed out — changes
358
+ * nothing: no status, output, retry, stat or event. The worker its executor
359
+ * kept busy is released, but only if it is still on that task.
360
+ */
361
+ releaseWorkerAfterLateReport(task) {
362
+ const taskId = task.definition.id;
363
+ const agent = task.assignedAgent ? this.agents.get(task.assignedAgent) : undefined;
364
+ this.log('debug', `Ignored late report for ${task.status} task ${taskId}`);
365
+ task.assignedAgent = undefined;
366
+ if (agent?.currentTask === taskId) {
367
+ agent.status = 'idle';
368
+ agent.currentTask = undefined;
369
+ agent.lastActivity = new Date();
370
+ this.processQueue();
371
+ }
372
+ }
373
+ /**
374
+ * A failed workflow must stop dispatching: its queued tasks leave the queue and
375
+ * every unfinished task becomes cancelled. A task whose executor is still busy
376
+ * keeps its worker until that executor reports back (see
377
+ * `releaseWorkerAfterLateReport`); an effect it already started is not undone.
378
+ */
379
+ abandonUnfinishedTasks(instance) {
380
+ for (const task of instance.tasks.values()) {
381
+ if (this.isTerminal(task))
382
+ continue;
383
+ task.status = 'cancelled';
384
+ task.completedAt = new Date();
385
+ const queueIdx = this.taskQueue.indexOf(task);
386
+ if (queueIdx !== -1)
387
+ this.taskQueue.splice(queueIdx, 1);
388
+ }
389
+ }
390
+ assertWorkflowRunning(instance) {
391
+ if (instance.status !== 'running') {
392
+ throw new Error(`Workflow '${instance.instanceId}' is no longer running`);
393
+ }
394
+ }
395
+ createWorkflowTask(instance, definition) {
396
+ this.assertWorkflowRunning(instance);
397
+ const logicalTaskId = definition.id;
398
+ const runtimeTaskId = `${instance.instanceId}:${logicalTaskId}:${++this.workflowTaskSequence}`;
399
+ const task = this.createTask({ ...definition, id: runtimeTaskId });
400
+ this.workflowTaskIdentity.set(task, { instanceId: instance.instanceId, logicalTaskId });
401
+ instance.tasks.set(runtimeTaskId, task);
402
+ return task;
403
+ }
404
+ isDependencyComplete(task, dependencyId) {
405
+ const identity = this.workflowTaskIdentity.get(task);
406
+ if (!identity)
407
+ return this.tasks.get(dependencyId)?.status === 'completed';
408
+ const workflow = this.workflows.get(identity.instanceId);
409
+ if (!workflow)
410
+ return false;
411
+ let latestDependency;
412
+ for (const candidate of workflow.tasks.values()) {
413
+ const candidateIdentity = this.workflowTaskIdentity.get(candidate);
414
+ if (candidateIdentity?.logicalTaskId === dependencyId)
415
+ latestDependency = candidate;
416
+ }
417
+ return latestDependency?.status === 'completed';
418
+ }
419
+ pruneTerminalTasksForCapacity() {
420
+ if (this.tasks.size < this.config.maxTasks)
421
+ return;
422
+ for (const [taskId, task] of this.tasks) {
423
+ if (!this.isTerminal(task))
424
+ continue;
425
+ const identity = this.workflowTaskIdentity.get(task);
426
+ if (identity && this.workflows.get(identity.instanceId)?.status === 'running')
427
+ continue;
428
+ const hasOutstandingExecutor = task.status === 'cancelled' && task.assignedAgent !== undefined;
429
+ if (hasOutstandingExecutor)
430
+ continue;
431
+ const isRequiredByActiveStandaloneTask = Array.from(this.tasks.values()).some((candidate) => !this.isTerminal(candidate) &&
432
+ !this.workflowTaskIdentity.has(candidate) &&
433
+ candidate.definition.dependsOn?.includes(taskId));
434
+ if (isRequiredByActiveStandaloneTask)
435
+ continue;
436
+ this.tasks.delete(taskId);
437
+ if (this.tasks.size < this.config.maxTasks)
438
+ return;
439
+ }
440
+ }
339
441
  // ============================================================================
340
442
  // Queue Processing
341
443
  // ============================================================================
@@ -348,10 +450,7 @@ export class Orchestrator extends EventEmitter {
348
450
  for (const task of this.taskQueue) {
349
451
  // Check dependencies
350
452
  if (task.definition.dependsOn && task.definition.dependsOn.length > 0) {
351
- const allDepsComplete = task.definition.dependsOn.every((depId) => {
352
- const dep = this.tasks.get(depId);
353
- return dep?.status === 'completed';
354
- });
453
+ const allDepsComplete = task.definition.dependsOn.every((depId) => this.isDependencyComplete(task, depId));
355
454
  if (!allDepsComplete)
356
455
  continue;
357
456
  }
@@ -412,6 +511,7 @@ export class Orchestrator extends EventEmitter {
412
511
  instance.status = 'failed';
413
512
  instance.error = errorMessage;
414
513
  instance.completedAt = new Date();
514
+ this.abandonUnfinishedTasks(instance);
415
515
  this.emit('workflow_failed', { type: 'workflow_failed', instanceId, error: errorMessage });
416
516
  this.log('error', `Workflow failed: ${instanceId} - ${errorMessage}`);
417
517
  }
@@ -425,6 +525,7 @@ export class Orchestrator extends EventEmitter {
425
525
  for (const step of instance.definition.steps) {
426
526
  instance.currentStep = step.id;
427
527
  await this.executeWorkflowStep(instance, step, context);
528
+ this.assertWorkflowRunning(instance);
428
529
  instance.completedSteps.push(step.id);
429
530
  this.emit('workflow_step_completed', {
430
531
  type: 'workflow_step_completed',
@@ -438,6 +539,7 @@ export class Orchestrator extends EventEmitter {
438
539
  * Execute a workflow step
439
540
  */
440
541
  async executeWorkflowStep(instance, step, context) {
542
+ this.assertWorkflowRunning(instance);
441
543
  switch (step.type) {
442
544
  case 'task':
443
545
  await this.executeTaskStep(instance, step, context);
@@ -463,20 +565,21 @@ export class Orchestrator extends EventEmitter {
463
565
  if (!step.tasks || step.tasks.length === 0)
464
566
  return;
465
567
  for (const taskDef of step.tasks) {
568
+ this.assertWorkflowRunning(instance);
466
569
  // Substitute variables in task input
467
570
  const resolvedInput = this.resolveVariables(taskDef.input, context);
468
571
  const resolvedDef = { ...taskDef, input: resolvedInput };
469
- const task = this.createTask(resolvedDef);
470
- instance.tasks.set(task.definition.id, task);
572
+ const task = this.createWorkflowTask(instance, resolvedDef);
471
573
  this.queueTask(task.definition.id);
472
574
  // Wait for completion
473
575
  await this.waitForTask(task.definition.id);
576
+ this.assertWorkflowRunning(instance);
474
577
  // Add output to context — both under the task's namespaced key and,
475
578
  // optionally, under a user-provided alias for readability in
476
579
  // downstream conditions / tool inputs.
477
580
  const completedTask = this.tasks.get(task.definition.id);
478
581
  if (completedTask?.output) {
479
- context[`task_${task.definition.id}`] = completedTask.output;
582
+ context[`task_${taskDef.id}`] = completedTask.output;
480
583
  if (typeof taskDef.aliasAs === 'string' && taskDef.aliasAs.length > 0) {
481
584
  context[taskDef.aliasAs] = completedTask.output;
482
585
  }
@@ -497,6 +600,7 @@ export class Orchestrator extends EventEmitter {
497
600
  return branchContext;
498
601
  });
499
602
  const results = await Promise.all(branchPromises);
603
+ this.assertWorkflowRunning(instance);
500
604
  // Merge branch results
501
605
  for (let i = 0; i < results.length; i++) {
502
606
  context[`branch_${i}`] = results[i];
@@ -525,6 +629,7 @@ export class Orchestrator extends EventEmitter {
525
629
  const maxIterations = Math.max(1, Math.min(configuredLimit, 100));
526
630
  while (this.evaluateCondition(step.loopCondition || 'false', context) &&
527
631
  iteration < maxIterations) {
632
+ this.assertWorkflowRunning(instance);
528
633
  context['iteration'] = iteration;
529
634
  this.emit('loop_iteration_started', {
530
635
  type: 'loop_iteration_started',
@@ -557,6 +662,7 @@ export class Orchestrator extends EventEmitter {
557
662
  return;
558
663
  }
559
664
  const tasks = items.map((item, index) => async () => {
665
+ this.assertWorkflowRunning(instance);
560
666
  // Create local context fork for this batch item execution
561
667
  const localContext = { ...context, [varName]: item, index };
562
668
  this.emit('loop_iteration_started', {
@@ -581,6 +687,7 @@ export class Orchestrator extends EventEmitter {
581
687
  }
582
688
  });
583
689
  await Promise.all(workers);
690
+ this.assertWorkflowRunning(instance);
584
691
  }
585
692
  evaluateExpression(expression, context) {
586
693
  let resolved = expression;
@@ -0,0 +1,8 @@
1
+ import type { SessionMessage } from './session-store.js';
2
+ export declare class SessionDecryptionError extends Error {
3
+ constructor(cause: unknown);
4
+ }
5
+ export declare function hasEncryptedSessionContent(messages: SessionMessage[]): boolean;
6
+ /** Both async and legacy synchronous store readers use the same authenticated decoder. */
7
+ export declare function decryptSessionContent(messages: SessionMessage[], keyPath?: string): SessionMessage[];
8
+ export declare function encryptSessionContent(messages: SessionMessage[], keyPath?: string): Promise<SessionMessage[]>;
@@ -0,0 +1,76 @@
1
+ import { SessionEncryption } from '../security/session-encryption.js';
2
+ export class SessionDecryptionError extends Error {
3
+ constructor(cause) {
4
+ super('Cannot decrypt session: restore the original encryption key before continuing.', { cause });
5
+ this.name = 'SessionDecryptionError';
6
+ }
7
+ }
8
+ function envelope(messages) {
9
+ if (messages.length !== 1 || messages[0]?.type !== 'assistant')
10
+ return null;
11
+ let value;
12
+ try {
13
+ value = JSON.parse(messages[0].content);
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ if (!value || typeof value !== 'object' || !('__encrypted' in value) || value.__encrypted !== true)
19
+ return null;
20
+ if (!('data' in value))
21
+ throw new SessionDecryptionError(new Error('Missing encrypted data'));
22
+ return value;
23
+ }
24
+ export function hasEncryptedSessionContent(messages) {
25
+ return envelope(messages) !== null;
26
+ }
27
+ /** Both async and legacy synchronous store readers use the same authenticated decoder. */
28
+ export function decryptSessionContent(messages, keyPath) {
29
+ const wrapped = envelope(messages);
30
+ if (!wrapped)
31
+ return messages;
32
+ const encryption = new SessionEncryption({ keyPath, requirePersistentKey: true });
33
+ try {
34
+ if (!encryption.isEncrypted(wrapped.data) || ![1, 2].includes(wrapped.data.version))
35
+ throw new Error('Invalid encrypted envelope');
36
+ encryption.initializeForRead();
37
+ const decoded = encryption.decryptObject(wrapped.data);
38
+ if (!Array.isArray(decoded))
39
+ throw new Error('Invalid encrypted history');
40
+ const types = new Set(['user', 'assistant', 'tool_call', 'tool_result', 'reasoning', 'plan_progress', 'steer', 'diff_preview']);
41
+ return decoded.map((entry) => {
42
+ if (!entry || typeof entry !== 'object')
43
+ throw new Error('Invalid encrypted message');
44
+ const message = entry;
45
+ if (!types.has(message.type) || typeof message.content !== 'string' ||
46
+ typeof message.timestamp !== 'string' || !Number.isFinite(Date.parse(message.timestamp))) {
47
+ throw new Error('Invalid encrypted message');
48
+ }
49
+ // Legacy envelopes contain ChatEntry JSON; its timestamp is already serialized.
50
+ return { ...message };
51
+ });
52
+ }
53
+ catch (error) {
54
+ throw new SessionDecryptionError(error);
55
+ }
56
+ finally {
57
+ encryption.dispose();
58
+ }
59
+ }
60
+ export async function encryptSessionContent(messages, keyPath) {
61
+ if (hasEncryptedSessionContent(messages))
62
+ return messages;
63
+ const encryption = new SessionEncryption({ keyPath, requirePersistentKey: true });
64
+ try {
65
+ await encryption.initialize();
66
+ const data = encryption.encryptObject(messages);
67
+ if (!encryption.isEncrypted(data))
68
+ throw new Error('Session encryption produced plaintext');
69
+ return [{ type: 'assistant', timestamp: new Date().toISOString(),
70
+ content: JSON.stringify({ __encrypted: true, format: 'session-messages-v1', data }) }];
71
+ }
72
+ finally {
73
+ encryption.dispose();
74
+ }
75
+ }
76
+ //# sourceMappingURL=session-content.js.map
@@ -0,0 +1,64 @@
1
+ /**
2
+ * GUI → terminal session handoff (comparatif plan P6).
3
+ *
4
+ * Converts a text conversation (e.g. a Cowork session) into the CLI session
5
+ * store format so `buddy --resume <id>` continues it in the terminal.
6
+ *
7
+ * Contract:
8
+ * - only user/assistant TEXT turns are exported (no tool payloads, attachments
9
+ * or system/context blocks);
10
+ * - every turn goes through the data-redaction engine; secrets become
11
+ * placeholders and are counted, never copied;
12
+ * - the file is written atomically with mode 0600 in the CLI sessions dir
13
+ * (CODEBUDDY_SESSIONS_DIR or ~/.codebuddy/sessions); the id is deterministic
14
+ * (`cowork-<source id>`) so a later export refreshes the same terminal session;
15
+ * - the returned command carries no credential.
16
+ */
17
+ export interface HandoffTurn {
18
+ role: string;
19
+ text: string;
20
+ timestamp?: number | string;
21
+ }
22
+ export interface HandoffInput {
23
+ source: 'cowork';
24
+ sourceId: string;
25
+ name: string;
26
+ workingDirectory?: string;
27
+ model?: string;
28
+ createdAt?: number | string;
29
+ turns: HandoffTurn[];
30
+ }
31
+ export interface HandoffResult {
32
+ id: string;
33
+ path: string;
34
+ command: string;
35
+ messageCount: number;
36
+ redactions: number;
37
+ }
38
+ export declare function handoffSessionId(source: HandoffInput['source'], sourceId: string): string;
39
+ export declare function buildHandoffSession(input: HandoffInput, now?: Date): {
40
+ session: {
41
+ id: string;
42
+ name: string;
43
+ workingDirectory: string;
44
+ model: string;
45
+ messages: {
46
+ type: "user" | "assistant";
47
+ content: string;
48
+ timestamp: string;
49
+ }[];
50
+ createdAt: string;
51
+ lastAccessedAt: string;
52
+ metadata: {
53
+ handoffSource: "cowork";
54
+ handoffSourceId: string;
55
+ handoffAt: string;
56
+ };
57
+ };
58
+ redactions: number;
59
+ };
60
+ export declare function cliSessionsDir(): string;
61
+ export declare function writeHandoffSession(input: HandoffInput, options?: {
62
+ dir?: string;
63
+ now?: Date;
64
+ }): Promise<HandoffResult>;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * GUI → terminal session handoff (comparatif plan P6).
3
+ *
4
+ * Converts a text conversation (e.g. a Cowork session) into the CLI session
5
+ * store format so `buddy --resume <id>` continues it in the terminal.
6
+ *
7
+ * Contract:
8
+ * - only user/assistant TEXT turns are exported (no tool payloads, attachments
9
+ * or system/context blocks);
10
+ * - every turn goes through the data-redaction engine; secrets become
11
+ * placeholders and are counted, never copied;
12
+ * - the file is written atomically with mode 0600 in the CLI sessions dir
13
+ * (CODEBUDDY_SESSIONS_DIR or ~/.codebuddy/sessions); the id is deterministic
14
+ * (`cowork-<source id>`) so a later export refreshes the same terminal session;
15
+ * - the returned command carries no credential.
16
+ */
17
+ import fs from 'node:fs';
18
+ import os from 'node:os';
19
+ import path from 'node:path';
20
+ import { getDataRedactionEngine } from '../security/data-redaction.js';
21
+ import { writeJsonAtomic } from '../utils/atomic-write.js';
22
+ const MAX_TURN_CHARS = 200_000;
23
+ function isoTime(value, fallback) {
24
+ const parsed = typeof value === 'number' ? new Date(value) : typeof value === 'string' ? new Date(value) : undefined;
25
+ return parsed && !Number.isNaN(parsed.getTime()) ? parsed.toISOString() : fallback.toISOString();
26
+ }
27
+ export function handoffSessionId(source, sourceId) {
28
+ const safe = sourceId.replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 80);
29
+ if (!safe)
30
+ throw new Error('HANDOFF_INVALID_SOURCE_ID');
31
+ return `${source}-${safe}`;
32
+ }
33
+ export function buildHandoffSession(input, now = new Date()) {
34
+ const engine = getDataRedactionEngine();
35
+ let redactions = 0;
36
+ const redact = (text) => {
37
+ const result = engine.redact(text);
38
+ redactions += result.redactions.length;
39
+ return result.redacted;
40
+ };
41
+ const messages = input.turns
42
+ .filter((turn) => (turn.role === 'user' || turn.role === 'assistant') && typeof turn.text === 'string' && turn.text.trim())
43
+ .map((turn) => ({
44
+ type: turn.role,
45
+ content: redact(turn.text.slice(0, MAX_TURN_CHARS)),
46
+ timestamp: isoTime(turn.timestamp, now),
47
+ }));
48
+ const createdAt = isoTime(input.createdAt, now);
49
+ const session = {
50
+ id: handoffSessionId(input.source, input.sourceId),
51
+ name: redact(input.name || input.sourceId).slice(0, 200),
52
+ workingDirectory: input.workingDirectory && path.isAbsolute(input.workingDirectory) ? input.workingDirectory : process.cwd(),
53
+ model: input.model || 'unknown',
54
+ messages,
55
+ createdAt,
56
+ lastAccessedAt: now.toISOString(),
57
+ metadata: { handoffSource: input.source, handoffSourceId: input.sourceId, handoffAt: now.toISOString() },
58
+ };
59
+ return { session, redactions };
60
+ }
61
+ export function cliSessionsDir() {
62
+ return path.resolve(process.env.CODEBUDDY_SESSIONS_DIR || path.join(os.homedir(), '.codebuddy', 'sessions'));
63
+ }
64
+ export async function writeHandoffSession(input, options = {}) {
65
+ const { session, redactions } = buildHandoffSession(input, options.now);
66
+ if (session.messages.length === 0)
67
+ throw new Error('HANDOFF_EMPTY_CONVERSATION');
68
+ const dir = options.dir ?? cliSessionsDir();
69
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
70
+ const filePath = path.join(dir, `${session.id}.json`);
71
+ await writeJsonAtomic(filePath, session, { mode: 0o600 });
72
+ return {
73
+ id: session.id,
74
+ path: filePath,
75
+ command: `buddy --resume ${session.id}`,
76
+ messageCount: session.messages.length,
77
+ redactions,
78
+ };
79
+ }
80
+ //# sourceMappingURL=session-handoff.js.map
@@ -0,0 +1,4 @@
1
+ import type { ChatEntry } from '../agent/types.js';
2
+ import type { CodeBuddyMessage } from '../codebuddy/client.js';
3
+ /** Restore the provider transcript, not just the visible chat bubbles. */
4
+ export declare function restoreSessionHistory(entries: ChatEntry[]): CodeBuddyMessage[];
@@ -0,0 +1,38 @@
1
+ import { repairToolCallPairs } from '../context/transcript-repair.js';
2
+ import { sanitizeModelOutput } from '../utils/output-sanitizer.js';
3
+ /** Restore the provider transcript, not just the visible chat bubbles. */
4
+ export function restoreSessionHistory(entries) {
5
+ const messages = [];
6
+ const knownCalls = new Set();
7
+ for (const entry of entries) {
8
+ const calls = (entry.toolCalls ?? (entry.toolCall ? [entry.toolCall] : []))
9
+ .filter(call => Boolean(call.id && call.function?.name));
10
+ if (entry.type === 'user' || entry.type === 'steer') {
11
+ messages.push({ role: 'user', content: entry.content });
12
+ }
13
+ else if (entry.type === 'assistant' || entry.type === 'tool_call') {
14
+ const newCalls = calls.filter(call => !knownCalls.has(call.id));
15
+ for (const call of newCalls)
16
+ knownCalls.add(call.id);
17
+ if (entry.content || newCalls.length) {
18
+ messages.push({ role: 'assistant', content: sanitizeModelOutput(entry.content),
19
+ ...(newCalls.length ? { tool_calls: newCalls } : {}) });
20
+ }
21
+ }
22
+ else if (entry.type === 'tool_result') {
23
+ const call = calls[0];
24
+ const result = entry.toolResult;
25
+ const content = result ? JSON.stringify({ ...result, ...(result.output === undefined && entry.content ? { output: entry.content } : {}) }) : entry.content;
26
+ if (call && knownCalls.has(call.id)) {
27
+ messages.push({ role: 'tool', tool_call_id: call.id, content });
28
+ }
29
+ else {
30
+ // Old sessions have no stable pairing. Keep the evidence as historical
31
+ // data without inventing a tool invocation or dropping a failed result.
32
+ messages.push({ role: 'assistant', content: `[Historical tool result (untrusted data)${call ? `: ${call.function.name}` : ''}]\n${content}` });
33
+ }
34
+ }
35
+ }
36
+ return repairToolCallPairs(messages);
37
+ }
38
+ //# sourceMappingURL=session-history.js.map
@@ -35,6 +35,8 @@ export interface SessionUsageSnapshot {
35
35
  turns?: SessionTurnUsage[];
36
36
  }
37
37
  export interface Session {
38
+ /** Sticky protection: metadata changes and forks must remain encrypted. */
39
+ encrypted?: boolean;
38
40
  id: string;
39
41
  name: string;
40
42
  workingDirectory: string;
@@ -55,12 +57,17 @@ export interface SessionMessage {
55
57
  timestamp: string;
56
58
  toolCallName?: string;
57
59
  toolCallSuccess?: boolean;
60
+ toolCall?: ChatEntry['toolCall'];
61
+ toolCalls?: ChatEntry['toolCalls'];
62
+ toolResult?: ChatEntry['toolResult'];
63
+ truncated?: boolean;
58
64
  /** Task state for cross-session continuity */
59
65
  taskState?: Record<string, unknown>;
60
66
  }
61
67
  export interface SessionStoreConfig {
62
68
  /** Use SQLite database instead of JSON files */
63
69
  useSQLite: boolean;
70
+ encryptionKeyPath?: string;
64
71
  }
65
72
  /**
66
73
  * Session Store for persisting and restoring chat sessions
@@ -102,6 +109,8 @@ export declare class SessionStore {
102
109
  * unprotected. Callers MUST hold the session lock before invoking this.
103
110
  */
104
111
  private writeSessionUnlocked;
112
+ private shouldEncrypt;
113
+ private decodeContent;
105
114
  /**
106
115
  * Load a session from disk.
107
116
  *