@phuetz/code-buddy 2.0.0 → 2.2.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 (421) hide show
  1. package/README.fr.md +56 -0
  2. package/README.md +74 -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 +176 -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.d.ts +7 -0
  13. package/dist/agent/execution/tool-selection-strategy.js +32 -2
  14. package/dist/agent/facades/session-facade.d.ts +0 -6
  15. package/dist/agent/facades/session-facade.js +0 -45
  16. package/dist/agent/hermes-claw-migrate.d.ts +0 -21
  17. package/dist/agent/hermes-claw-migrate.js +9 -4
  18. package/dist/agent/hermes-protocol-gateways.js +8 -1
  19. package/dist/agent/repo-profiler.js +17 -4
  20. package/dist/agent/self-improvement/authored-tool-runtime.js +10 -2
  21. package/dist/agent/self-improvement/evolution/ast-novelty.d.ts +5 -0
  22. package/dist/agent/self-improvement/evolution/ast-novelty.js +58 -23
  23. package/dist/agent/self-improvement/evolution/code-variant-store.d.ts +3 -0
  24. package/dist/agent/self-improvement/evolution/evolution-engine.js +2 -0
  25. package/dist/agent/self-improvement/evolution/protected-paths.js +6 -0
  26. package/dist/agent/self-improvement/evolution/variant-fitness.d.ts +13 -2
  27. package/dist/agent/self-improvement/evolution/variant-fitness.js +184 -58
  28. package/dist/agent/self-improvement/evolution/worktree-scorer.d.ts +2 -0
  29. package/dist/agent/self-improvement/evolution/worktree-scorer.js +15 -0
  30. package/dist/agent/self-improvement/evolutionary-archive.d.ts +2 -1
  31. package/dist/agent/self-improvement/evolutionary-archive.js +34 -5
  32. package/dist/agent/self-improvement/proposal-store.d.ts +1 -0
  33. package/dist/agent/self-improvement/proposal-store.js +79 -17
  34. package/dist/agent/self-improvement/skill-apply-journal.d.ts +44 -0
  35. package/dist/agent/self-improvement/skill-apply-journal.js +178 -0
  36. package/dist/agent/self-improvement/skill-behavior-benchmark.d.ts +3 -0
  37. package/dist/agent/self-improvement/skill-behavior-benchmark.js +19 -0
  38. package/dist/agent/self-improvement/skill-behavior-gate.d.ts +34 -0
  39. package/dist/agent/self-improvement/skill-behavior-gate.js +96 -0
  40. package/dist/agent/self-improvement/skill-engine.d.ts +22 -0
  41. package/dist/agent/self-improvement/skill-engine.js +385 -13
  42. package/dist/agent/self-improvement/skill-gate.d.ts +2 -0
  43. package/dist/agent/self-improvement/skill-gate.js +17 -2
  44. package/dist/agent/self-improvement/skill-mutator.d.ts +6 -1
  45. package/dist/agent/self-improvement/skill-mutator.js +51 -15
  46. package/dist/agent/self-improvement/skill-types.d.ts +2 -1
  47. package/dist/agent/self-improvement/types.d.ts +11 -0
  48. package/dist/agent/tool-handler.d.ts +1 -1
  49. package/dist/agent/tool-handler.js +28 -9
  50. package/dist/auth/profile-manager.js +1 -1
  51. package/dist/channels/companion-channel-turn.d.ts +53 -4
  52. package/dist/channels/companion-channel-turn.js +218 -16
  53. package/dist/channels/slash-parity.d.ts +4 -6
  54. package/dist/channels/slash-parity.js +9 -44
  55. package/dist/cli/session-commands.d.ts +9 -0
  56. package/dist/cli/session-commands.js +45 -5
  57. package/dist/cli/session-picker.d.ts +43 -0
  58. package/dist/cli/session-picker.js +128 -0
  59. package/dist/codebuddy/a2a-call-tool-defs.d.ts +26 -0
  60. package/dist/codebuddy/a2a-call-tool-defs.js +12 -0
  61. package/dist/codebuddy/fleet-tool-defs.d.ts +2 -0
  62. package/dist/codebuddy/fleet-tool-defs.js +55 -0
  63. package/dist/codebuddy/providers/chatgpt-headers.d.ts +18 -0
  64. package/dist/codebuddy/providers/chatgpt-headers.js +37 -0
  65. package/dist/codebuddy/providers/provider-chatgpt-responses.d.ts +2 -1
  66. package/dist/codebuddy/providers/provider-chatgpt-responses.js +10 -23
  67. package/dist/codebuddy/providers/provider-openai-compat.d.ts +7 -0
  68. package/dist/codebuddy/providers/provider-openai-compat.js +29 -1
  69. package/dist/codebuddy/ragchat-tool-defs.d.ts +31 -0
  70. package/dist/codebuddy/ragchat-tool-defs.js +19 -0
  71. package/dist/codebuddy/resource-catalog-tool-defs.d.ts +28 -0
  72. package/dist/codebuddy/resource-catalog-tool-defs.js +18 -0
  73. package/dist/codebuddy/tool-definitions/agent-tools.js +2 -2
  74. package/dist/codebuddy/tool-definitions/code-exec-tools.js +5 -1
  75. package/dist/codebuddy/tool-definitions/cron-tools.js +6 -0
  76. package/dist/codebuddy/tool-definitions/self-describe-tools.js +6 -1
  77. package/dist/codebuddy/tools.d.ts +0 -6
  78. package/dist/codebuddy/tools.js +25 -3
  79. package/dist/commands/cli/daemon-commands.js +3 -1
  80. package/dist/commands/cli/evolve-command.js +21 -5
  81. package/dist/commands/cli/fleet-collaboration-commands.d.ts +2 -0
  82. package/dist/commands/cli/fleet-collaboration-commands.js +57 -0
  83. package/dist/commands/cli/fleet-commands.js +29 -0
  84. package/dist/commands/cli/fleet-mission-commands.d.ts +3 -0
  85. package/dist/commands/cli/fleet-mission-commands.js +72 -0
  86. package/dist/commands/cli/fleet-rooms-commands.d.ts +10 -0
  87. package/dist/commands/cli/fleet-rooms-commands.js +289 -0
  88. package/dist/commands/cli/tools-commands.js +2 -1
  89. package/dist/commands/cli/triage-command.d.ts +8 -0
  90. package/dist/commands/cli/triage-command.js +72 -0
  91. package/dist/commands/cli/utility-commands.js +25 -1
  92. package/dist/commands/client-dispatcher.d.ts +7 -0
  93. package/dist/commands/client-dispatcher.js +73 -21
  94. package/dist/commands/cron-cli/index.d.ts +3 -1
  95. package/dist/commands/cron-cli/index.js +25 -0
  96. package/dist/commands/device-auth.d.ts +10 -0
  97. package/dist/commands/device-auth.js +91 -0
  98. package/dist/commands/enhanced-command-handler.d.ts +9 -0
  99. package/dist/commands/enhanced-command-handler.js +28 -6
  100. package/dist/commands/handlers/agent-handlers.d.ts +1 -1
  101. package/dist/commands/handlers/agent-handlers.js +4 -4
  102. package/dist/commands/handlers/channel-handlers.js +53 -2
  103. package/dist/commands/handlers/export-handlers.d.ts +3 -2
  104. package/dist/commands/handlers/export-handlers.js +55 -28
  105. package/dist/commands/handlers/extra-handlers.js +5 -1
  106. package/dist/commands/handlers/grill-me-handler.js +26 -18
  107. package/dist/commands/handlers/memory-handlers.d.ts +6 -2
  108. package/dist/commands/handlers/memory-handlers.js +35 -15
  109. package/dist/commands/handlers/missing-handlers.d.ts +2 -2
  110. package/dist/commands/handlers/missing-handlers.js +12 -4
  111. package/dist/commands/handlers/resources-handler.d.ts +12 -0
  112. package/dist/commands/handlers/resources-handler.js +52 -0
  113. package/dist/commands/handlers/starter-handlers.d.ts +1 -1
  114. package/dist/commands/handlers/starter-handlers.js +32 -2
  115. package/dist/commands/handlers/switch-handler.d.ts +1 -1
  116. package/dist/commands/handlers/switch-handler.js +15 -0
  117. package/dist/commands/handlers/test-handlers.js +4 -86
  118. package/dist/commands/handlers/ui-handlers.js +11 -5
  119. package/dist/commands/login-chatgpt.d.ts +8 -0
  120. package/dist/commands/login-chatgpt.js +17 -0
  121. package/dist/commands/mcp-import.d.ts +11 -0
  122. package/dist/commands/mcp-import.js +67 -0
  123. package/dist/commands/mcp-invoke.d.ts +2 -0
  124. package/dist/commands/mcp-invoke.js +68 -0
  125. package/dist/commands/mcp.d.ts +13 -0
  126. package/dist/commands/mcp.js +66 -21
  127. package/dist/commands/pipeline.d.ts +7 -1
  128. package/dist/commands/pipeline.js +104 -7
  129. package/dist/commands/resources.d.ts +2 -0
  130. package/dist/commands/resources.js +64 -0
  131. package/dist/commands/skills-cli/index.js +35 -12
  132. package/dist/commands/slash/builtin-commands.js +16 -3
  133. package/dist/commands/slash/index.d.ts +3 -1
  134. package/dist/commands/slash/index.js +2 -0
  135. package/dist/commands/slash/surfaces.d.ts +53 -0
  136. package/dist/commands/slash/surfaces.js +177 -0
  137. package/dist/commands/slash/types.d.ts +17 -0
  138. package/dist/commands/try.d.ts +1 -1
  139. package/dist/commands/try.js +1 -1
  140. package/dist/companion/companion-identity.d.ts +43 -0
  141. package/dist/companion/companion-identity.js +136 -0
  142. package/dist/companion/companion-toolset.d.ts +66 -0
  143. package/dist/companion/companion-toolset.js +368 -0
  144. package/dist/companion/companion-turn.d.ts +10 -2
  145. package/dist/companion/companion-turn.js +38 -1
  146. package/dist/config/code-exec-policy.d.ts +25 -0
  147. package/dist/config/code-exec-policy.js +39 -0
  148. package/dist/config/model-tools.d.ts +9 -0
  149. package/dist/config/model-tools.js +103 -0
  150. package/dist/context/context-manager-v2.js +2 -2
  151. package/dist/context/lm-resizer-compressor.js +3 -2
  152. package/dist/context/lm-resizer-diagnostics.d.ts +9 -0
  153. package/dist/context/lm-resizer-diagnostics.js +30 -0
  154. package/dist/context/tool-output-masking.js +3 -4
  155. package/dist/curator/curator.d.ts +6 -0
  156. package/dist/curator/curator.js +53 -3
  157. package/dist/daemon/cron-agent-bridge.d.ts +6 -0
  158. package/dist/daemon/cron-agent-bridge.js +31 -4
  159. package/dist/doctor/index.d.ts +11 -2
  160. package/dist/doctor/index.js +84 -21
  161. package/dist/doctor/integrations.d.ts +197 -0
  162. package/dist/doctor/integrations.js +241 -0
  163. package/dist/doctor/triage.d.ts +108 -0
  164. package/dist/doctor/triage.js +304 -0
  165. package/dist/errors/crash-handler.js +2 -0
  166. package/dist/fleet/collaboration.d.ts +44 -0
  167. package/dist/fleet/collaboration.js +170 -0
  168. package/dist/fleet/fleet-listener.d.ts +1 -0
  169. package/dist/fleet/fleet-listener.js +87 -46
  170. package/dist/fleet/fleet-registry.d.ts +2 -0
  171. package/dist/fleet/resource-catalog.d.ts +245 -0
  172. package/dist/fleet/resource-catalog.js +211 -0
  173. package/dist/fleet/rooms/room-access.d.ts +138 -0
  174. package/dist/fleet/rooms/room-access.js +196 -0
  175. package/dist/fleet/rooms/room-client.d.ts +158 -0
  176. package/dist/fleet/rooms/room-client.js +685 -0
  177. package/dist/fleet/rooms/room-event.d.ts +139 -0
  178. package/dist/fleet/rooms/room-event.js +330 -0
  179. package/dist/fleet/rooms/room-filter.d.ts +48 -0
  180. package/dist/fleet/rooms/room-filter.js +152 -0
  181. package/dist/fleet/rooms/room-hub.d.ts +154 -0
  182. package/dist/fleet/rooms/room-hub.js +363 -0
  183. package/dist/fleet/rooms/room-identity.d.ts +26 -0
  184. package/dist/fleet/rooms/room-identity.js +82 -0
  185. package/dist/fleet/rooms/room-lock.d.ts +34 -0
  186. package/dist/fleet/rooms/room-lock.js +180 -0
  187. package/dist/fleet/rooms/room-observations.d.ts +7 -0
  188. package/dist/fleet/rooms/room-observations.js +61 -0
  189. package/dist/fleet/rooms/room-server.d.ts +28 -0
  190. package/dist/fleet/rooms/room-server.js +75 -0
  191. package/dist/fleet/rooms/room-store.d.ts +119 -0
  192. package/dist/fleet/rooms/room-store.js +448 -0
  193. package/dist/fleet/rooms/room-ws-bridge.d.ts +27 -0
  194. package/dist/fleet/rooms/room-ws-bridge.js +137 -0
  195. package/dist/fleet/sensory-mission-bridge.d.ts +42 -0
  196. package/dist/fleet/sensory-mission-bridge.js +136 -0
  197. package/dist/git/worktree-sessions.js +1 -0
  198. package/dist/harness/contract.d.ts +4 -4
  199. package/dist/harness/fleet-supervisor.d.ts +16 -0
  200. package/dist/harness/fleet-supervisor.js +64 -0
  201. package/dist/harness/index.d.ts +4 -0
  202. package/dist/harness/index.js +4 -0
  203. package/dist/harness/mission-runner.d.ts +3 -0
  204. package/dist/harness/mission-runner.js +53 -0
  205. package/dist/harness/mission-store.d.ts +93 -0
  206. package/dist/harness/mission-store.js +297 -0
  207. package/dist/harness/tool-harness.d.ts +56 -0
  208. package/dist/harness/tool-harness.js +153 -0
  209. package/dist/hooks/use-enhanced-input.js +48 -4
  210. package/dist/hooks/use-input-handler.d.ts +1 -0
  211. package/dist/hooks/use-input-handler.js +82 -22
  212. package/dist/hooks/use-input-history.js +1 -0
  213. package/dist/identity/lisa-introspection.js +5 -2
  214. package/dist/identity/operational-self-model.d.ts +2 -0
  215. package/dist/identity/operational-self-model.js +4 -0
  216. package/dist/index.js +52 -6
  217. package/dist/mcp/client.d.ts +11 -0
  218. package/dist/mcp/client.js +68 -3
  219. package/dist/mcp/config.d.ts +20 -0
  220. package/dist/mcp/config.js +121 -8
  221. package/dist/mcp/import-normalize.d.ts +12 -0
  222. package/dist/mcp/import-normalize.js +114 -0
  223. package/dist/mcp/mcp-oauth-constants.d.ts +8 -0
  224. package/dist/mcp/mcp-oauth-constants.js +9 -0
  225. package/dist/mcp/mcp-oauth-provider.d.ts +80 -0
  226. package/dist/mcp/mcp-oauth-provider.js +241 -0
  227. package/dist/mcp/mcp-oauth.d.ts +60 -1
  228. package/dist/mcp/mcp-oauth.js +241 -65
  229. package/dist/mcp/transports.d.ts +14 -5
  230. package/dist/mcp/transports.js +112 -101
  231. package/dist/mcp/types.d.ts +5 -0
  232. package/dist/media/comfyui-recipe-contract.d.ts +7 -7
  233. package/dist/memory/facts-memory.d.ts +11 -1
  234. package/dist/memory/facts-memory.js +47 -8
  235. package/dist/memory/memory-candidate-queue.d.ts +2 -2
  236. package/dist/memory/memory-candidate-queue.js +6 -6
  237. package/dist/memory/persistent-memory.d.ts +13 -8
  238. package/dist/memory/persistent-memory.js +77 -30
  239. package/dist/nodes/device-node.js +5 -9
  240. package/dist/observability/mobile-supervision-gateway-contract.js +20 -7
  241. package/dist/observability/run-event-writer.d.ts +21 -0
  242. package/dist/observability/run-event-writer.js +81 -0
  243. package/dist/observability/run-store.d.ts +5 -0
  244. package/dist/observability/run-store.js +18 -13
  245. package/dist/observability/run-trajectory.js +1 -1
  246. package/dist/optimization/prompt-cache.d.ts +19 -2
  247. package/dist/optimization/prompt-cache.js +41 -6
  248. package/dist/orchestration/orchestrator.d.ts +21 -0
  249. package/dist/orchestration/orchestrator.js +122 -15
  250. package/dist/persistence/session-content.d.ts +8 -0
  251. package/dist/persistence/session-content.js +76 -0
  252. package/dist/persistence/session-handoff.d.ts +64 -0
  253. package/dist/persistence/session-handoff.js +80 -0
  254. package/dist/persistence/session-history.d.ts +4 -0
  255. package/dist/persistence/session-history.js +38 -0
  256. package/dist/persistence/session-store.d.ts +9 -0
  257. package/dist/persistence/session-store.js +48 -20
  258. package/dist/personas/persona-manager.js +17 -1
  259. package/dist/plugins/code-explorer/CodeExplorerManager.d.ts +10 -4
  260. package/dist/plugins/code-explorer/CodeExplorerManager.js +69 -16
  261. package/dist/plugins/plugin-manager.js +10 -1
  262. package/dist/prompts/prompt-manager.js +11 -4
  263. package/dist/prompts/system-base.js +6 -2
  264. package/dist/protocols/a2a/codebuddy-executor.d.ts +11 -2
  265. package/dist/protocols/a2a/codebuddy-executor.js +9 -8
  266. package/dist/protocols/a2a/index.d.ts +1 -1
  267. package/dist/protocols/a2a/jsonrpc-v1.d.ts +41 -0
  268. package/dist/protocols/a2a/jsonrpc-v1.js +145 -0
  269. package/dist/protocols/a2a/peer-config.d.ts +20 -0
  270. package/dist/protocols/a2a/peer-config.js +55 -0
  271. package/dist/providers/codex-oauth.js +30 -6
  272. package/dist/providers/model-provider-compat.js +2 -1
  273. package/dist/sandbox/os-sandbox.d.ts +1 -1
  274. package/dist/scheduler/cron-scheduler.d.ts +15 -2
  275. package/dist/scheduler/cron-scheduler.js +35 -4
  276. package/dist/scheduler/job-notepad.d.ts +69 -0
  277. package/dist/scheduler/job-notepad.js +339 -0
  278. package/dist/security/audit-logger.d.ts +1 -1
  279. package/dist/security/compute-confinement.d.ts +4 -0
  280. package/dist/security/compute-confinement.js +89 -0
  281. package/dist/security/session-encryption.d.ts +4 -0
  282. package/dist/security/session-encryption.js +31 -0
  283. package/dist/sensory/voice-loop.d.ts +17 -0
  284. package/dist/sensory/voice-loop.js +72 -15
  285. package/dist/server/auth/device-session-context.d.ts +9 -0
  286. package/dist/server/auth/device-session-context.js +13 -0
  287. package/dist/server/auth/device-store.d.ts +89 -0
  288. package/dist/server/auth/device-store.js +261 -0
  289. package/dist/server/auth/device-token.d.ts +3 -0
  290. package/dist/server/auth/device-token.js +6 -0
  291. package/dist/server/auth/jwt.js +4 -0
  292. package/dist/server/index.d.ts +0 -10
  293. package/dist/server/index.js +28 -0
  294. package/dist/server/middleware/auth.js +3 -2
  295. package/dist/server/routes/a2a-jsonrpc.d.ts +9 -0
  296. package/dist/server/routes/a2a-jsonrpc.js +66 -0
  297. package/dist/server/routes/a2a-protocol.d.ts +12 -0
  298. package/dist/server/routes/a2a-protocol.js +1 -1
  299. package/dist/server/routes/device-auth.d.ts +4 -0
  300. package/dist/server/routes/device-auth.js +40 -0
  301. package/dist/server/types.d.ts +4 -0
  302. package/dist/server/websocket/handler.d.ts +13 -0
  303. package/dist/server/websocket/handler.js +102 -17
  304. package/dist/services/prompt-builder.d.ts +2 -0
  305. package/dist/services/prompt-builder.js +16 -6
  306. package/dist/services/runtime-settings-context.d.ts +81 -0
  307. package/dist/services/runtime-settings-context.js +79 -0
  308. package/dist/skills/executor.js +2 -0
  309. package/dist/skills/hub.js +16 -14
  310. package/dist/skills/local-inventory.d.ts +21 -0
  311. package/dist/skills/local-inventory.js +72 -0
  312. package/dist/skills/skill-loader.js +2 -2
  313. package/dist/skills/skill-usage-store.d.ts +49 -0
  314. package/dist/skills/skill-usage-store.js +121 -0
  315. package/dist/testing/ai-integration-tests.js +8 -5
  316. package/dist/themes/theme-manager.d.ts +12 -0
  317. package/dist/themes/theme-manager.js +29 -0
  318. package/dist/themes/theme-schema.d.ts +24 -24
  319. package/dist/tools/a2a-call-tool.d.ts +34 -0
  320. package/dist/tools/a2a-call-tool.js +75 -0
  321. package/dist/tools/bash/streaming-executor.d.ts +1 -0
  322. package/dist/tools/bash/streaming-executor.js +78 -90
  323. package/dist/tools/code-exec-preflight-runner.d.ts +41 -0
  324. package/dist/tools/code-exec-preflight-runner.js +254 -0
  325. package/dist/tools/code-exec-preflight.d.ts +44 -0
  326. package/dist/tools/code-exec-preflight.js +501 -0
  327. package/dist/tools/code-exec-tool.d.ts +48 -1
  328. package/dist/tools/code-exec-tool.js +191 -47
  329. package/dist/tools/comment-watcher.js +29 -12
  330. package/dist/tools/core-code-inspection.d.ts +56 -0
  331. package/dist/tools/core-code-inspection.js +86 -0
  332. package/dist/tools/create-skill-tool.d.ts +2 -3
  333. package/dist/tools/create-skill-tool.js +16 -75
  334. package/dist/tools/cronjob-tool.d.ts +1 -0
  335. package/dist/tools/cronjob-tool.js +1 -0
  336. package/dist/tools/env-doctor-tool.d.ts +2 -0
  337. package/dist/tools/env-doctor-tool.js +4 -3
  338. package/dist/tools/execute-code-rpc-invoker.d.ts +1 -1
  339. package/dist/tools/execute-code-rpc-invoker.js +35 -19
  340. package/dist/tools/execute-code-runner.d.ts +2 -0
  341. package/dist/tools/execute-code-runner.js +52 -17
  342. package/dist/tools/fleet-room-tool.d.ts +104 -0
  343. package/dist/tools/fleet-room-tool.js +124 -0
  344. package/dist/tools/integration-tool-hints.d.ts +4 -0
  345. package/dist/tools/integration-tool-hints.js +19 -0
  346. package/dist/tools/list-peers-tool.d.ts +3 -0
  347. package/dist/tools/list-peers-tool.js +11 -4
  348. package/dist/tools/media-generation-tool.d.ts +16 -1
  349. package/dist/tools/media-generation-tool.js +341 -9
  350. package/dist/tools/merge-conflict-tool.js +53 -25
  351. package/dist/tools/metadata.d.ts +1 -5
  352. package/dist/tools/metadata.js +30 -28
  353. package/dist/tools/peer-tool-invoke-tool.d.ts +61 -0
  354. package/dist/tools/peer-tool-invoke-tool.js +396 -0
  355. package/dist/tools/ragchat-tool.d.ts +39 -0
  356. package/dist/tools/ragchat-tool.js +105 -0
  357. package/dist/tools/registry/fleet-tools.d.ts +21 -3
  358. package/dist/tools/registry/fleet-tools.js +137 -4
  359. package/dist/tools/registry/index.d.ts +1 -1
  360. package/dist/tools/registry/index.js +2 -2
  361. package/dist/tools/registry/knowledge-tools.d.ts +2 -2
  362. package/dist/tools/registry/knowledge-tools.js +2 -2
  363. package/dist/tools/registry/lessons-tools.d.ts +7 -0
  364. package/dist/tools/registry/lessons-tools.js +43 -3
  365. package/dist/tools/registry/memory-tools.d.ts +1 -1
  366. package/dist/tools/registry/memory-tools.js +8 -8
  367. package/dist/tools/registry/research-tools.d.ts +1 -0
  368. package/dist/tools/registry/research-tools.js +4 -13
  369. package/dist/tools/registry/self-describe-tools.d.ts +1 -1
  370. package/dist/tools/registry/self-describe-tools.js +43 -30
  371. package/dist/tools/resource-catalog-tool.d.ts +38 -0
  372. package/dist/tools/resource-catalog-tool.js +46 -0
  373. package/dist/tools/skills-inspection-tool.js +30 -1
  374. package/dist/tools/stream-tool-output.d.ts +3 -0
  375. package/dist/tools/stream-tool-output.js +33 -0
  376. package/dist/tools/tool-call-scheduler.d.ts +10 -0
  377. package/dist/tools/tool-call-scheduler.js +58 -0
  378. package/dist/tools/tool-effect.d.ts +5 -0
  379. package/dist/tools/tool-effect.js +26 -0
  380. package/dist/tools/tool-search.d.ts +12 -11
  381. package/dist/tools/tool-search.js +67 -26
  382. package/dist/tools/types.d.ts +6 -0
  383. package/dist/ui/components/ChatHistory.d.ts +1 -1
  384. package/dist/ui/components/ChatHistory.js +2 -2
  385. package/dist/ui/components/ChatInput.d.ts +2 -1
  386. package/dist/ui/components/ChatInput.js +42 -88
  387. package/dist/ui/components/ChatInterface.d.ts +4 -2
  388. package/dist/ui/components/ChatInterface.js +27 -63
  389. package/dist/ui/components/CommandSuggestions.d.ts +0 -1
  390. package/dist/ui/components/CommandSuggestions.js +4 -6
  391. package/dist/ui/components/FileAutocomplete.js +1 -1
  392. package/dist/ui/components/KeyboardHelp.js +8 -6
  393. package/dist/ui/components/ModelSelection.js +6 -5
  394. package/dist/ui/components/StatusBar.d.ts +2 -1
  395. package/dist/ui/components/StatusBar.js +8 -7
  396. package/dist/ui/context/theme-context.js +3 -2
  397. package/dist/ui/index.d.ts +1 -1
  398. package/dist/ui/index.js +1 -1
  399. package/dist/ui/utils/markdown-renderer.js +4 -13
  400. package/dist/utils/atomic-write.d.ts +22 -0
  401. package/dist/utils/atomic-write.js +29 -0
  402. package/dist/utils/bounded-output.d.ts +16 -0
  403. package/dist/utils/bounded-output.js +63 -0
  404. package/dist/utils/config-validation/schema.d.ts +2 -2
  405. package/dist/utils/device-store-file.d.ts +4 -0
  406. package/dist/utils/device-store-file.js +40 -0
  407. package/dist/utils/export-manager.d.ts +9 -0
  408. package/dist/utils/export-manager.js +19 -0
  409. package/dist/utils/first-use-hints.d.ts +25 -0
  410. package/dist/utils/first-use-hints.js +58 -0
  411. package/dist/utils/graceful-shutdown.d.ts +8 -0
  412. package/dist/utils/graceful-shutdown.js +23 -7
  413. package/dist/utils/history-manager.js +1 -1
  414. package/dist/utils/update-notifier.js +16 -18
  415. package/dist/wizard/environment-detection.d.ts +5 -1
  416. package/dist/wizard/environment-detection.js +49 -7
  417. package/dist/wizard/onboarding.d.ts +5 -1
  418. package/dist/wizard/onboarding.js +90 -30
  419. package/dist/wizard/provider-onboarding.d.ts +2 -0
  420. package/dist/wizard/provider-onboarding.js +55 -17
  421. package/package.json +7 -3
@@ -7,6 +7,7 @@ import { ConfirmationService } from "../utils/confirmation-service.js";
7
7
  import { useEnhancedInput } from "./use-enhanced-input.js";
8
8
  import { getErrorMessage } from "../types/index.js";
9
9
  import { filterCommandSuggestions } from "../ui/components/CommandSuggestions.js";
10
+ import { isModelCompatibleWithProvider } from "../providers/model-provider-compat.js";
10
11
  import { loadModelConfig } from "../utils/model-config.js";
11
12
  // Import enhanced features
12
13
  import { getSlashCommandManager } from "../commands/slash-commands.js";
@@ -18,11 +19,13 @@ import { extractFileReference, getFileSuggestions } from "../ui/components/FileA
18
19
  import { getInteractionLogger } from "../logging/interaction-logger.js";
19
20
  import { maybeContinueGoalAfterTurn } from "../goals/goal-loop.js";
20
21
  import { logger } from '../utils/logger.js';
22
+ import { takeFirstUseHint } from "../utils/first-use-hints.js";
21
23
  // Import history manager for persistent command history
22
24
  import { getHistoryManager } from "../utils/history-manager.js";
23
25
  export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProcessing, setIsStreaming, setTokenCount, setProcessingTime, setCurrentActivity, processingStartTime, isProcessing, isStreaming, isConfirmationActive = false, appendStreamingContent, finalizeStreamingEntry, updateToolCallEntry, }) {
24
26
  const [showCommandSuggestions, setShowCommandSuggestions] = useState(false);
25
27
  const [selectedCommandIndex, setSelectedCommandIndex] = useState(0);
28
+ const commandSelectionMoved = useRef(false);
26
29
  const [showModelSelection, setShowModelSelection] = useState(false);
27
30
  const [selectedModelIndex, setSelectedModelIndex] = useState(0);
28
31
  const [showFileAutocomplete, setShowFileAutocomplete] = useState(false);
@@ -33,6 +36,22 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
33
36
  const sessionFlags = confirmationService.getSessionFlags();
34
37
  return sessionFlags.allOperations;
35
38
  });
39
+ const runningTurn = useRef(false);
40
+ const pendingMessages = useRef([]);
41
+ const [queuedMessageCount, setQueuedMessageCount] = useState(0);
42
+ useEffect(() => {
43
+ if (isProcessing || isStreaming || runningTurn.current)
44
+ return;
45
+ const next = pendingMessages.current.shift();
46
+ if (next !== undefined) {
47
+ setQueuedMessageCount(pendingMessages.current.length);
48
+ // No turn is streaming here, so a tip entry cannot split a streaming message.
49
+ const tip = takeFirstUseHint('message_queued');
50
+ if (tip)
51
+ setChatHistory((prev) => [...prev, { type: "assistant", content: `💡 ${tip}`, timestamp: new Date() }]);
52
+ void processUserMessage(next);
53
+ }
54
+ }, [isProcessing, isStreaming, queuedMessageCount]);
36
55
  // Track last escape time for double-escape detection
37
56
  const lastEscapeTimeRef = useRef(0);
38
57
  const DOUBLE_ESCAPE_THRESHOLD = 500; // ms
@@ -115,6 +134,15 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
115
134
  return false; // Let default escape handling work
116
135
  };
117
136
  const handleCommandSuggestionsNav = (key) => {
137
+ const commandName = input.trim().split(/\s+/)[0]?.slice(1);
138
+ // Enter runs an explicitly typed command; Tab is completion only.
139
+ if (key.return && !commandSelectionMoved.current && commandName &&
140
+ (getSlashCommandManager().getCommand(commandName) || commandName === 'models')) {
141
+ setShowCommandSuggestions(false);
142
+ setSelectedCommandIndex(0);
143
+ void handleInputSubmit(input);
144
+ return true;
145
+ }
118
146
  const filteredSuggestions = filterCommandSuggestions(commandSuggestions, input);
119
147
  if (filteredSuggestions.length === 0) {
120
148
  setShowCommandSuggestions(false);
@@ -123,14 +151,16 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
123
151
  }
124
152
  else {
125
153
  if (key.upArrow) {
154
+ commandSelectionMoved.current = true;
126
155
  setSelectedCommandIndex((prev) => prev === 0 ? filteredSuggestions.length - 1 : prev - 1);
127
156
  return true;
128
157
  }
129
158
  if (key.downArrow) {
159
+ commandSelectionMoved.current = true;
130
160
  setSelectedCommandIndex((prev) => (prev + 1) % filteredSuggestions.length);
131
161
  return true;
132
162
  }
133
- if (key.tab || key.return) {
163
+ if (key.tab || key.return || (key.rightArrow && !key.ctrl && !key.meta && !key.shift && cursorPosition === input.length)) {
134
164
  const safeIndex = Math.min(selectedCommandIndex, filteredSuggestions.length - 1);
135
165
  const selectedSuggestion = filteredSuggestions[safeIndex];
136
166
  let newInput;
@@ -151,7 +181,14 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
151
181
  }
152
182
  else {
153
183
  // For commands, just use the command
154
- newInput = selectedSuggestion.command + " ";
184
+ newInput = selectedSuggestion.command;
185
+ if (key.return) {
186
+ setShowCommandSuggestions(false);
187
+ setSelectedCommandIndex(0);
188
+ void handleInputSubmit(newInput);
189
+ return true;
190
+ }
191
+ newInput += ' ';
155
192
  }
156
193
  setInput(newInput);
157
194
  setCursorPosition(newInput.length);
@@ -163,6 +200,13 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
163
200
  return false;
164
201
  };
165
202
  const handleModelSelectionNav = (key) => {
203
+ if (key.leftArrow || key.escape) {
204
+ setShowModelSelection(false);
205
+ setSelectedModelIndex(0);
206
+ return true;
207
+ }
208
+ if (availableModels.length === 0)
209
+ return true;
166
210
  if (key.upArrow) {
167
211
  setSelectedModelIndex((prev) => prev === 0 ? availableModels.length - 1 : prev - 1);
168
212
  return true;
@@ -171,18 +215,8 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
171
215
  setSelectedModelIndex((prev) => (prev + 1) % availableModels.length);
172
216
  return true;
173
217
  }
174
- if (key.tab || key.return) {
218
+ if (key.tab || key.return || key.rightArrow) {
175
219
  const selectedModel = availableModels[selectedModelIndex];
176
- // Delegate to Dispatcher implicitly via handleDirectCommand?
177
- // No, UI navigation logic remains here, but the action can be manual.
178
- // Or we can construct a command string and let dispatcher handle it.
179
- // But we have state setters here.
180
- // Let's keep UI state manipulation here for selection, but action execution via command if possible.
181
- // Actually, standard behavior:
182
- // agent.setModel(selectedModel.model);
183
- // updateCurrentModel(selectedModel.model);
184
- // ...
185
- // We can use a helper, but for now let's leave this UI logic as is, or use handleDirectCommand("/models " + model)
186
220
  if (selectedModel) {
187
221
  handleDirectCommand(`/models ${selectedModel.model}`);
188
222
  }
@@ -190,7 +224,7 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
190
224
  setSelectedModelIndex(0);
191
225
  return true;
192
226
  }
193
- return false;
227
+ return true; // Keep typing from modifying the draft while choosing a model.
194
228
  };
195
229
  const handleFileAutocompleteNav = (key) => {
196
230
  if (fileSuggestions.length > 0) {
@@ -202,7 +236,7 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
202
236
  setSelectedFileIndex((prev) => (prev + 1) % fileSuggestions.length);
203
237
  return true;
204
238
  }
205
- if (key.tab || key.return) {
239
+ if (key.tab || key.return || (key.rightArrow && !key.ctrl && !key.meta && !key.shift && cursorPosition === input.length)) {
206
240
  const selectedFile = fileSuggestions[selectedFileIndex];
207
241
  const { startPos } = extractFileReference(input);
208
242
  if (selectedFile && startPos >= 0) {
@@ -336,12 +370,19 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
336
370
  await handleDirectCommand(userInput);
337
371
  }
338
372
  else {
339
- await processUserMessage(userInput);
373
+ if (runningTurn.current || pendingMessages.current.length > 0) {
374
+ pendingMessages.current.push(userInput);
375
+ setQueuedMessageCount(pendingMessages.current.length);
376
+ }
377
+ else {
378
+ await processUserMessage(userInput);
379
+ }
340
380
  }
341
381
  }
342
382
  };
343
383
  // Removed handleShellBypass as it's now in ClientCommandDispatcher
344
384
  const handleInputChange = (newInput) => {
385
+ commandSelectionMoved.current = false;
345
386
  // Update command suggestions based on input
346
387
  if (newInput.startsWith("/")) {
347
388
  setShowCommandSuggestions(true);
@@ -369,11 +410,17 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
369
410
  onSubmit: handleInputSubmit,
370
411
  onSpecialKey: handleSpecialKey,
371
412
  disabled: isConfirmationActive,
413
+ multiline: true,
372
414
  });
373
415
  // Hook up the actual input handling
374
416
  useInput((inputChar, key) => {
417
+ // A picker owns the keyboard, including text-editing shortcuts such as Ctrl+J.
418
+ if (showModelSelection) {
419
+ handleModelSelectionNav(key);
420
+ return;
421
+ }
375
422
  handleInput(inputChar, key);
376
- });
423
+ }, { isActive: !isConfirmationActive });
377
424
  // Update command suggestions when input changes
378
425
  useEffect(() => {
379
426
  handleInputChange(input);
@@ -387,9 +434,13 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
387
434
  }));
388
435
  }, []);
389
436
  // Load models from configuration with fallback to defaults
437
+ const current = agent.getCurrentModel?.();
438
+ const provider = agent.getClient?.().getCurrentProvider?.();
390
439
  const availableModels = useMemo(() => {
391
- return loadModelConfig(); // Return directly, interface already matches
392
- }, []);
440
+ const models = [...(current ? [{ model: current }] : []), ...loadModelConfig()];
441
+ return models.filter((option, index) => models.findIndex(other => other.model === option.model) === index &&
442
+ isModelCompatibleWithProvider(option.model, provider));
443
+ }, [current, provider]);
393
444
  const handleDirectCommand = async (input) => {
394
445
  const context = {
395
446
  agent,
@@ -411,6 +462,7 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
411
462
  return await ClientCommandDispatcher.dispatch(input, context);
412
463
  };
413
464
  const processUserMessage = async (userInput) => {
465
+ runningTurn.current = true;
414
466
  const mySeq = ++turnSeqRef.current;
415
467
  goalInterruptedRef.current = false;
416
468
  const userEntry = {
@@ -431,7 +483,8 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
431
483
  }
432
484
  setIsProcessing(true);
433
485
  setCurrentActivity?.('Sending to LLM...');
434
- clearInput();
486
+ // The submit handler already cleared the sent text. A queued turn must
487
+ // preserve whatever draft the user is now editing.
435
488
  let fullResponseContent = "";
436
489
  try {
437
490
  setIsStreaming(true);
@@ -622,6 +675,10 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
622
675
  setIsStreaming(false);
623
676
  setCurrentActivity?.('');
624
677
  }
678
+ // Persist the finished turn before a queued message or exit can start (never throws).
679
+ await agent.persistInteractiveSession();
680
+ runningTurn.current = false;
681
+ setIsStreaming(false);
625
682
  setIsProcessing(false);
626
683
  setCurrentActivity?.('');
627
684
  processingStartTime.current = 0;
@@ -648,10 +705,12 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
648
705
  }
649
706
  // Skip the continuation if a newer turn started while we were judging —
650
707
  // the user's message preempts the loop and gets judged after its turn.
651
- if (outcome?.continuationPrompt && turnSeqRef.current === mySeq) {
708
+ if (outcome?.continuationPrompt && turnSeqRef.current === mySeq && pendingMessages.current.length === 0) {
652
709
  const continuation = outcome.continuationPrompt;
653
710
  setTimeout(() => {
654
- void processUserMessage(continuation);
711
+ if (turnSeqRef.current === mySeq && !runningTurn.current && pendingMessages.current.length === 0) {
712
+ void processUserMessage(continuation);
713
+ }
655
714
  }, 50);
656
715
  }
657
716
  }
@@ -673,6 +732,7 @@ export function useInputHandler({ agent, chatHistory, setChatHistory, setIsProce
673
732
  availableModels,
674
733
  agent,
675
734
  autoEditEnabled,
735
+ queuedMessageCount,
676
736
  handleInputSubmit,
677
737
  };
678
738
  }
@@ -28,6 +28,7 @@ export function useInputHistory() {
28
28
  }
29
29
  else if (currentIndex === history.length - 1) {
30
30
  newIndex = -1;
31
+ setCurrentIndex(-1);
31
32
  return originalInput;
32
33
  }
33
34
  else {
@@ -165,7 +165,8 @@ const DESCRIBE_PATTERNS = [
165
165
  /\bde quoi (?:es tu|etes vous) (?:fait|faite|faits|faites|compose|composee|composes|composees)\b/,
166
166
  /\bqui (?:es tu|etes vous)\b/,
167
167
  /\b(?:quell?e est )?(?:ton|votre) architecture\b/,
168
- /\b(?:quels? (?:sont )?)?(?:tes|vos) (?:capteurs?|outils?|modules?)(?: (?:sont|restent|semblent))?(?: (?:actifs?|disponibles?|operationnels?))?\b/,
168
+ /\bquels? (?:sont )?(?:tes|vos) (?:capteurs?|outils?|modules?)(?: (?:sont|restent|semblent))?(?: (?:actifs?|disponibles?|operationnels?))?\b/,
169
+ /^(?:tes|vos) (?:capteurs?|outils?|modules?)(?: (?:sont|restent|semblent))?(?: (?:actifs?|disponibles?|operationnels?))?\s*[?.!]*$/,
169
170
  /\bquels? (?:modules?|composants?|outils?) (?:as tu|avez vous)\b/,
170
171
  /\bquell?es? (?:sont )?(?:tes|vos) limit(?:e|es|ation|ations)\b/,
171
172
  /\b(?:lisa|l assistant|l assistante|l agent)\b.{0,96}\b(?:conscient|consciente|conscience d elle meme|modele d elle meme|se connaitre)\b/,
@@ -354,7 +355,9 @@ export function renderLisaOperationalSelfResponse(model, raw, intent) {
354
355
  }
355
356
  /** Classify a request about Lisa's own technical implementation. */
356
357
  export function classifyLisaIntrospection(raw) {
357
- const text = normalizeIntrospectionText(raw);
358
+ // Using tools to do a task does not make the tools the inspection target.
359
+ // Keep any explicit target that follows (e.g. 'inspect your own code').
360
+ const text = normalizeIntrospectionText(raw).replace(/\b(?:(?:utilise|utilisez|utiliser) (?:tes|vos)|use your) (?:outils|tools)\b/g, '');
358
361
  if (!text)
359
362
  return null;
360
363
  // A request for introspection of the user's life, emotions, relationship, or
@@ -44,6 +44,8 @@ export interface OperationalSelfArea {
44
44
  state: 'verified' | 'partial' | 'unavailable';
45
45
  }
46
46
  export interface CompanionRuntimeEvidence {
47
+ /** Live CLI theme, only when attested by that UI host. */
48
+ theme?: string;
47
49
  model?: string;
48
50
  provider?: string;
49
51
  /** False when the report is built locally without dispatching a model. */
@@ -809,6 +809,10 @@ function runtimeFacts(runtime, observedAt) {
809
809
  : undefined));
810
810
  facts.push(fact(observedAt, 'turn.surface', 'Surface', surface ? 'verified' : 'unknown', surface || 'inconnue', ['agent turn metadata']));
811
811
  facts.push(fact(observedAt, 'turn.permission', 'Mode de permission', permissionMode ? 'verified' : 'unknown', permissionMode || 'inconnu', ['agent turn metadata']));
812
+ const theme = surface === 'cli' ? normalizedRuntimeIdentifier(runtime.theme) : undefined;
813
+ if (theme) {
814
+ facts.push(fact(observedAt, 'turn.theme', 'Thème CLI actif', 'verified', theme, ['in-process Ink ThemeManager']));
815
+ }
812
816
  const registered = normalizedToolNames(runtime.registeredToolNames);
813
817
  const exposed = normalizedToolNames(runtime.exposedToolNames);
814
818
  facts.push(fact(observedAt, 'tools.registered', 'Outils enregistrés', runtime.registeredToolNames !== undefined ? 'verified' : 'unknown', runtime.registeredToolNames !== undefined
package/dist/index.js CHANGED
@@ -1328,7 +1328,7 @@ program
1328
1328
  .option("--no-emoji", "disable emoji in output")
1329
1329
  .option("--list-models", "list available models from the API endpoint and exit")
1330
1330
  .option("--continue", "continue from the most recent saved session (like mistral-vibe)")
1331
- .option("--resume <sessionId>", "resume a specific session by ID (supports partial matching)")
1331
+ .option("--resume [sessionId]", "resume a specific session by ID (supports partial matching); without an ID, pick a recent session")
1332
1332
  .option("--search-sessions <query>", "search saved sessions by content")
1333
1333
  .option("--max-price <dollars>", "maximum cost in dollars before stopping (like mistral-vibe)", "10.0")
1334
1334
  .option("--auto-approve", "automatically approve all tool executions (like mistral-vibe)")
@@ -1519,6 +1519,14 @@ program
1519
1519
  cli.info(` ${lastSession.messages.length} messages, last accessed: ${lastSession.lastAccessedAt.toLocaleString()}\n`);
1520
1520
  }
1521
1521
  // Handle --resume flag (resume specific session by ID, like mistral-vibe)
1522
+ if (options.resume === true) {
1523
+ // P6: `--resume` without an ID opens the recent-session picker.
1524
+ const { pickRecentSession } = await import("./cli/session-commands.js");
1525
+ const picked = await pickRecentSession(20);
1526
+ if (!picked)
1527
+ process.exit(process.exitCode ?? 1);
1528
+ options.resume = picked;
1529
+ }
1522
1530
  if (options.resume) {
1523
1531
  const { getSessionStore } = await import("./persistence/session-store.js");
1524
1532
  const sessionStore = getSessionStore();
@@ -1534,7 +1542,9 @@ program
1534
1542
  }
1535
1543
  await sessionStore.resumeSession(session.id);
1536
1544
  cli.info(`📂 Resuming session: ${session.name} (${session.id.slice(0, 8)})`);
1537
- cli.info(` ${session.messages.length} messages, last accessed: ${session.lastAccessedAt.toLocaleString()}\n`);
1545
+ cli.info(` ${session.messages.length} messages, last accessed: ${session.lastAccessedAt.toLocaleString()}`);
1546
+ const { buildSessionRecap, formatSessionRecap } = await import("./cli/session-picker.js");
1547
+ cli.info(`${formatSessionRecap(buildSessionRecap(session)).join("\n")}\n`);
1538
1548
  }
1539
1549
  // Load environment before changing cwd so root .env values (API keys) remain available
1540
1550
  // even when --directory points to a workspace without its own .env file.
@@ -1900,6 +1910,7 @@ program
1900
1910
  await ensureUserSettingsDirectory();
1901
1911
  recordStartupPhase('user-settings-done');
1902
1912
  // ── Crash recovery: detect unclean shutdown and offer session resume ──
1913
+ let crashResumed = false;
1903
1914
  if (!options.resume && !options.continue) {
1904
1915
  try {
1905
1916
  const { checkCrashRecovery, clearRecoveryFiles } = await import('./errors/crash-recovery.js');
@@ -1921,6 +1932,7 @@ program
1921
1932
  const session = await sessionStore.getSessionByPartialId(recovery.sessionId);
1922
1933
  if (session) {
1923
1934
  await sessionStore.resumeSession(session.id);
1935
+ crashResumed = true;
1924
1936
  cli.info(` Resuming session: ${session.name} (${session.messages.length} messages)`);
1925
1937
  }
1926
1938
  else {
@@ -1978,7 +1990,24 @@ program
1978
1990
  // Only the interactive TUI enables it, so cron/headless/sub-agent runs cannot
1979
1991
  // trigger a review (recursion + cost safety).
1980
1992
  agent.enableBackgroundReview();
1981
- render(React.createElement(ChatInterface, { agent, initialMessage }), inkOptions);
1993
+ // Memory fact reconciliation (remember tool and /remember) must use the
1994
+ // provider this session runs on, never a provider auto-detected from env.
1995
+ const { setFactsMemorySessionClient } = await import('./memory/facts-memory.js');
1996
+ setFactsMemorySessionClient(agent.getClient());
1997
+ // A resumed session (--resume, --continue, crash recovery) must reach both the model
1998
+ // history and the chat view; resumeSession() alone only selects the store's current session.
1999
+ let initialHistory;
2000
+ if (options.resume || options.continue || crashResumed) {
2001
+ const { getSessionStore } = await import('./persistence/session-store.js');
2002
+ const resumedStore = getSessionStore();
2003
+ const resumedId = resumedStore.getCurrentSessionId();
2004
+ const resumed = resumedId ? await resumedStore.loadSession(resumedId) : null;
2005
+ if (resumed) {
2006
+ agent.hydratePersistedSession(resumed);
2007
+ initialHistory = agent.getChatHistory();
2008
+ }
2009
+ }
2010
+ render(React.createElement(ChatInterface, { agent, initialMessage, initialHistory }), inkOptions);
1982
2011
  // Initialize plugin system in background (non-blocking)
1983
2012
  setImmediate(async () => {
1984
2013
  try {
@@ -2733,12 +2762,13 @@ program
2733
2762
  cli.error(LOGIN_NEEDS_BROWSER_MESSAGE);
2734
2763
  process.exit(1);
2735
2764
  }
2736
- const { loginInteractive, getCodexAuthFilePath } = await import("./providers/codex-oauth.js");
2765
+ const { getCodexAuthFilePath } = await import("./providers/codex-oauth.js");
2766
+ const { loginChatGptWithBrowser } = await import('./commands/login-chatgpt.js');
2737
2767
  cli.stdout("🔐 ChatGPT login");
2738
- cli.stdout("Opening your browser to https://auth.openai.com/oauth/authorize ...");
2768
+ cli.stdout("Opening your browser for ChatGPT sign-in ...");
2739
2769
  cli.stdout("Sign in with your ChatGPT account, then return to this terminal.\n");
2740
2770
  try {
2741
- const auth = await loginInteractive();
2771
+ const auth = await loginChatGptWithBrowser(cli);
2742
2772
  cli.stdout("✅ Authenticated successfully");
2743
2773
  if (auth.email)
2744
2774
  cli.stdout(` Account: ${auth.email}`);
@@ -3080,6 +3110,10 @@ addLazyCommandGroup(program, 'curator', 'Propose-only maintenance report (memory
3080
3110
  const { registerCuratorCommand } = await import('./commands/curator-cli.js');
3081
3111
  registerCuratorCommand(program);
3082
3112
  });
3113
+ addLazyCommandGroup(program, 'triage', 'Write a local redacted support bundle and prompt (no network, launches nothing)', async () => {
3114
+ const { registerTriageCommand } = await import('./commands/cli/triage-command.js');
3115
+ registerTriageCommand(program);
3116
+ });
3083
3117
  addLazyCommandGroup(program, 'gateway-pairing', 'Operator approval for gateway device pairing', async () => {
3084
3118
  const { registerGatewayPairingCommands } = await import('./commands/cli/native-engine-commands.js');
3085
3119
  registerGatewayPairingCommands(program);
@@ -3116,6 +3150,14 @@ addLazyCommand(program, 'token', 'Mint a signed JWT for the API and the mobile P
3116
3150
  const { createTokenCommand } = await import('./commands/token.js');
3117
3151
  return createTokenCommand();
3118
3152
  });
3153
+ addLazyCommand(program, 'pair', 'Pair an Android authenticator with a local one-time code', async () => {
3154
+ const { createPairCommand } = await import('./commands/device-auth.js');
3155
+ return createPairCommand();
3156
+ });
3157
+ addLazyCommand(program, 'devices', 'Manage Android authentication devices', async () => {
3158
+ const { createDevicesCommand } = await import('./commands/device-auth.js');
3159
+ return createDevicesCommand();
3160
+ });
3119
3161
  addLazyCommandGroup(program, 'fleet', 'Inspect Fleet routing and dispatch policy decisions', async () => {
3120
3162
  const { registerFleetCommands } = await import('./commands/cli/fleet-commands.js');
3121
3163
  registerFleetCommands(program);
@@ -3298,6 +3340,10 @@ addLazyCommand(program, 'lessons', 'Manage lessons learned — self-improvement
3298
3340
  const { createLessonsCommand } = await import('./commands/lessons.js');
3299
3341
  return createLessonsCommand();
3300
3342
  });
3343
+ addLazyCommand(program, 'resources', 'Explicit network resource inventory and read-only health selection', async () => {
3344
+ const { createResourcesCommand } = await import('./commands/resources.js');
3345
+ return createResourcesCommand();
3346
+ });
3301
3347
  // Spec — BMAD-inspired spec-driven, review-gated work pipeline
3302
3348
  addLazyCommandGroup(program, 'spec', 'Spec-driven, review-gated work pipeline (durable stories; approve before implementing)', async () => {
3303
3349
  const { createSpecCommand } = await import('./commands/spec.js');
@@ -14,9 +14,20 @@ export declare class MCPManager extends EventEmitter {
14
14
  private serverStatuses;
15
15
  private retryCounts;
16
16
  private healthCheckIntervals;
17
+ private reconnectTimers;
18
+ /**
19
+ * Bumped by removeServer. A still-running addServerInternal must not overwrite
20
+ * 'disconnected' with 'error' nor schedule autoReconnect (which would reopen
21
+ * the OAuth browser ~1s after an explicit cancel).
22
+ */
23
+ private connectEpochs;
17
24
  private serverAddPromises;
18
25
  private initializationPromise;
19
26
  addServer(config: MCPServerConfig): Promise<void>;
27
+ private currentEpoch;
28
+ private bumpConnectEpoch;
29
+ private assertConnectCurrent;
30
+ private clearReconnectTimer;
20
31
  private addServerInternal;
21
32
  private startHealthCheck;
22
33
  private stopHealthCheck;
@@ -1,6 +1,8 @@
1
+ import { mcpToolAllowed, validateMCPToolFilter } from './import-normalize.js';
1
2
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
3
  import { EventEmitter } from "events";
3
4
  import { createTransport } from "./transports.js";
5
+ import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js";
4
6
  import { logger } from "../utils/logger.js";
5
7
  export const DEFAULT_MCP_INIT_TIMEOUT_MS = 15_000;
6
8
  const MAX_MCP_INIT_TIMEOUT_MS = 10 * 60_000;
@@ -25,9 +27,19 @@ export class MCPManager extends EventEmitter {
25
27
  serverStatuses = new Map();
26
28
  retryCounts = new Map();
27
29
  healthCheckIntervals = new Map();
30
+ reconnectTimers = new Map();
31
+ /**
32
+ * Bumped by removeServer. A still-running addServerInternal must not overwrite
33
+ * 'disconnected' with 'error' nor schedule autoReconnect (which would reopen
34
+ * the OAuth browser ~1s after an explicit cancel).
35
+ */
36
+ connectEpochs = new Map();
28
37
  serverAddPromises = new Map();
29
38
  initializationPromise = null;
30
39
  async addServer(config) {
40
+ if (config.enabled === false)
41
+ return;
42
+ validateMCPToolFilter(config.toolFilter);
31
43
  if (this.serverStatuses.get(config.name) === 'connected' &&
32
44
  this.clients.has(config.name) &&
33
45
  this.transports.has(config.name)) {
@@ -47,9 +59,30 @@ export class MCPManager extends EventEmitter {
47
59
  }
48
60
  }
49
61
  }
62
+ currentEpoch(serverName) {
63
+ return this.connectEpochs.get(serverName) ?? 0;
64
+ }
65
+ bumpConnectEpoch(serverName) {
66
+ const next = this.currentEpoch(serverName) + 1;
67
+ this.connectEpochs.set(serverName, next);
68
+ return next;
69
+ }
70
+ assertConnectCurrent(serverName, epoch) {
71
+ if (this.currentEpoch(serverName) !== epoch) {
72
+ throw new Error(`MCP server "${serverName}" connection cancelled`);
73
+ }
74
+ }
75
+ clearReconnectTimer(serverName) {
76
+ const timer = this.reconnectTimers.get(serverName);
77
+ if (timer) {
78
+ clearTimeout(timer);
79
+ this.reconnectTimers.delete(serverName);
80
+ }
81
+ }
50
82
  async addServerInternal(config) {
51
83
  this.serverConfigs.set(config.name, config);
52
84
  this.serverStatuses.set(config.name, 'connecting');
85
+ const epoch = this.currentEpoch(config.name);
53
86
  try {
54
87
  // Handle legacy stdio-only configuration
55
88
  let transportConfig = config.transport;
@@ -76,8 +109,24 @@ export class MCPManager extends EventEmitter {
76
109
  });
77
110
  this.clients.set(config.name, client);
78
111
  // Connect
79
- const sdkTransport = await transport.connect();
80
- await client.connect(sdkTransport);
112
+ let sdkTransport = await transport.connect();
113
+ this.assertConnectCurrent(config.name, epoch);
114
+ try {
115
+ await client.connect(sdkTransport);
116
+ this.assertConnectCurrent(config.name, epoch);
117
+ }
118
+ catch (error) {
119
+ // First connection may end in UnauthorizedError once the OAuth code has been
120
+ // exchanged (SDK contract): reconnect once with the stored token.
121
+ if (!(error instanceof UnauthorizedError) || transportConfig.auth?.type !== 'oauth')
122
+ throw error;
123
+ await transport.disconnect().catch(() => undefined);
124
+ this.assertConnectCurrent(config.name, epoch);
125
+ sdkTransport = await transport.connect();
126
+ this.assertConnectCurrent(config.name, epoch);
127
+ await client.connect(sdkTransport);
128
+ this.assertConnectCurrent(config.name, epoch);
129
+ }
81
130
  // Drain the captured MCP stderr to the logger. The stream only exists
82
131
  // for stdio transports created with stderr:'pipe' (see StdioTransport);
83
132
  // without a consumer the PassThrough back-pressures the child once its
@@ -92,8 +141,11 @@ export class MCPManager extends EventEmitter {
92
141
  }
93
142
  // List available tools
94
143
  const toolsResult = await client.listTools();
144
+ this.assertConnectCurrent(config.name, epoch);
95
145
  // Register tools
96
146
  for (const tool of toolsResult.tools) {
147
+ if (!mcpToolAllowed(tool.name, config.toolFilter))
148
+ continue;
97
149
  const mcpTool = {
98
150
  name: `mcp__${config.name}__${tool.name}`,
99
151
  description: tool.description || `Tool from ${config.name} server`,
@@ -108,6 +160,10 @@ export class MCPManager extends EventEmitter {
108
160
  this.emit('serverAdded', config.name, toolsResult.tools.length);
109
161
  }
110
162
  catch (error) {
163
+ if (this.currentEpoch(config.name) !== epoch) {
164
+ // Explicit removeServer raced this connect; keep disconnected.
165
+ throw error;
166
+ }
111
167
  this.serverStatuses.set(config.name, 'error');
112
168
  this.handleServerError(config.name, error);
113
169
  throw error;
@@ -153,8 +209,13 @@ export class MCPManager extends EventEmitter {
153
209
  if (retryCount < maxRetries) {
154
210
  this.retryCounts.set(serverName, retryCount + 1);
155
211
  const delay = Math.min(1000 * Math.pow(2, retryCount), 30000);
212
+ const epochAtError = this.currentEpoch(serverName);
156
213
  logger.info(`Attempting to reconnect to ${serverName} in ${delay}ms (attempt ${retryCount + 1}/${maxRetries})`);
157
- setTimeout(async () => {
214
+ this.clearReconnectTimer(serverName);
215
+ const timer = setTimeout(async () => {
216
+ this.reconnectTimers.delete(serverName);
217
+ if (this.currentEpoch(serverName) !== epochAtError)
218
+ return;
158
219
  try {
159
220
  await this.removeServer(serverName);
160
221
  await this.addServer(config);
@@ -163,6 +224,7 @@ export class MCPManager extends EventEmitter {
163
224
  logger.debug(`Reconnection attempt failed for ${serverName}`, { reconnectError });
164
225
  }
165
226
  }, delay);
227
+ this.reconnectTimers.set(serverName, timer);
166
228
  }
167
229
  else {
168
230
  logger.error(`Max reconnection attempts reached for ${serverName}`);
@@ -173,6 +235,9 @@ export class MCPManager extends EventEmitter {
173
235
  return this.serverStatuses.get(serverName);
174
236
  }
175
237
  async removeServer(serverName) {
238
+ this.bumpConnectEpoch(serverName);
239
+ this.clearReconnectTimer(serverName);
240
+ this.serverAddPromises.delete(serverName);
176
241
  this.stopHealthCheck(serverName);
177
242
  this.serverStatuses.set(serverName, 'disconnected');
178
243
  // Remove tools
@@ -7,8 +7,28 @@ export type { MCPConfig } from "./types.js";
7
7
  export interface LoadMCPConfigOptions {
8
8
  /** Include disabled entries for inventory and diagnostics. Runtime callers omit this. */
9
9
  includeDisabled?: boolean;
10
+ /**
11
+ * Project directory whose `.codebuddy/mcp.json` and `.codebuddy/settings.json` are read.
12
+ * Defaults to `process.cwd()` (historical behaviour). Lets diagnostics inspect another
13
+ * project without `process.chdir()`, which is global state shared across awaits.
14
+ */
15
+ cwd?: string;
10
16
  }
11
17
  export declare function loadMCPConfig(options?: LoadMCPConfigOptions): MCPConfig;
18
+ export interface MCPConfigReadOnlyResult {
19
+ servers: MCPServerConfig[];
20
+ /** Sanitized warnings (no config content, no secrets). Empty when all sources are missing/valid. */
21
+ warnings: string[];
22
+ }
23
+ /**
24
+ * Strictly read-only MCP configuration loader for diagnostics. Reads the same
25
+ * three sources with the same precedence, disabled masking, and env resolution
26
+ * as `loadMCPConfig`, but never restores/renames/chmods/creates config or
27
+ * backups/temporaries. Corrupt or unreadable sources yield sanitized warnings
28
+ * instead of being silently treated as "no server configured". Missing config
29
+ * is normal and produces no warning.
30
+ */
31
+ export declare function loadMCPConfigReadOnly(options?: LoadMCPConfigOptions): MCPConfigReadOnlyResult;
12
32
  export declare function saveMCPConfig(config: MCPConfig): void;
13
33
  export declare function addMCPServer(config: MCPServerConfig): void;
14
34
  export declare function removeMCPServer(serverName: string): void;