@aitne/daemon 0.1.3 → 0.1.6

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/dist/adapters/notification-manager.d.ts +12 -0
  2. package/dist/adapters/notification-manager.d.ts.map +1 -1
  3. package/dist/adapters/notification-manager.js +39 -1
  4. package/dist/adapters/notification-manager.js.map +1 -1
  5. package/dist/adapters/whatsapp-adapter.d.ts.map +1 -1
  6. package/dist/adapters/whatsapp-adapter.js +0 -1
  7. package/dist/adapters/whatsapp-adapter.js.map +1 -1
  8. package/dist/api/integration-route-gate.d.ts +15 -11
  9. package/dist/api/integration-route-gate.d.ts.map +1 -1
  10. package/dist/api/integration-route-gate.js +60 -23
  11. package/dist/api/integration-route-gate.js.map +1 -1
  12. package/dist/api/json-body.d.ts +22 -7
  13. package/dist/api/json-body.d.ts.map +1 -1
  14. package/dist/api/json-body.js +27 -8
  15. package/dist/api/json-body.js.map +1 -1
  16. package/dist/api/routes/agent.d.ts.map +1 -1
  17. package/dist/api/routes/agent.js +25 -0
  18. package/dist/api/routes/agent.js.map +1 -1
  19. package/dist/api/routes/backends.d.ts.map +1 -1
  20. package/dist/api/routes/backends.js +96 -1
  21. package/dist/api/routes/backends.js.map +1 -1
  22. package/dist/api/routes/books.js +1 -1
  23. package/dist/api/routes/books.js.map +1 -1
  24. package/dist/api/routes/commands.d.ts.map +1 -1
  25. package/dist/api/routes/commands.js +16 -13
  26. package/dist/api/routes/commands.js.map +1 -1
  27. package/dist/api/routes/context.d.ts.map +1 -1
  28. package/dist/api/routes/context.js +26 -3
  29. package/dist/api/routes/context.js.map +1 -1
  30. package/dist/api/routes/dashboard.d.ts.map +1 -1
  31. package/dist/api/routes/dashboard.js +103 -5
  32. package/dist/api/routes/dashboard.js.map +1 -1
  33. package/dist/api/routes/fs.d.ts +23 -0
  34. package/dist/api/routes/fs.d.ts.map +1 -0
  35. package/dist/api/routes/fs.js +156 -0
  36. package/dist/api/routes/fs.js.map +1 -0
  37. package/dist/api/routes/fs.logic.d.ts +62 -0
  38. package/dist/api/routes/fs.logic.d.ts.map +1 -0
  39. package/dist/api/routes/fs.logic.js +137 -0
  40. package/dist/api/routes/fs.logic.js.map +1 -0
  41. package/dist/api/routes/github.d.ts.map +1 -1
  42. package/dist/api/routes/github.js +38 -5
  43. package/dist/api/routes/github.js.map +1 -1
  44. package/dist/api/routes/health.d.ts.map +1 -1
  45. package/dist/api/routes/health.js +4 -2
  46. package/dist/api/routes/health.js.map +1 -1
  47. package/dist/api/routes/integrations.d.ts +35 -6
  48. package/dist/api/routes/integrations.d.ts.map +1 -1
  49. package/dist/api/routes/integrations.js +192 -15
  50. package/dist/api/routes/integrations.js.map +1 -1
  51. package/dist/api/routes/mail.d.ts.map +1 -1
  52. package/dist/api/routes/mail.js +112 -46
  53. package/dist/api/routes/mail.js.map +1 -1
  54. package/dist/api/routes/metrics.d.ts +1 -0
  55. package/dist/api/routes/metrics.d.ts.map +1 -1
  56. package/dist/api/routes/metrics.js +24 -0
  57. package/dist/api/routes/metrics.js.map +1 -1
  58. package/dist/api/routes/observations.d.ts.map +1 -1
  59. package/dist/api/routes/observations.js +696 -30
  60. package/dist/api/routes/observations.js.map +1 -1
  61. package/dist/api/routes/setup-migrate.d.ts +9 -1
  62. package/dist/api/routes/setup-migrate.d.ts.map +1 -1
  63. package/dist/api/routes/setup-migrate.js +4 -2
  64. package/dist/api/routes/setup-migrate.js.map +1 -1
  65. package/dist/api/routes/skills.d.ts +9 -1
  66. package/dist/api/routes/skills.d.ts.map +1 -1
  67. package/dist/api/routes/skills.js +77 -17
  68. package/dist/api/routes/skills.js.map +1 -1
  69. package/dist/api/routes/voice.d.ts.map +1 -1
  70. package/dist/api/routes/voice.js +62 -4
  71. package/dist/api/routes/voice.js.map +1 -1
  72. package/dist/api/routes/wiki.d.ts +4 -0
  73. package/dist/api/routes/wiki.d.ts.map +1 -0
  74. package/dist/api/routes/wiki.js +1075 -0
  75. package/dist/api/routes/wiki.js.map +1 -0
  76. package/dist/api/server.d.ts +13 -0
  77. package/dist/api/server.d.ts.map +1 -1
  78. package/dist/api/server.js +27 -1
  79. package/dist/api/server.js.map +1 -1
  80. package/dist/bootstrap/adapters.d.ts +109 -0
  81. package/dist/bootstrap/adapters.d.ts.map +1 -0
  82. package/dist/bootstrap/adapters.js +237 -0
  83. package/dist/bootstrap/adapters.js.map +1 -0
  84. package/dist/bootstrap/catchup.d.ts +23 -0
  85. package/dist/bootstrap/catchup.d.ts.map +1 -0
  86. package/dist/bootstrap/catchup.js +124 -0
  87. package/dist/bootstrap/catchup.js.map +1 -0
  88. package/dist/bootstrap/schedule-helpers.d.ts +18 -0
  89. package/dist/bootstrap/schedule-helpers.d.ts.map +1 -0
  90. package/dist/bootstrap/schedule-helpers.js +96 -0
  91. package/dist/bootstrap/schedule-helpers.js.map +1 -0
  92. package/dist/bootstrap/services.d.ts +60 -0
  93. package/dist/bootstrap/services.d.ts.map +1 -0
  94. package/dist/bootstrap/services.js +209 -0
  95. package/dist/bootstrap/services.js.map +1 -0
  96. package/dist/config.d.ts.map +1 -1
  97. package/dist/config.js +26 -0
  98. package/dist/config.js.map +1 -1
  99. package/dist/core/agent-core.d.ts +25 -0
  100. package/dist/core/agent-core.d.ts.map +1 -1
  101. package/dist/core/agent-core.js.map +1 -1
  102. package/dist/core/backends/backend-router.d.ts +28 -1
  103. package/dist/core/backends/backend-router.d.ts.map +1 -1
  104. package/dist/core/backends/backend-router.js +58 -4
  105. package/dist/core/backends/backend-router.js.map +1 -1
  106. package/dist/core/backends/claude-auth.d.ts +70 -0
  107. package/dist/core/backends/claude-auth.d.ts.map +1 -0
  108. package/dist/core/backends/claude-auth.js +198 -0
  109. package/dist/core/backends/claude-auth.js.map +1 -0
  110. package/dist/core/backends/claude-code-core.d.ts +47 -119
  111. package/dist/core/backends/claude-code-core.d.ts.map +1 -1
  112. package/dist/core/backends/claude-code-core.js +166 -1561
  113. package/dist/core/backends/claude-code-core.js.map +1 -1
  114. package/dist/core/backends/claude-delegated.d.ts +86 -0
  115. package/dist/core/backends/claude-delegated.d.ts.map +1 -0
  116. package/dist/core/backends/claude-delegated.js +801 -0
  117. package/dist/core/backends/claude-delegated.js.map +1 -0
  118. package/dist/core/backends/claude-errors.d.ts +39 -0
  119. package/dist/core/backends/claude-errors.d.ts.map +1 -0
  120. package/dist/core/backends/claude-errors.js +71 -0
  121. package/dist/core/backends/claude-errors.js.map +1 -0
  122. package/dist/core/backends/claude-probe.d.ts +103 -0
  123. package/dist/core/backends/claude-probe.d.ts.map +1 -0
  124. package/dist/core/backends/claude-probe.js +336 -0
  125. package/dist/core/backends/claude-probe.js.map +1 -0
  126. package/dist/core/backends/claude-tool-collection.d.ts +135 -0
  127. package/dist/core/backends/claude-tool-collection.d.ts.map +1 -0
  128. package/dist/core/backends/claude-tool-collection.js +1093 -0
  129. package/dist/core/backends/claude-tool-collection.js.map +1 -0
  130. package/dist/core/backends/codex-core.d.ts.map +1 -1
  131. package/dist/core/backends/codex-core.js +36 -0
  132. package/dist/core/backends/codex-core.js.map +1 -1
  133. package/dist/core/backends/gemini-cli-core.d.ts +45 -5
  134. package/dist/core/backends/gemini-cli-core.d.ts.map +1 -1
  135. package/dist/core/backends/gemini-cli-core.js +146 -36
  136. package/dist/core/backends/gemini-cli-core.js.map +1 -1
  137. package/dist/core/backends/plan-presets.d.ts +3 -1
  138. package/dist/core/backends/plan-presets.d.ts.map +1 -1
  139. package/dist/core/backends/plan-presets.js +42 -2
  140. package/dist/core/backends/plan-presets.js.map +1 -1
  141. package/dist/core/backends/prompt-utils.d.ts +1 -0
  142. package/dist/core/backends/prompt-utils.d.ts.map +1 -1
  143. package/dist/core/backends/prompt-utils.js +60 -3
  144. package/dist/core/backends/prompt-utils.js.map +1 -1
  145. package/dist/core/bang-commands/commands-help.d.ts +5 -0
  146. package/dist/core/bang-commands/commands-help.d.ts.map +1 -0
  147. package/dist/core/bang-commands/commands-help.js +69 -0
  148. package/dist/core/bang-commands/commands-help.js.map +1 -0
  149. package/dist/core/bang-commands/commands-wiki.d.ts +75 -0
  150. package/dist/core/bang-commands/commands-wiki.d.ts.map +1 -0
  151. package/dist/core/bang-commands/commands-wiki.js +574 -0
  152. package/dist/core/bang-commands/commands-wiki.js.map +1 -0
  153. package/dist/core/bang-commands/index.d.ts +4 -2
  154. package/dist/core/bang-commands/index.d.ts.map +1 -1
  155. package/dist/core/bang-commands/index.js +15 -1
  156. package/dist/core/bang-commands/index.js.map +1 -1
  157. package/dist/core/bang-commands/registry.d.ts +47 -4
  158. package/dist/core/bang-commands/registry.d.ts.map +1 -1
  159. package/dist/core/bang-commands/registry.js +85 -15
  160. package/dist/core/bang-commands/registry.js.map +1 -1
  161. package/dist/core/context-builder.d.ts +53 -12
  162. package/dist/core/context-builder.d.ts.map +1 -1
  163. package/dist/core/context-builder.js +240 -92
  164. package/dist/core/context-builder.js.map +1 -1
  165. package/dist/core/daemon-api-cli.d.ts.map +1 -1
  166. package/dist/core/daemon-api-cli.js +50 -2
  167. package/dist/core/daemon-api-cli.js.map +1 -1
  168. package/dist/core/dispatcher-date-utils.d.ts +49 -0
  169. package/dist/core/dispatcher-date-utils.d.ts.map +1 -0
  170. package/dist/core/dispatcher-date-utils.js +132 -0
  171. package/dist/core/dispatcher-date-utils.js.map +1 -0
  172. package/dist/core/dispatcher-error-handling.d.ts +159 -0
  173. package/dist/core/dispatcher-error-handling.d.ts.map +1 -0
  174. package/dist/core/dispatcher-error-handling.js +393 -0
  175. package/dist/core/dispatcher-error-handling.js.map +1 -0
  176. package/dist/core/dispatcher-hourly-check.d.ts +150 -0
  177. package/dist/core/dispatcher-hourly-check.d.ts.map +1 -0
  178. package/dist/core/dispatcher-hourly-check.js +665 -0
  179. package/dist/core/dispatcher-hourly-check.js.map +1 -0
  180. package/dist/core/dispatcher-message-handler.d.ts +170 -0
  181. package/dist/core/dispatcher-message-handler.d.ts.map +1 -0
  182. package/dist/core/dispatcher-message-handler.js +1064 -0
  183. package/dist/core/dispatcher-message-handler.js.map +1 -0
  184. package/dist/core/dispatcher-morning-routine.d.ts +169 -0
  185. package/dist/core/dispatcher-morning-routine.d.ts.map +1 -0
  186. package/dist/core/dispatcher-morning-routine.js +449 -0
  187. package/dist/core/dispatcher-morning-routine.js.map +1 -0
  188. package/dist/core/dispatcher-prompt.d.ts +107 -0
  189. package/dist/core/dispatcher-prompt.d.ts.map +1 -0
  190. package/dist/core/dispatcher-prompt.js +227 -0
  191. package/dist/core/dispatcher-prompt.js.map +1 -0
  192. package/dist/core/dispatcher-repository-helpers.d.ts +39 -0
  193. package/dist/core/dispatcher-repository-helpers.d.ts.map +1 -0
  194. package/dist/core/dispatcher-repository-helpers.js +86 -0
  195. package/dist/core/dispatcher-repository-helpers.js.map +1 -0
  196. package/dist/core/dispatcher-result-processor.d.ts +168 -0
  197. package/dist/core/dispatcher-result-processor.d.ts.map +1 -0
  198. package/dist/core/dispatcher-result-processor.js +533 -0
  199. package/dist/core/dispatcher-result-processor.js.map +1 -0
  200. package/dist/core/dispatcher-scheduled-tasks.d.ts +406 -0
  201. package/dist/core/dispatcher-scheduled-tasks.d.ts.map +1 -0
  202. package/dist/core/dispatcher-scheduled-tasks.js +1032 -0
  203. package/dist/core/dispatcher-scheduled-tasks.js.map +1 -0
  204. package/dist/core/dispatcher-types.d.ts +411 -0
  205. package/dist/core/dispatcher-types.d.ts.map +1 -0
  206. package/dist/core/dispatcher-types.js +106 -0
  207. package/dist/core/dispatcher-types.js.map +1 -0
  208. package/dist/core/dispatcher.d.ts +122 -610
  209. package/dist/core/dispatcher.d.ts.map +1 -1
  210. package/dist/core/dispatcher.js +365 -3521
  211. package/dist/core/dispatcher.js.map +1 -1
  212. package/dist/core/integration-health.d.ts +18 -10
  213. package/dist/core/integration-health.d.ts.map +1 -1
  214. package/dist/core/integration-health.js +31 -1
  215. package/dist/core/integration-health.js.map +1 -1
  216. package/dist/core/integration-lifecycle.d.ts +65 -0
  217. package/dist/core/integration-lifecycle.d.ts.map +1 -1
  218. package/dist/core/integration-lifecycle.js +163 -14
  219. package/dist/core/integration-lifecycle.js.map +1 -1
  220. package/dist/core/integration-main-backend.d.ts +40 -0
  221. package/dist/core/integration-main-backend.d.ts.map +1 -1
  222. package/dist/core/integration-main-backend.js +89 -2
  223. package/dist/core/integration-main-backend.js.map +1 -1
  224. package/dist/core/management-md.d.ts +51 -17
  225. package/dist/core/management-md.d.ts.map +1 -1
  226. package/dist/core/management-md.js +233 -56
  227. package/dist/core/management-md.js.map +1 -1
  228. package/dist/core/metrics.d.ts +127 -0
  229. package/dist/core/metrics.d.ts.map +1 -1
  230. package/dist/core/metrics.js +256 -1
  231. package/dist/core/metrics.js.map +1 -1
  232. package/dist/core/output-language-policy.d.ts +74 -0
  233. package/dist/core/output-language-policy.d.ts.map +1 -0
  234. package/dist/core/output-language-policy.js +194 -0
  235. package/dist/core/output-language-policy.js.map +1 -0
  236. package/dist/core/prompts.d.ts +3 -1
  237. package/dist/core/prompts.d.ts.map +1 -1
  238. package/dist/core/prompts.js +161 -3
  239. package/dist/core/prompts.js.map +1 -1
  240. package/dist/core/repository-management-docs.d.ts +24 -0
  241. package/dist/core/repository-management-docs.d.ts.map +1 -1
  242. package/dist/core/repository-management-docs.js +210 -26
  243. package/dist/core/repository-management-docs.js.map +1 -1
  244. package/dist/core/roadmap-validate.js +13 -1
  245. package/dist/core/roadmap-validate.js.map +1 -1
  246. package/dist/core/routine-acquisition-plan.d.ts +182 -0
  247. package/dist/core/routine-acquisition-plan.d.ts.map +1 -0
  248. package/dist/core/routine-acquisition-plan.js +367 -0
  249. package/dist/core/routine-acquisition-plan.js.map +1 -0
  250. package/dist/core/routine-fetch-window-retry.d.ts +109 -0
  251. package/dist/core/routine-fetch-window-retry.d.ts.map +1 -0
  252. package/dist/core/routine-fetch-window-retry.js +210 -0
  253. package/dist/core/routine-fetch-window-retry.js.map +1 -0
  254. package/dist/core/routine-fetch-window-runner.d.ts +427 -0
  255. package/dist/core/routine-fetch-window-runner.d.ts.map +1 -0
  256. package/dist/core/routine-fetch-window-runner.js +1591 -0
  257. package/dist/core/routine-fetch-window-runner.js.map +1 -0
  258. package/dist/core/routine-windows.d.ts +171 -0
  259. package/dist/core/routine-windows.d.ts.map +1 -0
  260. package/dist/core/routine-windows.js +377 -0
  261. package/dist/core/routine-windows.js.map +1 -0
  262. package/dist/core/scheduler.d.ts +50 -2
  263. package/dist/core/scheduler.d.ts.map +1 -1
  264. package/dist/core/scheduler.js +88 -7
  265. package/dist/core/scheduler.js.map +1 -1
  266. package/dist/core/skill-curation/declarations.d.ts.map +1 -1
  267. package/dist/core/skill-curation/declarations.js +11 -12
  268. package/dist/core/skill-curation/declarations.js.map +1 -1
  269. package/dist/core/skill-source-paths.d.ts +14 -0
  270. package/dist/core/skill-source-paths.d.ts.map +1 -0
  271. package/dist/core/skill-source-paths.js +82 -0
  272. package/dist/core/skill-source-paths.js.map +1 -0
  273. package/dist/core/skills-compiler.d.ts +29 -0
  274. package/dist/core/skills-compiler.d.ts.map +1 -1
  275. package/dist/core/skills-compiler.js +166 -30
  276. package/dist/core/skills-compiler.js.map +1 -1
  277. package/dist/core/skills-manifest.d.ts.map +1 -1
  278. package/dist/core/skills-manifest.js +72 -0
  279. package/dist/core/skills-manifest.js.map +1 -1
  280. package/dist/core/system-reset.d.ts +25 -0
  281. package/dist/core/system-reset.d.ts.map +1 -1
  282. package/dist/core/system-reset.js +72 -2
  283. package/dist/core/system-reset.js.map +1 -1
  284. package/dist/core/wiki/approval-queue.d.ts +31 -0
  285. package/dist/core/wiki/approval-queue.d.ts.map +1 -0
  286. package/dist/core/wiki/approval-queue.js +44 -0
  287. package/dist/core/wiki/approval-queue.js.map +1 -0
  288. package/dist/core/wiki/bridge.d.ts +74 -0
  289. package/dist/core/wiki/bridge.d.ts.map +1 -0
  290. package/dist/core/wiki/bridge.js +405 -0
  291. package/dist/core/wiki/bridge.js.map +1 -0
  292. package/dist/core/wiki/compile-lock.d.ts +42 -0
  293. package/dist/core/wiki/compile-lock.d.ts.map +1 -0
  294. package/dist/core/wiki/compile-lock.js +55 -0
  295. package/dist/core/wiki/compile-lock.js.map +1 -0
  296. package/dist/core/wiki/compile-preview.d.ts +8 -0
  297. package/dist/core/wiki/compile-preview.d.ts.map +1 -0
  298. package/dist/core/wiki/compile-preview.js +200 -0
  299. package/dist/core/wiki/compile-preview.js.map +1 -0
  300. package/dist/core/wiki/cost-estimate.d.ts +30 -0
  301. package/dist/core/wiki/cost-estimate.d.ts.map +1 -0
  302. package/dist/core/wiki/cost-estimate.js +243 -0
  303. package/dist/core/wiki/cost-estimate.js.map +1 -0
  304. package/dist/core/wiki/dispatcher.d.ts +48 -0
  305. package/dist/core/wiki/dispatcher.d.ts.map +1 -0
  306. package/dist/core/wiki/dispatcher.js +92 -0
  307. package/dist/core/wiki/dispatcher.js.map +1 -0
  308. package/dist/core/wiki/git-precompile.d.ts +86 -0
  309. package/dist/core/wiki/git-precompile.d.ts.map +1 -0
  310. package/dist/core/wiki/git-precompile.js +96 -0
  311. package/dist/core/wiki/git-precompile.js.map +1 -0
  312. package/dist/core/wiki/import-migrate.d.ts +38 -0
  313. package/dist/core/wiki/import-migrate.d.ts.map +1 -0
  314. package/dist/core/wiki/import-migrate.js +310 -0
  315. package/dist/core/wiki/import-migrate.js.map +1 -0
  316. package/dist/core/wiki/import-probe.d.ts +76 -0
  317. package/dist/core/wiki/import-probe.d.ts.map +1 -0
  318. package/dist/core/wiki/import-probe.js +245 -0
  319. package/dist/core/wiki/import-probe.js.map +1 -0
  320. package/dist/core/wiki/index-cache.d.ts +39 -0
  321. package/dist/core/wiki/index-cache.d.ts.map +1 -0
  322. package/dist/core/wiki/index-cache.js +152 -0
  323. package/dist/core/wiki/index-cache.js.map +1 -0
  324. package/dist/core/wiki/multi-url-dispatch.d.ts +52 -0
  325. package/dist/core/wiki/multi-url-dispatch.d.ts.map +1 -0
  326. package/dist/core/wiki/multi-url-dispatch.js +72 -0
  327. package/dist/core/wiki/multi-url-dispatch.js.map +1 -0
  328. package/dist/core/wiki/wiki-fts.d.ts +75 -0
  329. package/dist/core/wiki/wiki-fts.d.ts.map +1 -0
  330. package/dist/core/wiki/wiki-fts.js +265 -0
  331. package/dist/core/wiki/wiki-fts.js.map +1 -0
  332. package/dist/core/wiki/workspaces.d.ts +101 -0
  333. package/dist/core/wiki/workspaces.d.ts.map +1 -0
  334. package/dist/core/wiki/workspaces.js +352 -0
  335. package/dist/core/wiki/workspaces.js.map +1 -0
  336. package/dist/core/wiki/write-strategy.d.ts +70 -0
  337. package/dist/core/wiki/write-strategy.d.ts.map +1 -0
  338. package/dist/core/wiki/write-strategy.js +112 -0
  339. package/dist/core/wiki/write-strategy.js.map +1 -0
  340. package/dist/core/workdir.d.ts +8 -1
  341. package/dist/core/workdir.d.ts.map +1 -1
  342. package/dist/core/workdir.js +4 -1
  343. package/dist/core/workdir.js.map +1 -1
  344. package/dist/db/observations.d.ts +45 -2
  345. package/dist/db/observations.d.ts.map +1 -1
  346. package/dist/db/observations.js +112 -14
  347. package/dist/db/observations.js.map +1 -1
  348. package/dist/db/schema.d.ts.map +1 -1
  349. package/dist/db/schema.js +135 -25
  350. package/dist/db/schema.js.map +1 -1
  351. package/dist/db/wiki-store.d.ts +3 -0
  352. package/dist/db/wiki-store.d.ts.map +1 -0
  353. package/dist/db/wiki-store.js +7 -0
  354. package/dist/db/wiki-store.js.map +1 -0
  355. package/dist/index.js +159 -610
  356. package/dist/index.js.map +1 -1
  357. package/dist/messaging/url-extract.d.ts +8 -0
  358. package/dist/messaging/url-extract.d.ts.map +1 -0
  359. package/dist/messaging/url-extract.js +41 -0
  360. package/dist/messaging/url-extract.js.map +1 -0
  361. package/dist/observers/delegated-sync-worker.d.ts +52 -1
  362. package/dist/observers/delegated-sync-worker.d.ts.map +1 -1
  363. package/dist/observers/delegated-sync-worker.js +75 -18
  364. package/dist/observers/delegated-sync-worker.js.map +1 -1
  365. package/dist/observers/imminent-event-scheduler.d.ts +20 -7
  366. package/dist/observers/imminent-event-scheduler.d.ts.map +1 -1
  367. package/dist/observers/imminent-event-scheduler.js +134 -29
  368. package/dist/observers/imminent-event-scheduler.js.map +1 -1
  369. package/dist/observers/mail-poller.d.ts +12 -5
  370. package/dist/observers/mail-poller.d.ts.map +1 -1
  371. package/dist/observers/mail-poller.js +36 -14
  372. package/dist/observers/mail-poller.js.map +1 -1
  373. package/dist/observers/manager.d.ts +37 -5
  374. package/dist/observers/manager.d.ts.map +1 -1
  375. package/dist/observers/manager.js +28 -10
  376. package/dist/observers/manager.js.map +1 -1
  377. package/dist/safety/always-disallowed.d.ts +65 -0
  378. package/dist/safety/always-disallowed.d.ts.map +1 -1
  379. package/dist/safety/always-disallowed.js +106 -10
  380. package/dist/safety/always-disallowed.js.map +1 -1
  381. package/dist/safety/audit.d.ts +46 -1
  382. package/dist/safety/audit.d.ts.map +1 -1
  383. package/dist/safety/audit.js +79 -16
  384. package/dist/safety/audit.js.map +1 -1
  385. package/dist/safety/risk-classifier.d.ts.map +1 -1
  386. package/dist/safety/risk-classifier.js +29 -0
  387. package/dist/safety/risk-classifier.js.map +1 -1
  388. package/dist/services/delegated-backend-invoker.d.ts +1 -51
  389. package/dist/services/delegated-backend-invoker.d.ts.map +1 -1
  390. package/dist/services/delegated-backend-invoker.js +41 -480
  391. package/dist/services/delegated-backend-invoker.js.map +1 -1
  392. package/dist/services/delegated-invoker-audit.d.ts +94 -0
  393. package/dist/services/delegated-invoker-audit.d.ts.map +1 -0
  394. package/dist/services/delegated-invoker-audit.js +238 -0
  395. package/dist/services/delegated-invoker-audit.js.map +1 -0
  396. package/dist/services/delegated-invoker-cache-hits.d.ts +34 -0
  397. package/dist/services/delegated-invoker-cache-hits.d.ts.map +1 -0
  398. package/dist/services/delegated-invoker-cache-hits.js +104 -0
  399. package/dist/services/delegated-invoker-cache-hits.js.map +1 -0
  400. package/dist/services/delegated-invoker-janitors.d.ts +28 -0
  401. package/dist/services/delegated-invoker-janitors.d.ts.map +1 -0
  402. package/dist/services/delegated-invoker-janitors.js +104 -0
  403. package/dist/services/delegated-invoker-janitors.js.map +1 -0
  404. package/dist/services/delegated-invoker-utils.d.ts +42 -0
  405. package/dist/services/delegated-invoker-utils.d.ts.map +1 -0
  406. package/dist/services/delegated-invoker-utils.js +100 -0
  407. package/dist/services/delegated-invoker-utils.js.map +1 -0
  408. package/dist/services/delegated-task-runtime.d.ts +1 -1
  409. package/dist/services/delegated-task-runtime.js +1 -1
  410. package/dist/services/integrations/snapshot-partitions.d.ts +5 -0
  411. package/dist/services/integrations/snapshot-partitions.d.ts.map +1 -1
  412. package/dist/services/integrations/snapshot-partitions.js +12 -0
  413. package/dist/services/integrations/snapshot-partitions.js.map +1 -1
  414. package/dist/services/voice/transcriber-impl.d.ts.map +1 -1
  415. package/dist/services/voice/transcriber-impl.js +7 -8
  416. package/dist/services/voice/transcriber-impl.js.map +1 -1
  417. package/dist/settings/runtime-settings.d.ts +12 -1
  418. package/dist/settings/runtime-settings.d.ts.map +1 -1
  419. package/dist/settings/runtime-settings.js +59 -1
  420. package/dist/settings/runtime-settings.js.map +1 -1
  421. package/package.json +2 -2
@@ -1,291 +1,58 @@
1
1
  import { query, } from "@anthropic-ai/claude-agent-sdk";
2
- import { collectSessionDeniedTools, INTEGRATION_DESCRIPTORS, INTEGRATION_KEYS, isAutonomousProcessKey, matchRunAllowedToolPattern, } from "@aitne/shared";
3
- import { readIntegrations } from "../../db/integrations-store.js";
2
+ import { isAutonomousProcessKey, } from "@aitne/shared";
4
3
  import { buildExecutionPrompt, buildSummaryPrompt, } from "./prompt-utils.js";
5
- import { homedir } from "node:os";
6
- import { resolve as resolvePath, isAbsolute } from "node:path";
7
4
  import { getContextDir } from "../../config.js";
8
5
  import { materializeMcpForSession } from "../../services/mcp/session-materializer.js";
9
6
  import { parseMcpToolName } from "../../services/mcp/risk.js";
10
7
  import { logMcpToolCall, updateMcpToolCallResult } from "../../services/mcp/tool-audit.js";
11
- import { BackendQuotaError, BackendDecisiveFailure, DelegatedProxyTimeoutError, classifyAbortReason, } from "../agent-core.js";
12
- import { IdleWatchdog } from "./idle-watchdog.js";
13
- import { DELEGATED_PROXY_DEFAULTS } from "../../services/delegated-proxy-config.js";
14
- import { buildDelegatedToolPrompt, emptyCost, flattenToolResultContent, tryParseToolResult, withDurationMs, } from "../../services/delegated-tool-runtime.js";
8
+ import { BackendQuotaError, BackendDecisiveFailure, } from "../agent-core.js";
9
+ import { flattenToolResultContent } from "../../services/delegated-tool-runtime.js";
10
+ import { runDelegatedTool as runDelegatedToolFn, runDelegatedTask as runDelegatedTaskFn, } from "./claude-delegated.js";
15
11
  import { createSessionWorkdir, cleanupSessionWorkdir } from "../workdir.js";
16
12
  import { buildDaemonApiCliEnv } from "../daemon-api-cli.js";
17
13
  import { createLogger } from "../../logging.js";
18
14
  import { DEFAULT_CLAUDE_HIGH_MODEL, DEFAULT_CLAUDE_MEDIUM_MODEL, findRegisteredModel, getModelsForBackend, } from "./model-registry.js";
19
- import { readClaudeCredentials } from "./claude-credentials-store.js";
20
- import { ALWAYS_DISALLOWED_TOOLS, classifyAbsoluteBlock, } from "../../safety/always-disallowed.js";
21
- import { recordAbsoluteBlockAudit } from "../../safety/absolute-block-audit.js";
22
- import { isPathInsideOrEqual, shellPathForms, } from "../path-compat.js";
23
- import { CliPathCache, isPlausibleAnthropicApiKey } from "./cli-utils.js";
24
- import { probeApiKeyServerSide } from "./api-key-probe.js";
15
+ import { ALWAYS_DISALLOWED_TOOLS } from "../../safety/always-disallowed.js";
16
+ import { CliPathCache } from "./cli-utils.js";
25
17
  import { extractSilentApiErrors, logSilentApiErrors, } from "./silent-api-error-detector.js";
18
+ import { CLAUDE_PROBE_TOOLS_PROMPT, computeDelegatedClaudeTools, computeNativeClaudeTools, describeClaudeProbeResultError, extractClaudeProbeTools, } from "./claude-probe.js";
19
+ import { AgentTimeoutError, extractClaudeCodeQuotaResetHint, isClaudeCodeMaxBudgetError, isClaudeCodeQuotaError, } from "./claude-errors.js";
20
+ import { checkAuth as checkAuthFn, checkAuthDetailed as checkAuthDetailedFn, getErrorCode, getErrorMessage, getErrorStatus, getErrorType, isAuthError, } from "./claude-auth.js";
21
+ import { buildSecurityHooks, getAllowedTools as getAllowedToolsFn, getDelegatedClaudeTools as getDelegatedClaudeToolsFn, getNativeClaudeTools as getNativeClaudeToolsFn, getSessionDeniedTools as getSessionDeniedToolsFn, } from "./claude-tool-collection.js";
22
+ // Re-exports kept narrow on purpose: only the symbols `claude-code-core.test.ts`
23
+ // imports from this module. Internal consumers (this file, claude-auth.ts,
24
+ // claude-tool-collection.ts) import directly from `./claude-probe.js` /
25
+ // `./claude-errors.js` so the re-export is not a second public entry point.
26
+ // Each symbol here previously lived in this file before the §8 file-split;
27
+ // keeping them re-exported preserves the test's import path without
28
+ // re-routing the test suite. See `docs/design/appendices/file-split-plan.md` §8.
29
+ export { AgentTimeoutError, CLAUDE_PROBE_TOOLS_PROMPT, computeDelegatedClaudeTools, computeNativeClaudeTools, extractClaudeCodeQuotaResetHint, isClaudeCodeQuotaError, };
26
30
  const logger = createLogger("claude-code-core");
27
31
  /**
28
- * Detect which cloud-provider mode the Claude Code SDK is currently
29
- * configured for, based on the documented `CLAUDE_CODE_USE_*` env flags.
30
- * Returns null when running in the default direct-API-key / OAuth mode.
32
+ * SDK `settingSources` opt-in for the daemon's `query()` calls.
31
33
  *
32
- * Required-env spec follows the official Claude Code docs (verified
33
- * 2026-05):
34
- * - **Bedrock** only `AWS_REGION` is required by Claude Code itself
35
- * (the docs explicitly call this out). AWS credentials flow through
36
- * the SDK's default credential chain, which can come from any of
37
- * AWS_ACCESS_KEY_ID/SECRET, AWS_BEARER_TOKEN_BEDROCK, AWS_PROFILE, or
38
- * `~/.aws/`. Requiring access-key + secret here would falsely fail
39
- * bearer-token / profile setups.
40
- * - **Vertex** `ANTHROPIC_VERTEX_PROJECT_ID` + `CLOUD_ML_REGION`.
41
- * GCP credentials flow through ADC, which can be `gcloud auth
42
- * application-default login` or a `GOOGLE_APPLICATION_CREDENTIALS`
43
- * file path; we don't require either at this layer.
44
- * - **Foundry** — `ANTHROPIC_FOUNDRY_RESOURCE` OR
45
- * `ANTHROPIC_FOUNDRY_BASE_URL`. The API key is optional; without it,
46
- * Claude Code uses the Azure DefaultAzureCredential chain.
34
+ * The Claude Agent SDK defaults `settingSources` to `[]` ("SDK isolation
35
+ * mode", per `sdk.d.ts` — no filesystem settings loaded). With the default,
36
+ * the spawned Claude Code subprocess does NOT read `~/.claude/settings.json`,
37
+ * which is where the user's claude.ai-bound MCP connectors live
38
+ * (`mcp__claude_ai_Gmail__*`, `mcp__claude_ai_Google_Calendar__*`,
39
+ * `mcp__claude_ai_Notion__*`, etc.). Without that, an integration in
40
+ * `native:claude` mode emits an acquisition plan whose tools do not exist
41
+ * in the session, the pre-pass produces no JSON, and the parent routine
42
+ * collapses to a heartbeat-only shadow run.
47
43
  *
48
- * The SDK reads these flags itself; the daemon only inspects them so
49
- * the auth probes (`checkAuth` / `checkAuthDetailed`) can return the
50
- * right `method` and skip the Anthropic-API probe (which would 401
51
- * against a Bedrock / Vertex / Foundry deployment).
52
- */
53
- function detectCloudProviderEnv() {
54
- if (process.env.CLAUDE_CODE_USE_BEDROCK?.trim() === "1") {
55
- const missing = [];
56
- if (!process.env.AWS_REGION?.trim())
57
- missing.push("AWS_REGION");
58
- return {
59
- method: "bedrock",
60
- flagEnvVar: "CLAUDE_CODE_USE_BEDROCK",
61
- label: "Amazon Bedrock",
62
- missing,
63
- };
64
- }
65
- if (process.env.CLAUDE_CODE_USE_VERTEX?.trim() === "1") {
66
- const required = [
67
- "ANTHROPIC_VERTEX_PROJECT_ID",
68
- "CLOUD_ML_REGION",
69
- ];
70
- return {
71
- method: "vertex",
72
- flagEnvVar: "CLAUDE_CODE_USE_VERTEX",
73
- label: "Google Vertex AI",
74
- missing: required.filter((name) => !process.env[name]?.trim()),
75
- };
76
- }
77
- if (process.env.CLAUDE_CODE_USE_FOUNDRY?.trim() === "1") {
78
- const hasResource = Boolean(process.env.ANTHROPIC_FOUNDRY_RESOURCE?.trim());
79
- const hasBaseUrl = Boolean(process.env.ANTHROPIC_FOUNDRY_BASE_URL?.trim());
80
- return {
81
- method: "foundry",
82
- flagEnvVar: "CLAUDE_CODE_USE_FOUNDRY",
83
- label: "Microsoft Foundry",
84
- missing: hasResource || hasBaseUrl
85
- ? []
86
- : ["ANTHROPIC_FOUNDRY_RESOURCE or ANTHROPIC_FOUNDRY_BASE_URL"],
87
- };
88
- }
89
- return null;
90
- }
91
- // Probe data is derived from `INTEGRATION_DESCRIPTORS.backendConnectors.claude`
92
- // at module init, so adding a new delegated integration only requires the
93
- // registry update — the prompt, prefix list, and tool-name regex follow
94
- // automatically. Before this was registry-driven, every new integration
95
- // silently broke its own probe (`present` permanently false) until someone
96
- // remembered to edit four constants in lockstep. See
97
- // `docs/design/17-delegated-mode-v2.md` §7.1.
98
- //
99
- // Tool names span two character classes:
100
- // - Gmail / Calendar: `search_threads`, `list_events` (snake_case only)
101
- // - Notion: `notion-search`, `notion-create-pages` (kebab-case)
102
- // The trailing `[A-Za-z0-9_-]+` class covers both. Namespace strings are
103
- // regex-escaped because Codex (`._`) and Gemini (`.`) namespaces contain
104
- // metacharacters.
105
- function buildClaudeProbeData() {
106
- const meta = [];
107
- for (const key of INTEGRATION_KEYS) {
108
- const descriptor = INTEGRATION_DESCRIPTORS[key];
109
- const connector = descriptor.backendConnectors.claude;
110
- if (!connector)
111
- continue;
112
- const seen = new Set();
113
- for (const tools of Object.values(connector.capabilityTools)) {
114
- for (const t of tools)
115
- seen.add(t);
116
- }
117
- meta.push({
118
- displayName: descriptor.displayName,
119
- toolNamespace: connector.toolNamespace,
120
- requiredCapabilities: connector.requiredCapabilities,
121
- capabilityToolNames: Array.from(seen),
122
- });
123
- }
124
- const prefixes = meta.map((m) => m.toolNamespace);
125
- const lines = ["Use `ToolSearch` only."];
126
- for (const m of meta) {
127
- // Keyword query: display name + required capability words split on
128
- // `_` / `-`. Mirrors the pre-registry-driven prompts (Gmail used
129
- // `'Gmail search read draft label'`, Calendar used `'Google Calendar
130
- // list get create update delete event'`) — both are display name +
131
- // requiredCapabilities expanded into word tokens. Using the
132
- // capability set (semantic) rather than every capability tool name
133
- // (mechanical) keeps ToolSearch's token-overlap ranking sharp; a
134
- // bag-of-tool-name-fragments query dilutes the signal across
135
- // ~15-30 tokens per integration and demotes the actually-relevant
136
- // hits.
137
- const queryWords = [
138
- m.displayName,
139
- ...m.requiredCapabilities.flatMap((c) => c.split(/[-_]/)),
140
- ]
141
- .join(" ")
142
- .replace(/\s+/g, " ")
143
- .trim();
144
- lines.push(`Search for ${m.displayName} connector tools with query '${queryWords}' and max_results 20.`);
145
- }
146
- lines.push("Do not call any of the searched MCP tools.");
147
- lines.push(`After the searches, print every full tool name returned that starts with one of: ${prefixes
148
- .map((p) => `'${p}'`)
149
- .join(", ")}.`);
150
- // Per-integration "must include" hints: ToolSearch caps results at
151
- // max_results, so lower-ranked tools (e.g. Gmail's label_* family) can
152
- // be missed by keyword search. Listing them explicitly nudges the agent
153
- // to print them when they do appear in any of the searches above.
154
- for (const m of meta) {
155
- if (m.capabilityToolNames.length === 0)
156
- continue;
157
- const fullNames = m.capabilityToolNames
158
- .map((n) => m.toolNamespace + n)
159
- .join(", ");
160
- lines.push(`Include these ${m.displayName} tools if present: ${fullNames}.`);
161
- }
162
- lines.push("One tool name per line. No markdown fences. No explanation. If no such tools are available, print NONE.");
163
- // Defense: an empty prefix list would compile to `\b(?:)[A-Za-z0-9_-]+\b`,
164
- // which matches any word — every connector probe would falsely "succeed".
165
- // Realistically `INTEGRATION_DESCRIPTORS` is non-empty, but the guard
166
- // keeps a future registry rollback from corrupting probe semantics.
167
- const escapedPrefixes = prefixes.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
168
- const regex = escapedPrefixes.length === 0
169
- ? /(?!)/g
170
- : new RegExp(`\\b(?:${escapedPrefixes.join("|")})[A-Za-z0-9_-]+\\b`, "g");
171
- return { prompt: lines.join(" "), prefixes, regex };
172
- }
173
- const CLAUDE_PROBE_DATA = buildClaudeProbeData();
174
- export const CLAUDE_PROBE_TOOLS_PROMPT = CLAUDE_PROBE_DATA.prompt;
175
- const CLAUDE_PROBE_TOOL_PREFIXES = CLAUDE_PROBE_DATA.prefixes;
176
- const CLAUDE_CONNECTOR_TOOL_RE = CLAUDE_PROBE_DATA.regex;
177
- /**
178
- * The built-in Claude Code tool that loads schemas for deferred MCP tools.
179
- * When a session inherits many MCP servers from the user's global config,
180
- * the CLI defers a portion of the tool schemas; the model must call
181
- * `ToolSearch` to bring a specific tool's schema into the working set
182
- * before invoking it. The proxy explicitly allows it (see runDelegatedTool)
183
- * and the stream parser excludes it from `wrongToolName` capture so a
184
- * partial-trace failure (`ToolSearch` + max_turns before the connector
185
- * call) classifies as `no_tool_call` rather than the misleading
186
- * `wrong_tool=ToolSearch`.
187
- */
188
- const DEFERRED_TOOL_DISCOVERY_TOOL_NAME = "ToolSearch";
189
- /**
190
- * Registry-driven allowlist entries for integrations currently delegated to
191
- * Claude. Under `permissionMode: "dontAsk"`, any tool not in the SDK's
192
- * `allowedTools` is silently denied — so a delegated Gmail / Calendar
193
- * integration whose skill instructs the agent to call
194
- * `mcp__claude_ai_Gmail__search_threads` will fail with "permission denied"
195
- * unless that exact tool name is pre-authorized here.
196
- *
197
- * Pure over `(integrations)` — callers pass the record read from
198
- * `db/integrations-store.ts#readIntegrations`. The SDK's MCP tool matcher is
199
- * literal (`mcp__<server>__<tool>` — see `services/mcp/risk.ts`), so we
200
- * enumerate every capability tool the registry declares rather than using a
201
- * wildcard. This guarantees the allowlist only widens by what the registry
202
- * explicitly advertises; adding a new connector tool demands a registry
203
- * update first.
44
+ * Opting in to `['user']` brings Claude in line with Codex and Gemini,
45
+ * both of which load `~/.codex/config.toml` / `~/.gemini/settings.json`
46
+ * by default at CLI spawn (they have no `--setting-sources=` equivalent
47
+ * to suppress). This is the parity surface for native mode.
204
48
  *
205
- * Only integrations whose `delegatedBackend === "claude"` contribute a
206
- * Codex-delegated Gmail integration must not widen Claude's surface.
49
+ * Trade-off user-scoped file hooks (e.g. notify.sh entries under
50
+ * `~/.claude/settings.json` `hooks.{Notification,Stop,PermissionRequest}`)
51
+ * are also loaded. Programmatic hooks passed via `query({ hooks })`
52
+ * layer on top rather than suppress them. Users with noisy file hooks
53
+ * should scope them with matchers; the daemon does not strip them.
207
54
  */
208
- export function computeDelegatedClaudeTools(integrations) {
209
- const out = new Set();
210
- for (const key of INTEGRATION_KEYS) {
211
- const state = integrations[key];
212
- if (!state || state.mode !== "delegated")
213
- continue;
214
- if (state.delegatedBackend !== "claude")
215
- continue;
216
- const connector = INTEGRATION_DESCRIPTORS[key].backendConnectors.claude;
217
- if (!connector)
218
- continue;
219
- for (const toolNames of Object.values(connector.capabilityTools)) {
220
- for (const toolName of toolNames) {
221
- out.add(connector.toolNamespace + toolName);
222
- }
223
- }
224
- }
225
- return Array.from(out);
226
- }
227
- export class AgentTimeoutError extends Error {
228
- timeoutMs;
229
- constructor(timeoutMs) {
230
- super(`Agent execution exceeded timeout of ${timeoutMs}ms`);
231
- this.timeoutMs = timeoutMs;
232
- this.name = "AgentTimeoutError";
233
- }
234
- }
235
- export function isClaudeCodeQuotaError(error) {
236
- if (!(error instanceof Error)) {
237
- return false;
238
- }
239
- const maybeRecord = error;
240
- const message = error.message.toLowerCase();
241
- const code = typeof maybeRecord.code === "string" ? maybeRecord.code.toLowerCase() : "";
242
- const type = typeof maybeRecord.type === "string" ? maybeRecord.type.toLowerCase() : "";
243
- if (maybeRecord.status === 429) {
244
- return true;
245
- }
246
- if (code.includes("rate") || code.includes("quota")) {
247
- return true;
248
- }
249
- if (type.includes("rate") || type.includes("quota")) {
250
- return true;
251
- }
252
- return /rate.?limit|quota|too many requests|you['']?\s*ve hit your limit/.test(message);
253
- }
254
- export function isClaudeCodeMaxBudgetError(error) {
255
- const message = error instanceof Error
256
- ? error.message
257
- : typeof error === "string"
258
- ? error
259
- : "";
260
- const code = typeof error === "object" && error !== null && typeof error.code === "string"
261
- ? error.code
262
- : "";
263
- const type = typeof error === "object" && error !== null && typeof error.type === "string"
264
- ? error.type
265
- : "";
266
- return /max(?:imum)? budget|max_budget_usd|budget limit|per-turn budget/i.test(`${message} ${code} ${type}`);
267
- }
268
- export function extractClaudeCodeQuotaResetHint(error) {
269
- if (!(error instanceof Error)) {
270
- return null;
271
- }
272
- const match = /resets?\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)\s*(?:\(([^)]+)\))?/i.exec(error.message);
273
- if (!match) {
274
- return null;
275
- }
276
- const rawHour = Number(match[1]);
277
- const meridiem = match[3].toLowerCase();
278
- let hour = rawHour % 12;
279
- if (meridiem === "pm") {
280
- hour += 12;
281
- }
282
- return {
283
- hour,
284
- minute: match[2] ? Number(match[2]) : 0,
285
- timeZone: match[4]?.trim() || undefined,
286
- rawLabel: error.message.slice(match.index).replace(/^resets?\s+/i, "").trim(),
287
- };
288
- }
55
+ const CLAUDE_SDK_SETTING_SOURCES = ["user"];
289
56
  /**
290
57
  * ClaudeCodeCore intentionally does NOT run a pre-flight `checkAuth()`
291
58
  * gate inside `execute()` / `runTurn()`. Codex and Gemini each call
@@ -494,7 +261,7 @@ export class ClaudeCodeCore {
494
261
  return await this.runWithRetry(() => this.executeOnce(params, streamCallbacks), { eventType: params.event.type, modelId: params.modelId });
495
262
  }
496
263
  async executeOnce(params, streamCallbacks) {
497
- const { prompt, context, event, modelId, maxTurns, maxBudgetUsd, sessionDir, persistSession = false, conversationHistory, webSearchEnabled = false, } = params;
264
+ const { prompt, context, event, modelId, maxTurns, maxBudgetUsd, sessionDir, persistSession = false, conversationHistory, webSearchEnabled = false, wikiUrlFetchEnabled = false, } = params;
498
265
  const fullPrompt = buildExecutionPrompt(prompt, context, event, conversationHistory);
499
266
  const startMs = Date.now();
500
267
  const actualModelId = this.resolveActualModelId(modelId);
@@ -502,15 +269,18 @@ export class ClaudeCodeCore {
502
269
  // that may be resumed later). Otherwise create a disposable temp dir.
503
270
  // The disposable path also receives user skills so agent DMs that don't
504
271
  // take the persistent-workdir path can still discover user-authored skills.
272
+ const wikiWorkspaceName = typeof event.data?.workspace === "string" ? event.data.workspace : undefined;
505
273
  const useSessionDir = sessionDir ?? createSessionWorkdir(this.config.workspaceDir, event.type, `${this.config.dataDir}/skills`, {
506
274
  backendId: this.backendId,
507
275
  processKey: params.processKey,
508
276
  character: this.config.character,
277
+ ...(wikiWorkspaceName ? { wikiWorkspaceName } : {}),
509
278
  });
510
279
  const isOwnedTempDir = !sessionDir;
511
280
  const daemonReadToken = this.readTokenManager?.issue(useSessionDir) ?? this.readToken;
512
281
  const mcp = await this.materializeMcp(useSessionDir, params.processKey);
513
282
  const delegatedTools = this.getDelegatedClaudeTools();
283
+ const nativeTools = this.getNativeClaudeTools();
514
284
  const sessionDeniedTools = this.getSessionDeniedTools();
515
285
  logger.info({
516
286
  eventType: event.type,
@@ -519,6 +289,7 @@ export class ClaudeCodeCore {
519
289
  promptLen: fullPrompt.length,
520
290
  mcpServers: mcp.servers.map((s) => s.id),
521
291
  delegatedToolCount: delegatedTools.length,
292
+ nativeToolCount: nativeTools.length,
522
293
  sessionDeniedToolCount: sessionDeniedTools.length,
523
294
  }, "Agent execute started");
524
295
  try {
@@ -572,7 +343,20 @@ export class ClaudeCodeCore {
572
343
  }
573
344
  : {
574
345
  permissionMode: "dontAsk",
575
- allowedTools: this.getAllowedTools(webSearchEnabled, delegatedTools),
346
+ // WIKI_BUILDER_DESIGN.md §4.3 — `wikiUrlFetchEnabled` is
347
+ // honoured inside `getAllowedTools` (same gating contract as
348
+ // `webSearchEnabled`: suppressed when the user configured
349
+ // a custom `allowedToolsOverride`). Keeps the widening
350
+ // centralised and unit-testable.
351
+ //
352
+ // `wikiApiOnlyWrites` is the symmetric narrowing for every
353
+ // `wiki.*` process key: strip `Write` / `Edit` from the
354
+ // session allowlist so the agent cannot bypass the Wiki API
355
+ // path-classifier + `agent_actions` audit trail by writing
356
+ // a vault path directly. The wiki-agent profile and every
357
+ // wiki skill body already forbid this in prose; the SDK
358
+ // gate makes the prose enforceable.
359
+ allowedTools: this.getAllowedTools(webSearchEnabled, delegatedTools, nativeTools, wikiUrlFetchEnabled, params.processKey?.startsWith("wiki.") ?? false),
576
360
  disallowedTools: [
577
361
  ...ALWAYS_DISALLOWED_TOOLS,
578
362
  ...this.config.disallowedTools,
@@ -585,7 +369,14 @@ export class ClaudeCodeCore {
585
369
  mcpServers: mcp.claudeMcpServers,
586
370
  }
587
371
  : {}),
588
- hooks: this.getSecurityHooks(allowMode),
372
+ settingSources: [...CLAUDE_SDK_SETTING_SOURCES],
373
+ // When the per-execute clamp is active we already swapped Allow
374
+ // mode back to strict `dontAsk` + an explicit allowedTools list.
375
+ // The PreToolUse hooks must follow the same posture: keeping
376
+ // `allowMode=true` here would drop the curl localhost-only check
377
+ // and the jq env/file-flag check, leaving exfil paths open even
378
+ // though the clamp itself permits `Bash(curl *)` and `Bash(jq *)`.
379
+ hooks: this.getSecurityHooks(optimizerClampActive ? false : allowMode),
589
380
  persistSession,
590
381
  includePartialMessages: !!streamCallbacks,
591
382
  ...this.buildAdvisorSettings(),
@@ -628,6 +419,7 @@ export class ClaudeCodeCore {
628
419
  // keys. `materializeMcp` short-circuits on no context / no servers.
629
420
  const mcp = await this.materializeMcp(sessionDir, undefined);
630
421
  const delegatedTools = this.getDelegatedClaudeTools();
422
+ const nativeTools = this.getNativeClaudeTools();
631
423
  const sessionDeniedTools = this.getSessionDeniedTools();
632
424
  logger.info({
633
425
  sessionId,
@@ -635,6 +427,7 @@ export class ClaudeCodeCore {
635
427
  maxTurns,
636
428
  mcpServers: mcp.servers.map((s) => s.id),
637
429
  delegatedToolCount: delegatedTools.length,
430
+ nativeToolCount: nativeTools.length,
638
431
  sessionDeniedToolCount: sessionDeniedTools.length,
639
432
  }, "Agent resume started");
640
433
  // Use the same cwd as the original execute() so the SDK can find
@@ -670,7 +463,7 @@ export class ClaudeCodeCore {
670
463
  }
671
464
  : {
672
465
  permissionMode: "dontAsk",
673
- allowedTools: this.getAllowedTools(webSearchEnabled, delegatedTools),
466
+ allowedTools: this.getAllowedTools(webSearchEnabled, delegatedTools, nativeTools),
674
467
  disallowedTools: [
675
468
  ...ALWAYS_DISALLOWED_TOOLS,
676
469
  ...this.config.disallowedTools,
@@ -681,6 +474,7 @@ export class ClaudeCodeCore {
681
474
  ...(mcp.claudeMcpServers
682
475
  ? { mcpServers: mcp.claudeMcpServers }
683
476
  : {}),
477
+ settingSources: [...CLAUDE_SDK_SETTING_SOURCES],
684
478
  hooks: this.getSecurityHooks(allowMode),
685
479
  includePartialMessages: !!streamCallbacks,
686
480
  ...this.buildAdvisorSettings(),
@@ -770,46 +564,26 @@ export class ClaudeCodeCore {
770
564
  rawLabel: hint.rawLabel,
771
565
  };
772
566
  }
567
+ // Transitional shims — file-split-plan §15. The implementations live in
568
+ // `./claude-auth.ts`; these methods stay on the class so internal call
569
+ // sites (`this.getErrorMessage(...)`, etc.) and test files that reach in
570
+ // via `(core as any).isAuthError(...)` keep compiling against the same
571
+ // surface. Remove once all callers are migrated to the module-level
572
+ // exports.
773
573
  isAuthError(error) {
774
- const status = this.getErrorStatus(error);
775
- if (status === 401 || status === 403) {
776
- return true;
777
- }
778
- const code = this.getErrorCode(error)?.toLowerCase() ?? "";
779
- const type = this.getErrorType(error)?.toLowerCase() ?? "";
780
- if (code.includes("auth") ||
781
- code.includes("forbidden") ||
782
- code.includes("unauthorized") ||
783
- type.includes("auth") ||
784
- type.includes("forbidden") ||
785
- type.includes("unauthorized")) {
786
- return true;
787
- }
788
- return /unauthorized|forbidden|authentication|invalid api key|login required/i.test(this.getErrorMessage(error));
574
+ return isAuthError(error);
789
575
  }
790
576
  getErrorStatus(error) {
791
- return typeof error === "object" && error !== null && "status" in error
792
- ? error.status
793
- : undefined;
577
+ return getErrorStatus(error);
794
578
  }
795
579
  getErrorCode(error) {
796
- return typeof error === "object" && error !== null && typeof error.code === "string"
797
- ? error.code
798
- : undefined;
580
+ return getErrorCode(error);
799
581
  }
800
582
  getErrorType(error) {
801
- return typeof error === "object" && error !== null && typeof error.type === "string"
802
- ? error.type
803
- : undefined;
583
+ return getErrorType(error);
804
584
  }
805
585
  getErrorMessage(error) {
806
- if (error instanceof Error) {
807
- return error.message;
808
- }
809
- if (typeof error === "string") {
810
- return error;
811
- }
812
- return "Claude backend execution failed";
586
+ return getErrorMessage(error);
813
587
  }
814
588
  async sleep(ms) {
815
589
  await new Promise((resolve) => {
@@ -839,6 +613,7 @@ export class ClaudeCodeCore {
839
613
  systemPrompt: { type: "preset", preset: "claude_code" },
840
614
  permissionMode: "dontAsk",
841
615
  allowedTools: [],
616
+ settingSources: [...CLAUDE_SDK_SETTING_SOURCES],
842
617
  },
843
618
  });
844
619
  const result = await this.withTimeout(stream, () => this.consumeStream(stream, summaryModel, startMs), this.config.executeTimeoutMinutes);
@@ -849,128 +624,23 @@ export class ClaudeCodeCore {
849
624
  cleanupSessionWorkdir(sessionDir);
850
625
  }
851
626
  }
627
+ /**
628
+ * Cheap presence check used by the reactive execute path. Detailed probe
629
+ * lives in `checkAuthDetailed`. Implementation moved to `./claude-auth.ts`
630
+ * (file-split-plan §8, Tier 2); this stays as a thin forwarder so the
631
+ * `IAgentCore` interface contract is unchanged and existing call sites in
632
+ * `BackendRouter` / `AuthHealthMonitor` continue to work.
633
+ */
852
634
  async checkAuth() {
853
- // Presence check only — this is the cheap gate used from the reactive
854
- // execute path. Detailed probe lives in checkAuthDetailed().
855
- const cloud = detectCloudProviderEnv();
856
- if (cloud) {
857
- if (cloud.missing.length > 0) {
858
- return {
859
- ok: false,
860
- reason: `${cloud.flagEnvVar}=1 but missing required env vars: ${cloud.missing.join(", ")}`,
861
- };
862
- }
863
- return { ok: true, method: cloud.method };
864
- }
865
- const rawApiKey = process.env.ANTHROPIC_API_KEY?.trim();
866
- if (rawApiKey) {
867
- if (!isPlausibleAnthropicApiKey(rawApiKey)) {
868
- return {
869
- ok: false,
870
- reason: "ANTHROPIC_API_KEY is set but does not look like an Anthropic key (expected `sk-ant-…`).",
871
- };
872
- }
873
- return { ok: true, method: "api_key" };
874
- }
875
- if (!this.cliPath) {
876
- return {
877
- ok: false,
878
- reason: "Claude Code CLI is not installed or not on PATH. Run `npm install -g @anthropic-ai/claude-code`.",
879
- };
880
- }
881
- return { ok: true, method: "cli_login" };
635
+ return checkAuthFn({ cliPath: this.cliPath });
882
636
  }
883
637
  /**
884
- * Detailed auth probe used by AuthHealthMonitor and the dashboard setup
885
- * wizard. Two modes:
886
- * - **API key** (`ANTHROPIC_API_KEY`): format check + server-side probe
887
- * via `probeApiKeyServerSide("anthropic", ...)` (roadmap §9.1).
888
- * Throws on network/timeout so `checkAll()` records `probe_network_error`.
889
- * - **CLI login**: reads `~/.claude/credentials.json` for `refreshToken`.
890
- * Never writes to the Keychain or credentials file — refresh is left
891
- * to the CLI (Phase 0 confirmed rotating refresh_tokens; daemon-driven
892
- * refresh would race and corrupt state).
638
+ * Detailed auth probe used by `AuthHealthMonitor` and the dashboard setup
639
+ * wizard. Implementation moved to `./claude-auth.ts`; see the comment on
640
+ * `checkAuth` above.
893
641
  */
894
642
  async checkAuthDetailed() {
895
- const cloud = detectCloudProviderEnv();
896
- if (cloud) {
897
- if (cloud.missing.length > 0) {
898
- return {
899
- ok: false,
900
- status: "missing",
901
- method: cloud.method,
902
- detail: `${cloud.flagEnvVar}=1 but missing: ${cloud.missing.join(", ")}`,
903
- recoveryCommand: `Set the missing env vars or unset ${cloud.flagEnvVar}`,
904
- };
905
- }
906
- // Real auth happens inside the SDK against AWS / GCP / Azure. The
907
- // daemon does not run a server-side probe for cloud providers — the
908
- // first execution will surface any credential failure. Mark the
909
- // status as `ok` here so the dashboard reports "Configured (cloud)";
910
- // AuthHealthMonitor still re-runs this check hourly so a malformed
911
- // env (env vars cleared after launch) flips the cache to `missing`.
912
- return {
913
- ok: true,
914
- status: "ok",
915
- method: cloud.method,
916
- detail: `Configured via ${cloud.label} — runtime auth verified by Claude Code SDK`,
917
- };
918
- }
919
- const rawApiKey = process.env.ANTHROPIC_API_KEY?.trim();
920
- if (rawApiKey) {
921
- if (!isPlausibleAnthropicApiKey(rawApiKey)) {
922
- return {
923
- ok: false,
924
- status: "expired",
925
- method: "api_key",
926
- detail: "ANTHROPIC_API_KEY does not match Anthropic key format (expected `sk-ant-…`).",
927
- recoveryCommand: "Unset ANTHROPIC_API_KEY or replace it with a valid Anthropic API key",
928
- };
929
- }
930
- // Format is plausible — attempt a server-side probe to detect
931
- // revoked keys within 1 hourly cycle (roadmap §9.1). On network
932
- // failure, the probe throws and the caller (checkAll or check-auth
933
- // route) records `probe_network_error` without flipping DB cache.
934
- const probe = await probeApiKeyServerSide("anthropic", rawApiKey);
935
- return {
936
- ok: probe.ok,
937
- status: probe.ok ? "ok" : "expired",
938
- method: "api_key",
939
- detail: probe.detail,
940
- ...(!probe.ok && {
941
- recoveryCommand: "Unset ANTHROPIC_API_KEY or replace it with a valid Anthropic API key",
942
- }),
943
- };
944
- }
945
- if (!this.cliPath) {
946
- return {
947
- ok: false,
948
- status: "missing",
949
- method: "cli_login",
950
- detail: "Claude Code CLI not found on PATH",
951
- recoveryCommand: "npm install -g @anthropic-ai/claude-code",
952
- };
953
- }
954
- const bundle = await readClaudeCredentials();
955
- if (!bundle) {
956
- return {
957
- ok: false,
958
- status: "expired",
959
- method: "cli_login",
960
- detail: "No Claude credentials found",
961
- recoveryCommand: "claude auth login",
962
- };
963
- }
964
- if (!bundle.refreshToken) {
965
- return {
966
- ok: false,
967
- status: "expired",
968
- method: "cli_login",
969
- detail: "Credentials lack refresh_token — run `claude auth login`",
970
- recoveryCommand: "claude auth login",
971
- };
972
- }
973
- return { ok: true, status: "ok", method: "oauth" };
643
+ return checkAuthDetailedFn({ cliPath: this.cliPath });
974
644
  }
975
645
  /**
976
646
  * Phase 5 §4.11 live probe. Claude Code 2.1+ may defer large MCP tool
@@ -995,6 +665,7 @@ export class ClaudeCodeCore {
995
665
  systemPrompt: { type: "preset", preset: "claude_code" },
996
666
  permissionMode: "dontAsk",
997
667
  allowedTools: ["ToolSearch"],
668
+ settingSources: [...CLAUDE_SDK_SETTING_SOURCES],
998
669
  },
999
670
  });
1000
671
  const tools = await new Promise((resolve, reject) => {
@@ -1131,6 +802,12 @@ export class ClaudeCodeCore {
1131
802
  if (blockType === "tool_use" && blockName === "Bash") {
1132
803
  const toolUseId = block.id;
1133
804
  const cmd = (block.input?.command ?? "");
805
+ logger.info({
806
+ eventType,
807
+ sessionId,
808
+ toolUseId,
809
+ cmd: typeof cmd === "string" ? cmd.slice(0, 400) : null,
810
+ }, "Bash tool_use");
1134
811
  if (typeof toolUseId === "string" &&
1135
812
  typeof cmd === "string" &&
1136
813
  ClaudeCodeCore.isContextUpdateCommand(cmd)) {
@@ -1217,6 +894,14 @@ export class ClaudeCodeCore {
1217
894
  }
1218
895
  }
1219
896
  const resultText = flattenToolResultContent(block.content);
897
+ if (isError) {
898
+ logger.info({
899
+ eventType,
900
+ sessionId,
901
+ toolUseId,
902
+ resultText: resultText.slice(0, 600),
903
+ }, "tool_result error");
904
+ }
1220
905
  const apiErrors = extractSilentApiErrors(resultText);
1221
906
  if (apiErrors.length > 0) {
1222
907
  logSilentApiErrors(logger, apiErrors, {
@@ -1379,1163 +1064,83 @@ export class ClaudeCodeCore {
1379
1064
  * curation intent while letting delegated mode widen the surface to
1380
1065
  * whatever the registry already advertised.
1381
1066
  */
1382
- getAllowedTools(webSearchEnabled, delegatedTools = []) {
1383
- const base = this.config.allowedToolsOverride ?? [
1384
- "Read",
1385
- "Glob",
1386
- "Grep",
1387
- "Write",
1388
- "Edit",
1389
- "Skill", // user skills (external-services, obsidian-*, observations, ...)
1390
- "Bash(curl *)", // curl broadly allowed; hooks restrict to localhost
1391
- "Bash(git *)", // Git operations
1392
- "Bash(jq *)", // safe JSON post-processor for curl pipelines
1393
- ];
1394
- const merged = new Set(base);
1395
- if (!this.config.allowedToolsOverride && webSearchEnabled) {
1396
- merged.add("WebSearch");
1397
- }
1398
- for (const tool of delegatedTools)
1399
- merged.add(tool);
1400
- return Array.from(merged);
1067
+ // Transitional shims file-split-plan §15. Implementations live in
1068
+ // `./claude-tool-collection.ts`; the class methods stay so existing call
1069
+ // sites and the test file (`(core as any).getAllowedTools(...)`) keep
1070
+ // working without modification. See the comment header of
1071
+ // `claude-tool-collection.ts` for the design rationale.
1072
+ getAllowedTools(webSearchEnabled, delegatedTools = [], nativeTools = [], wikiUrlFetchEnabled = false, wikiApiOnlyWrites = false) {
1073
+ return getAllowedToolsFn(this.config, webSearchEnabled, delegatedTools, nativeTools, wikiUrlFetchEnabled, wikiApiOnlyWrites);
1401
1074
  }
1402
- /**
1403
- * Thin wrapper around the pure `computeDelegatedClaudeTools` helper that
1404
- * pulls the integrations record from the wired mcp context. Returns `[]`
1405
- * when the context is not yet wired (tests, startup ordering) — which
1406
- * matches the pre-fix behavior for sessions that ran before the daemon
1407
- * finished composing. A one-shot warning is emitted in `setMcpContext`
1408
- * confirming wiring.
1409
- */
1410
1075
  getDelegatedClaudeTools() {
1411
- if (!this.mcpContext)
1412
- return [];
1413
- try {
1414
- const integrations = readIntegrations(this.mcpContext.db);
1415
- return computeDelegatedClaudeTools(integrations);
1416
- }
1417
- catch (err) {
1418
- logger.warn({ err }, "Failed to read integrations for delegated-tool allowlist — proceeding without delegated tools");
1419
- return [];
1420
- }
1076
+ return getDelegatedClaudeToolsFn(this.mcpContext);
1077
+ }
1078
+ getNativeClaudeTools() {
1079
+ return getNativeClaudeToolsFn(this.mcpContext);
1080
+ }
1081
+ getSessionDeniedTools() {
1082
+ return getSessionDeniedToolsFn(this.mcpContext);
1421
1083
  }
1422
1084
  /**
1423
- * DELEGATED-MODE-V2-DESIGN.md §4.3.3same-backend deny enforcement at
1424
- * the SDK boundary. For every integration whose `delegatedBackend === "claude"`,
1425
- * expand `state.deniedTools` against the connector's known tools and emit
1426
- * the namespaced names (`mcp__claude_ai_<X>__<tool>`). The SDK refuses any
1427
- * tool listed in `disallowedTools` regardless of `allowedTools` — hard
1428
- * enforcement.
1429
- *
1430
- * Returns `[]` when context isn't wired (tests / pre-startup) and on read
1431
- * failures, matching the conservative pattern used by
1432
- * `getDelegatedClaudeTools`.
1085
+ * Security hooksthin shim that forwards to `buildSecurityHooks` in
1086
+ * `./claude-tool-collection.ts`. The implementation lives there as a
1087
+ * pure factory consuming a `SecurityHooksDeps` record. See that module
1088
+ * for hook semantics; see `file-split-plan.md` §8 + §15 for why the
1089
+ * shim stays here.
1433
1090
  */
1434
- getSessionDeniedTools() {
1435
- if (!this.mcpContext)
1436
- return [];
1437
- try {
1438
- const integrations = readIntegrations(this.mcpContext.db);
1439
- const map = collectSessionDeniedTools(integrations, "claude");
1440
- const out = [];
1441
- for (const names of map.values()) {
1442
- for (const n of names)
1443
- out.push(n);
1444
- }
1445
- return out;
1446
- }
1447
- catch (err) {
1448
- logger.warn({ err }, "Failed to read integrations for same-backend denied-tools — proceeding without per-integration deny");
1449
- return [];
1450
- }
1091
+ getSecurityHooks(allowMode = false) {
1092
+ return buildSecurityHooks({
1093
+ config: this.config,
1094
+ writeTracker: this.writeTracker,
1095
+ // Thunk see `SecurityHooksDeps.getMcpContext` JSDoc. The hook
1096
+ // reads the live reference at fire time so it picks up any
1097
+ // `setMcpContext` call that happens between hook build and the
1098
+ // SDK invoking the matcher (faithful to original this.mcpContext
1099
+ // semantics).
1100
+ getMcpContext: () => this.mcpContext,
1101
+ }, allowMode);
1451
1102
  }
1452
1103
  /**
1453
- * Security hooks:
1454
- * 1. Bash(curl *) restrict to localhost Daemon API, block connection-override flags. (strict only)
1455
- * 2. Bash(jq *) — block file-access flags and the `env` filter (process env exfiltration). (strict only)
1456
- * 3. Write/Edit — block writes into the session helper dir and context dir, mark vault writes.
1104
+ * Delegated-execution deps bundle. Built fresh per call so the captured
1105
+ * `readTokenManager` / `readToken` reflect the latest state of the core's
1106
+ * mutable fields (both can be re-wired post-construction via
1107
+ * `setReadToken` / `setReadTokenManager`).
1457
1108
  *
1458
- * In allow mode the curl and jq hooks are dropped, but the Write/Edit hook
1459
- * stays: the context-dir chokepoint exists for memory integrity (today-write
1460
- * lock, md_file_snapshots, CONTEXT_WRITE_PERMISSIONS), not permissions.
1109
+ * Snapshot semantics once handed to `runDelegated{Tool,Task}Fn`, the
1110
+ * deps record is treated as immutable for the duration of the call. This
1111
+ * is a deliberate, narrow strengthening of the pre-split behavior: the
1112
+ * original re-read `this.readTokenManager` at revoke time, so a
1113
+ * (hypothetical) mid-call replacement would revoke on the *new* manager
1114
+ * — leaving a scope leak on the manager that issued the token.
1115
+ * Production never replaces the manager after boot (`index.ts` wires it
1116
+ * exactly once via `setReadTokenManager?`), so the two behaviors converge
1117
+ * in practice; the new shape is simply more robust by construction.
1461
1118
  */
1462
- getSecurityHooks(allowMode = false) {
1463
- const bashCurlHook = async (input) => {
1464
- const toolInput = input.tool_input;
1465
- const cmd = toolInput?.command ?? "";
1466
- if (/\bcurl\b/.test(cmd)) {
1467
- const urls = cmd.match(/https?:\/\/[^\s'"]+/g) ?? [];
1468
- if (urls.length === 0) {
1469
- return {
1470
- decision: "block",
1471
- reason: "curl command must contain an explicit localhost URL",
1472
- };
1473
- }
1474
- for (const url of urls) {
1475
- try {
1476
- const parsed = new URL(url);
1477
- if (parsed.hostname !== "localhost" && parsed.hostname !== "127.0.0.1") {
1478
- return {
1479
- decision: "block",
1480
- reason: `curl target not allowed: ${url} (host: ${parsed.hostname})`,
1481
- };
1482
- }
1483
- const effectivePort = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
1484
- if (effectivePort !== String(this.config.apiPort)) {
1485
- return {
1486
- decision: "block",
1487
- reason: `curl target port not allowed: ${effectivePort}`,
1488
- };
1489
- }
1490
- }
1491
- catch {
1492
- return {
1493
- decision: "block",
1494
- reason: `curl target URL is malformed: ${url}`,
1495
- };
1496
- }
1497
- }
1498
- if (/--connect-to|--resolve|--config\b|(?:^|\s)-[a-zA-Z]*K|--proxy\b|(?:^|\s)-[a-zA-Z]*x|--socks/.test(cmd)) {
1499
- return {
1500
- decision: "block",
1501
- reason: "curl connection override flags not allowed (--connect-to, --resolve, --config, --proxy)",
1502
- };
1503
- }
1504
- }
1505
- return { continue: true };
1506
- };
1507
- const bashJqHook = async (input) => {
1508
- const toolInput = input.tool_input;
1509
- const cmd = toolInput?.command ?? "";
1510
- if (!/\bjq\b/.test(cmd))
1511
- return { continue: true };
1512
- // Narrow to THIS jq invocation's own args (up to the next pipe / chain op)
1513
- // so that later pipeline stages are not inspected by the jq rules.
1514
- //
1515
- // Known approximation: `[^|;&]*` does not respect shell quoting, so a
1516
- // jq filter with a `|` INSIDE a quoted expression (e.g. `jq 'env | keys'`)
1517
- // will truncate `jqPart` at the first `|` regardless of whether that `|`
1518
- // is a jq pipe inside quotes or an actual shell pipeline break. This is
1519
- // intentionally conservative on the safe side: the env-filter check
1520
- // below still fires on the truncated left half (`jq 'env `), so attack
1521
- // payloads are still blocked. The downside is slightly reduced precision
1522
- // on benign expressions containing the jq `|` operator — those get
1523
- // scanned only up to the first pipe, not their full extent.
1524
- const jqMatch = cmd.match(/\bjq\b([^|;&]*)/);
1525
- if (!jqMatch)
1526
- return { continue: true };
1527
- const jqPart = jqMatch[0];
1528
- // (a) Block file-access flags — --slurpfile / --rawfile read arbitrary
1529
- // files, which would bypass the Read deny list (~/.ssh/**, .env, etc.).
1530
- if (/(?:^|\s)--slurpfile\b/.test(jqPart) || /(?:^|\s)--rawfile\b/.test(jqPart)) {
1531
- return {
1532
- decision: "block",
1533
- reason: "jq --slurpfile and --rawfile are not allowed " +
1534
- "(would bypass Read(.env) / Read(~/.ssh/**) disallow rules).",
1535
- };
1536
- }
1537
- // (b) Block module loading — -L <dir> + import can load filter code from
1538
- // the filesystem, effectively RCE inside the jq process.
1539
- if (/(?:^|\s)-L(?:\s|=|$)/.test(jqPart)) {
1540
- return {
1541
- decision: "block",
1542
- reason: "jq -L (module load path) is not allowed.",
1543
- };
1544
- }
1545
- // (c) Block the `env` filter. `jq env`, `jq -n env`, `jq 'env.FOO'`,
1546
- // `jq '. , env'` all dump the daemon's process.env to stdout. Process.env
1547
- // on this daemon is expected to be clean (secrets live in the keychain),
1548
- // but defense-in-depth: if OPENAI_API_KEY or similar is ever exported at
1549
- // launch, the env filter is the shortest exfil path.
1550
- //
1551
- // Heuristic: match bare `env` NOT preceded by a field-access dot or word
1552
- // char, and NOT followed by a word char. This matches jq's env filter
1553
- // (`env`, `env.HOME`, `(env)`, `env|keys`) while leaving field access
1554
- // like `.env`, `.env_var`, `.data.environments` untouched.
1555
- if (/(?:^|[^\w.])env(?!\w)/.test(jqPart)) {
1556
- return {
1557
- decision: "block",
1558
- reason: "jq env filter is not allowed — it dumps the daemon process " +
1559
- "environment, which is a known exfiltration vector for any " +
1560
- "secrets loaded via .env at startup.",
1561
- };
1562
- }
1563
- return { continue: true };
1564
- };
1565
- /**
1566
- * Block any Bash command that references the context-directory path.
1567
- *
1568
- * Rationale: the daemon API is the ONLY sanctioned write channel for
1569
- * context files — it enforces today-write-lock, md_file_snapshots,
1570
- * CONTEXT_WRITE_PERMISSIONS, and onPromptContextChanged. In strict mode,
1571
- * the allowlist (Bash narrowed to curl/git/jq) + fileWriteHook keeps
1572
- * this chokepoint intact. In allow mode Bash is unrestricted, so an
1573
- * agent could bypass via `echo > today.md`, `tee`, `python -c 'open…'`,
1574
- * `git log … > context/…`, etc.
1575
- *
1576
- * This hook runs in BOTH modes (defense-in-depth). The heuristic is
1577
- * deliberately conservative: we block the command if its raw text
1578
- * contains any of the well-known path representations of the context
1579
- * dir. This has false positives for read-only uses (`cat context/*.md`),
1580
- * but the agent always has the Read tool and the daemon GET endpoint
1581
- * available, so a blocked shell-read is an error message, not a
1582
- * capability loss. Obfuscation beyond the handled variants (dynamic
1583
- * path construction inside a subshell) is out of scope — the threat
1584
- * model is "the model picks a shell workaround", not adversarial
1585
- * prompt injection.
1586
- */
1587
- const bashContextWriteHook = async (input) => {
1588
- const toolInput = input.tool_input;
1589
- const cmd = toolInput?.command ?? "";
1590
- if (typeof cmd !== "string" || cmd.length === 0)
1591
- return { continue: true };
1592
- const absContextDir = resolvePath(getContextDir(this.config));
1593
- const home = homedir();
1594
- const pathForms = shellPathForms(absContextDir, home);
1595
- for (const form of pathForms) {
1596
- if (cmd.includes(form)) {
1597
- return {
1598
- decision: "block",
1599
- reason: `Bash commands that reference the context directory (${absContextDir}) are ` +
1600
- `not allowed. Use the daemon API: ` +
1601
- `GET/PUT/PATCH http://localhost:${this.config.apiPort}/api/context/<path>. ` +
1602
- `The API enforces today-write-lock, md_file_snapshots, CONTEXT_WRITE_PERMISSIONS, ` +
1603
- `and onPromptContextChanged — bypassing it via shell redirects or script ` +
1604
- `engines leaves the memory layer inconsistent. Matched path form: ${form}.`,
1605
- };
1606
- }
1607
- }
1608
- return { continue: true };
1609
- };
1610
- const fileWriteHook = async (input) => {
1611
- const hookInput = input;
1612
- const toolInput = hookInput.tool_input;
1613
- const rawFilePath = toolInput?.file_path;
1614
- if (typeof rawFilePath !== "string" || rawFilePath.length === 0) {
1615
- return { continue: true };
1616
- }
1617
- const filePath = rawFilePath;
1618
- const cwd = hookInput.cwd;
1619
- if (!cwd && !isAbsolute(filePath))
1620
- return { continue: true };
1621
- const absFile = resolvePath(cwd ?? "/", filePath);
1622
- // (a) Block writes into the session-local helper dir. The `curl` shim in
1623
- // `.pa/bin/` carries daemon-auth env at execution time; letting the model
1624
- // rewrite it would turn the helper into a secret exfiltration vector.
1625
- const absHelperDir = resolvePath(cwd ?? "/", ".pa");
1626
- const withinHelperDir = isPathInsideOrEqual(absHelperDir, absFile);
1627
- if (withinHelperDir) {
1628
- return {
1629
- decision: "block",
1630
- reason: "Direct Write/Edit to .pa is forbidden. " +
1631
- "Session helper binaries are managed by the daemon.",
1632
- };
1633
- }
1634
- // (b) Block writes into the context dir.
1635
- const contextDir = getContextDir(this.config);
1636
- const absContextDir = resolvePath(contextDir);
1637
- const withinContext = isPathInsideOrEqual(absContextDir, absFile);
1638
- if (withinContext) {
1639
- return {
1640
- decision: "block",
1641
- reason: `Direct Write/Edit to context dir is forbidden. ` +
1642
- `Use the daemon API instead: ` +
1643
- `PUT http://localhost:${this.config.apiPort}/api/context/<path> (full replace) or ` +
1644
- `PATCH http://localhost:${this.config.apiPort}/api/context/<path> (section op). ` +
1645
- `The API enforces CONTEXT_WRITE_PERMISSIONS, morningRoutineLock, md_file_snapshots, ` +
1646
- `onPromptContextChanged, and expectedMtime concurrency. Path: ${absFile}`,
1647
- };
1648
- }
1649
- // (c) Mark vault-scoped writes for observer attribution.
1650
- // Targets the EXTERNAL Obsidian vault; the ObsidianWatcher observer
1651
- // watches that path and would otherwise misattribute agent writes
1652
- // as user writes.
1653
- if (!this.writeTracker)
1654
- return { continue: true };
1655
- const vaultPath = this.config.externalObsidianVaultPath;
1656
- if (!vaultPath)
1657
- return { continue: true };
1658
- const absVault = resolvePath(vaultPath);
1659
- const withinVault = isPathInsideOrEqual(absVault, absFile);
1660
- if (!withinVault)
1661
- return { continue: true };
1662
- this.writeTracker.markWriting(absFile);
1663
- logger.debug({ filePath: absFile }, "vault write pre-marked for observer attribution");
1664
- return { continue: true };
1665
- };
1666
- // EXECUTION-MODE-DESIGN.md §6 — absolute-block audit hook. Runs ahead
1667
- // of every other Bash/Read/Write/Edit hook in both modes. The SDK-level
1668
- // `disallowedTools` rejection is the authoritative block; this hook is
1669
- // redundant defense-in-depth that also writes the `blocked_absolute`
1670
- // audit row so the owner can see the layer is active.
1671
- const makeAbsoluteBlockHook = (toolName, argField) => async (input) => {
1672
- const toolInput = input.tool_input;
1673
- const raw = toolInput?.[argField];
1674
- if (typeof raw !== "string")
1675
- return { continue: true };
1676
- const match = classifyAbsoluteBlock(toolName, raw);
1677
- if (!match)
1678
- return { continue: true };
1679
- recordAbsoluteBlockAudit({
1680
- db: this.mcpContext?.db,
1681
- backend: "claude",
1682
- mode: this.config.claudeExecutionPermissionMode,
1683
- match,
1684
- toolName,
1685
- });
1686
- return {
1687
- decision: "block",
1688
- reason: `Absolute-block layer denied this ${toolName} call ` +
1689
- `(category: ${match.category}). This rule holds in both Safe ` +
1690
- `and Allow modes — see EXECUTION-MODE-DESIGN.md §6.`,
1691
- };
1692
- };
1693
- const bashAbsoluteBlockHook = makeAbsoluteBlockHook("Bash", "command");
1694
- const readAbsoluteBlockHook = makeAbsoluteBlockHook("Read", "file_path");
1695
- const writeAbsoluteBlockHook = makeAbsoluteBlockHook("Write", "file_path");
1696
- const editAbsoluteBlockHook = makeAbsoluteBlockHook("Edit", "file_path");
1697
- // The context-write hook is always attached to Bash — it is the only
1698
- // guarantee that the daemon-API chokepoint for memory files survives
1699
- // allow mode (where curl/jq restrictions are dropped and Bash can
1700
- // otherwise redirect into context/*.md freely).
1701
- //
1702
- // The absolute-block audit hook is appended LAST on every matcher
1703
- // (§6.3). Appended rather than prepended so existing per-index hook
1704
- // tests keep pointing at the same functions; semantically it is a
1705
- // fallback defense whose practical effect is duplicating the SDK's
1706
- // `disallowedTools` rejection into an `agent_actions` row.
1119
+ delegatedDeps() {
1707
1120
  return {
1708
- PreToolUse: [
1709
- {
1710
- matcher: "Bash",
1711
- hooks: allowMode
1712
- ? [bashContextWriteHook, bashAbsoluteBlockHook]
1713
- : [
1714
- bashCurlHook,
1715
- bashJqHook,
1716
- bashContextWriteHook,
1717
- bashAbsoluteBlockHook,
1718
- ],
1719
- },
1720
- { matcher: "Write", hooks: [fileWriteHook, writeAbsoluteBlockHook] },
1721
- { matcher: "Edit", hooks: [fileWriteHook, editAbsoluteBlockHook] },
1722
- { matcher: "Read", hooks: [readAbsoluteBlockHook] },
1723
- ],
1121
+ apiPort: this.config.apiPort,
1122
+ readToken: this.readToken,
1123
+ readTokenManager: this.readTokenManager,
1724
1124
  };
1725
1125
  }
1726
1126
  /**
1727
- * Delegated proxy invocation — Claude SDK path.
1728
- *
1729
- * Spawns a one-shot SDK `query()` constrained to a single `allowedTools`
1730
- * entry (the requested connector tool), parses the structured stream
1731
- * for the matching `tool_use` `tool_result` pair, and returns the raw
1732
- * tool result. The model's surrounding text is discarded — the route
1733
- * handler only sees the connector's structured payload.
1734
- *
1735
- * Error classes (DelegatedToolErrorClass):
1736
- * - `wrong_tool` — model called a tool we did not request (anti-prompt-injection).
1737
- * - `no_tool_call` — model never invoked the tool within `maxTurns`.
1738
- * - `tool_error` — connector returned `is_error: true`.
1739
- * - `parse_error` — stream produced no terminal `result` event.
1740
- * - `auth_error` — SDK reported `authentication_failed`.
1741
- * - `timeout` — invoker's wall-clock signal aborted the stream.
1742
- * - `subprocess_crashed` — exception thrown out of the iterator.
1743
- *
1744
- * Cost is captured from the terminal `SDKResultMessage` regardless of
1745
- * subtype so the invoker can attribute partial spend on the failure
1746
- * paths (no_tool_call, wrong_tool, tool_error).
1127
+ * Delegated proxy invocation — DELEGATED-PROXY-API-DESIGN.md §4.5.
1128
+ * Implementation moved to `./claude-delegated.ts` (file-split-plan §8,
1129
+ * Tier 2); this stays as a thin forwarder so the `IAgentCore` interface
1130
+ * contract and existing call sites in `BackendRouter` /
1131
+ * `DelegatedBackendInvoker` continue to dispatch through
1132
+ * `core.runDelegatedTool`.
1747
1133
  */
1748
1134
  async runDelegatedTool(params) {
1749
- const startMs = Date.now();
1750
- const { toolName, toolArgs, modelId, maxTurns, maxBudgetUsd, sessionDir } = params;
1751
- const prompt = buildDelegatedToolPrompt(toolName, toolArgs);
1752
- const daemonReadToken = this.readTokenManager?.issue(sessionDir) ?? this.readToken;
1753
- let stream = null;
1754
- const aborted = { value: false };
1755
- // `abortReason` carries the reason that caused `aborted.value=true`
1756
- // so the post-loop classifier can map idle-watchdog aborts to
1757
- // `errorClass="timeout"`. Falls back to the caller's signal reason
1758
- // when only the caller initiated the abort.
1759
- let abortReason = null;
1760
- const closeStream = () => {
1761
- void (async () => {
1762
- try {
1763
- await stream?.return?.(undefined);
1764
- }
1765
- catch {
1766
- /* stream already closed */
1767
- }
1768
- })();
1769
- };
1770
- const onAbort = () => {
1771
- aborted.value = true;
1772
- abortReason = params.abortSignal?.reason ?? null;
1773
- closeStream();
1774
- };
1775
- if (params.abortSignal) {
1776
- if (params.abortSignal.aborted) {
1777
- aborted.value = true;
1778
- abortReason = params.abortSignal.reason ?? null;
1779
- }
1780
- else {
1781
- params.abortSignal.addEventListener("abort", onAbort, { once: true });
1782
- }
1783
- }
1784
- // Idle watchdog. Claude SDK runs in-process; cold-start is
1785
- // negligible (no MCP/CLI load) so the typical first message lands
1786
- // within 1-3 s. A 30 s idle threshold catches a stuck SDK iterator
1787
- // (network stall, server-side hang) without false-tripping a slow
1788
- // tool. On trip we close the stream the same way `onAbort` does and
1789
- // record the trip in `abortReason` so the classifier returns
1790
- // `errorClass="timeout"` (uniform with CLI backends).
1791
- const idleTimeoutMs = DELEGATED_PROXY_DEFAULTS.idleTimeoutMsByBackend.claude
1792
- ?? DELEGATED_PROXY_DEFAULTS.idleTimeoutMs;
1793
- const idleWatchdog = new IdleWatchdog({
1794
- idleTimeoutMs,
1795
- onTimeout: (idleMs) => {
1796
- if (aborted.value)
1797
- return;
1798
- aborted.value = true;
1799
- abortReason = new DelegatedProxyTimeoutError(`claude SDK stream idle for ${idleMs}ms (limit ${idleTimeoutMs}ms)`);
1800
- logger.warn({ idleMs, idleTimeoutMs, toolName }, "claude delegated proxy idle watchdog tripped");
1801
- closeStream();
1802
- },
1803
- });
1804
- try {
1805
- stream = query({
1806
- prompt,
1807
- options: {
1808
- model: modelId,
1809
- maxTurns,
1810
- maxBudgetUsd,
1811
- cwd: sessionDir,
1812
- env: buildDaemonApiCliEnv(sessionDir, this.config.apiPort, { readToken: daemonReadToken, sessionBackend: "claude" }),
1813
- systemPrompt: { type: "preset", preset: "claude_code" },
1814
- permissionMode: "dontAsk",
1815
- // The connector tool must be pre-authorized — Claude SDK with
1816
- // permissionMode="dontAsk" silently denies anything not in
1817
- // allowedTools.
1818
- //
1819
- // ToolSearch is Claude Code's deferred-tool discovery mechanism:
1820
- // when many MCP servers are registered in the user's global
1821
- // config (~/.claude.json: Notion, Gmail, GCal, Drive, Figma,
1822
- // Canva, Hugging Face, …) the CLI ships only a working set of
1823
- // tool schemas and defers the rest. To call a deferred tool, the
1824
- // model must first call ToolSearch to load its schema. Without
1825
- // ToolSearch allowed, the proxy's first turn was wasted on a
1826
- // denied ToolSearch call (audit log 2026-04-29: 1 Notion failure
1827
- // logged as `wrong_tool=ToolSearch`, 5 logged as
1828
- // `subprocess_crashed: Reached maximum number of turns (2)` —
1829
- // the model retried other approaches and exhausted the budget).
1830
- //
1831
- // Allowing ToolSearch is safe: allowedTools enforcement still
1832
- // gates which tools can be CALLED, and ToolSearch only loads
1833
- // schemas into context. The proxy parser below also skips
1834
- // ToolSearch when capturing `wrongToolName` so a ToolSearch+
1835
- // partial-result trace classifies as `no_tool_call` rather than
1836
- // misleading `wrong_tool=ToolSearch`.
1837
- //
1838
- // TODO(future): a cleaner architectural fix is to materialize a
1839
- // session-local `.mcp.json` containing only the relevant
1840
- // connector's MCP server and pass `strictMcpConfig: true` —
1841
- // that prevents deferral entirely (one MCP server → schemas fit
1842
- // in the working set). Punted because it requires extracting
1843
- // server configs from the user's global file per integration.
1844
- allowedTools: [toolName, DEFERRED_TOOL_DISCOVERY_TOOL_NAME],
1845
- // Defense-in-depth: even with allowedTools restricted to a tight
1846
- // set, keep the absolute-block layer (rm -rf, sudo, secret file
1847
- // reads) merged so a future relaxation of allowedTools can't
1848
- // accidentally drop these guarantees.
1849
- disallowedTools: [...ALWAYS_DISALLOWED_TOOLS],
1850
- // Adaptive thinking is the SDK default for thinking-capable
1851
- // models (Haiku 4.5+ / Sonnet 4.6+). Per Anthropic's docs
1852
- // thinking happens within a single API call so it does not
1853
- // typically burn an extra turn — but for a proxy that issues
1854
- // one named tool call with explicit args, thinking adds latency
1855
- // and tokens for no benefit. The proxy.md profile says "no
1856
- // narration"; disabling thinking aligns runtime behavior with
1857
- // that intent.
1858
- thinking: { type: "disabled" },
1859
- },
1860
- });
1861
- let capturedToolUseId = null;
1862
- let capturedToolResult = undefined;
1863
- let capturedToolErrorMessage = null;
1864
- let wrongToolName = null;
1865
- let cost = emptyCost();
1866
- let terminalSubtype = null;
1867
- let terminalIsError = false;
1868
- let terminalErrors = [];
1869
- try {
1870
- idleWatchdog.start();
1871
- for await (const message of stream) {
1872
- idleWatchdog.beat();
1873
- if (aborted.value) {
1874
- break;
1875
- }
1876
- if (message.type === "assistant") {
1877
- const assistantMsg = message;
1878
- const blocks = assistantMsg.message?.content;
1879
- if (!Array.isArray(blocks))
1880
- continue;
1881
- for (const block of blocks) {
1882
- if (!block || typeof block !== "object")
1883
- continue;
1884
- const blockType = block.type;
1885
- if (blockType !== "tool_use")
1886
- continue;
1887
- const blockName = block.name;
1888
- const blockId = block.id;
1889
- if (typeof blockName !== "string" || typeof blockId !== "string") {
1890
- continue;
1891
- }
1892
- if (blockName === toolName) {
1893
- if (capturedToolUseId === null) {
1894
- capturedToolUseId = blockId;
1895
- }
1896
- }
1897
- else if (blockName === DEFERRED_TOOL_DISCOVERY_TOOL_NAME) {
1898
- // Expected intermediate step for loading the connector's
1899
- // deferred MCP schema — not a violation. Do not capture as
1900
- // wrongToolName so a partial trace (ToolSearch + max_turns
1901
- // before the connector call) classifies as `no_tool_call`
1902
- // instead of misleading `wrong_tool=ToolSearch`.
1903
- }
1904
- else if (wrongToolName === null) {
1905
- wrongToolName = blockName;
1906
- // Early abort: bound the wall-clock spend on a wrong_tool
1907
- // failure to ~5s. Set `aborted` so the next loop iteration
1908
- // breaks; close the SDK stream so any pending tool_use
1909
- // doesn't continue. The post-loop classifier checks
1910
- // `wrongToolName` BEFORE the abort branch, so the failure
1911
- // is correctly attributed.
1912
- aborted.value = true;
1913
- closeStream();
1914
- }
1915
- }
1916
- }
1917
- else if (message.type === "user") {
1918
- const userMsg = message;
1919
- const content = userMsg.message?.content;
1920
- if (!Array.isArray(content))
1921
- continue;
1922
- for (const block of content) {
1923
- if (!block || typeof block !== "object")
1924
- continue;
1925
- if (block.type !== "tool_result")
1926
- continue;
1927
- const tuid = block.tool_use_id;
1928
- if (tuid !== capturedToolUseId)
1929
- continue;
1930
- const isToolError = block.is_error === true;
1931
- const rawContent = block.content;
1932
- const flat = flattenToolResultContent(rawContent);
1933
- if (isToolError) {
1934
- capturedToolErrorMessage =
1935
- flat.trim().length > 0 ? flat : "tool returned is_error";
1936
- }
1937
- else if (capturedToolResult === undefined) {
1938
- capturedToolResult = tryParseToolResult(flat);
1939
- }
1940
- }
1941
- }
1942
- else if (message.type === "result") {
1943
- const r = message;
1944
- terminalSubtype = r.subtype;
1945
- terminalIsError = r.is_error;
1946
- cost = {
1947
- tokensInput: r.usage.input_tokens ?? 0,
1948
- tokensOutput: r.usage.output_tokens ?? 0,
1949
- cacheCreationTokens: r.usage.cache_creation_input_tokens ?? 0,
1950
- cacheReadTokens: r.usage.cache_read_input_tokens ?? 0,
1951
- costUsd: r.total_cost_usd ?? 0,
1952
- durationMs: r.duration_ms ?? Date.now() - startMs,
1953
- numTurns: r.num_turns ?? 0,
1954
- };
1955
- if (r.subtype !== "success" && "errors" in r && Array.isArray(r.errors)) {
1956
- terminalErrors = r.errors;
1957
- }
1958
- // The result message is terminal per SDK semantics. Break out
1959
- // before the next iterator step. When `r.is_error` is true, the
1960
- // SDK's transport sets `lastErrorResultText` and throws on the
1961
- // next `readMessages` iteration — wrapping it as
1962
- // `Error("Claude Code returned an error result: <text>")`. That
1963
- // throw would land in the outer catch and misclassify as
1964
- // `subprocess_crashed`, discarding the captured cost. Audit log
1965
- // (2026-04-29) showed 5 such failures with num_turns=0,
1966
- // tokens=0, masking that this was actually `error_max_turns`.
1967
- // Breaking here lets the post-loop classifier run with the
1968
- // captured terminalSubtype, terminalErrors, wrongToolName, and
1969
- // cost intact.
1970
- break;
1971
- }
1972
- }
1973
- }
1974
- finally {
1975
- idleWatchdog.stop();
1976
- try {
1977
- await stream?.return?.(undefined);
1978
- }
1979
- catch {
1980
- /* stream already closed */
1981
- }
1982
- }
1983
- cost = withDurationMs(cost, startMs);
1984
- // wrong_tool check hoisted above the abort branch because the
1985
- // early-abort path sets `aborted.value` AND `wrongToolName`.
1986
- // Without this ordering the failure would surface as `cancelled`
1987
- // instead of the actual upstream cause. The `abortReason` field
1988
- // distinguishes idle-watchdog aborts (errorClass="timeout") from
1989
- // caller-initiated cancels (errorClass="cancelled" unless the
1990
- // caller's reason was itself a `DelegatedProxyTimeoutError`).
1991
- if (wrongToolName !== null) {
1992
- return {
1993
- ok: false,
1994
- errorClass: "wrong_tool",
1995
- message: `model called '${wrongToolName}' instead of requested '${toolName}'`,
1996
- cost,
1997
- };
1998
- }
1999
- if (aborted.value) {
2000
- const reason = abortReason ?? params.abortSignal?.reason;
2001
- const errorClass = classifyAbortReason(reason);
2002
- const idleAbort = reason instanceof DelegatedProxyTimeoutError
2003
- && /idle/.test(reason.message);
2004
- return {
2005
- ok: false,
2006
- errorClass,
2007
- message: errorClass === "timeout"
2008
- ? (idleAbort
2009
- ? `delegated proxy stream went idle (no claude SDK events for ${idleTimeoutMs}ms)`
2010
- : "delegated proxy timed out (wall-clock)")
2011
- : "delegated proxy cancelled by caller",
2012
- cost,
2013
- };
2014
- }
2015
- if (capturedToolResult !== undefined) {
2016
- return { ok: true, toolResult: capturedToolResult, cost };
2017
- }
2018
- if (capturedToolErrorMessage !== null) {
2019
- return {
2020
- ok: false,
2021
- errorClass: "tool_error",
2022
- message: capturedToolErrorMessage,
2023
- cost,
2024
- };
2025
- }
2026
- // Map specific terminal subtypes before falling through to
2027
- // no_tool_call. The model can fail for auth or budget reasons
2028
- // before ever emitting a tool_use block.
2029
- if (terminalSubtype === "error_during_execution" && terminalErrors.length > 0) {
2030
- const joined = terminalErrors.join("; ");
2031
- if (/auth|unauthorized|authentication_failed|invalid api key/i.test(joined)) {
2032
- return {
2033
- ok: false,
2034
- errorClass: "auth_error",
2035
- message: joined,
2036
- cost,
2037
- };
2038
- }
2039
- return {
2040
- ok: false,
2041
- errorClass: "tool_error",
2042
- message: joined,
2043
- cost,
2044
- };
2045
- }
2046
- if (terminalSubtype === null && !terminalIsError) {
2047
- // Stream ended before any terminal `result` arrived — abnormal
2048
- // termination not classified as an abort. Treat as parse_error
2049
- // so the route handler can surface the bug rather than retrying.
2050
- return {
2051
- ok: false,
2052
- errorClass: "parse_error",
2053
- message: "Claude SDK stream ended without a terminal result message",
2054
- cost,
2055
- };
2056
- }
2057
- return {
2058
- ok: false,
2059
- errorClass: "no_tool_call",
2060
- message: `model did not invoke '${toolName}' within ${maxTurns} turns (subtype=${terminalSubtype ?? "unknown"})`,
2061
- cost,
2062
- };
2063
- }
2064
- catch (err) {
2065
- const message = err instanceof Error ? err.message : String(err);
2066
- const cost = withDurationMs(emptyCost(), startMs);
2067
- // Map auth-shape exceptions before the catch-all subprocess_crashed.
2068
- if (/authentication_failed|unauthorized|invalid api key|sk-ant-/i.test(message)) {
2069
- return { ok: false, errorClass: "auth_error", message, cost };
2070
- }
2071
- if (aborted.value) {
2072
- return {
2073
- ok: false,
2074
- errorClass: classifyAbortReason(abortReason ?? params.abortSignal?.reason),
2075
- message,
2076
- cost,
2077
- };
2078
- }
2079
- return { ok: false, errorClass: "subprocess_crashed", message, cost };
2080
- }
2081
- finally {
2082
- params.abortSignal?.removeEventListener("abort", onAbort);
2083
- this.readTokenManager?.revoke(sessionDir);
2084
- }
1135
+ return runDelegatedToolFn(this.delegatedDeps(), params);
2085
1136
  }
2086
1137
  /**
2087
- * DELEGATED-TASK-MODE-DESIGN.md §9.1 — Claude SDK task mode. The
2088
- * subprocess plans + executes 1..N MCP calls within `allowedTools` and
2089
- * emits a final assistant message that the runtime helper validates
2090
- * against the caller's `outputSchema`.
2091
- *
2092
- * Stream parsing differences from `runDelegatedTool`:
2093
- * - We accept multiple `tool_use` blocks (counted against `maxToolCalls`).
2094
- * - We track per-tool durations to feed `onToolStep`.
2095
- * - We capture the *final* assistant text (after the last tool turn)
2096
- * as the validation target, not a single tool's `tool_result`.
2097
- *
2098
- * Safety:
2099
- * - `allowedTools` already excludes the destructive set when
2100
- * `allowDestructive: false`; the SDK will not surface those tools.
2101
- * - `disallowedTools` is the absolute-block layer + the destructive
2102
- * set as defense-in-depth (so a future relaxation of allowedTools
2103
- * can't accidentally widen the surface).
2104
- *
2105
- * The §6.2 "no retry after write" rule is enforced at the invoker
2106
- * layer; this method just signals via `writeClassToolFired` whether
2107
- * any destructive tool ran during the task.
1138
+ * Delegated task-mode invocation — DELEGATED-TASK-MODE-DESIGN.md §9.1.
1139
+ * Implementation moved to `./claude-delegated.ts`; see the comment on
1140
+ * `runDelegatedTool` above.
2108
1141
  */
2109
1142
  async runDelegatedTask(params) {
2110
- const startMs = Date.now();
2111
- const { systemPrompt, allowedTools, destructiveTools, writeClassTools, modelId, maxToolCalls, maxBudgetUsd, sessionDir, onToolStep, } = params;
2112
- const daemonReadToken = this.readTokenManager?.issue(sessionDir) ?? this.readToken;
2113
- const trace = [];
2114
- // §6.2 / §7.4 — match against the *write-class* set (destructive ∪
2115
- // reversible writes), not just destructive. Otherwise reversible
2116
- // write tools like `create_draft` slip past the retry guard and the
2117
- // single retry creates a duplicate side effect.
2118
- //
2119
- // Phase 1 (`/exec`) entries are fully-qualified exact names — the
2120
- // exact-equality fast path inside `matchRunAllowedToolPattern` covers
2121
- // them at one comparison. Phase 2 (`/api/delegated/run`) may pass
2122
- // `*`-suffixed glob patterns derived from the caller's allowedTools
2123
- // (DELEGATED-TASK-MODE-DESIGN.md §4.2); the shared helper handles both.
2124
- const writeClassMatcher = (name) => writeClassTools.some((pattern) => matchRunAllowedToolPattern(pattern, name));
2125
- let writeClassToolFired = false;
2126
- // DELEGATED-TASK-MODE-DESIGN.md §13 Phase 3.1 — Claude SDK
2127
- // structured-output. When the invoker passed `structuredOutputEnabled:
2128
- // true` AND a `wrappedSchema`, configure `outputFormat` so the SDK
2129
- // validates the model's final emission against the schema (with its
2130
- // own internal retries) and surfaces it on `SDKResultSuccess.structured_output`.
2131
- // We still capture the assistant text as a fallback — if the SDK
2132
- // returns success without `structured_output` (older subtype, future
2133
- // shape change, kill-switch flips off mid-call), the existing text
2134
- // path takes over.
2135
- const useStructuredOutput = params.structuredOutputEnabled === true
2136
- && !!params.wrappedSchema;
2137
- let capturedStructured;
2138
- let sawStructuredOutputRetryError = false;
2139
- let stream = null;
2140
- const aborted = { value: false };
2141
- const onAbort = () => {
2142
- aborted.value = true;
2143
- void (async () => {
2144
- try {
2145
- await stream?.return?.(undefined);
2146
- }
2147
- catch {
2148
- /* stream already closed */
2149
- }
2150
- })();
2151
- };
2152
- if (params.abortSignal) {
2153
- if (params.abortSignal.aborted) {
2154
- aborted.value = true;
2155
- }
2156
- else {
2157
- params.abortSignal.addEventListener("abort", onAbort, { once: true });
2158
- }
2159
- }
2160
- const pendingByUseId = new Map();
2161
- let toolCallCount = 0;
2162
- let loopAborted = false;
2163
- let assistantTextChunks = [];
2164
- /** The "final" assistant message is the most recent assistant message
2165
- * that contained NO `tool_use` block. The SDK emits one assistant
2166
- * message per turn; the planning turns mix text + tool_use, the
2167
- * closing turn is text-only. */
2168
- let lastAssistantTextOnlyChunks = [];
2169
- try {
2170
- stream = query({
2171
- prompt: systemPrompt,
2172
- options: {
2173
- model: modelId,
2174
- maxTurns: Math.max(2, maxToolCalls + 1),
2175
- maxBudgetUsd,
2176
- cwd: sessionDir,
2177
- env: buildDaemonApiCliEnv(sessionDir, this.config.apiPort, { readToken: daemonReadToken, sessionBackend: "claude" }),
2178
- systemPrompt: { type: "preset", preset: "claude_code" },
2179
- permissionMode: "dontAsk",
2180
- allowedTools: [...allowedTools],
2181
- // Defense-in-depth: absolute-block layer + destructive denies.
2182
- // Destructive entries are redundant with the allowedTools
2183
- // subtraction (when allowDestructive=false) but kept so a
2184
- // future allowedTools widening doesn't drop the guarantee.
2185
- disallowedTools: [
2186
- ...ALWAYS_DISALLOWED_TOOLS,
2187
- ...(params.allowDestructive ? [] : destructiveTools),
2188
- ],
2189
- // §13 Phase 3.1 — bind the wrapped schema (user schema OR
2190
- // confirmation envelope OR error envelope) to SDK 0.2.98's
2191
- // `outputFormat`. Result message carries `structured_output`
2192
- // which we read below. Off when the kill switch is false or
2193
- // the invoker omitted the wrapped schema.
2194
- ...(useStructuredOutput && params.wrappedSchema
2195
- ? {
2196
- outputFormat: {
2197
- type: "json_schema",
2198
- schema: params.wrappedSchema,
2199
- },
2200
- }
2201
- : {}),
2202
- },
2203
- });
2204
- let cost = emptyCost();
2205
- try {
2206
- for await (const message of stream) {
2207
- if (aborted.value || loopAborted)
2208
- break;
2209
- if (message.type === "assistant") {
2210
- const assistantMsg = message;
2211
- const blocks = assistantMsg.message?.content;
2212
- if (!Array.isArray(blocks))
2213
- continue;
2214
- const textChunks = [];
2215
- let sawToolUse = false;
2216
- for (const block of blocks) {
2217
- if (!block || typeof block !== "object")
2218
- continue;
2219
- const blockType = block.type;
2220
- if (blockType === "text") {
2221
- const text = block.text;
2222
- if (typeof text === "string")
2223
- textChunks.push(text);
2224
- continue;
2225
- }
2226
- if (blockType !== "tool_use")
2227
- continue;
2228
- sawToolUse = true;
2229
- const blockName = block.name;
2230
- const blockId = block.id;
2231
- const blockArgs = block.input;
2232
- if (typeof blockName !== "string" || typeof blockId !== "string") {
2233
- continue;
2234
- }
2235
- toolCallCount += 1;
2236
- if (toolCallCount > maxToolCalls) {
2237
- // §7.5 — once the cap is exceeded, abort. The next
2238
- // tool_use is treated as overrun.
2239
- loopAborted = true;
2240
- aborted.value = true;
2241
- try {
2242
- await stream?.return?.(undefined);
2243
- }
2244
- catch {
2245
- /* already closed */
2246
- }
2247
- break;
2248
- }
2249
- if (writeClassMatcher(blockName)) {
2250
- writeClassToolFired = true;
2251
- }
2252
- pendingByUseId.set(blockId, {
2253
- name: blockName,
2254
- args: blockArgs,
2255
- startedAt: Date.now(),
2256
- });
2257
- }
2258
- assistantTextChunks = assistantTextChunks.concat(textChunks);
2259
- if (!sawToolUse && textChunks.length > 0) {
2260
- lastAssistantTextOnlyChunks = textChunks;
2261
- }
2262
- }
2263
- else if (message.type === "user") {
2264
- const userMsg = message;
2265
- const content = userMsg.message?.content;
2266
- if (!Array.isArray(content))
2267
- continue;
2268
- for (const block of content) {
2269
- if (!block || typeof block !== "object")
2270
- continue;
2271
- if (block.type !== "tool_result")
2272
- continue;
2273
- const tuid = block.tool_use_id;
2274
- if (typeof tuid !== "string")
2275
- continue;
2276
- const pending = pendingByUseId.get(tuid);
2277
- if (!pending)
2278
- continue;
2279
- pendingByUseId.delete(tuid);
2280
- const isToolError = block.is_error === true;
2281
- // `tool_result` content is either a string or an array of
2282
- // content blocks (typically a single `{type:"text", text}`).
2283
- // The MCP SDK wraps connector JSON responses by serializing
2284
- // to that text body, so the response-shape walker
2285
- // downstream wants the parsed object. Pull the first text
2286
- // block, JSON-parse when possible, fallback to the raw
2287
- // string so the field is always populated for ok steps.
2288
- let parsedToolResult;
2289
- const blockContent = block.content;
2290
- if (typeof blockContent === "string") {
2291
- try {
2292
- parsedToolResult = JSON.parse(blockContent);
2293
- }
2294
- catch {
2295
- parsedToolResult = blockContent;
2296
- }
2297
- }
2298
- else if (Array.isArray(blockContent)) {
2299
- const firstText = blockContent.find((b) => !!b
2300
- && typeof b === "object"
2301
- && b.type === "text"
2302
- && typeof b.text === "string");
2303
- if (firstText) {
2304
- try {
2305
- parsedToolResult = JSON.parse(firstText.text);
2306
- }
2307
- catch {
2308
- parsedToolResult = firstText.text;
2309
- }
2310
- }
2311
- else {
2312
- parsedToolResult = blockContent;
2313
- }
2314
- }
2315
- const step = {
2316
- toolName: pending.name,
2317
- toolArgs: pending.args,
2318
- durationMs: Date.now() - pending.startedAt,
2319
- status: isToolError ? "error" : "ok",
2320
- costUsd: null,
2321
- tokensInput: null,
2322
- tokensOutput: null,
2323
- toolResult: parsedToolResult,
2324
- };
2325
- trace.push(step);
2326
- onToolStep?.(step);
2327
- }
2328
- }
2329
- else if (message.type === "result") {
2330
- const r = message;
2331
- cost = {
2332
- tokensInput: r.usage.input_tokens ?? 0,
2333
- tokensOutput: r.usage.output_tokens ?? 0,
2334
- cacheCreationTokens: r.usage.cache_creation_input_tokens ?? 0,
2335
- cacheReadTokens: r.usage.cache_read_input_tokens ?? 0,
2336
- costUsd: r.total_cost_usd ?? 0,
2337
- durationMs: r.duration_ms ?? Date.now() - startMs,
2338
- numTurns: r.num_turns ?? 0,
2339
- };
2340
- // §13 Phase 3.1 — capture structured output when present, and
2341
- // map the SDK's structured-output-retry-exhausted subtype to
2342
- // a parse_error so the invoker classifies it consistently
2343
- // with the text-extract path.
2344
- if (r.subtype === "success") {
2345
- const success = r;
2346
- if (success.structured_output !== undefined) {
2347
- capturedStructured = success.structured_output;
2348
- }
2349
- }
2350
- else if (r.subtype === "error_max_structured_output_retries") {
2351
- sawStructuredOutputRetryError = true;
2352
- }
2353
- }
2354
- }
2355
- }
2356
- finally {
2357
- try {
2358
- await stream?.return?.(undefined);
2359
- }
2360
- catch {
2361
- /* already closed */
2362
- }
2363
- }
2364
- cost = withDurationMs(cost, startMs);
2365
- if (loopAborted) {
2366
- return {
2367
- ok: false,
2368
- errorClass: "loop_aborted",
2369
- message: `subprocess exceeded maxToolCalls=${maxToolCalls}`,
2370
- cost,
2371
- trace,
2372
- writeClassToolFired,
2373
- };
2374
- }
2375
- if (aborted.value) {
2376
- const errorClass = classifyAbortReason(params.abortSignal?.reason);
2377
- return {
2378
- ok: false,
2379
- errorClass,
2380
- message: errorClass === "timeout"
2381
- ? "delegated task timed out (wall-clock)"
2382
- : "delegated task cancelled by caller",
2383
- cost,
2384
- trace,
2385
- writeClassToolFired,
2386
- };
2387
- }
2388
- const finalText = lastAssistantTextOnlyChunks.length > 0
2389
- ? lastAssistantTextOnlyChunks.join("\n").trim()
2390
- : assistantTextChunks.join("\n").trim();
2391
- // §13 Phase 3.1 — `error_max_structured_output_retries` typically
2392
- // fires when the model wanted to emit a §7.2 confirmation envelope
2393
- // or §5.1 error envelope, neither of which satisfies the user's
2394
- // narrow schema. The assistant text emissions captured during those
2395
- // retries land in `assistantTextChunks` / `lastAssistantTextOnlyChunks`,
2396
- // so the invoker's text-extract chain can route them via
2397
- // `detectConfirmationEnvelope` / `detectErrorEnvelope`. Only return
2398
- // `parse_error` if there is also no usable text — otherwise fall
2399
- // through to the text-emission path (no `structuredOutput` field
2400
- // set, so the invoker uses `rawAssistantText`).
2401
- if (sawStructuredOutputRetryError
2402
- && capturedStructured === undefined
2403
- && finalText.length === 0) {
2404
- return {
2405
- ok: false,
2406
- errorClass: "parse_error",
2407
- message: "Claude SDK exhausted structured-output retries and emitted no text fallback.",
2408
- cost,
2409
- trace,
2410
- writeClassToolFired,
2411
- };
2412
- }
2413
- // §13 Phase 3.1 — when the SDK supplied `structured_output`, that
2414
- // is the validated final emission; the assistant text may be empty
2415
- // (the SDK consumes the JSON internally on success). Skip the
2416
- // empty-text parse_error guard in that case.
2417
- if (capturedStructured === undefined && finalText.length === 0) {
2418
- return {
2419
- ok: false,
2420
- errorClass: "parse_error",
2421
- message: "Claude SDK stream ended without a text-only assistant turn",
2422
- cost,
2423
- trace,
2424
- writeClassToolFired,
2425
- };
2426
- }
2427
- return {
2428
- ok: true,
2429
- // When structured output is present, `rawAssistantText` is purely
2430
- // a fallback; the invoker prefers `structuredOutput`. Carry both
2431
- // so a future kill-switch flip mid-restart still sees an
2432
- // extractable text emission.
2433
- rawAssistantText: finalText,
2434
- cost,
2435
- trace,
2436
- writeClassToolFired,
2437
- ...(capturedStructured !== undefined
2438
- ? { structuredOutput: capturedStructured }
2439
- : {}),
2440
- };
2441
- }
2442
- catch (err) {
2443
- const message = err instanceof Error ? err.message : String(err);
2444
- const cost = withDurationMs(emptyCost(), startMs);
2445
- if (/authentication_failed|unauthorized|invalid api key|sk-ant-/i.test(message)) {
2446
- return {
2447
- ok: false,
2448
- errorClass: "auth_error",
2449
- message,
2450
- cost,
2451
- trace,
2452
- writeClassToolFired,
2453
- };
2454
- }
2455
- if (aborted.value) {
2456
- return {
2457
- ok: false,
2458
- errorClass: classifyAbortReason(params.abortSignal?.reason),
2459
- message,
2460
- cost,
2461
- trace,
2462
- writeClassToolFired,
2463
- };
2464
- }
2465
- return {
2466
- ok: false,
2467
- errorClass: "subprocess_crashed",
2468
- message,
2469
- cost,
2470
- trace,
2471
- writeClassToolFired,
2472
- };
2473
- }
2474
- finally {
2475
- params.abortSignal?.removeEventListener("abort", onAbort);
2476
- this.readTokenManager?.revoke(sessionDir);
2477
- }
2478
- }
2479
- }
2480
- function extractClaudeProbeTools(message) {
2481
- if (!message || typeof message !== "object")
2482
- return [];
2483
- const out = [];
2484
- const record = message;
2485
- if (record.type === "system" && record.subtype === "init" && Array.isArray(record.tools)) {
2486
- addClaudeProbeTools(record.tools, out);
2487
- }
2488
- if (record.type === "assistant" || record.type === "user") {
2489
- addClaudeProbeTools(record.message, out);
2490
- }
2491
- if (record.type === "result") {
2492
- addClaudeProbeTools(record.result, out);
2493
- }
2494
- return out;
2495
- }
2496
- function addClaudeProbeTools(value, out, depth = 0) {
2497
- if (depth > 8 || value === null || value === undefined)
2498
- return;
2499
- if (typeof value === "string") {
2500
- for (const match of value.matchAll(CLAUDE_CONNECTOR_TOOL_RE)) {
2501
- out.push(match[0]);
2502
- }
2503
- if (isClaudeProbeToolName(value))
2504
- out.push(value);
2505
- return;
2506
- }
2507
- if (Array.isArray(value)) {
2508
- for (const item of value)
2509
- addClaudeProbeTools(item, out, depth + 1);
2510
- return;
2511
- }
2512
- if (typeof value !== "object")
2513
- return;
2514
- const record = value;
2515
- addClaudeProbeTools(record.tool_name, out, depth + 1);
2516
- addClaudeProbeTools(record.text, out, depth + 1);
2517
- addClaudeProbeTools(record.content, out, depth + 1);
2518
- addClaudeProbeTools(record.message, out, depth + 1);
2519
- addClaudeProbeTools(record.result, out, depth + 1);
2520
- addClaudeProbeTools(record.tools, out, depth + 1);
2521
- }
2522
- function isClaudeProbeToolName(value) {
2523
- return CLAUDE_PROBE_TOOL_PREFIXES.some((prefix) => {
2524
- if (!value.startsWith(prefix))
2525
- return false;
2526
- // Hyphen is part of the alphabet for kebab-case connectors (Notion's
2527
- // `notion-search` etc.). Snake-case connectors keep working because
2528
- // `_` is still in the class.
2529
- return /^[A-Za-z0-9_-]+$/.test(value.slice(prefix.length));
2530
- });
2531
- }
2532
- function describeClaudeProbeResultError(result) {
2533
- if ("result" in result && typeof result.result === "string" && result.result.trim()) {
2534
- return result.result.trim();
2535
- }
2536
- if ("errors" in result && Array.isArray(result.errors) && result.errors.length > 0) {
2537
- return result.errors.join("; ");
1143
+ return runDelegatedTaskFn(this.delegatedDeps(), params);
2538
1144
  }
2539
- return result.subtype;
2540
1145
  }
2541
1146
  //# sourceMappingURL=claude-code-core.js.map