@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
@@ -16,6 +16,7 @@ import { spawn } from 'node:child_process';
16
16
  import { randomUUID } from 'node:crypto';
17
17
  import { BaseTool } from './base-tool.js';
18
18
  import { logger } from '../utils/logger.js';
19
+ import { ToolCallScheduler } from './tool-call-scheduler.js';
19
20
  export const CODE_EXEC_RUNTIME_CONTEXT_KEY = 'codeExecRuntime';
20
21
  /** Attach a runtime without changing any other tool-execution context data. */
21
22
  export function attachCodeExecRuntime(context, runtime) {
@@ -58,6 +59,22 @@ export const CODE_EXEC_LIMITS = Object.freeze({
58
59
  scopeTtlMs: 2 * 60 * 60 * 1000,
59
60
  });
60
61
  const scopedStates = new Map();
62
+ const scopeSchedulers = new Map();
63
+ async function runScoped(scopeId, action, signal) {
64
+ let entry = scopeSchedulers.get(scopeId);
65
+ if (!entry) {
66
+ entry = { scheduler: new ToolCallScheduler(1), users: 0 };
67
+ scopeSchedulers.set(scopeId, entry);
68
+ }
69
+ entry.users++;
70
+ try {
71
+ return await entry.scheduler.schedule(false, action, signal);
72
+ }
73
+ finally {
74
+ if (--entry.users === 0)
75
+ scopeSchedulers.delete(scopeId);
76
+ }
77
+ }
61
78
  /** Backward-compatible direct-use runtime. Production ToolHandler never uses it. */
62
79
  let legacySessionId = 'default';
63
80
  let legacyExecutor = null;
@@ -167,17 +184,19 @@ function truncate(value, limit) {
167
184
  return value.slice(0, Math.max(0, limit - marker.length)) + marker;
168
185
  }
169
186
  function jsonSafe(value) {
170
- const seen = new WeakSet();
187
+ const ancestors = [];
171
188
  try {
172
- const encoded = JSON.stringify(value, (_key, current) => {
189
+ const encoded = JSON.stringify(value, function (_key, current) {
173
190
  if (typeof current === 'bigint')
174
191
  return current.toString();
175
192
  if (typeof current === 'function' || typeof current === 'symbol')
176
193
  return undefined;
177
194
  if (current && typeof current === 'object') {
178
- if (seen.has(current))
195
+ while (ancestors.length && ancestors[ancestors.length - 1] !== this)
196
+ ancestors.pop();
197
+ if (ancestors.includes(current))
179
198
  return '[Circular]';
180
- seen.add(current);
199
+ ancestors.push(current);
181
200
  }
182
201
  return current;
183
202
  });
@@ -195,23 +214,35 @@ function jsonSafe(value) {
195
214
  return String(value);
196
215
  }
197
216
  }
198
- function serializeToolResult(result) {
217
+ function serializeToolResult(result, format) {
218
+ if (format !== 'legacy') {
219
+ const payload = jsonSafe(result);
220
+ if (payload && typeof payload === 'object' && 'truncated' in payload && 'preview' in payload) {
221
+ return JSON.stringify({ success: result.success, truncated: true,
222
+ output: truncate(result.output ?? String(payload.preview), CODE_EXEC_LIMITS.maxToolResultChars - CODE_EXEC_LIMITS.maxErrorChars),
223
+ ...(result.error ? { error: truncate(result.error, CODE_EXEC_LIMITS.maxErrorChars) } : {}),
224
+ });
225
+ }
226
+ return JSON.stringify(payload);
227
+ }
199
228
  const payload = result.success
200
229
  ? (result.output ?? result.data ?? '')
201
230
  : { error: truncate(result.error ?? 'Tool execution failed', CODE_EXEC_LIMITS.maxErrorChars) };
202
231
  return JSON.stringify(jsonSafe(payload));
203
232
  }
204
- function sanitizeToolName(name) {
233
+ export function sanitizeToolName(name) {
205
234
  const replaced = name.replace(/[^a-zA-Z0-9_$]/g, '_');
206
235
  if (!replaced)
207
236
  return '_tool';
208
237
  return /^[a-zA-Z_$]/.test(replaced) ? replaced : `_${replaced}`;
209
238
  }
210
- function buildToolBindings(toolNames) {
239
+ export function buildToolBindings(toolNames) {
211
240
  const bindings = [];
212
241
  const seenTools = new Set();
213
242
  const seenBindings = new Set(['call']);
214
- for (const toolName of toolNames) {
243
+ // Keep discovery reachable even when direct bindings hit their budget.
244
+ const ordered = toolNames.includes('tool_search') ? ['tool_search', ...toolNames] : toolNames;
245
+ for (const toolName of ordered) {
215
246
  if (bindings.length >= CODE_EXEC_LIMITS.maxAvailableTools)
216
247
  break;
217
248
  if (typeof toolName !== 'string' ||
@@ -222,9 +253,11 @@ function buildToolBindings(toolNames) {
222
253
  continue;
223
254
  }
224
255
  seenTools.add(toolName);
225
- const exposedName = sanitizeToolName(toolName);
226
- if (seenBindings.has(exposedName))
227
- continue;
256
+ const baseName = sanitizeToolName(toolName);
257
+ let exposedName = baseName;
258
+ let suffix = 2;
259
+ while (seenBindings.has(exposedName))
260
+ exposedName = `${baseName}_${suffix++}`;
228
261
  seenBindings.add(exposedName);
229
262
  bindings.push({ exposedName, toolName });
230
263
  }
@@ -305,27 +338,31 @@ async function execute(message) {
305
338
  const outputLimit = ${CODE_EXEC_LIMITS.maxOutputChars};
306
339
  const storeEntries = JSON.stringify(message.storeEntries || []);
307
340
  const toolBindings = JSON.stringify(message.toolBindings || []);
308
- const allTools = JSON.stringify((message.toolBindings || []).map((entry) => entry.toolName));
341
+ const allTools = JSON.stringify(message.toolMetadata || (message.toolBindings || []).map((entry) => ({ name: entry.toolName, description: '' })));
309
342
  const bootstrap =
310
343
  'const __cbOutput = []; let __cbOutputLength = 0; let __cbTruncated = false; let __cbYielded = false;' +
311
344
  'const __cbStore = new Map(' + storeEntries + ');' +
312
345
  'const __cbBindings = ' + toolBindings + ';' +
313
346
  'const __cbBridge = globalThis.__codeBuddyBridge; delete globalThis.__codeBuddyBridge;' +
347
+ 'const __cbYield = globalThis.__codeBuddyYield; delete globalThis.__codeBuddyYield;' +
314
348
  'function __cbFormat(value) { if (typeof value === "string") return value; try { const json = JSON.stringify(value); return json === undefined ? String(value) : json; } catch { return String(value); } }' +
315
349
  'function __cbAppend(values, prefix = "") { if (__cbTruncated) return; const line = prefix + values.map(__cbFormat).join(" "); const separator = __cbOutput.length ? "\\n" : ""; const remaining = ' + outputLimit + ' - __cbOutputLength; if (remaining <= 0) { __cbTruncated = true; return; } const next = separator + line; if (next.length > remaining) { __cbOutput.push(next.slice(0, remaining)); __cbOutputLength += remaining; __cbTruncated = true; return; } __cbOutput.push(next); __cbOutputLength += next.length; }' +
316
350
  'function text(content) { __cbAppend([content]); }' +
317
351
  'function store(key, value) { if (typeof key !== "string" || key.length === 0 || key.length > 256) throw new Error("store key must be 1..256 characters"); const encoded = JSON.stringify(value); if (encoded === undefined) throw new Error("store values must be JSON-compatible"); const cloned = JSON.parse(encoded); const candidate = new Map(__cbStore); candidate.set(key, cloned); if (candidate.size > ${CODE_EXEC_LIMITS.maxStoreEntries}) throw new Error("store entry limit reached"); const total = JSON.stringify(Array.from(candidate.entries())).length; if (total > ${CODE_EXEC_LIMITS.maxStoreBytes}) throw new Error("store byte limit reached"); __cbStore.set(key, cloned); }' +
318
352
  'function load(key) { const value = __cbStore.get(key); return value === undefined ? undefined : JSON.parse(JSON.stringify(value)); }' +
319
- 'function yield_control() { __cbYielded = true; }' +
353
+ 'let __cbEmittedLength = 0; async function yield_control() { __cbYielded = true; const output = __cbOutput.join(""); const delta = output.slice(__cbEmittedLength); __cbEmittedLength = output.length; await __cbYield(delta); }' +
320
354
  'const tools = Object.create(null);' +
321
355
  'for (const binding of __cbBindings) { tools[binding.exposedName] = async function(args = {}) { const encoded = JSON.stringify(args); if (encoded === undefined || args === null || typeof args !== "object" || Array.isArray(args)) throw new Error("tool arguments must be a JSON object"); return JSON.parse(await __cbBridge(binding.toolName, encoded)); }; }' +
322
- 'tools.call = async function(name, args = {}) { if (typeof name !== "string") throw new Error("tool name must be a string"); const binding = __cbBindings.find((entry) => entry.toolName === name); if (!binding) throw new Error("tool is not available: " + name); const encoded = JSON.stringify(args); if (encoded === undefined || args === null || typeof args !== "object" || Array.isArray(args)) throw new Error("tool arguments must be a JSON object"); return JSON.parse(await __cbBridge(binding.toolName, encoded)); };' +
356
+ 'tools.call = async function(name, args = {}) { if (typeof name !== "string") throw new Error("tool name must be a string"); const encoded = JSON.stringify(args); if (encoded === undefined || args === null || typeof args !== "object" || Array.isArray(args)) throw new Error("tool arguments must be a JSON object"); return JSON.parse(await __cbBridge(name, encoded)); };' +
323
357
  'Object.freeze(tools); Object.freeze(__cbBindings);' +
324
- 'const ALL_TOOLS = Object.freeze(' + allTools + ');' +
358
+ 'const ALL_TOOLS = Object.freeze(' + allTools + '.map(Object.freeze)); const ALL_TOOL_NAMES = Object.freeze(ALL_TOOLS.map(t => t.name));' +
325
359
  'const console = Object.freeze({ log: (...args) => __cbAppend(args), error: (...args) => __cbAppend(args, "[ERROR] "), warn: (...args) => __cbAppend(args, "[WARN] ") });';
326
360
 
327
361
  const sandbox = Object.create(null);
328
362
  sandbox.__codeBuddyBridge = bridgeCall;
363
+ const yieldOutput = (output) => send({ type: 'output', output });
364
+ Object.setPrototypeOf(yieldOutput, null);
365
+ sandbox.__codeBuddyYield = yieldOutput;
329
366
  const context = vm.createContext(sandbox, {
330
367
  name: 'codebuddy-code-exec',
331
368
  codeGeneration: { strings: false, wasm: false },
@@ -370,12 +407,60 @@ function childExecArgs() {
370
407
  args.push('-e', SANDBOX_RUNNER_SOURCE);
371
408
  return args;
372
409
  }
373
- function terminateChild(child) {
374
- try {
375
- if (child.connected)
376
- child.disconnect();
410
+ /** Upper bound on waiting for the sandbox process to report `close`. */
411
+ export const CHILD_CLOSE_GRACE_MS = 2_000;
412
+ /**
413
+ * Wait for the OBSERVED `close` of the sandbox process — the event that says
414
+ * the process ended and its stdio is released. `exit` alone is not enough:
415
+ * `exitCode` can be set while stdio is still open, and the sandbox runs with
416
+ * the caller's workspace as its working directory, so the caller must not be
417
+ * told the workspace is free before the process really let go of it.
418
+ *
419
+ * @returns true when `close` was observed (or there is no process to wait for),
420
+ * false when the grace elapsed first. The caller decides what an
421
+ * unterminated sandbox means; this function never claims success.
422
+ */
423
+ export function waitForChildClose(child, graceMs = CHILD_CLOSE_GRACE_MS) {
424
+ // Spawn failed: no process exists, nothing holds the workspace, nothing to wait for.
425
+ if (child.pid === undefined && child.exitCode === null && child.signalCode === null) {
426
+ return Promise.resolve(true);
377
427
  }
378
- catch { /* already disconnected */ }
428
+ return new Promise((resolve) => {
429
+ // Named handlers so every listener and the timer are removed on both paths.
430
+ const onClose = () => settle(true);
431
+ const onTimeout = () => settle(false);
432
+ // Deliberately referenced: a killed child can leave nothing else pending,
433
+ // and an unreferenced timer would let the host exit with this promise
434
+ // unsettled instead of finishing the run. The wait stays bounded by graceMs.
435
+ const timer = setTimeout(onTimeout, graceMs);
436
+ const settle = (closed) => {
437
+ clearTimeout(timer);
438
+ child.removeListener('close', onClose);
439
+ resolve(closed);
440
+ };
441
+ child.on('close', onClose);
442
+ });
443
+ }
444
+ /**
445
+ * A sandbox process that did not close within the grace is a cleanup failure:
446
+ * it may still hold the workspace. Never report success in that case, and keep
447
+ * the original diagnosis when the run had already failed.
448
+ */
449
+ export function sandboxNotTerminatedResult(result, graceMs = CHILD_CLOSE_GRACE_MS) {
450
+ const notice = `Sandbox process did not terminate within ${graceMs}ms; its workspace may still be locked`;
451
+ return {
452
+ ...result,
453
+ success: false,
454
+ output: result.output ? `${result.output}\n${notice}` : notice,
455
+ };
456
+ }
457
+ function terminateChild(child) {
458
+ // No explicit disconnect(): measured on Node 24, closing the IPC channel by
459
+ // hand before the kill suppresses the process 'close' event entirely (only
460
+ // 'disconnect' and 'exit' arrive), and 'close' is the signal that the process
461
+ // released its stdio — and with it the working directory. The channel is
462
+ // closed by the kill anyway ('disconnect' still fires), and no message can be
463
+ // sent after this point: `settled` is already true.
379
464
  try {
380
465
  if (!child.killed)
381
466
  child.kill('SIGKILL');
@@ -384,11 +469,15 @@ function terminateChild(child) {
384
469
  }
385
470
  async function runInChild(code, timeoutMs, runtime, state) {
386
471
  const toolBindings = buildToolBindings(runtime.availableTools);
387
- const allowedTools = new Set(toolBindings.map((binding) => binding.toolName));
472
+ // Canonical calls use the full scoped catalogue; only JS shortcuts are capped.
473
+ const allowedTools = new Set(runtime.availableTools.filter(name => typeof name === 'string' && name && !['exec', 'code_exec'].includes(name)));
388
474
  return await new Promise((resolve) => {
389
475
  let settled = false;
390
476
  let stderr = '';
391
- let toolQueue = Promise.resolve();
477
+ const scheduler = new ToolCallScheduler();
478
+ const controller = new AbortController();
479
+ let toolCallCount = 0;
480
+ let emittedChars = 0;
392
481
  const child = spawn(process.execPath, childExecArgs(), {
393
482
  cwd: runtime.cwd || process.cwd(),
394
483
  env: {
@@ -406,10 +495,16 @@ async function runInChild(code, timeoutMs, runtime, state) {
406
495
  if (settled)
407
496
  return;
408
497
  settled = true;
498
+ controller.abort();
409
499
  clearTimeout(timer);
410
500
  runtime.abortSignal?.removeEventListener('abort', onAbort);
411
501
  terminateChild(child);
412
- resolve(result);
502
+ // Report completion only once `close` was observed: callers treat the
503
+ // resolved promise as "the sandbox no longer touches my workspace". A
504
+ // process that never closes is reported as a failure, never as success.
505
+ void waitForChildClose(child).then((closed) => {
506
+ resolve(closed ? result : sandboxNotTerminatedResult(result));
507
+ });
413
508
  };
414
509
  const timer = setTimeout(() => {
415
510
  finish({
@@ -443,8 +538,22 @@ async function runInChild(code, timeoutMs, runtime, state) {
443
538
  const message = raw;
444
539
  if (!message || typeof message !== 'object' || settled)
445
540
  return;
541
+ if (message.type === 'output') {
542
+ if (typeof message.output === 'string' && emittedChars + message.output.length <= CODE_EXEC_LIMITS.maxOutputChars) {
543
+ emittedChars += message.output.length;
544
+ try {
545
+ runtime.onOutput?.(message.output);
546
+ }
547
+ catch { /* Observers do not control execution. */ }
548
+ }
549
+ return;
550
+ }
446
551
  if (message.type === 'tool_call') {
447
- toolQueue = toolQueue.then(async () => {
552
+ if (++toolCallCount > CODE_EXEC_LIMITS.maxToolCalls || typeof message.argsJson !== 'string' || message.argsJson.length > CODE_EXEC_LIMITS.maxCodeChars) {
553
+ finish({ success: false, output: 'code_exec tool-call or argument limit exceeded' });
554
+ return;
555
+ }
556
+ void scheduler.schedule(runtime.parallelTools?.includes(message.toolName) === true, async () => {
448
557
  if (settled || !child.connected)
449
558
  return;
450
559
  if (!allowedTools.has(message.toolName) ||
@@ -476,13 +585,13 @@ async function runInChild(code, timeoutMs, runtime, state) {
476
585
  return;
477
586
  }
478
587
  try {
479
- const result = await runtime.executor(message.toolName, args);
588
+ const result = await runtime.executor(message.toolName, args, controller.signal);
480
589
  if (!settled && child.connected) {
481
590
  child.send({
482
591
  type: 'tool_result',
483
592
  id: message.id,
484
593
  ok: true,
485
- valueJson: serializeToolResult(result),
594
+ valueJson: serializeToolResult(result, runtime.resultFormat),
486
595
  });
487
596
  }
488
597
  }
@@ -536,6 +645,10 @@ async function runInChild(code, timeoutMs, runtime, state) {
536
645
  code,
537
646
  timeoutMs,
538
647
  toolBindings,
648
+ toolMetadata: toolBindings.map(binding => ({
649
+ name: binding.toolName,
650
+ description: runtime.toolMetadata?.find(tool => tool.name === binding.toolName)?.description.slice(0, 2000) ?? '',
651
+ })),
539
652
  storeEntries: Array.from(state.values.entries()),
540
653
  }, (error) => {
541
654
  if (error)
@@ -563,6 +676,10 @@ export class CodeExecTool extends BaseTool {
563
676
  type: 'number',
564
677
  description: `Execution timeout in milliseconds (${CODE_EXEC_LIMITS.minTimeoutMs}..${CODE_EXEC_LIMITS.maxTimeoutMs}, default ${CODE_EXEC_LIMITS.defaultTimeoutMs}).`,
565
678
  },
679
+ typecheck: {
680
+ type: 'boolean',
681
+ description: 'Perform isolated TypeScript typechecking before execution. Rejects code on diagnostic errors with zero runtime side effects. Default false.',
682
+ },
566
683
  };
567
684
  }
568
685
  validate(input) {
@@ -587,6 +704,13 @@ export class CodeExecTool extends BaseTool {
587
704
  errors: [`timeout_ms must be between ${CODE_EXEC_LIMITS.minTimeoutMs} and ${CODE_EXEC_LIMITS.maxTimeoutMs}`],
588
705
  };
589
706
  }
707
+ if (args.typecheck !== undefined &&
708
+ typeof args.typecheck !== 'boolean') {
709
+ return {
710
+ valid: false,
711
+ errors: ['typecheck must be a boolean'],
712
+ };
713
+ }
590
714
  return { valid: true };
591
715
  }
592
716
  async execute(input, context) {
@@ -595,9 +719,11 @@ export class CodeExecTool extends BaseTool {
595
719
  return this.error(validation.errors?.join('; ') ?? 'Invalid input');
596
720
  const code = input.code;
597
721
  const timeoutMs = normalizeTimeout(input.timeout_ms);
722
+ const typecheck = input.typecheck === true;
598
723
  const injectedRuntime = runtimeFromContext(context);
599
- const runtime = injectedRuntime ?? {
724
+ const runtime = injectedRuntime ? { ...injectedRuntime } : {
600
725
  scopeId: legacyScopeId(),
726
+ resultFormat: 'legacy',
601
727
  sessionId: legacySessionId,
602
728
  cwd: context?.cwd ?? process.cwd(),
603
729
  availableTools: legacyAvailableTools,
@@ -606,28 +732,46 @@ export class CodeExecTool extends BaseTool {
606
732
  error: `No ToolHandler executor is attached for ${toolName}`,
607
733
  })),
608
734
  };
609
- const state = getScopedState(runtime);
610
- const startedAt = Date.now();
611
- const childResult = await runInChild(code, timeoutMs, runtime, state);
612
- const elapsed = Date.now() - startedAt;
613
- if (!childResult.success) {
614
- logger.debug('code_exec failed', {
615
- scopeId: runtime.scopeId,
616
- timedOut: childResult.timedOut === true,
617
- elapsed,
735
+ runtime.abortSignal ??= context?.abortSignal;
736
+ let codeToRun = code;
737
+ if (typecheck) {
738
+ const { runCodeExecPreflight } = await import('./code-exec-preflight.js');
739
+ const preflightResult = await runCodeExecPreflight(code, runtime, context, {
740
+ signal: runtime.abortSignal,
618
741
  });
619
- return this.error(truncate(childResult.output, CODE_EXEC_LIMITS.maxErrorChars));
620
- }
621
- // Commit state transactionally only after a successful script.
622
- if (childResult.storeEntries) {
623
- state.values = new Map(childResult.storeEntries);
624
- state.lastAccess = Date.now();
742
+ if (!preflightResult.success) {
743
+ return this.error(preflightResult.error || 'TypeScript preflight validation failed');
744
+ }
745
+ if (preflightResult.transpiledCode) {
746
+ codeToRun = preflightResult.transpiledCode;
747
+ }
625
748
  }
626
- const status = childResult.yielded
627
- ? `Script yielded control after ${elapsed}ms`
628
- : `Script completed in ${elapsed}ms`;
629
- const output = childResult.output ? `${status}\n\n${childResult.output}` : status;
630
- return this.success(truncate(output, CODE_EXEC_LIMITS.maxOutputChars));
749
+ return runScoped(runtime.scopeId, async () => {
750
+ if (runtime.abortSignal?.aborted)
751
+ return this.error('Script cancelled');
752
+ const state = getScopedState(runtime);
753
+ const startedAt = Date.now();
754
+ const childResult = await runInChild(codeToRun, timeoutMs, runtime, state);
755
+ const elapsed = Date.now() - startedAt;
756
+ if (!childResult.success) {
757
+ logger.debug('code_exec failed', {
758
+ scopeId: runtime.scopeId,
759
+ timedOut: childResult.timedOut === true,
760
+ elapsed,
761
+ });
762
+ return this.error(truncate(childResult.output, CODE_EXEC_LIMITS.maxErrorChars));
763
+ }
764
+ // Commit state transactionally only after a successful script.
765
+ if (childResult.storeEntries) {
766
+ state.values = new Map(childResult.storeEntries);
767
+ state.lastAccess = Date.now();
768
+ }
769
+ const status = childResult.yielded
770
+ ? `Script yielded control after ${elapsed}ms`
771
+ : `Script completed in ${elapsed}ms`;
772
+ const output = childResult.output ? `${status}\n\n${childResult.output}` : status;
773
+ return this.success(truncate(output, CODE_EXEC_LIMITS.maxOutputChars));
774
+ }, runtime.abortSignal).catch(error => this.error(error instanceof Error ? error.message : String(error)));
631
775
  }
632
776
  }
633
777
  /** Factory used by all FormalToolRegistry assembly paths. */
@@ -1,9 +1,25 @@
1
1
  import { UnifiedVfsRouter } from '../services/vfs/unified-vfs-router.js';
2
2
  import * as path from "path";
3
- import { exec } from "child_process";
4
- import { promisify } from "util";
3
+ import { execFile } from "child_process";
5
4
  import { EventEmitter } from "events";
6
- const execAsync = promisify(exec);
5
+ const RIPGREP_TIMEOUT_MS = 30_000;
6
+ /**
7
+ * Run ripgrep without a shell. Resolves with stdout; exit status 1 means
8
+ * "no match" and resolves empty. Missing binary, timeout or real errors reject
9
+ * so the caller can fall back to the manual scan.
10
+ */
11
+ function runRipgrep(args, cwd) {
12
+ return new Promise((resolve, reject) => {
13
+ const child = execFile("rg", args, { cwd, maxBuffer: 10 * 1024 * 1024, timeout: RIPGREP_TIMEOUT_MS, windowsHide: true }, (error, stdout) => {
14
+ if (!error)
15
+ return resolve(stdout);
16
+ if (error.code === 1 && !error.killed)
17
+ return resolve("");
18
+ reject(error);
19
+ });
20
+ child.stdin?.end();
21
+ });
22
+ }
7
23
  const DEFAULT_TRIGGERS = [
8
24
  {
9
25
  pattern: "AI:",
@@ -110,15 +126,16 @@ export class CommentWatcher extends EventEmitter {
110
126
  // Build pattern for ripgrep
111
127
  const patterns = this.config.triggers.map((t) => t.pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
112
128
  const pattern = patterns.join("|");
113
- // Build ignore patterns
114
- const ignores = this.config.ignoreDirs.map((d) => `--glob '!${d}/**'`).join(" ");
115
- // Build file type filters
116
- const types = this.config.fileExtensions.map((e) => `--glob '*${e}'`).join(" ");
129
+ const rgArgs = [
130
+ "-n", "--no-heading", "--color=never", "--ignore-case",
131
+ ...this.config.ignoreDirs.flatMap((d) => ["--glob", `!${d}/**`]),
132
+ ...this.config.fileExtensions.flatMap((e) => ["--glob", `*${e}`]),
133
+ // Explicit path: without it ripgrep reads the inherited stdin pipe, which
134
+ // never closes inside the CLI, and the scan hangs forever.
135
+ "--", pattern, ".",
136
+ ];
117
137
  try {
118
- const { stdout } = await execAsync(`rg -n "${pattern}" ${ignores} ${types} || true`, {
119
- cwd: this.projectRoot,
120
- maxBuffer: 10 * 1024 * 1024,
121
- });
138
+ const stdout = await runRipgrep(rgArgs, this.projectRoot);
122
139
  const lines = stdout.trim().split("\n").filter(Boolean);
123
140
  for (const line of lines) {
124
141
  // Format: file:line:content
@@ -133,7 +150,7 @@ export class CommentWatcher extends EventEmitter {
133
150
  const triggerMatch = content.match(trigger.regex);
134
151
  if (triggerMatch) {
135
152
  this.detectedComments.push({
136
- file,
153
+ file: path.resolve(this.projectRoot, file),
137
154
  line: parseInt(lineNum),
138
155
  column: content.indexOf(triggerMatch[0]),
139
156
  trigger: trigger.pattern,
@@ -0,0 +1,56 @@
1
+ import type { CoreRootResolution } from '../identity/operational-self-model.js';
2
+ export interface CoreCodeRequest {
3
+ operation: 'list' | 'read' | 'search';
4
+ path?: string;
5
+ query?: string;
6
+ line?: number;
7
+ offset?: number;
8
+ }
9
+ /** Read only implementation files within the already-attested core, never the user's project. */
10
+ export declare function inspectCoreCode(core: CoreRootResolution, request: CoreCodeRequest): {
11
+ operation: string;
12
+ path: string;
13
+ line: number;
14
+ totalLines: number;
15
+ content: string;
16
+ truncated: boolean;
17
+ entries?: undefined;
18
+ offset?: undefined;
19
+ nextOffset?: undefined;
20
+ totalEntries?: undefined;
21
+ query?: undefined;
22
+ matches?: undefined;
23
+ } | {
24
+ operation: string;
25
+ path: string;
26
+ entries: {
27
+ name: string;
28
+ directory: boolean;
29
+ }[];
30
+ offset: number;
31
+ nextOffset: number | null;
32
+ totalEntries: number;
33
+ truncated: boolean;
34
+ line?: undefined;
35
+ totalLines?: undefined;
36
+ content?: undefined;
37
+ query?: undefined;
38
+ matches?: undefined;
39
+ } | {
40
+ operation: string;
41
+ path: string;
42
+ query: string;
43
+ matches: {
44
+ path: string;
45
+ line: number;
46
+ text: string;
47
+ }[];
48
+ truncated: boolean;
49
+ line?: undefined;
50
+ totalLines?: undefined;
51
+ content?: undefined;
52
+ entries?: undefined;
53
+ offset?: undefined;
54
+ nextOffset?: undefined;
55
+ totalEntries?: undefined;
56
+ };
@@ -0,0 +1,86 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ const TEXT_FILE = /\.(?:[cm]?[jt]sx?|md)$/i;
4
+ const MAX_FILE_BYTES = 512 * 1024;
5
+ /** Read only implementation files within the already-attested core, never the user's project. */
6
+ export function inspectCoreCode(core, request) {
7
+ if (core.layout === 'unknown')
8
+ throw new Error('Code Buddy implementation root is not attested.');
9
+ const root = fs.realpathSync(core.root);
10
+ const relative = request.path || (core.layout === 'source' ? 'src' : 'dist');
11
+ if (path.isAbsolute(relative) || relative.includes('\\') || relative.split('/').some(p => p === '..' || p.startsWith('.')) ||
12
+ !/^(src|dist|packages)(\/|$)/.test(relative)) {
13
+ throw new Error('Use a relative implementation path under src/, dist/ or packages/.');
14
+ }
15
+ const target = path.resolve(root, relative);
16
+ function confined(file) {
17
+ const real = fs.realpathSync(file);
18
+ const rel = path.relative(root, real);
19
+ return !path.isAbsolute(rel) && /^(src|dist|packages)(\/|$)/.test(rel.split(path.sep).join('/')) &&
20
+ !rel.split(path.sep).some(part => part.startsWith('.'));
21
+ }
22
+ if (!confined(target))
23
+ throw new Error('Implementation path escapes the core root.');
24
+ const stat = fs.statSync(target);
25
+ if (request.operation === 'read') {
26
+ if (!stat.isFile() || !TEXT_FILE.test(target) || stat.size > MAX_FILE_BYTES)
27
+ throw new Error('Expected an implementation text file of at most 512 KiB.');
28
+ const lines = fs.readFileSync(target, 'utf8').split('\n');
29
+ const start = Math.max(1, request.line ?? 1);
30
+ return { operation: 'read', path: relative, line: start, totalLines: lines.length,
31
+ content: lines.slice(start - 1, start + 119).map((text, i) => `${start + i}: ${text.slice(0, 500)}`).join('\n'),
32
+ truncated: start + 119 < lines.length };
33
+ }
34
+ if (!stat.isDirectory())
35
+ throw new Error('Expected an implementation directory.');
36
+ if (request.operation === 'list') {
37
+ const entries = fs.readdirSync(target, { withFileTypes: true }).filter(entry => !entry.name.startsWith('.') && entry.name !== 'node_modules' && !entry.isSymbolicLink() &&
38
+ (entry.isDirectory() || TEXT_FILE.test(entry.name)));
39
+ const offset = request.offset ?? 0;
40
+ if (!Number.isSafeInteger(offset) || offset < 0)
41
+ throw new Error('List offset must be a non-negative integer.');
42
+ const nextOffset = offset + 100 < entries.length ? offset + 100 : null;
43
+ return { operation: 'list', path: relative, entries: entries.slice(offset, offset + 100).map(entry => ({
44
+ name: entry.name, directory: entry.isDirectory(),
45
+ })), offset, nextOffset, totalEntries: entries.length, truncated: nextOffset !== null };
46
+ }
47
+ if (!request.query?.trim() || request.query.length > 160)
48
+ throw new Error('Search requires a literal query of 1–160 characters.');
49
+ const query = request.query.toLowerCase();
50
+ const matches = [];
51
+ const pending = [target];
52
+ let visited = 0;
53
+ let bytes = 0;
54
+ while (pending.length && visited < 4000 && matches.length < 30 && bytes < 8 * 1024 * 1024) {
55
+ const dir = pending.shift();
56
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
57
+ if (++visited >= 4000 || matches.length >= 30 || bytes >= 8 * 1024 * 1024)
58
+ break;
59
+ if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.isSymbolicLink())
60
+ continue;
61
+ const file = path.join(dir, entry.name);
62
+ if (!confined(file))
63
+ continue;
64
+ if (entry.isDirectory()) {
65
+ pending.push(file);
66
+ continue;
67
+ }
68
+ if (!entry.isFile() || !TEXT_FILE.test(entry.name))
69
+ continue;
70
+ const size = fs.statSync(file).size;
71
+ if (size > MAX_FILE_BYTES)
72
+ continue;
73
+ bytes += size;
74
+ const lines = fs.readFileSync(file, 'utf8').split('\n');
75
+ for (let i = 0; i < lines.length && matches.length < 30; i++) {
76
+ if (lines[i].toLowerCase().includes(query))
77
+ matches.push({
78
+ path: path.relative(root, file).split(path.sep).join('/'), line: i + 1, text: lines[i].slice(0, 500),
79
+ });
80
+ }
81
+ }
82
+ }
83
+ return { operation: 'search', path: relative, query: request.query, matches,
84
+ truncated: pending.length > 0 || visited >= 4000 || matches.length >= 30 || bytes >= 8 * 1024 * 1024 };
85
+ }
86
+ //# sourceMappingURL=core-code-inspection.js.map
@@ -10,7 +10,7 @@
10
10
  * commands it uses repeatedly and codify them as reusable skills.
11
11
  *
12
12
  * Skill file location:
13
- * .codebuddy/skills/workspace/<slug>/SKILL.md
13
+ * .codebuddy/skills/<authored-slug>/SKILL.md
14
14
  *
15
15
  * The tool generates valid SKILL.md frontmatter and validates that
16
16
  * the slug does not already exist before writing.
@@ -36,8 +36,7 @@ export interface CreateSkillInput {
36
36
  overwrite?: boolean;
37
37
  }
38
38
  export declare class CreateSkillTool {
39
- private get workspaceDir();
40
- execute(input: CreateSkillInput): Promise<ToolResult>;
39
+ execute(input: CreateSkillInput, cwd?: string): Promise<ToolResult>;
41
40
  getSchema(): {
42
41
  name: string;
43
42
  description: string;