@vellumai/assistant 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1068) hide show
  1. package/.dockerignore +27 -0
  2. package/.env.example +22 -0
  3. package/Dockerfile +99 -0
  4. package/Dockerfile.sandbox +5 -0
  5. package/README.md +248 -0
  6. package/bun.lock +1723 -0
  7. package/bunfig.toml +2 -0
  8. package/docs/skills.md +158 -0
  9. package/drizzle/0000_dizzy_maggott.sql +301 -0
  10. package/drizzle/meta/0000_snapshot.json +1999 -0
  11. package/drizzle/meta/_journal.json +13 -0
  12. package/drizzle.config.ts +7 -0
  13. package/eslint.config.mjs +17 -0
  14. package/hook-templates/debug-prompt-logger/hook.json +7 -0
  15. package/hook-templates/debug-prompt-logger/run.sh +68 -0
  16. package/knip.json +9 -0
  17. package/package.json +70 -0
  18. package/scripts/capture-x-graphql.ts +545 -0
  19. package/scripts/ipc/check-contract-inventory.ts +104 -0
  20. package/scripts/ipc/check-swift-decoder-drift.ts +166 -0
  21. package/scripts/ipc/generate-swift.ts +492 -0
  22. package/scripts/test-filesystem-tools.sh +48 -0
  23. package/scripts/test.sh +127 -0
  24. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +2485 -0
  25. package/src/__tests__/account-registry.test.ts +245 -0
  26. package/src/__tests__/active-skill-tools.test.ts +378 -0
  27. package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
  28. package/src/__tests__/agent-loop-thinking.test.ts +81 -0
  29. package/src/__tests__/agent-loop.test.ts +1135 -0
  30. package/src/__tests__/anthropic-provider.test.ts +778 -0
  31. package/src/__tests__/app-builder-tool-scripts.test.ts +290 -0
  32. package/src/__tests__/app-bundler.test.ts +292 -0
  33. package/src/__tests__/app-executors.test.ts +613 -0
  34. package/src/__tests__/app-git-history.test.ts +176 -0
  35. package/src/__tests__/app-git-service.test.ts +169 -0
  36. package/src/__tests__/app-open-proxy.test.ts +62 -0
  37. package/src/__tests__/asset-materialize-tool.test.ts +452 -0
  38. package/src/__tests__/asset-search-tool.test.ts +477 -0
  39. package/src/__tests__/assistant-attachment-directive.test.ts +401 -0
  40. package/src/__tests__/assistant-attachments.test.ts +437 -0
  41. package/src/__tests__/assistant-event-hub.test.ts +226 -0
  42. package/src/__tests__/assistant-event.test.ts +123 -0
  43. package/src/__tests__/assistant-events-sse-hardening.test.ts +315 -0
  44. package/src/__tests__/attachments-store.test.ts +476 -0
  45. package/src/__tests__/attachments.test.ts +134 -0
  46. package/src/__tests__/audit-log-rotation.test.ts +154 -0
  47. package/src/__tests__/browser-fill-credential.test.ts +309 -0
  48. package/src/__tests__/browser-manager.test.ts +203 -0
  49. package/src/__tests__/browser-runtime-check.test.ts +55 -0
  50. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +68 -0
  51. package/src/__tests__/browser-skill-endstate.test.ts +195 -0
  52. package/src/__tests__/bundle-scanner.test.ts +313 -0
  53. package/src/__tests__/call-bridge.test.ts +517 -0
  54. package/src/__tests__/call-constants.test.ts +40 -0
  55. package/src/__tests__/call-domain.test.ts +163 -0
  56. package/src/__tests__/call-orchestrator.test.ts +625 -0
  57. package/src/__tests__/call-recovery.test.ts +518 -0
  58. package/src/__tests__/call-routes-http.test.ts +699 -0
  59. package/src/__tests__/call-state-machine.test.ts +143 -0
  60. package/src/__tests__/call-state.test.ts +174 -0
  61. package/src/__tests__/call-store.test.ts +691 -0
  62. package/src/__tests__/channel-approval-routes.test.ts +2356 -0
  63. package/src/__tests__/channel-approval.test.ts +299 -0
  64. package/src/__tests__/channel-approvals.test.ts +521 -0
  65. package/src/__tests__/channel-delivery-store.test.ts +447 -0
  66. package/src/__tests__/channel-guardian.test.ts +1005 -0
  67. package/src/__tests__/checker.test.ts +3519 -0
  68. package/src/__tests__/clarification-resolver.test.ts +159 -0
  69. package/src/__tests__/classifier.test.ts +67 -0
  70. package/src/__tests__/claude-code-skill-regression.test.ts +127 -0
  71. package/src/__tests__/claude-code-tool-profiles.test.ts +88 -0
  72. package/src/__tests__/cli-discover.test.ts +85 -0
  73. package/src/__tests__/cli.test.ts +26 -0
  74. package/src/__tests__/clipboard.test.ts +80 -0
  75. package/src/__tests__/commit-guarantee.test.ts +335 -0
  76. package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
  77. package/src/__tests__/compaction.benchmark.test.ts +176 -0
  78. package/src/__tests__/computer-use-session-compaction.test.ts +132 -0
  79. package/src/__tests__/computer-use-session-lifecycle.test.ts +293 -0
  80. package/src/__tests__/computer-use-session-working-dir.test.ts +117 -0
  81. package/src/__tests__/computer-use-skill-baseline.test.ts +74 -0
  82. package/src/__tests__/computer-use-skill-endstate.test.ts +89 -0
  83. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +217 -0
  84. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +107 -0
  85. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +54 -0
  86. package/src/__tests__/computer-use-tools.test.ts +250 -0
  87. package/src/__tests__/config-schema.test.ts +1462 -0
  88. package/src/__tests__/conflict-intent-tokenization.test.ts +141 -0
  89. package/src/__tests__/conflict-policy.test.ts +121 -0
  90. package/src/__tests__/conflict-store.test.ts +332 -0
  91. package/src/__tests__/connection-policy.test.ts +102 -0
  92. package/src/__tests__/contacts-tools.test.ts +331 -0
  93. package/src/__tests__/context-memory-e2e.test.ts +434 -0
  94. package/src/__tests__/context-token-estimator.test.ts +135 -0
  95. package/src/__tests__/context-window-manager.test.ts +376 -0
  96. package/src/__tests__/contradiction-checker.test.ts +314 -0
  97. package/src/__tests__/conversation-store.test.ts +612 -0
  98. package/src/__tests__/credential-broker-browser-fill.test.ts +517 -0
  99. package/src/__tests__/credential-broker-server-use.test.ts +554 -0
  100. package/src/__tests__/credential-broker.test.ts +167 -0
  101. package/src/__tests__/credential-host-pattern-match.test.ts +104 -0
  102. package/src/__tests__/credential-metadata-store.test.ts +779 -0
  103. package/src/__tests__/credential-policy-validate.test.ts +121 -0
  104. package/src/__tests__/credential-resolve.test.ts +328 -0
  105. package/src/__tests__/credential-security-e2e.test.ts +352 -0
  106. package/src/__tests__/credential-security-invariants.test.ts +583 -0
  107. package/src/__tests__/credential-selection.test.ts +354 -0
  108. package/src/__tests__/credential-vault-unit.test.ts +780 -0
  109. package/src/__tests__/credential-vault.test.ts +852 -0
  110. package/src/__tests__/daemon-assistant-events.test.ts +164 -0
  111. package/src/__tests__/daemon-server-session-init.test.ts +522 -0
  112. package/src/__tests__/date-context.test.ts +373 -0
  113. package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
  114. package/src/__tests__/delete-managed-skill-tool.test.ts +97 -0
  115. package/src/__tests__/diff.test.ts +121 -0
  116. package/src/__tests__/domain-normalize.test.ts +112 -0
  117. package/src/__tests__/domain-policy.test.ts +124 -0
  118. package/src/__tests__/doordash-client.test.ts +186 -0
  119. package/src/__tests__/doordash-session.test.ts +152 -0
  120. package/src/__tests__/dynamic-page-surface.test.ts +91 -0
  121. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +132 -0
  122. package/src/__tests__/edit-engine.test.ts +180 -0
  123. package/src/__tests__/elevenlabs-client.test.ts +271 -0
  124. package/src/__tests__/email-cli.test.ts +283 -0
  125. package/src/__tests__/encrypted-store.test.ts +332 -0
  126. package/src/__tests__/entity-extractor.test.ts +190 -0
  127. package/src/__tests__/ephemeral-permissions.test.ts +362 -0
  128. package/src/__tests__/evaluate-typescript-tool.test.ts +286 -0
  129. package/src/__tests__/event-bus.test.ts +222 -0
  130. package/src/__tests__/file-edit-tool.test.ts +122 -0
  131. package/src/__tests__/file-ops-service.test.ts +330 -0
  132. package/src/__tests__/file-read-tool.test.ts +75 -0
  133. package/src/__tests__/file-write-tool.test.ts +113 -0
  134. package/src/__tests__/filesystem-tools.test.ts +579 -0
  135. package/src/__tests__/fixtures/credential-security-fixtures.ts +181 -0
  136. package/src/__tests__/fixtures/media-reuse-fixtures.ts +126 -0
  137. package/src/__tests__/fixtures/mock-signup-server.ts +387 -0
  138. package/src/__tests__/fixtures/proxy-fixtures.ts +147 -0
  139. package/src/__tests__/followup-tools.test.ts +303 -0
  140. package/src/__tests__/forbidden-legacy-symbols.test.ts +71 -0
  141. package/src/__tests__/fuzzy-match-property.test.ts +216 -0
  142. package/src/__tests__/fuzzy-match.test.ts +138 -0
  143. package/src/__tests__/gateway-only-enforcement.test.ts +631 -0
  144. package/src/__tests__/gemini-image-service.test.ts +261 -0
  145. package/src/__tests__/gemini-provider.test.ts +651 -0
  146. package/src/__tests__/get-weather.test.ts +318 -0
  147. package/src/__tests__/gmail-integration.test.ts +73 -0
  148. package/src/__tests__/handlers-add-trust-rule-metadata.test.ts +202 -0
  149. package/src/__tests__/handlers-cu-observation-blob.test.ts +352 -0
  150. package/src/__tests__/handlers-ipc-blob-probe.test.ts +191 -0
  151. package/src/__tests__/handlers-slack-config.test.ts +200 -0
  152. package/src/__tests__/handlers-task-submit-slash.test.ts +38 -0
  153. package/src/__tests__/handlers-telegram-config.test.ts +968 -0
  154. package/src/__tests__/handlers-twilio-config.test.ts +659 -0
  155. package/src/__tests__/handlers-twitter-config.test.ts +858 -0
  156. package/src/__tests__/headless-browser-interactions.test.ts +536 -0
  157. package/src/__tests__/headless-browser-navigate.test.ts +211 -0
  158. package/src/__tests__/headless-browser-read-tools.test.ts +261 -0
  159. package/src/__tests__/headless-browser-snapshot.test.ts +185 -0
  160. package/src/__tests__/history-repair-observability.test.ts +56 -0
  161. package/src/__tests__/history-repair.test.ts +510 -0
  162. package/src/__tests__/home-base-bootstrap.test.ts +82 -0
  163. package/src/__tests__/hooks-blocking.test.ts +128 -0
  164. package/src/__tests__/hooks-cli.test.ts +144 -0
  165. package/src/__tests__/hooks-config.test.ts +93 -0
  166. package/src/__tests__/hooks-discovery.test.ts +199 -0
  167. package/src/__tests__/hooks-integration.test.ts +189 -0
  168. package/src/__tests__/hooks-manager.test.ts +187 -0
  169. package/src/__tests__/hooks-runner.test.ts +182 -0
  170. package/src/__tests__/hooks-settings.test.ts +154 -0
  171. package/src/__tests__/hooks-templates.test.ts +137 -0
  172. package/src/__tests__/hooks-ts-runner.test.ts +125 -0
  173. package/src/__tests__/hooks-watch.test.ts +100 -0
  174. package/src/__tests__/host-file-edit-tool.test.ts +228 -0
  175. package/src/__tests__/host-file-read-tool.test.ts +123 -0
  176. package/src/__tests__/host-file-write-tool.test.ts +136 -0
  177. package/src/__tests__/host-shell-tool.test.ts +562 -0
  178. package/src/__tests__/ingress-reconcile.test.ts +581 -0
  179. package/src/__tests__/ingress-url-consistency.test.ts +214 -0
  180. package/src/__tests__/intent-routing.test.ts +259 -0
  181. package/src/__tests__/ipc-blob-store.test.ts +315 -0
  182. package/src/__tests__/ipc-contract-inventory.test.ts +54 -0
  183. package/src/__tests__/ipc-contract.test.ts +74 -0
  184. package/src/__tests__/ipc-protocol.test.ts +113 -0
  185. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  186. package/src/__tests__/ipc-snapshot.test.ts +1769 -0
  187. package/src/__tests__/ipc-validate.test.ts +407 -0
  188. package/src/__tests__/key-migration.test.ts +206 -0
  189. package/src/__tests__/keychain.test.ts +258 -0
  190. package/src/__tests__/llm-usage-store.test.ts +221 -0
  191. package/src/__tests__/managed-skill-lifecycle.test.ts +257 -0
  192. package/src/__tests__/managed-store.test.ts +608 -0
  193. package/src/__tests__/media-generate-image.test.ts +238 -0
  194. package/src/__tests__/media-reuse-story.e2e.test.ts +676 -0
  195. package/src/__tests__/media-visibility-policy.test.ts +141 -0
  196. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +235 -0
  197. package/src/__tests__/memory-lifecycle-e2e.test.ts +481 -0
  198. package/src/__tests__/memory-query-builder.test.ts +59 -0
  199. package/src/__tests__/memory-recall-quality.test.ts +846 -0
  200. package/src/__tests__/memory-regressions.experimental.test.ts +538 -0
  201. package/src/__tests__/memory-regressions.test.ts +4435 -0
  202. package/src/__tests__/memory-retrieval-budget.test.ts +49 -0
  203. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  204. package/src/__tests__/migration-cli-flows.test.ts +169 -0
  205. package/src/__tests__/migration-ordering.test.ts +249 -0
  206. package/src/__tests__/mock-signup-server.test.ts +528 -0
  207. package/src/__tests__/oauth-callback-registry.test.ts +92 -0
  208. package/src/__tests__/oauth2-gateway-transport.test.ts +285 -0
  209. package/src/__tests__/onboarding-starter-tasks.test.ts +176 -0
  210. package/src/__tests__/onboarding-template-contract.test.ts +58 -0
  211. package/src/__tests__/openai-provider.test.ts +753 -0
  212. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  213. package/src/__tests__/parser.test.ts +472 -0
  214. package/src/__tests__/path-classifier.test.ts +73 -0
  215. package/src/__tests__/path-policy.test.ts +435 -0
  216. package/src/__tests__/platform-move-helper.test.ts +99 -0
  217. package/src/__tests__/platform-socket-path.test.ts +52 -0
  218. package/src/__tests__/platform-workspace-migration.test.ts +1000 -0
  219. package/src/__tests__/platform.test.ts +131 -0
  220. package/src/__tests__/playbook-execution.test.ts +502 -0
  221. package/src/__tests__/playbook-tools.test.ts +340 -0
  222. package/src/__tests__/prebuilt-home-base-seed.test.ts +75 -0
  223. package/src/__tests__/pricing.test.ts +256 -0
  224. package/src/__tests__/profile-compiler.test.ts +374 -0
  225. package/src/__tests__/provider-commit-message-generator.test.ts +342 -0
  226. package/src/__tests__/provider-registry-ollama.test.ts +16 -0
  227. package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
  228. package/src/__tests__/proxy-approval-callback.test.ts +601 -0
  229. package/src/__tests__/public-ingress-urls.test.ts +256 -0
  230. package/src/__tests__/qdrant-manager.test.ts +267 -0
  231. package/src/__tests__/ratelimit.test.ts +297 -0
  232. package/src/__tests__/recurrence-engine-rruleset.test.ts +175 -0
  233. package/src/__tests__/recurrence-engine.test.ts +78 -0
  234. package/src/__tests__/recurrence-types.test.ts +79 -0
  235. package/src/__tests__/registry.test.ts +494 -0
  236. package/src/__tests__/relay-server.test.ts +688 -0
  237. package/src/__tests__/reminder-store.test.ts +223 -0
  238. package/src/__tests__/reminder.test.ts +229 -0
  239. package/src/__tests__/request-file-tool.test.ts +158 -0
  240. package/src/__tests__/run-orchestrator-assistant-events.test.ts +227 -0
  241. package/src/__tests__/run-orchestrator.test.ts +425 -0
  242. package/src/__tests__/runtime-attachment-metadata.test.ts +189 -0
  243. package/src/__tests__/runtime-events-sse-parity.test.ts +343 -0
  244. package/src/__tests__/runtime-events-sse.test.ts +162 -0
  245. package/src/__tests__/runtime-runs-http.test.ts +438 -0
  246. package/src/__tests__/runtime-runs.test.ts +260 -0
  247. package/src/__tests__/sandbox-diagnostics.test.ts +408 -0
  248. package/src/__tests__/sandbox-host-parity.test.ts +950 -0
  249. package/src/__tests__/scaffold-managed-skill-tool.test.ts +253 -0
  250. package/src/__tests__/schedule-store.test.ts +484 -0
  251. package/src/__tests__/schedule-tools.test.ts +783 -0
  252. package/src/__tests__/scheduler-recurrence.test.ts +430 -0
  253. package/src/__tests__/script-proxy-certs.test.ts +90 -0
  254. package/src/__tests__/script-proxy-connect-tunnel.test.ts +177 -0
  255. package/src/__tests__/script-proxy-decision-trace.test.ts +156 -0
  256. package/src/__tests__/script-proxy-http-forwarder.test.ts +281 -0
  257. package/src/__tests__/script-proxy-injection-runtime.test.ts +401 -0
  258. package/src/__tests__/script-proxy-mitm-handler.test.ts +407 -0
  259. package/src/__tests__/script-proxy-policy-runtime.test.ts +287 -0
  260. package/src/__tests__/script-proxy-policy.test.ts +310 -0
  261. package/src/__tests__/script-proxy-rewrite-specificity.test.ts +135 -0
  262. package/src/__tests__/script-proxy-router.test.ts +180 -0
  263. package/src/__tests__/script-proxy-session-manager.test.ts +382 -0
  264. package/src/__tests__/script-proxy-session-runtime.test.ts +113 -0
  265. package/src/__tests__/secret-allowlist.test.ts +230 -0
  266. package/src/__tests__/secret-ingress-handler.test.ts +110 -0
  267. package/src/__tests__/secret-onetime-send.test.ts +130 -0
  268. package/src/__tests__/secret-prompt-log-hygiene.test.ts +106 -0
  269. package/src/__tests__/secret-response-routing.test.ts +93 -0
  270. package/src/__tests__/secret-scanner-executor.test.ts +348 -0
  271. package/src/__tests__/secret-scanner.test.ts +900 -0
  272. package/src/__tests__/secure-keys.test.ts +323 -0
  273. package/src/__tests__/server-history-render.test.ts +431 -0
  274. package/src/__tests__/session-abort-tool-results.test.ts +240 -0
  275. package/src/__tests__/session-conflict-gate.test.ts +1136 -0
  276. package/src/__tests__/session-error.test.ts +369 -0
  277. package/src/__tests__/session-evictor.test.ts +188 -0
  278. package/src/__tests__/session-init.benchmark.test.ts +465 -0
  279. package/src/__tests__/session-load-history-repair.test.ts +222 -0
  280. package/src/__tests__/session-pre-run-repair.test.ts +213 -0
  281. package/src/__tests__/session-process-bridge.test.ts +242 -0
  282. package/src/__tests__/session-profile-injection.test.ts +444 -0
  283. package/src/__tests__/session-provider-retry-repair.test.ts +306 -0
  284. package/src/__tests__/session-queue.test.ts +1535 -0
  285. package/src/__tests__/session-runtime-assembly.test.ts +476 -0
  286. package/src/__tests__/session-runtime-workspace.test.ts +183 -0
  287. package/src/__tests__/session-skill-tools.test.ts +2431 -0
  288. package/src/__tests__/session-slash-known.test.ts +368 -0
  289. package/src/__tests__/session-slash-queue.test.ts +288 -0
  290. package/src/__tests__/session-slash-unknown.test.ts +271 -0
  291. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  292. package/src/__tests__/session-tool-setup-app-refresh.test.ts +473 -0
  293. package/src/__tests__/session-tool-setup-memory-scope.test.ts +140 -0
  294. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +140 -0
  295. package/src/__tests__/session-undo.test.ts +75 -0
  296. package/src/__tests__/session-workspace-cache-state.test.ts +246 -0
  297. package/src/__tests__/session-workspace-injection.test.ts +327 -0
  298. package/src/__tests__/session-workspace-tool-tracking.test.ts +240 -0
  299. package/src/__tests__/shared-filesystem-errors.test.ts +78 -0
  300. package/src/__tests__/shell-credential-ref.test.ts +187 -0
  301. package/src/__tests__/shell-identity.test.ts +256 -0
  302. package/src/__tests__/shell-parser-fuzz.test.ts +544 -0
  303. package/src/__tests__/shell-parser-property.test.ts +433 -0
  304. package/src/__tests__/shell-tool-proxy-mode.test.ts +272 -0
  305. package/src/__tests__/signup-e2e.test.ts +353 -0
  306. package/src/__tests__/size-guard.test.ts +117 -0
  307. package/src/__tests__/skill-include-graph.test.ts +303 -0
  308. package/src/__tests__/skill-load-tool.test.ts +409 -0
  309. package/src/__tests__/skill-projection.benchmark.test.ts +338 -0
  310. package/src/__tests__/skill-script-runner-host.test.ts +489 -0
  311. package/src/__tests__/skill-script-runner-sandbox.test.ts +349 -0
  312. package/src/__tests__/skill-script-runner.test.ts +159 -0
  313. package/src/__tests__/skill-tool-factory.test.ts +252 -0
  314. package/src/__tests__/skill-tool-manifest.test.ts +658 -0
  315. package/src/__tests__/skill-version-hash.test.ts +182 -0
  316. package/src/__tests__/skills.test.ts +680 -0
  317. package/src/__tests__/slash-commands-catalog.test.ts +86 -0
  318. package/src/__tests__/slash-commands-parser.test.ts +119 -0
  319. package/src/__tests__/slash-commands-resolver.test.ts +193 -0
  320. package/src/__tests__/slash-commands-rewrite.test.ts +39 -0
  321. package/src/__tests__/speaker-identification.test.ts +52 -0
  322. package/src/__tests__/starter-bundle.test.ts +136 -0
  323. package/src/__tests__/starter-task-flow.test.ts +143 -0
  324. package/src/__tests__/subagent-manager-notify.test.ts +404 -0
  325. package/src/__tests__/subagent-tools.test.ts +801 -0
  326. package/src/__tests__/subagent-types.test.ts +78 -0
  327. package/src/__tests__/swarm-orchestrator.test.ts +428 -0
  328. package/src/__tests__/swarm-plan-validator.test.ts +330 -0
  329. package/src/__tests__/swarm-recursion.test.ts +165 -0
  330. package/src/__tests__/swarm-router-planner.test.ts +208 -0
  331. package/src/__tests__/swarm-session-integration.test.ts +274 -0
  332. package/src/__tests__/swarm-tool.test.ts +145 -0
  333. package/src/__tests__/swarm-worker-backend.test.ts +129 -0
  334. package/src/__tests__/swarm-worker-runner.test.ts +272 -0
  335. package/src/__tests__/system-prompt.test.ts +439 -0
  336. package/src/__tests__/task-compiler.test.ts +284 -0
  337. package/src/__tests__/task-management-tools.test.ts +936 -0
  338. package/src/__tests__/task-runner.test.ts +216 -0
  339. package/src/__tests__/task-scheduler.test.ts +217 -0
  340. package/src/__tests__/task-tools.test.ts +595 -0
  341. package/src/__tests__/terminal-sandbox-docker.test.ts +1064 -0
  342. package/src/__tests__/terminal-sandbox.integration.test.ts +178 -0
  343. package/src/__tests__/terminal-sandbox.test.ts +202 -0
  344. package/src/__tests__/terminal-tools.test.ts +840 -0
  345. package/src/__tests__/test-support/browser-skill-harness.ts +90 -0
  346. package/src/__tests__/test-support/computer-use-skill-harness.ts +45 -0
  347. package/src/__tests__/tool-audit-listener.test.ts +113 -0
  348. package/src/__tests__/tool-domain-event-publisher.test.ts +253 -0
  349. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  350. package/src/__tests__/tool-executor-lifecycle-events.test.ts +516 -0
  351. package/src/__tests__/tool-executor-redaction.test.ts +289 -0
  352. package/src/__tests__/tool-executor-shell-integration.test.ts +301 -0
  353. package/src/__tests__/tool-executor.test.ts +1989 -0
  354. package/src/__tests__/tool-metrics-listener.test.ts +225 -0
  355. package/src/__tests__/tool-notification-listener.test.ts +49 -0
  356. package/src/__tests__/tool-permission-simulate-handler.test.ts +336 -0
  357. package/src/__tests__/tool-policy.test.ts +54 -0
  358. package/src/__tests__/tool-profiling-listener.test.ts +268 -0
  359. package/src/__tests__/tool-result-truncation.test.ts +217 -0
  360. package/src/__tests__/tool-trace-listener.test.ts +226 -0
  361. package/src/__tests__/top-level-renderer.test.ts +121 -0
  362. package/src/__tests__/top-level-scanner.test.ts +141 -0
  363. package/src/__tests__/trace-emitter.test.ts +173 -0
  364. package/src/__tests__/trust-store.test.ts +1605 -0
  365. package/src/__tests__/turn-commit.test.ts +554 -0
  366. package/src/__tests__/twilio-provider.test.ts +329 -0
  367. package/src/__tests__/twilio-routes-elevenlabs.test.ts +375 -0
  368. package/src/__tests__/twilio-routes-twiml.test.ts +127 -0
  369. package/src/__tests__/twilio-routes.test.ts +577 -0
  370. package/src/__tests__/twitter-auth-handler.test.ts +667 -0
  371. package/src/__tests__/twitter-cli-error-shaping.test.ts +208 -0
  372. package/src/__tests__/twitter-cli-routing.test.ts +252 -0
  373. package/src/__tests__/twitter-oauth-client.test.ts +209 -0
  374. package/src/__tests__/url-safety.test.ts +418 -0
  375. package/src/__tests__/view-image-tool.test.ts +217 -0
  376. package/src/__tests__/weather-skill-regression.test.ts +225 -0
  377. package/src/__tests__/web-fetch.test.ts +869 -0
  378. package/src/__tests__/web-search.test.ts +584 -0
  379. package/src/__tests__/workspace-git-service.test.ts +1153 -0
  380. package/src/__tests__/workspace-heartbeat-service.test.ts +486 -0
  381. package/src/__tests__/workspace-lifecycle.test.ts +292 -0
  382. package/src/__tests__/workspace-policy.test.ts +213 -0
  383. package/src/agent/attachments.ts +35 -0
  384. package/src/agent/loop.ts +500 -0
  385. package/src/agent/message-types.ts +17 -0
  386. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  387. package/src/autonomy/autonomy-resolver.ts +60 -0
  388. package/src/autonomy/autonomy-store.ts +122 -0
  389. package/src/autonomy/disposition-mapper.ts +31 -0
  390. package/src/autonomy/index.ts +11 -0
  391. package/src/autonomy/types.ts +39 -0
  392. package/src/bundler/app-bundler.ts +295 -0
  393. package/src/bundler/bundle-scanner.ts +535 -0
  394. package/src/bundler/bundle-signer.ts +124 -0
  395. package/src/bundler/manifest.ts +21 -0
  396. package/src/bundler/signature-verifier.ts +184 -0
  397. package/src/calls/call-bridge.ts +168 -0
  398. package/src/calls/call-constants.ts +48 -0
  399. package/src/calls/call-domain.ts +430 -0
  400. package/src/calls/call-orchestrator.ts +498 -0
  401. package/src/calls/call-recovery.ts +207 -0
  402. package/src/calls/call-state-machine.ts +68 -0
  403. package/src/calls/call-state.ts +87 -0
  404. package/src/calls/call-store.ts +422 -0
  405. package/src/calls/elevenlabs-client.ts +97 -0
  406. package/src/calls/elevenlabs-config.ts +31 -0
  407. package/src/calls/relay-server.ts +390 -0
  408. package/src/calls/speaker-identification.ts +213 -0
  409. package/src/calls/twilio-config.ts +45 -0
  410. package/src/calls/twilio-provider.ts +263 -0
  411. package/src/calls/twilio-rest.ts +156 -0
  412. package/src/calls/twilio-routes.ts +311 -0
  413. package/src/calls/types.ts +39 -0
  414. package/src/calls/voice-provider.ts +14 -0
  415. package/src/calls/voice-quality.ts +114 -0
  416. package/src/cli/autonomy.ts +188 -0
  417. package/src/cli/config-commands.ts +334 -0
  418. package/src/cli/contacts.ts +149 -0
  419. package/src/cli/core-commands.ts +784 -0
  420. package/src/cli/doordash.ts +1055 -0
  421. package/src/cli/email-guardrails.ts +200 -0
  422. package/src/cli/email.ts +405 -0
  423. package/src/cli/ipc-client.ts +82 -0
  424. package/src/cli/main-screen.tsx +53 -0
  425. package/src/cli/map.ts +270 -0
  426. package/src/cli/twitter.ts +754 -0
  427. package/src/cli.ts +918 -0
  428. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  429. package/src/commands/cc-command-registry.ts +209 -0
  430. package/src/config/bundled-skills/.gitkeep +0 -0
  431. package/src/config/bundled-skills/agentmail/SKILL.md +128 -0
  432. package/src/config/bundled-skills/agentmail/icon.svg +21 -0
  433. package/src/config/bundled-skills/app-builder/SKILL.md +1404 -0
  434. package/src/config/bundled-skills/app-builder/TOOLS.json +279 -0
  435. package/src/config/bundled-skills/app-builder/icon.svg +9 -0
  436. package/src/config/bundled-skills/app-builder/tools/app-create.ts +15 -0
  437. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +10 -0
  438. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +11 -0
  439. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +10 -0
  440. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +18 -0
  441. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +11 -0
  442. package/src/config/bundled-skills/app-builder/tools/app-list.ts +10 -0
  443. package/src/config/bundled-skills/app-builder/tools/app-query.ts +10 -0
  444. package/src/config/bundled-skills/app-builder/tools/app-update.ts +20 -0
  445. package/src/config/bundled-skills/browser/SKILL.md +28 -0
  446. package/src/config/bundled-skills/browser/TOOLS.json +234 -0
  447. package/src/config/bundled-skills/browser/tools/browser-click.ts +9 -0
  448. package/src/config/bundled-skills/browser/tools/browser-close.ts +9 -0
  449. package/src/config/bundled-skills/browser/tools/browser-extract.ts +9 -0
  450. package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +9 -0
  451. package/src/config/bundled-skills/browser/tools/browser-navigate.ts +9 -0
  452. package/src/config/bundled-skills/browser/tools/browser-press-key.ts +9 -0
  453. package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +9 -0
  454. package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +9 -0
  455. package/src/config/bundled-skills/browser/tools/browser-type.ts +9 -0
  456. package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +9 -0
  457. package/src/config/bundled-skills/claude-code/SKILL.md +50 -0
  458. package/src/config/bundled-skills/claude-code/TOOLS.json +40 -0
  459. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +9 -0
  460. package/src/config/bundled-skills/computer-use/SKILL.md +17 -0
  461. package/src/config/bundled-skills/computer-use/TOOLS.json +326 -0
  462. package/src/config/bundled-skills/computer-use/tools/computer-use-click.ts +9 -0
  463. package/src/config/bundled-skills/computer-use/tools/computer-use-done.ts +9 -0
  464. package/src/config/bundled-skills/computer-use/tools/computer-use-double-click.ts +9 -0
  465. package/src/config/bundled-skills/computer-use/tools/computer-use-drag.ts +9 -0
  466. package/src/config/bundled-skills/computer-use/tools/computer-use-key.ts +9 -0
  467. package/src/config/bundled-skills/computer-use/tools/computer-use-open-app.ts +9 -0
  468. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +9 -0
  469. package/src/config/bundled-skills/computer-use/tools/computer-use-respond.ts +9 -0
  470. package/src/config/bundled-skills/computer-use/tools/computer-use-right-click.ts +9 -0
  471. package/src/config/bundled-skills/computer-use/tools/computer-use-run-applescript.ts +9 -0
  472. package/src/config/bundled-skills/computer-use/tools/computer-use-scroll.ts +9 -0
  473. package/src/config/bundled-skills/computer-use/tools/computer-use-type-text.ts +9 -0
  474. package/src/config/bundled-skills/computer-use/tools/computer-use-wait.ts +9 -0
  475. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  476. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  477. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +57 -0
  478. package/src/config/bundled-skills/contacts/tools/contact-search.ts +60 -0
  479. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +66 -0
  480. package/src/config/bundled-skills/document/SKILL.md +26 -0
  481. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  482. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  483. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  484. package/src/config/bundled-skills/doordash/SKILL.md +163 -0
  485. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  486. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  487. package/src/config/bundled-skills/followups/icon.svg +24 -0
  488. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  489. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  490. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  491. package/src/config/bundled-skills/google-calendar/SKILL.md +51 -0
  492. package/src/config/bundled-skills/google-calendar/TOOLS.json +108 -0
  493. package/src/config/bundled-skills/google-calendar/calendar-client.ts +165 -0
  494. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +21 -0
  495. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +42 -0
  496. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +13 -0
  497. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +30 -0
  498. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +41 -0
  499. package/src/config/bundled-skills/google-calendar/tools/shared.ts +18 -0
  500. package/src/config/bundled-skills/google-calendar/types.ts +97 -0
  501. package/src/config/bundled-skills/image-studio/SKILL.md +32 -0
  502. package/src/config/bundled-skills/image-studio/TOOLS.json +42 -0
  503. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +115 -0
  504. package/src/config/bundled-skills/macos-automation/SKILL.md +66 -0
  505. package/src/config/bundled-skills/messaging/SKILL.md +153 -0
  506. package/src/config/bundled-skills/messaging/TOOLS.json +357 -0
  507. package/src/config/bundled-skills/messaging/tools/gmail-archive.ts +23 -0
  508. package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +23 -0
  509. package/src/config/bundled-skills/messaging/tools/gmail-batch-label.ts +25 -0
  510. package/src/config/bundled-skills/messaging/tools/gmail-draft.ts +26 -0
  511. package/src/config/bundled-skills/messaging/tools/gmail-label.ts +25 -0
  512. package/src/config/bundled-skills/messaging/tools/gmail-trash.ts +23 -0
  513. package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +84 -0
  514. package/src/config/bundled-skills/messaging/tools/messaging-analyze-activity.ts +18 -0
  515. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +125 -0
  516. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +16 -0
  517. package/src/config/bundled-skills/messaging/tools/messaging-draft.ts +49 -0
  518. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +21 -0
  519. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +25 -0
  520. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +28 -0
  521. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +32 -0
  522. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +22 -0
  523. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +31 -0
  524. package/src/config/bundled-skills/messaging/tools/shared.ts +76 -0
  525. package/src/config/bundled-skills/messaging/tools/slack-add-reaction.ts +25 -0
  526. package/src/config/bundled-skills/messaging/tools/slack-leave-channel.ts +23 -0
  527. package/src/config/bundled-skills/phone-calls/SKILL.md +533 -0
  528. package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
  529. package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
  530. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +98 -0
  531. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +54 -0
  532. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +76 -0
  533. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +113 -0
  534. package/src/config/bundled-skills/public-ingress/SKILL.md +200 -0
  535. package/src/config/bundled-skills/reminder/SKILL.md +20 -0
  536. package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
  537. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
  538. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
  539. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
  540. package/src/config/bundled-skills/schedule/SKILL.md +74 -0
  541. package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
  542. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
  543. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
  544. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
  545. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
  546. package/src/config/bundled-skills/self-upgrade/SKILL.md +68 -0
  547. package/src/config/bundled-skills/start-the-day/SKILL.md +70 -0
  548. package/src/config/bundled-skills/start-the-day/icon.svg +13 -0
  549. package/src/config/bundled-skills/subagent/SKILL.md +25 -0
  550. package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
  551. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
  552. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
  553. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
  554. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
  555. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
  556. package/src/config/bundled-skills/tasks/SKILL.md +28 -0
  557. package/src/config/bundled-skills/tasks/TOOLS.json +281 -0
  558. package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
  559. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
  560. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
  561. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
  562. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
  563. package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
  564. package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +9 -0
  565. package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
  566. package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
  567. package/src/config/bundled-skills/transcribe/SKILL.md +25 -0
  568. package/src/config/bundled-skills/transcribe/TOOLS.json +32 -0
  569. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +370 -0
  570. package/src/config/bundled-skills/twitter/SKILL.md +220 -0
  571. package/src/config/bundled-skills/watcher/SKILL.md +27 -0
  572. package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
  573. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
  574. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
  575. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
  576. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
  577. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
  578. package/src/config/bundled-skills/weather/SKILL.md +37 -0
  579. package/src/config/bundled-skills/weather/TOOLS.json +32 -0
  580. package/src/config/bundled-skills/weather/icon.svg +24 -0
  581. package/src/config/bundled-skills/weather/tools/get-weather.ts +9 -0
  582. package/src/config/computer-use-prompt.ts +97 -0
  583. package/src/config/defaults.ts +263 -0
  584. package/src/config/loader.ts +339 -0
  585. package/src/config/schema.ts +1436 -0
  586. package/src/config/skill-state.ts +95 -0
  587. package/src/config/skills.ts +972 -0
  588. package/src/config/system-prompt.ts +675 -0
  589. package/src/config/templates/BOOTSTRAP.md +70 -0
  590. package/src/config/templates/IDENTITY.md +25 -0
  591. package/src/config/templates/LOOKS.md +25 -0
  592. package/src/config/templates/SOUL.md +37 -0
  593. package/src/config/templates/USER.md +19 -0
  594. package/src/config/types.ts +42 -0
  595. package/src/config/vellum-skills/chatgpt-import/SKILL.md +24 -0
  596. package/src/config/vellum-skills/chatgpt-import/TOOLS.json +23 -0
  597. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +284 -0
  598. package/src/config/vellum-skills/deploy-fullstack-vercel/SKILL.md +179 -0
  599. package/src/config/vellum-skills/document-writer/SKILL.md +195 -0
  600. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +199 -0
  601. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +153 -0
  602. package/src/config/vellum-skills/telegram-setup/SKILL.md +143 -0
  603. package/src/config/vellum-skills/twilio-setup/SKILL.md +213 -0
  604. package/src/contacts/contact-store.ts +410 -0
  605. package/src/contacts/index.ts +11 -0
  606. package/src/contacts/types.ts +28 -0
  607. package/src/context/token-estimator.ts +108 -0
  608. package/src/context/tool-result-truncation.ts +128 -0
  609. package/src/context/window-manager.ts +531 -0
  610. package/src/daemon/assistant-attachments.ts +691 -0
  611. package/src/daemon/classifier.ts +110 -0
  612. package/src/daemon/computer-use-session.ts +903 -0
  613. package/src/daemon/connection-policy.ts +41 -0
  614. package/src/daemon/date-context.ts +136 -0
  615. package/src/daemon/handlers/apps.ts +530 -0
  616. package/src/daemon/handlers/browser.ts +54 -0
  617. package/src/daemon/handlers/computer-use.ts +187 -0
  618. package/src/daemon/handlers/config.ts +1517 -0
  619. package/src/daemon/handlers/diagnostics.ts +338 -0
  620. package/src/daemon/handlers/documents.ts +173 -0
  621. package/src/daemon/handlers/home-base.ts +78 -0
  622. package/src/daemon/handlers/identity.ts +127 -0
  623. package/src/daemon/handlers/index.ts +129 -0
  624. package/src/daemon/handlers/misc.ts +331 -0
  625. package/src/daemon/handlers/open-bundle-handler.ts +80 -0
  626. package/src/daemon/handlers/publish.ts +187 -0
  627. package/src/daemon/handlers/sessions.ts +555 -0
  628. package/src/daemon/handlers/shared.ts +570 -0
  629. package/src/daemon/handlers/signing.ts +37 -0
  630. package/src/daemon/handlers/skills.ts +486 -0
  631. package/src/daemon/handlers/subagents.ts +210 -0
  632. package/src/daemon/handlers/twitter-auth.ts +198 -0
  633. package/src/daemon/handlers/work-items.ts +632 -0
  634. package/src/daemon/handlers/workspace-files.ts +75 -0
  635. package/src/daemon/handlers.ts +17 -0
  636. package/src/daemon/history-repair.ts +214 -0
  637. package/src/daemon/ipc-blob-store.ts +231 -0
  638. package/src/daemon/ipc-contract-inventory.json +495 -0
  639. package/src/daemon/ipc-contract-inventory.ts +126 -0
  640. package/src/daemon/ipc-contract.ts +2551 -0
  641. package/src/daemon/ipc-protocol.ts +75 -0
  642. package/src/daemon/ipc-validate.ts +188 -0
  643. package/src/daemon/lifecycle.ts +582 -0
  644. package/src/daemon/main.ts +21 -0
  645. package/src/daemon/media-visibility-policy.ts +57 -0
  646. package/src/daemon/ride-shotgun-handler.ts +309 -0
  647. package/src/daemon/server.ts +1215 -0
  648. package/src/daemon/session-agent-loop.ts +922 -0
  649. package/src/daemon/session-attachments.ts +196 -0
  650. package/src/daemon/session-conflict-gate.ts +184 -0
  651. package/src/daemon/session-dynamic-profile.ts +63 -0
  652. package/src/daemon/session-error.ts +290 -0
  653. package/src/daemon/session-evictor.ts +196 -0
  654. package/src/daemon/session-history.ts +437 -0
  655. package/src/daemon/session-lifecycle.ts +147 -0
  656. package/src/daemon/session-media-retry.ts +147 -0
  657. package/src/daemon/session-memory.ts +212 -0
  658. package/src/daemon/session-messaging.ts +145 -0
  659. package/src/daemon/session-notifiers.ts +193 -0
  660. package/src/daemon/session-process.ts +323 -0
  661. package/src/daemon/session-queue-manager.ts +82 -0
  662. package/src/daemon/session-runtime-assembly.ts +447 -0
  663. package/src/daemon/session-skill-tools.ts +356 -0
  664. package/src/daemon/session-slash.ts +305 -0
  665. package/src/daemon/session-surfaces.ts +702 -0
  666. package/src/daemon/session-tool-setup.ts +523 -0
  667. package/src/daemon/session-usage.ts +72 -0
  668. package/src/daemon/session-workspace.ts +19 -0
  669. package/src/daemon/session.ts +400 -0
  670. package/src/daemon/tls-certs.ts +189 -0
  671. package/src/daemon/trace-emitter.ts +82 -0
  672. package/src/daemon/video-thumbnail.ts +62 -0
  673. package/src/daemon/watch-handler.ts +274 -0
  674. package/src/doordash/client.ts +999 -0
  675. package/src/doordash/queries.ts +1311 -0
  676. package/src/doordash/query-extractor.ts +93 -0
  677. package/src/doordash/session.ts +82 -0
  678. package/src/email/provider.ts +117 -0
  679. package/src/email/providers/agentmail.ts +317 -0
  680. package/src/email/providers/index.ts +58 -0
  681. package/src/email/service.ts +303 -0
  682. package/src/email/types.ts +126 -0
  683. package/src/events/bus.ts +157 -0
  684. package/src/events/domain-events.ts +83 -0
  685. package/src/events/index.ts +18 -0
  686. package/src/events/tool-audit-listener.ts +80 -0
  687. package/src/events/tool-domain-event-publisher.ts +111 -0
  688. package/src/events/tool-metrics-listener.ts +159 -0
  689. package/src/events/tool-notification-listener.ts +17 -0
  690. package/src/events/tool-profiling-listener.ts +158 -0
  691. package/src/events/tool-trace-listener.ts +75 -0
  692. package/src/export/formatter.ts +98 -0
  693. package/src/followups/followup-store.ts +168 -0
  694. package/src/followups/index.ts +10 -0
  695. package/src/followups/types.ts +29 -0
  696. package/src/gallery/default-gallery.ts +795 -0
  697. package/src/gallery/gallery-manifest.ts +24 -0
  698. package/src/home-base/app-link-store.ts +82 -0
  699. package/src/home-base/bootstrap.ts +68 -0
  700. package/src/home-base/prebuilt/index.html +662 -0
  701. package/src/home-base/prebuilt/seed-metadata.json +21 -0
  702. package/src/home-base/prebuilt/seed.ts +112 -0
  703. package/src/home-base/prebuilt-home-base-updater.ts +30 -0
  704. package/src/hooks/cli.ts +163 -0
  705. package/src/hooks/config.ts +88 -0
  706. package/src/hooks/discovery.ts +110 -0
  707. package/src/hooks/manager.ts +124 -0
  708. package/src/hooks/runner.ts +123 -0
  709. package/src/hooks/templates.ts +52 -0
  710. package/src/hooks/types.ts +72 -0
  711. package/src/inbound/public-ingress-urls.ts +123 -0
  712. package/src/index.ts +81 -0
  713. package/src/instrument.ts +60 -0
  714. package/src/logfire.ts +99 -0
  715. package/src/media/gemini-image-service.ts +136 -0
  716. package/src/memory/account-store.ts +108 -0
  717. package/src/memory/admin.ts +211 -0
  718. package/src/memory/app-git-service.ts +295 -0
  719. package/src/memory/app-store.ts +577 -0
  720. package/src/memory/attachments-store.ts +397 -0
  721. package/src/memory/channel-delivery-store.ts +353 -0
  722. package/src/memory/channel-guardian-store.ts +669 -0
  723. package/src/memory/checkpoints.ts +52 -0
  724. package/src/memory/clarification-resolver.ts +298 -0
  725. package/src/memory/conflict-intent.ts +157 -0
  726. package/src/memory/conflict-policy.ts +73 -0
  727. package/src/memory/conflict-store.ts +350 -0
  728. package/src/memory/contradiction-checker.ts +358 -0
  729. package/src/memory/conversation-key-store.ts +122 -0
  730. package/src/memory/conversation-store.ts +470 -0
  731. package/src/memory/db.ts +1991 -0
  732. package/src/memory/embedding-backend.ts +229 -0
  733. package/src/memory/embedding-gemini.ts +52 -0
  734. package/src/memory/embedding-local.ts +65 -0
  735. package/src/memory/embedding-ollama.ts +55 -0
  736. package/src/memory/embedding-openai.ts +25 -0
  737. package/src/memory/entity-extractor.ts +474 -0
  738. package/src/memory/external-conversation-store.ts +234 -0
  739. package/src/memory/fingerprint.ts +20 -0
  740. package/src/memory/indexer.ts +156 -0
  741. package/src/memory/items-extractor.ts +461 -0
  742. package/src/memory/job-handlers/backfill.ts +139 -0
  743. package/src/memory/job-handlers/cleanup.ts +58 -0
  744. package/src/memory/job-handlers/conflict.ts +141 -0
  745. package/src/memory/job-handlers/embedding.ts +61 -0
  746. package/src/memory/job-handlers/extraction.ts +123 -0
  747. package/src/memory/job-handlers/index-maintenance.ts +54 -0
  748. package/src/memory/job-handlers/summarization.ts +286 -0
  749. package/src/memory/job-utils.ts +170 -0
  750. package/src/memory/jobs-store.ts +401 -0
  751. package/src/memory/jobs-worker.ts +313 -0
  752. package/src/memory/llm-request-log-store.ts +45 -0
  753. package/src/memory/llm-usage-store.ts +60 -0
  754. package/src/memory/message-content.ts +54 -0
  755. package/src/memory/profile-compiler.ts +160 -0
  756. package/src/memory/published-pages-store.ts +137 -0
  757. package/src/memory/qdrant-client.ts +366 -0
  758. package/src/memory/qdrant-manager.ts +242 -0
  759. package/src/memory/query-builder.ts +45 -0
  760. package/src/memory/retrieval-budget.ts +30 -0
  761. package/src/memory/retriever.ts +653 -0
  762. package/src/memory/runs-store.ts +305 -0
  763. package/src/memory/schema.ts +677 -0
  764. package/src/memory/search/entity.ts +298 -0
  765. package/src/memory/search/formatting.ts +207 -0
  766. package/src/memory/search/lexical.ts +227 -0
  767. package/src/memory/search/ranking.ts +401 -0
  768. package/src/memory/search/semantic.ts +121 -0
  769. package/src/memory/search/types.ts +137 -0
  770. package/src/memory/segmenter.ts +68 -0
  771. package/src/memory/shared-app-links-store.ts +138 -0
  772. package/src/memory/tool-usage-store.ts +62 -0
  773. package/src/messaging/activity-analyzer.ts +76 -0
  774. package/src/messaging/draft-store.ts +88 -0
  775. package/src/messaging/index.ts +3 -0
  776. package/src/messaging/provider-types.ts +80 -0
  777. package/src/messaging/provider.ts +52 -0
  778. package/src/messaging/providers/gmail/adapter.ts +193 -0
  779. package/src/messaging/providers/gmail/client.ts +204 -0
  780. package/src/messaging/providers/gmail/types.ts +90 -0
  781. package/src/messaging/providers/slack/adapter.ts +202 -0
  782. package/src/messaging/providers/slack/client.ts +198 -0
  783. package/src/messaging/providers/slack/types.ts +119 -0
  784. package/src/messaging/providers/telegram-bot/adapter.ts +162 -0
  785. package/src/messaging/providers/telegram-bot/client.ts +104 -0
  786. package/src/messaging/providers/telegram-bot/types.ts +15 -0
  787. package/src/messaging/registry.ts +35 -0
  788. package/src/messaging/style-analyzer.ts +159 -0
  789. package/src/messaging/thread-summarizer.ts +306 -0
  790. package/src/messaging/triage-engine.ts +323 -0
  791. package/src/messaging/types.ts +55 -0
  792. package/src/permissions/checker.ts +640 -0
  793. package/src/permissions/defaults.ts +254 -0
  794. package/src/permissions/prompter.ts +98 -0
  795. package/src/permissions/secret-prompter.ts +114 -0
  796. package/src/permissions/shell-identity.ts +227 -0
  797. package/src/permissions/trust-store.ts +607 -0
  798. package/src/permissions/types.ts +43 -0
  799. package/src/permissions/workspace-policy.ts +114 -0
  800. package/src/playbooks/index.ts +2 -0
  801. package/src/playbooks/playbook-compiler.ts +90 -0
  802. package/src/playbooks/types.ts +55 -0
  803. package/src/providers/anthropic/client.ts +751 -0
  804. package/src/providers/failover.ts +129 -0
  805. package/src/providers/fireworks/client.ts +20 -0
  806. package/src/providers/gemini/client.ts +285 -0
  807. package/src/providers/ollama/client.ts +30 -0
  808. package/src/providers/openai/client.ts +337 -0
  809. package/src/providers/openrouter/client.ts +20 -0
  810. package/src/providers/ratelimit.ts +93 -0
  811. package/src/providers/registry.ts +146 -0
  812. package/src/providers/retry.ts +81 -0
  813. package/src/providers/stream-timeout.ts +38 -0
  814. package/src/providers/types.ts +109 -0
  815. package/src/runtime/assistant-event-hub.ts +157 -0
  816. package/src/runtime/assistant-event.ts +82 -0
  817. package/src/runtime/channel-approval-parser.ts +60 -0
  818. package/src/runtime/channel-approval-types.ts +73 -0
  819. package/src/runtime/channel-approvals.ts +206 -0
  820. package/src/runtime/channel-guardian-service.ts +212 -0
  821. package/src/runtime/gateway-client.ts +58 -0
  822. package/src/runtime/http-server.ts +1076 -0
  823. package/src/runtime/http-types.ts +66 -0
  824. package/src/runtime/routes/app-routes.ts +174 -0
  825. package/src/runtime/routes/attachment-routes.ts +133 -0
  826. package/src/runtime/routes/call-routes.ts +190 -0
  827. package/src/runtime/routes/channel-routes.ts +1404 -0
  828. package/src/runtime/routes/conversation-routes.ts +352 -0
  829. package/src/runtime/routes/events-routes.ts +148 -0
  830. package/src/runtime/routes/run-routes.ts +257 -0
  831. package/src/runtime/routes/secret-routes.ts +76 -0
  832. package/src/runtime/run-orchestrator.ts +330 -0
  833. package/src/schedule/recurrence-engine.ts +162 -0
  834. package/src/schedule/recurrence-types.ts +67 -0
  835. package/src/schedule/schedule-store.ts +506 -0
  836. package/src/schedule/scheduler.ts +171 -0
  837. package/src/security/encrypted-store.ts +238 -0
  838. package/src/security/keychain.ts +252 -0
  839. package/src/security/oauth-callback-registry.ts +66 -0
  840. package/src/security/oauth2.ts +274 -0
  841. package/src/security/redaction.ts +89 -0
  842. package/src/security/secret-allowlist.ts +164 -0
  843. package/src/security/secret-ingress.ts +57 -0
  844. package/src/security/secret-scanner.ts +550 -0
  845. package/src/security/secure-keys.ts +180 -0
  846. package/src/security/token-manager.ts +141 -0
  847. package/src/services/published-app-updater.ts +69 -0
  848. package/src/services/vercel-deploy.ts +73 -0
  849. package/src/skills/active-skill-tools.ts +81 -0
  850. package/src/skills/clawhub.ts +414 -0
  851. package/src/skills/include-graph.ts +146 -0
  852. package/src/skills/managed-store.ts +233 -0
  853. package/src/skills/path-classifier.ts +128 -0
  854. package/src/skills/slash-commands.ts +174 -0
  855. package/src/skills/tool-manifest.ts +165 -0
  856. package/src/skills/version-hash.ts +110 -0
  857. package/src/slack/slack-webhook.ts +61 -0
  858. package/src/subagent/index.ts +19 -0
  859. package/src/subagent/manager.ts +511 -0
  860. package/src/subagent/types.ts +69 -0
  861. package/src/swarm/backend-claude-code.ts +145 -0
  862. package/src/swarm/index.ts +44 -0
  863. package/src/swarm/limits.ts +37 -0
  864. package/src/swarm/orchestrator.ts +279 -0
  865. package/src/swarm/plan-validator.ts +151 -0
  866. package/src/swarm/router-planner.ts +100 -0
  867. package/src/swarm/router-prompts.ts +36 -0
  868. package/src/swarm/synthesizer.ts +62 -0
  869. package/src/swarm/types.ts +62 -0
  870. package/src/swarm/worker-backend.ts +121 -0
  871. package/src/swarm/worker-prompts.ts +79 -0
  872. package/src/swarm/worker-runner.ts +164 -0
  873. package/src/tasks/SPEC.md +139 -0
  874. package/src/tasks/candidate-store.ts +86 -0
  875. package/src/tasks/ephemeral-permissions.ts +48 -0
  876. package/src/tasks/task-compiler.ts +199 -0
  877. package/src/tasks/task-runner.ts +90 -0
  878. package/src/tasks/task-scheduler.ts +21 -0
  879. package/src/tasks/task-store.ts +127 -0
  880. package/src/tasks/tool-sanitizer.ts +36 -0
  881. package/src/tools/apps/definitions.ts +59 -0
  882. package/src/tools/apps/executors.ts +313 -0
  883. package/src/tools/apps/open-proxy.ts +43 -0
  884. package/src/tools/apps/registry.ts +16 -0
  885. package/src/tools/assets/materialize.ts +218 -0
  886. package/src/tools/assets/search.ts +361 -0
  887. package/src/tools/browser/__tests__/auth-cache.test.ts +219 -0
  888. package/src/tools/browser/__tests__/auth-detector.test.ts +362 -0
  889. package/src/tools/browser/__tests__/jit-auth.test.ts +189 -0
  890. package/src/tools/browser/api-map.ts +293 -0
  891. package/src/tools/browser/auth-cache.ts +149 -0
  892. package/src/tools/browser/auth-detector.ts +347 -0
  893. package/src/tools/browser/auto-navigate.ts +270 -0
  894. package/src/tools/browser/browser-execution.ts +980 -0
  895. package/src/tools/browser/browser-handoff.ts +79 -0
  896. package/src/tools/browser/browser-manager.ts +715 -0
  897. package/src/tools/browser/browser-screencast.ts +217 -0
  898. package/src/tools/browser/headless-browser.ts +450 -0
  899. package/src/tools/browser/jit-auth.ts +51 -0
  900. package/src/tools/browser/network-recorder.ts +349 -0
  901. package/src/tools/browser/network-recording-types.ts +49 -0
  902. package/src/tools/browser/recording-store.ts +49 -0
  903. package/src/tools/browser/runtime-check.ts +43 -0
  904. package/src/tools/browser/x-auto-navigate.ts +207 -0
  905. package/src/tools/calls/call-end.ts +67 -0
  906. package/src/tools/calls/call-start.ts +81 -0
  907. package/src/tools/calls/call-status.ts +81 -0
  908. package/src/tools/claude-code/claude-code.ts +428 -0
  909. package/src/tools/computer-use/definitions.ts +443 -0
  910. package/src/tools/computer-use/registry.ts +22 -0
  911. package/src/tools/computer-use/request-computer-control.ts +53 -0
  912. package/src/tools/computer-use/skill-proxy-bridge.ts +28 -0
  913. package/src/tools/credentials/account-registry.ts +127 -0
  914. package/src/tools/credentials/broker-types.ts +107 -0
  915. package/src/tools/credentials/broker.ts +372 -0
  916. package/src/tools/credentials/domain-policy.ts +51 -0
  917. package/src/tools/credentials/host-pattern-match.ts +60 -0
  918. package/src/tools/credentials/metadata-store.ts +335 -0
  919. package/src/tools/credentials/policy-types.ts +52 -0
  920. package/src/tools/credentials/policy-validate.ts +80 -0
  921. package/src/tools/credentials/resolve.ts +122 -0
  922. package/src/tools/credentials/selection.ts +159 -0
  923. package/src/tools/credentials/tool-policy.ts +25 -0
  924. package/src/tools/credentials/vault.ts +657 -0
  925. package/src/tools/document/document-tool.ts +92 -0
  926. package/src/tools/document/editor-template.ts +237 -0
  927. package/src/tools/execution-target.ts +21 -0
  928. package/src/tools/execution-timeout.ts +49 -0
  929. package/src/tools/executor.ts +815 -0
  930. package/src/tools/filesystem/edit.ts +127 -0
  931. package/src/tools/filesystem/fuzzy-match.ts +202 -0
  932. package/src/tools/filesystem/read.ts +71 -0
  933. package/src/tools/filesystem/view-image.ts +199 -0
  934. package/src/tools/filesystem/write.ts +79 -0
  935. package/src/tools/followups/followup_create.ts +76 -0
  936. package/src/tools/followups/followup_list.ts +60 -0
  937. package/src/tools/followups/followup_resolve.ts +56 -0
  938. package/src/tools/host-filesystem/edit.ts +125 -0
  939. package/src/tools/host-filesystem/read.ts +80 -0
  940. package/src/tools/host-filesystem/write.ts +76 -0
  941. package/src/tools/host-terminal/cli-discover.ts +180 -0
  942. package/src/tools/host-terminal/host-shell.ts +191 -0
  943. package/src/tools/memory/definitions.ts +69 -0
  944. package/src/tools/memory/handlers.ts +246 -0
  945. package/src/tools/memory/register.ts +66 -0
  946. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  947. package/src/tools/network/domain-normalize.ts +85 -0
  948. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  949. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  950. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  951. package/src/tools/network/script-proxy/certs.ts +237 -0
  952. package/src/tools/network/script-proxy/connect-tunnel.ts +82 -0
  953. package/src/tools/network/script-proxy/http-forwarder.ts +151 -0
  954. package/src/tools/network/script-proxy/index.ts +28 -0
  955. package/src/tools/network/script-proxy/logging.ts +196 -0
  956. package/src/tools/network/script-proxy/mitm-handler.ts +269 -0
  957. package/src/tools/network/script-proxy/policy.ts +152 -0
  958. package/src/tools/network/script-proxy/router.ts +60 -0
  959. package/src/tools/network/script-proxy/server.ts +136 -0
  960. package/src/tools/network/script-proxy/session-manager.ts +534 -0
  961. package/src/tools/network/script-proxy/types.ts +125 -0
  962. package/src/tools/network/url-safety.ts +227 -0
  963. package/src/tools/network/web-fetch.ts +713 -0
  964. package/src/tools/network/web-search.ts +296 -0
  965. package/src/tools/policy-context.ts +29 -0
  966. package/src/tools/registry.ts +295 -0
  967. package/src/tools/reminder/reminder-store.ts +148 -0
  968. package/src/tools/reminder/reminder.ts +80 -0
  969. package/src/tools/schedule/create.ts +81 -0
  970. package/src/tools/schedule/delete.ts +28 -0
  971. package/src/tools/schedule/list.ts +69 -0
  972. package/src/tools/schedule/update.ts +97 -0
  973. package/src/tools/shared/filesystem/edit-engine.ts +56 -0
  974. package/src/tools/shared/filesystem/errors.ts +85 -0
  975. package/src/tools/shared/filesystem/file-ops-service.ts +215 -0
  976. package/src/tools/shared/filesystem/format-diff.ts +35 -0
  977. package/src/tools/shared/filesystem/path-policy.ts +125 -0
  978. package/src/tools/shared/filesystem/size-guard.ts +41 -0
  979. package/src/tools/shared/filesystem/types.ts +80 -0
  980. package/src/tools/shared/shell-output.ts +52 -0
  981. package/src/tools/skills/delete-managed.ts +60 -0
  982. package/src/tools/skills/load.ts +139 -0
  983. package/src/tools/skills/sandbox-runner.ts +279 -0
  984. package/src/tools/skills/scaffold-managed.ts +150 -0
  985. package/src/tools/skills/script-contract.ts +6 -0
  986. package/src/tools/skills/skill-script-runner.ts +86 -0
  987. package/src/tools/skills/skill-tool-factory.ts +64 -0
  988. package/src/tools/skills/vellum-catalog.ts +217 -0
  989. package/src/tools/subagent/abort.ts +33 -0
  990. package/src/tools/subagent/message.ts +39 -0
  991. package/src/tools/subagent/read.ts +67 -0
  992. package/src/tools/subagent/spawn.ts +46 -0
  993. package/src/tools/subagent/status.ts +45 -0
  994. package/src/tools/swarm/delegate.ts +183 -0
  995. package/src/tools/system/request-permission.ts +98 -0
  996. package/src/tools/system/version.ts +43 -0
  997. package/src/tools/tasks/index.ts +27 -0
  998. package/src/tools/tasks/task-delete.ts +82 -0
  999. package/src/tools/tasks/task-list.ts +44 -0
  1000. package/src/tools/tasks/task-run.ts +97 -0
  1001. package/src/tools/tasks/task-save.ts +47 -0
  1002. package/src/tools/tasks/work-item-enqueue.ts +234 -0
  1003. package/src/tools/tasks/work-item-list.ts +55 -0
  1004. package/src/tools/tasks/work-item-remove.ts +60 -0
  1005. package/src/tools/tasks/work-item-run.ts +78 -0
  1006. package/src/tools/tasks/work-item-update.ts +114 -0
  1007. package/src/tools/terminal/backends/docker.ts +372 -0
  1008. package/src/tools/terminal/backends/native.ts +190 -0
  1009. package/src/tools/terminal/backends/types.ts +26 -0
  1010. package/src/tools/terminal/evaluate-typescript.ts +275 -0
  1011. package/src/tools/terminal/parser.ts +413 -0
  1012. package/src/tools/terminal/safe-env.ts +37 -0
  1013. package/src/tools/terminal/sandbox-diagnostics.ts +149 -0
  1014. package/src/tools/terminal/sandbox.ts +44 -0
  1015. package/src/tools/terminal/shell.ts +257 -0
  1016. package/src/tools/tool-manifest.ts +198 -0
  1017. package/src/tools/types.ts +176 -0
  1018. package/src/tools/ui-surface/definitions.ts +244 -0
  1019. package/src/tools/ui-surface/registry.ts +14 -0
  1020. package/src/tools/watch/screen-watch.ts +130 -0
  1021. package/src/tools/watch/watch-state.ts +119 -0
  1022. package/src/tools/watcher/create.ts +64 -0
  1023. package/src/tools/watcher/delete.ts +27 -0
  1024. package/src/tools/watcher/digest.ts +50 -0
  1025. package/src/tools/watcher/list.ts +60 -0
  1026. package/src/tools/watcher/update.ts +56 -0
  1027. package/src/tools/weather/service.ts +551 -0
  1028. package/src/twitter/client.ts +690 -0
  1029. package/src/twitter/oauth-client.ts +102 -0
  1030. package/src/twitter/router.ts +101 -0
  1031. package/src/twitter/session.ts +91 -0
  1032. package/src/usage/actors.ts +24 -0
  1033. package/src/usage/types.ts +37 -0
  1034. package/src/util/clipboard.ts +33 -0
  1035. package/src/util/content-id.ts +16 -0
  1036. package/src/util/debounce.ts +88 -0
  1037. package/src/util/diff.ts +181 -0
  1038. package/src/util/errors.ts +129 -0
  1039. package/src/util/logger.ts +243 -0
  1040. package/src/util/network-info.ts +47 -0
  1041. package/src/util/platform.ts +632 -0
  1042. package/src/util/pricing.ts +150 -0
  1043. package/src/util/promise-guard.ts +37 -0
  1044. package/src/util/retry.ts +98 -0
  1045. package/src/util/spinner.ts +51 -0
  1046. package/src/util/time.ts +16 -0
  1047. package/src/util/truncate.ts +6 -0
  1048. package/src/util/xml.ts +4 -0
  1049. package/src/version.ts +3 -0
  1050. package/src/watcher/constants.ts +11 -0
  1051. package/src/watcher/engine.ts +199 -0
  1052. package/src/watcher/provider-registry.ts +15 -0
  1053. package/src/watcher/provider-types.ts +48 -0
  1054. package/src/watcher/providers/gmail.ts +198 -0
  1055. package/src/watcher/providers/google-calendar.ts +228 -0
  1056. package/src/watcher/providers/slack.ts +129 -0
  1057. package/src/watcher/watcher-store.ts +419 -0
  1058. package/src/work-items/work-item-runner.ts +171 -0
  1059. package/src/work-items/work-item-store.ts +325 -0
  1060. package/src/workspace/commit-message-enrichment-service.ts +284 -0
  1061. package/src/workspace/commit-message-provider.ts +95 -0
  1062. package/src/workspace/git-service.ts +857 -0
  1063. package/src/workspace/heartbeat-service.ts +345 -0
  1064. package/src/workspace/provider-commit-message-generator.ts +285 -0
  1065. package/src/workspace/top-level-renderer.ts +19 -0
  1066. package/src/workspace/top-level-scanner.ts +41 -0
  1067. package/src/workspace/turn-commit.ts +175 -0
  1068. package/tsconfig.json +21 -0
@@ -0,0 +1,1215 @@
1
+ import * as net from 'node:net';
2
+ import * as tls from 'node:tls';
3
+ import { randomBytes } from 'node:crypto';
4
+ import { existsSync, chmodSync, readFileSync, writeFileSync, readdirSync, watch, type FSWatcher } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { getSocketPath, getSessionTokenPath, getRootDir, getWorkspaceDir, getWorkspaceSkillsDir, getSandboxWorkingDir, removeSocketFile, getTCPPort, getTCPHost, isTCPEnabled, isIOSPairingEnabled } from '../util/platform.js';
7
+ import { ensureTlsCert } from './tls-certs.js';
8
+ import { getLocalIPv4 } from '../util/network-info.js';
9
+ import { hasNoAuthOverride } from './connection-policy.js';
10
+ import { getLogger } from '../util/logger.js';
11
+ import { getFailoverProvider, initializeProviders } from '../providers/registry.js';
12
+ import { RateLimitProvider } from '../providers/ratelimit.js';
13
+ import { getConfig, invalidateConfigCache } from '../config/loader.js';
14
+ import { buildSystemPrompt } from '../config/system-prompt.js';
15
+ import { clearCache as clearTrustCache } from '../permissions/trust-store.js';
16
+ import { resetAllowlist, validateAllowlistFile } from '../security/secret-allowlist.js';
17
+ import { checkIngressForSecrets } from '../security/secret-ingress.js';
18
+ import { IngressBlockedError } from '../util/errors.js';
19
+ import { clearEmbeddingBackendCache } from '../memory/embedding-backend.js';
20
+ import * as conversationStore from '../memory/conversation-store.js';
21
+ import * as attachmentsStore from '../memory/attachments-store.js';
22
+ import { Session, DEFAULT_MEMORY_POLICY, type SessionMemoryPolicy } from './session.js';
23
+ import { resolveChannelCapabilities } from './session-runtime-assembly.js';
24
+ import { ComputerUseSession } from './computer-use-session.js';
25
+ import {
26
+ serialize,
27
+ createMessageParser,
28
+ MAX_LINE_SIZE,
29
+ type ClientMessage,
30
+ type ServerMessage,
31
+ normalizeThreadType,
32
+ } from './ipc-protocol.js';
33
+ import { validateClientMessage } from './ipc-validate.js';
34
+ import { handleMessage, type HandlerContext, type SessionCreateOptions } from './handlers.js';
35
+ import { RunOrchestrator } from '../runtime/run-orchestrator.js';
36
+ import { ensureBlobDir, sweepStaleBlobs } from './ipc-blob-store.js';
37
+ import { bootstrapHomeBaseAppLink } from '../home-base/bootstrap.js';
38
+ import { assistantEventHub } from '../runtime/assistant-event-hub.js';
39
+ import { buildAssistantEvent } from '../runtime/assistant-event.js';
40
+ import { SessionEvictor } from './session-evictor.js';
41
+ import { getSubagentManager } from '../subagent/index.js';
42
+ import { tryRouteCallMessage } from '../calls/call-bridge.js';
43
+ import { resolveSlash } from './session-slash.js';
44
+ import { createUserMessage, createAssistantMessage } from '../agent/message-types.js';
45
+ import { registerDaemonCallbacks } from '../work-items/work-item-runner.js';
46
+ import { DebouncerMap } from '../util/debounce.js';
47
+
48
+ const log = getLogger('server');
49
+
50
+ function readPackageVersion(): string | undefined {
51
+ try {
52
+ const pkgPath = join(import.meta.dir, '../../package.json');
53
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version?: string };
54
+ return pkg.version;
55
+ } catch {
56
+ return undefined;
57
+ }
58
+ }
59
+
60
+ const daemonVersion = readPackageVersion();
61
+
62
+ export class DaemonServer {
63
+ private server: net.Server | null = null;
64
+ private tcpServer: tls.Server | null = null;
65
+ private sessions = new Map<string, Session>();
66
+ private socketToSession = new Map<net.Socket, string>();
67
+ private cuSessions = new Map<string, ComputerUseSession>();
68
+ private socketToCuSession = new Map<net.Socket, Set<string>>();
69
+ private connectedSockets = new Set<net.Socket>();
70
+ private socketSandboxOverride = new Map<net.Socket, boolean>();
71
+ private cuObservationParseSequence = new Map<string, number>();
72
+ // Persisted session options (e.g. systemPromptOverride, maxResponseTokens)
73
+ // so that evicted sessions can be recreated with the same overrides.
74
+ private sessionOptions = new Map<string, SessionCreateOptions>();
75
+ // Guards against duplicate session creation when multiple clients connect
76
+ // with the same conversation ID concurrently. The first caller creates the
77
+ // session; subsequent callers await the same promise.
78
+ private sessionCreating = new Map<string, Promise<Session>>();
79
+ // Shared across all sessions so maxRequestsPerMinute is enforced globally.
80
+ private sharedRequestTimestamps: number[] = [];
81
+ private socketPath: string;
82
+ private httpPort: number | undefined;
83
+ private watchers: FSWatcher[] = [];
84
+ private debounceTimers = new DebouncerMap({
85
+ defaultDelayMs: 200,
86
+ maxEntries: 1000,
87
+ protectedKeyPrefix: '__',
88
+ });
89
+ private suppressConfigReload = false;
90
+ private lastConfigFingerprint = '';
91
+ private lastConfigRefreshTime = 0;
92
+ private blobSweepTimer: ReturnType<typeof setInterval> | null = null;
93
+ private static readonly CONFIG_REFRESH_INTERVAL_MS = 30_000;
94
+ private static readonly MAX_CONNECTIONS = 50;
95
+ private static readonly AUTH_TIMEOUT_MS = 5_000;
96
+ private sessionToken = '';
97
+ private authenticatedSockets = new Set<net.Socket>();
98
+ private authTimeouts = new Map<net.Socket, ReturnType<typeof setTimeout>>();
99
+ private evictor: SessionEvictor;
100
+
101
+ /**
102
+ * Derive a SessionMemoryPolicy from the conversation's thread type and
103
+ * memory scope. Private conversations get an isolated scope with strict
104
+ * side-effect controls and default-fallback recall; standard conversations
105
+ * use the shared default scope with no restrictions.
106
+ */
107
+ private deriveMemoryPolicy(conversationId: string): SessionMemoryPolicy {
108
+ const threadType = conversationStore.getConversationThreadType(conversationId);
109
+ if (threadType === 'private') {
110
+ return {
111
+ scopeId: conversationStore.getConversationMemoryScopeId(conversationId),
112
+ includeDefaultFallback: true,
113
+ strictSideEffects: true,
114
+ };
115
+ }
116
+ return DEFAULT_MEMORY_POLICY;
117
+ }
118
+
119
+ private applyTransportMetadata(_session: Session, options: SessionCreateOptions | undefined): void {
120
+ const transport = options?.transport;
121
+ if (!transport) return;
122
+
123
+ // Transport metadata is available for future use but onboarding context
124
+ // is now handled via BOOTSTRAP.md in the system prompt.
125
+ log.debug({ channelId: transport.channelId }, 'Transport metadata received');
126
+ }
127
+
128
+ /**
129
+ * Logical assistant identifier used when publishing to the assistant-events hub.
130
+ * Defaults to 'default' for the IPC daemon runtime; override in tests or
131
+ * multi-tenant deployments where the daemon is scoped to a specific assistant.
132
+ */
133
+ assistantId: string = 'default';
134
+
135
+ constructor() {
136
+ this.socketPath = getSocketPath();
137
+ this.evictor = new SessionEvictor(this.sessions);
138
+ // Share the global rate-limit timestamps with the subagent manager.
139
+ getSubagentManager().sharedRequestTimestamps = this.sharedRequestTimestamps;
140
+ // Abort subagents when their parent session is evicted.
141
+ this.evictor.onEvict = (sessionId: string) => {
142
+ getSubagentManager().abortAllForParent(sessionId);
143
+ };
144
+ // Protect parent sessions that have active subagents from eviction.
145
+ this.evictor.shouldProtect = (sessionId: string) => {
146
+ const children = getSubagentManager().getChildrenOf(sessionId);
147
+ return children.some((c) => c.status === 'running' || c.status === 'pending');
148
+ };
149
+ // When a subagent finishes, inject the result into the parent session
150
+ // so the LLM automatically informs the user.
151
+ getSubagentManager().onSubagentFinished = (parentSessionId, message, sendToClient, notification) => {
152
+ const parentSession = this.sessions.get(parentSessionId);
153
+ if (!parentSession) {
154
+ log.warn({ parentSessionId }, 'Subagent finished but parent session not found');
155
+ return;
156
+ }
157
+ const requestId = `subagent-notify-${Date.now()}`;
158
+ // Store structured notification data in the DB for history reconstruction
159
+ const metadata = { subagentNotification: notification };
160
+ const enqueueResult = parentSession.enqueueMessage(message, [], sendToClient, requestId, undefined, undefined, metadata);
161
+ if (enqueueResult.rejected) {
162
+ log.warn({ parentSessionId }, 'Parent session queue full, dropping subagent notification');
163
+ return;
164
+ }
165
+ if (!enqueueResult.queued) {
166
+ // Parent is idle — send directly.
167
+ const messageId = parentSession.persistUserMessage(message, [], undefined, metadata);
168
+ parentSession.runAgentLoop(message, messageId, sendToClient).catch((err) => {
169
+ log.error({ parentSessionId, err }, 'Failed to process subagent notification in parent');
170
+ });
171
+ }
172
+ // If queued, it will be processed when the parent finishes its current turn.
173
+ };
174
+ }
175
+
176
+ async start(): Promise<void> {
177
+ // Clean up stale socket (only if it's actually a Unix socket)
178
+ removeSocketFile(this.socketPath);
179
+
180
+ // Initialize providers from config so they're available before any
181
+ // session is created. Without this, getProvider() throws because the
182
+ // registry is empty until a config file change triggers a reload.
183
+ const config = getConfig();
184
+ initializeProviders(config);
185
+ this.lastConfigFingerprint = this.configFingerprint(config);
186
+
187
+ try {
188
+ bootstrapHomeBaseAppLink();
189
+ } catch (err) {
190
+ log.warn({ err }, 'Failed to bootstrap Home Base app link at daemon startup');
191
+ }
192
+
193
+ this.evictor.start();
194
+
195
+ // Register daemon callbacks so tools can trigger work item execution
196
+ registerDaemonCallbacks({
197
+ getOrCreateSession: (conversationId) => this.getOrCreateSession(conversationId),
198
+ broadcast: (msg) => this.broadcast(msg),
199
+ });
200
+
201
+ ensureBlobDir();
202
+ this.blobSweepTimer = setInterval(() => {
203
+ sweepStaleBlobs(30 * 60 * 1000).catch((err) => {
204
+ log.warn({ err }, 'Blob sweep failed');
205
+ });
206
+ }, 5 * 60 * 1000);
207
+
208
+ this.startFileWatchers();
209
+
210
+ // Reuse existing session token from disk if present, so pairing
211
+ // (e.g. iOS QR code) survives daemon restarts. Only generate a
212
+ // new token when none exists on disk.
213
+ const tokenPath = getSessionTokenPath();
214
+ let existingToken: string | null = null;
215
+ try {
216
+ const raw = readFileSync(tokenPath, 'utf-8').trim();
217
+ if (raw.length >= 32) existingToken = raw;
218
+ } catch { /* file doesn't exist yet */ }
219
+
220
+ if (existingToken) {
221
+ this.sessionToken = existingToken;
222
+ log.info({ tokenPath }, 'Reusing existing session token');
223
+ } else {
224
+ this.sessionToken = randomBytes(32).toString('hex');
225
+ writeFileSync(tokenPath, this.sessionToken, { mode: 0o600 });
226
+ chmodSync(tokenPath, 0o600);
227
+ log.info({ tokenPath }, 'New session token generated');
228
+ }
229
+
230
+ // Generate TLS certificate before starting listeners so it's
231
+ // available synchronously in the listen callback.
232
+ let tlsCreds: { cert: string; key: string; fingerprint: string } | null = null;
233
+ if (isTCPEnabled()) {
234
+ try {
235
+ tlsCreds = await ensureTlsCert();
236
+ } catch (err) {
237
+ log.error({ err }, 'Failed to generate TLS certificate — TCP listener will not start');
238
+ }
239
+ }
240
+
241
+ return new Promise((resolve, reject) => {
242
+ this.server = net.createServer((socket) => {
243
+ this.handleConnection(socket);
244
+ });
245
+
246
+ const oldUmask = process.umask(0o177);
247
+
248
+ this.server.once('error', (err) => {
249
+ process.umask(oldUmask);
250
+ log.error({ err, socketPath: this.socketPath }, 'Server failed to start (is another daemon already running?)');
251
+ reject(err);
252
+ });
253
+
254
+ this.server.listen(this.socketPath, () => {
255
+ process.umask(oldUmask);
256
+ // Replace the one-shot startup handler with a permanent one
257
+ this.server!.removeAllListeners('error');
258
+ this.server!.on('error', (err) => {
259
+ log.error({ err, socketPath: this.socketPath }, 'Server socket error while running');
260
+ });
261
+ chmodSync(this.socketPath, 0o600);
262
+ log.info({ socketPath: this.socketPath }, 'Daemon server listening');
263
+
264
+ // Start TLS-encrypted TCP listener for iOS clients (alongside the Unix socket)
265
+ if (tlsCreds) {
266
+ const tcpPort = getTCPPort();
267
+ const tcpHost = getTCPHost();
268
+ this.tcpServer = tls.createServer(
269
+ { cert: tlsCreds.cert, key: tlsCreds.key },
270
+ (socket) => { this.handleConnection(socket); },
271
+ );
272
+ this.tcpServer.on('error', (err) => {
273
+ log.error({ err, tcpPort }, 'TLS TCP server error');
274
+ });
275
+ const fingerprint = tlsCreds.fingerprint;
276
+ this.tcpServer.listen(tcpPort, tcpHost, () => {
277
+ const localIP = getLocalIPv4();
278
+ log.info(
279
+ { tcpPort, tcpHost, fingerprint, localIP, iosPairing: isIOSPairingEnabled() },
280
+ 'TLS TCP listener started',
281
+ );
282
+ if (isIOSPairingEnabled() && localIP) {
283
+ log.warn(
284
+ { localIP, tcpPort },
285
+ 'iOS pairing enabled — daemon is reachable on the local network at %s:%d',
286
+ localIP, tcpPort,
287
+ );
288
+ }
289
+ });
290
+ }
291
+
292
+ resolve();
293
+ });
294
+ });
295
+ }
296
+
297
+ async stop(): Promise<void> {
298
+ getSubagentManager().disposeAll();
299
+ this.evictor.stop();
300
+ if (this.blobSweepTimer) {
301
+ clearInterval(this.blobSweepTimer);
302
+ this.blobSweepTimer = null;
303
+ }
304
+ this.stopFileWatchers();
305
+
306
+ // Session token is intentionally kept on disk so pairing
307
+ // (e.g. iOS QR code) survives daemon restarts. To regenerate,
308
+ // delete ~/.vellum/session-token and restart the daemon.
309
+
310
+ for (const timer of this.authTimeouts.values()) {
311
+ clearTimeout(timer);
312
+ }
313
+ this.authTimeouts.clear();
314
+ this.authenticatedSockets.clear();
315
+
316
+ // 1. Stop accepting new connections first. server.close() prevents new
317
+ // connections from arriving, so the cleanup below won't race with
318
+ // handleConnection() adding sockets that never get destroyed.
319
+ // Its callback fires once all existing connections have ended.
320
+ const serverClosed = new Promise<void>((resolve) => {
321
+ if (this.server) {
322
+ this.server.close(() => {
323
+ try {
324
+ removeSocketFile(this.socketPath);
325
+ } catch (err) {
326
+ log.warn({ err, socketPath: this.socketPath }, 'Failed to remove socket file during shutdown');
327
+ }
328
+ resolve();
329
+ });
330
+ } else {
331
+ resolve();
332
+ }
333
+ });
334
+
335
+ const tcpServerClosed = new Promise<void>((resolve) => {
336
+ if (this.tcpServer) {
337
+ this.tcpServer.close(() => resolve());
338
+ this.tcpServer = null;
339
+ } else {
340
+ resolve();
341
+ }
342
+ });
343
+
344
+ // 2. Now dispose sessions and destroy sockets. This lets server.close()
345
+ // finish promptly since all connections will be ended.
346
+ for (const session of this.sessions.values()) {
347
+ session.dispose();
348
+ }
349
+ this.sessions.clear();
350
+
351
+ for (const cuSession of this.cuSessions.values()) {
352
+ cuSession.abort();
353
+ }
354
+ this.cuSessions.clear();
355
+ this.socketToCuSession.clear();
356
+
357
+ for (const socket of this.connectedSockets) {
358
+ socket.destroy();
359
+ }
360
+ this.connectedSockets.clear();
361
+ this.socketToSession.clear();
362
+ this.socketSandboxOverride.clear();
363
+ this.cuObservationParseSequence.clear();
364
+
365
+ await Promise.all([serverClosed, tcpServerClosed]);
366
+ log.info('Daemon server stopped');
367
+ }
368
+
369
+ private startFileWatchers(): void {
370
+ const rootDir = getRootDir();
371
+ const workspaceDir = getWorkspaceDir();
372
+ const protectedDir = join(rootDir, 'protected');
373
+
374
+ // Watch workspace directory for config + prompt files
375
+ const workspaceHandlers: Record<string, () => void> = {
376
+ 'config.json': () => {
377
+ if (this.suppressConfigReload) return;
378
+ try {
379
+ this.refreshConfigFromSources();
380
+ } catch (err) {
381
+ log.error({ err, configPath: join(workspaceDir, 'config.json') }, 'Failed to reload config after file change. Previous config remains active.');
382
+ return;
383
+ }
384
+ },
385
+ 'SOUL.md': () => this.evictSessionsForReload(),
386
+ 'IDENTITY.md': () => this.evictSessionsForReload(),
387
+ 'USER.md': () => this.evictSessionsForReload(),
388
+ 'LOOKS.md': () => this.evictSessionsForReload(),
389
+ };
390
+
391
+ // Watch protected/ for trust rules and secret allowlist
392
+ const protectedHandlers: Record<string, () => void> = {
393
+ 'trust.json': () => {
394
+ clearTrustCache();
395
+ },
396
+ 'secret-allowlist.json': () => {
397
+ resetAllowlist();
398
+ try {
399
+ const errors = validateAllowlistFile();
400
+ if (errors && errors.length > 0) {
401
+ for (const e of errors) {
402
+ log.warn({ index: e.index, pattern: e.pattern }, `Invalid regex in secret-allowlist.json: ${e.message}`);
403
+ }
404
+ }
405
+ } catch (err) {
406
+ log.warn({ err }, 'Failed to validate secret-allowlist.json');
407
+ }
408
+ },
409
+ };
410
+
411
+ const watchDir = (dir: string, handlers: Record<string, () => void>, label: string): void => {
412
+ try {
413
+ const watcher = watch(dir, (_eventType, filename) => {
414
+ if (!filename) return;
415
+ const file = String(filename);
416
+ if (!handlers[file]) return;
417
+ this.debounceTimers.schedule(`file:${file}`, () => {
418
+ log.info({ file }, 'File changed, reloading');
419
+ handlers[file]();
420
+ });
421
+ });
422
+ this.watchers.push(watcher);
423
+ log.info({ dir }, `Watching ${label}`);
424
+ } catch (err) {
425
+ log.warn({ err, dir }, `Failed to watch ${label}. Hot-reload will be unavailable.`);
426
+ }
427
+ };
428
+
429
+ watchDir(workspaceDir, workspaceHandlers, 'workspace directory for config/prompt changes');
430
+ if (existsSync(protectedDir)) {
431
+ watchDir(protectedDir, protectedHandlers, 'protected directory for trust/allowlist changes');
432
+ }
433
+
434
+ this.startSkillsWatchers(() => this.evictSessionsForReload());
435
+ }
436
+
437
+ private configFingerprint(config: ReturnType<typeof getConfig>): string {
438
+ return JSON.stringify(config);
439
+ }
440
+
441
+ /**
442
+ * Record the runtime HTTP server port and broadcast it to all
443
+ * connected clients so they can enable the share UI immediately.
444
+ */
445
+ setHttpPort(port: number): void {
446
+ this.httpPort = port;
447
+ // Clients that connected before the HTTP server started received
448
+ // daemon_status with no httpPort. Broadcast the updated port so
449
+ // they can enable the share UI without reconnecting.
450
+ this.broadcast({
451
+ type: 'daemon_status',
452
+ httpPort: port,
453
+ version: daemonVersion,
454
+ });
455
+ }
456
+
457
+ /**
458
+ * Dispose and remove all in-memory sessions unconditionally.
459
+ * Called after `sessions clear` wipes the database so that stale
460
+ * sessions don't reference deleted conversation rows.
461
+ */
462
+ clearAllSessions(): number {
463
+ const count = this.sessions.size;
464
+ const subagentManager = getSubagentManager();
465
+ for (const id of this.sessions.keys()) {
466
+ this.evictor.remove(id);
467
+ subagentManager.abortAllForParent(id);
468
+ }
469
+ for (const session of this.sessions.values()) {
470
+ session.dispose();
471
+ }
472
+ this.sessions.clear();
473
+ this.sessionOptions.clear();
474
+ return count;
475
+ }
476
+
477
+ private evictSessionsForReload(): void {
478
+ const subagentManager = getSubagentManager();
479
+ for (const [id, session] of this.sessions) {
480
+ if (!session.isProcessing()) {
481
+ subagentManager.abortAllForParent(id);
482
+ session.dispose();
483
+ this.sessions.delete(id);
484
+ this.evictor.remove(id);
485
+ } else {
486
+ session.markStale();
487
+ }
488
+ }
489
+ }
490
+
491
+ /**
492
+ * Reload config from disk + secure storage, and refresh providers only
493
+ * when effective config values (including API keys) have changed.
494
+ */
495
+ private refreshConfigFromSources(): boolean {
496
+ invalidateConfigCache();
497
+ const config = getConfig();
498
+ const fingerprint = this.configFingerprint(config);
499
+ if (fingerprint === this.lastConfigFingerprint) {
500
+ return false;
501
+ }
502
+ // Default trust rules depend on config (e.g. skills.load.extraDirs),
503
+ // so clear the trust cache so rules are regenerated from fresh config.
504
+ clearTrustCache();
505
+ clearEmbeddingBackendCache();
506
+ const isFirstInit = this.lastConfigFingerprint === '';
507
+ initializeProviders(config);
508
+ this.lastConfigFingerprint = fingerprint;
509
+ if (!isFirstInit) {
510
+ this.evictSessionsForReload();
511
+ }
512
+ return true;
513
+ }
514
+
515
+ private stopFileWatchers(): void {
516
+ this.debounceTimers.cancelAll();
517
+ for (const watcher of this.watchers) {
518
+ watcher.close();
519
+ }
520
+ this.watchers = [];
521
+ }
522
+
523
+ private startSkillsWatchers(evictSessions: () => void): void {
524
+ const skillsDir = getWorkspaceSkillsDir();
525
+ if (!existsSync(skillsDir)) return;
526
+
527
+ const scheduleSkillsReload = (file: string): void => {
528
+ this.debounceTimers.schedule(`skills:${file}`, () => {
529
+ log.info({ file }, 'Skill file changed, reloading');
530
+ evictSessions();
531
+ });
532
+ };
533
+
534
+ try {
535
+ const recursiveWatcher = watch(skillsDir, { recursive: true }, (_eventType, filename) => {
536
+ scheduleSkillsReload(filename ? String(filename) : '(unknown)');
537
+ });
538
+ this.watchers.push(recursiveWatcher);
539
+ log.info({ dir: skillsDir }, 'Watching skills directory recursively');
540
+ return;
541
+ } catch (err) {
542
+ log.info({ err, dir: skillsDir }, 'Recursive skills watch unavailable; using per-directory watchers');
543
+ }
544
+
545
+ const childWatchers = new Map<string, FSWatcher>();
546
+
547
+ const watchDir = (dirPath: string, onChange: (filename: string) => void): FSWatcher | null => {
548
+ try {
549
+ const watcher = watch(dirPath, (_eventType, filename) => {
550
+ onChange(filename ? String(filename) : '(unknown)');
551
+ });
552
+ this.watchers.push(watcher);
553
+ return watcher;
554
+ } catch (err) {
555
+ log.warn({ err, dirPath }, 'Failed to watch skills directory');
556
+ return null;
557
+ }
558
+ };
559
+
560
+ const removeWatcher = (watcher: FSWatcher): void => {
561
+ const idx = this.watchers.indexOf(watcher);
562
+ if (idx !== -1) {
563
+ this.watchers.splice(idx, 1);
564
+ }
565
+ };
566
+
567
+ const refreshChildWatchers = (): void => {
568
+ const nextChildDirs = new Set<string>();
569
+
570
+ try {
571
+ const entries = readdirSync(skillsDir, { withFileTypes: true });
572
+ for (const entry of entries) {
573
+ if (!entry.isDirectory()) continue;
574
+ const childDir = join(skillsDir, entry.name);
575
+ nextChildDirs.add(childDir);
576
+
577
+ if (childWatchers.has(childDir)) continue;
578
+
579
+ const watcher = watchDir(childDir, (filename) => {
580
+ const label = filename === '(unknown)' ? entry.name : `${entry.name}/${filename}`;
581
+ scheduleSkillsReload(label);
582
+ });
583
+ if (watcher) {
584
+ childWatchers.set(childDir, watcher);
585
+ }
586
+ }
587
+ } catch (err) {
588
+ log.warn({ err, skillsDir }, 'Failed to enumerate skill directories');
589
+ return;
590
+ }
591
+
592
+ for (const [childDir, watcher] of childWatchers.entries()) {
593
+ if (nextChildDirs.has(childDir)) continue;
594
+ watcher.close();
595
+ childWatchers.delete(childDir);
596
+ removeWatcher(watcher);
597
+ }
598
+ };
599
+
600
+ const rootWatcher = watchDir(skillsDir, (filename) => {
601
+ scheduleSkillsReload(filename);
602
+ refreshChildWatchers();
603
+ });
604
+
605
+ if (!rootWatcher) return;
606
+
607
+ refreshChildWatchers();
608
+ log.info({ dir: skillsDir }, 'Watching skills directory with non-recursive fallback');
609
+ }
610
+
611
+ private handleConnection(socket: net.Socket): void {
612
+ if (this.connectedSockets.size >= DaemonServer.MAX_CONNECTIONS) {
613
+ log.warn({ current: this.connectedSockets.size, max: DaemonServer.MAX_CONNECTIONS }, 'Connection limit reached, rejecting client');
614
+ socket.once('error', (err) => {
615
+ log.error({ err }, 'Socket error while rejecting connection');
616
+ });
617
+ socket.write(serialize({ type: 'error', message: `Connection limit reached (max ${DaemonServer.MAX_CONNECTIONS})` }));
618
+ socket.destroy();
619
+ return;
620
+ }
621
+
622
+ log.info('Client connected');
623
+ this.connectedSockets.add(socket);
624
+ const parser = createMessageParser({ maxLineSize: MAX_LINE_SIZE });
625
+
626
+ // When the operator explicitly opts into unauthenticated connections
627
+ // (VELLUM_DAEMON_NOAUTH=1), auto-authenticate so clients that can't
628
+ // read the local session token file (e.g. SSH-forwarded sockets)
629
+ // aren't disconnected by the auth timeout. This is intentionally
630
+ // gated on a separate flag — a custom socket path alone (via
631
+ // VELLUM_DAEMON_SOCKET) no longer bypasses token auth.
632
+ if (hasNoAuthOverride()) {
633
+ this.authenticatedSockets.add(socket);
634
+ log.warn('Auto-authenticated client (VELLUM_DAEMON_NOAUTH is set — token auth bypassed)');
635
+ this.send(socket, { type: 'auth_result', success: true });
636
+ this.sendInitialSession(socket).catch((err) => {
637
+ log.error({ err }, 'Failed to send initial session info after auto-auth');
638
+ });
639
+ }
640
+
641
+ // Require authentication before sending session info or accepting
642
+ // commands. Clients must send { type: 'auth', token } as their
643
+ // first message within AUTH_TIMEOUT_MS.
644
+ const authTimer = setTimeout(() => {
645
+ if (!this.authenticatedSockets.has(socket)) {
646
+ log.warn('Client failed to authenticate within timeout, disconnecting');
647
+ this.send(socket, { type: 'error', message: 'Authentication timeout' });
648
+ socket.destroy();
649
+ }
650
+ }, DaemonServer.AUTH_TIMEOUT_MS);
651
+ this.authTimeouts.set(socket, authTimer);
652
+
653
+ socket.on('data', (data) => {
654
+ const chunkReceivedAtMs = Date.now();
655
+ const parseStartNs = process.hrtime.bigint();
656
+ let parsed;
657
+ try {
658
+ parsed = parser.feedRaw(data.toString());
659
+ } catch (err) {
660
+ log.error({ err }, 'IPC parse error (malformed JSON or message exceeded size limit), dropping client');
661
+ socket.write(serialize({ type: 'error', message: `IPC parse error: ${(err as Error).message}` }));
662
+ socket.destroy();
663
+ return;
664
+ }
665
+ const parsedAtMs = Date.now();
666
+ const parseDurationMs = Number(process.hrtime.bigint() - parseStartNs) / 1_000_000;
667
+ for (const entry of parsed) {
668
+ const msg = entry.msg;
669
+ if (typeof msg === 'object' && msg !== null && (msg as { type?: unknown }).type === 'cu_observation') {
670
+ const maybeSessionId = (msg as { sessionId?: unknown }).sessionId;
671
+ const sessionId = typeof maybeSessionId === 'string' ? maybeSessionId : 'unknown';
672
+ const previousSequence = this.cuObservationParseSequence.get(sessionId) ?? 0;
673
+ const sequence = previousSequence + 1;
674
+ this.cuObservationParseSequence.set(sessionId, sequence);
675
+ log.info({
676
+ sessionId,
677
+ sequence,
678
+ chunkReceivedAtMs,
679
+ parsedAtMs,
680
+ parseDurationMs,
681
+ messageBytes: entry.rawByteLength,
682
+ }, 'IPC_METRIC cu_observation_parse');
683
+ }
684
+ const result = validateClientMessage(msg);
685
+ if (!result.valid) {
686
+ log.warn({ reason: result.reason }, 'Invalid IPC message, dropping client');
687
+ socket.write(serialize({ type: 'error', message: `Invalid message: ${result.reason}` }));
688
+ socket.destroy();
689
+ return;
690
+ }
691
+
692
+ // Auth gate: first message must be 'auth' with a valid token.
693
+ if (!this.authenticatedSockets.has(socket)) {
694
+ const pendingTimer = this.authTimeouts.get(socket);
695
+ if (pendingTimer) {
696
+ clearTimeout(pendingTimer);
697
+ this.authTimeouts.delete(socket);
698
+ }
699
+
700
+ if (result.message.type === 'auth') {
701
+ const authMsg = result.message as { type: 'auth'; token: string };
702
+ if (authMsg.token === this.sessionToken) {
703
+ this.authenticatedSockets.add(socket);
704
+ this.send(socket, { type: 'auth_result', success: true });
705
+ this.sendInitialSession(socket).catch((err) => {
706
+ log.error({ err }, 'Failed to send initial session info after auth');
707
+ });
708
+ } else {
709
+ log.warn('Client provided invalid auth token');
710
+ this.send(socket, { type: 'auth_result', success: false, message: 'Invalid token' });
711
+ socket.destroy();
712
+ }
713
+ continue;
714
+ }
715
+
716
+ // Non-auth message from unauthenticated socket
717
+ log.warn({ type: result.message.type }, 'Unauthenticated client sent non-auth message, disconnecting');
718
+ this.send(socket, { type: 'error', message: 'Authentication required' });
719
+ socket.destroy();
720
+ return;
721
+ }
722
+
723
+ // If an already-authenticated socket sends an auth message (e.g.
724
+ // auto-auth'd client that also has a local token), respond with
725
+ // auth_result so the client doesn't hang waiting for the handshake.
726
+ if (result.message.type === 'auth') {
727
+ this.send(socket, { type: 'auth_result', success: true });
728
+ continue;
729
+ }
730
+
731
+ this.dispatchMessage(result.message, socket);
732
+ }
733
+ });
734
+
735
+ socket.on('close', () => {
736
+ const pendingAuthTimer = this.authTimeouts.get(socket);
737
+ if (pendingAuthTimer) {
738
+ clearTimeout(pendingAuthTimer);
739
+ this.authTimeouts.delete(socket);
740
+ }
741
+ this.authenticatedSockets.delete(socket);
742
+ this.connectedSockets.delete(socket);
743
+ this.socketSandboxOverride.delete(socket);
744
+ const sessionId = this.socketToSession.get(socket);
745
+ if (sessionId) {
746
+ const session = this.sessions.get(sessionId);
747
+ if (session) {
748
+ session.abort();
749
+ }
750
+ getSubagentManager().abortAllForParent(sessionId);
751
+ }
752
+ this.socketToSession.delete(socket);
753
+ const cuSessionIds = this.socketToCuSession.get(socket);
754
+ if (cuSessionIds) {
755
+ for (const cuSessionId of cuSessionIds) {
756
+ this.cuObservationParseSequence.delete(cuSessionId);
757
+ const cuSession = this.cuSessions.get(cuSessionId);
758
+ if (cuSession) {
759
+ cuSession.abort();
760
+ this.cuSessions.delete(cuSessionId);
761
+ }
762
+ }
763
+ }
764
+ this.socketToCuSession.delete(socket);
765
+ log.info('Client disconnected');
766
+ });
767
+
768
+ socket.on('error', (err) => {
769
+ log.error({ err, remoteAddress: socket.remoteAddress }, 'Client socket error');
770
+ });
771
+ }
772
+
773
+ /** Low-level wire write — does not publish to the assistant-events hub. */
774
+ private writeToSocket(socket: net.Socket, msg: ServerMessage): void {
775
+ if (!socket.destroyed && socket.writable) {
776
+ socket.write(serialize(msg));
777
+ }
778
+ }
779
+
780
+ private send(socket: net.Socket, msg: ServerMessage): void {
781
+ this.writeToSocket(socket, msg);
782
+ // Best-effort sessionId: prefer message field, fall back to socket binding.
783
+ const msgRecord = msg as unknown as Record<string, unknown>;
784
+ const sessionId =
785
+ ('sessionId' in msg && typeof msgRecord.sessionId === 'string'
786
+ ? msgRecord.sessionId as string
787
+ : undefined) ?? this.socketToSession.get(socket);
788
+ this.publishAssistantEvent(msg, sessionId, this.assistantId);
789
+ }
790
+
791
+ broadcast(msg: ServerMessage, excludeSocket?: net.Socket): void {
792
+ for (const socket of this.authenticatedSockets) {
793
+ if (socket === excludeSocket) continue;
794
+ this.writeToSocket(socket, msg); // bypass per-socket hub publish
795
+ }
796
+ // Publish once for the broadcast. Prefer message-level sessionId; fall back
797
+ // to excludeSocket's session binding so session-scoped events (e.g.
798
+ // assistant_text_delta emitted without a sessionId field) are correctly tagged.
799
+ const msgRecord = msg as unknown as Record<string, unknown>;
800
+ const sessionId =
801
+ ('sessionId' in msg && typeof msgRecord.sessionId === 'string'
802
+ ? msgRecord.sessionId as string
803
+ : undefined) ?? (excludeSocket ? this.socketToSession.get(excludeSocket) : undefined);
804
+ this.publishAssistantEvent(msg, sessionId, this.assistantId);
805
+ }
806
+
807
+ /**
808
+ * Publish `msg` as an `AssistantEvent` to the process-level hub.
809
+ * Publishes are serialized via a promise chain so that subscribers always
810
+ * observe events in the order they were sent (e.g. text deltas before
811
+ * message_complete), even when subscriber callbacks are async.
812
+ */
813
+ private _hubChain: Promise<void> = Promise.resolve();
814
+
815
+ private publishAssistantEvent(msg: ServerMessage, sessionId?: string, assistantId?: string): void {
816
+ const event = buildAssistantEvent(assistantId ?? this.assistantId, msg, sessionId);
817
+ this._hubChain = this._hubChain
818
+ .then(() => assistantEventHub.publish(event))
819
+ .catch((err: unknown) => {
820
+ log.warn({ err }, 'assistant-events hub subscriber threw during IPC send');
821
+ });
822
+ }
823
+
824
+ private async sendInitialSession(socket: net.Socket): Promise<void> {
825
+ // Only send session info for an existing conversation. Don't create one —
826
+ // the client will create its own session via session_create when the user
827
+ // sends a message. Creating one here would produce an orphaned session
828
+ // that the macOS client rejects (correlation ID mismatch) but that still
829
+ // appears in session_list on subsequent launches.
830
+ const conversation = conversationStore.getLatestConversation();
831
+ if (!conversation) {
832
+ this.send(socket, {
833
+ type: 'daemon_status',
834
+ httpPort: this.httpPort,
835
+ version: daemonVersion,
836
+ });
837
+ return;
838
+ }
839
+
840
+ // Warm session state for commands like undo/usage after reconnect without
841
+ // rebinding the active IPC output client to this passive socket.
842
+ await this.getOrCreateSession(conversation.id, undefined, false);
843
+
844
+ this.send(socket, {
845
+ type: 'session_info',
846
+ sessionId: conversation.id,
847
+ title: conversation.title ?? 'New Conversation',
848
+ threadType: normalizeThreadType(conversation.threadType),
849
+ });
850
+
851
+ this.send(socket, {
852
+ type: 'daemon_status',
853
+ httpPort: this.httpPort,
854
+ version: daemonVersion,
855
+ });
856
+ }
857
+
858
+ private async getOrCreateSession(
859
+ conversationId: string,
860
+ socket?: net.Socket,
861
+ rebindClient = true,
862
+ options?: SessionCreateOptions,
863
+ ): Promise<Session> {
864
+ let session = this.sessions.get(conversationId);
865
+ const sendToClient = socket
866
+ ? (msg: ServerMessage) => this.send(socket, msg)
867
+ : () => {};
868
+ const maybeBindClient = (target: Session): void => {
869
+ if (!rebindClient || !socket) return;
870
+ target.updateClient(sendToClient);
871
+ target.setSandboxOverride(this.socketSandboxOverride.get(socket));
872
+ // Update the sender for any active child subagents so they route
873
+ // through the new socket instead of the stale one from spawn time.
874
+ getSubagentManager().updateParentSender(conversationId, sendToClient);
875
+ };
876
+
877
+ // Persist session options so they survive eviction/recreation.
878
+ if (options && Object.values(options).some(v => v !== undefined)) {
879
+ this.sessionOptions.set(conversationId, {
880
+ ...this.sessionOptions.get(conversationId),
881
+ ...options,
882
+ });
883
+ }
884
+
885
+ if (!session || (session.isStale() && !session.isProcessing())) {
886
+ // Dispose the outgoing stale session before replacing it.
887
+ if (session) {
888
+ getSubagentManager().abortAllForParent(conversationId);
889
+ session.dispose();
890
+ }
891
+
892
+ // Check if another caller is already creating this session.
893
+ // Without this guard, two concurrent getOrCreateSession calls for the
894
+ // same conversationId would both pass the null/stale check, both create
895
+ // a Session + loadFromDb(), and the second set() would orphan the first.
896
+ const pending = this.sessionCreating.get(conversationId);
897
+ if (pending) {
898
+ session = await pending;
899
+ maybeBindClient(session);
900
+ return session;
901
+ }
902
+
903
+ // Recover stored options for this conversation (survives eviction).
904
+ const storedOptions = this.sessionOptions.get(conversationId);
905
+
906
+ const createPromise = (async () => {
907
+ const config = getConfig();
908
+ let provider = getFailoverProvider(config.provider, config.providerOrder);
909
+ const { rateLimit } = config;
910
+ if (rateLimit.maxRequestsPerMinute > 0 || rateLimit.maxTokensPerSession > 0) {
911
+ provider = new RateLimitProvider(provider, rateLimit, this.sharedRequestTimestamps);
912
+ }
913
+ const workingDir = getSandboxWorkingDir();
914
+
915
+ const systemPrompt = storedOptions?.systemPromptOverride ?? buildSystemPrompt();
916
+ const maxTokens = storedOptions?.maxResponseTokens ?? config.maxTokens;
917
+
918
+ const memoryPolicy = this.deriveMemoryPolicy(conversationId);
919
+ const newSession = new Session(
920
+ conversationId,
921
+ provider,
922
+ systemPrompt,
923
+ maxTokens,
924
+ rebindClient ? sendToClient : () => {},
925
+ workingDir,
926
+ (msg) => this.broadcast(msg, socket),
927
+ memoryPolicy,
928
+ );
929
+ // When created without a socket (HTTP path), mark the session
930
+ // so interactive prompts (e.g. host attachment reads) can fail
931
+ // fast instead of waiting for a timeout with no client to respond.
932
+ if (!socket) {
933
+ newSession.updateClient(sendToClient, true);
934
+ }
935
+ await newSession.loadFromDb();
936
+ this.applyTransportMetadata(newSession, storedOptions);
937
+ if (rebindClient && socket) {
938
+ newSession.setSandboxOverride(this.socketSandboxOverride.get(socket));
939
+ }
940
+ this.sessions.set(conversationId, newSession);
941
+ return newSession;
942
+ })();
943
+
944
+ this.sessionCreating.set(conversationId, createPromise);
945
+ try {
946
+ session = await createPromise;
947
+ } finally {
948
+ this.sessionCreating.delete(conversationId);
949
+ }
950
+ this.evictor.touch(conversationId);
951
+ } else {
952
+ // Rebind to the new socket so IPC goes to the current client.
953
+ maybeBindClient(session);
954
+ this.applyTransportMetadata(session, options);
955
+ this.evictor.touch(conversationId);
956
+ }
957
+ return session;
958
+ }
959
+
960
+ private handlerContext(): HandlerContext {
961
+ return {
962
+ sessions: this.sessions,
963
+ socketToSession: this.socketToSession,
964
+ cuSessions: this.cuSessions,
965
+ socketToCuSession: this.socketToCuSession,
966
+ cuObservationParseSequence: this.cuObservationParseSequence,
967
+ socketSandboxOverride: this.socketSandboxOverride,
968
+ sharedRequestTimestamps: this.sharedRequestTimestamps,
969
+ debounceTimers: this.debounceTimers,
970
+ suppressConfigReload: this.suppressConfigReload,
971
+ setSuppressConfigReload: (value: boolean) => { this.suppressConfigReload = value; },
972
+ updateConfigFingerprint: () => {
973
+ this.lastConfigFingerprint = this.configFingerprint(getConfig());
974
+ this.lastConfigRefreshTime = Date.now();
975
+ },
976
+ send: (socket, msg) => this.send(socket, msg),
977
+ broadcast: (msg) => this.broadcast(msg),
978
+ clearAllSessions: () => this.clearAllSessions(),
979
+ getOrCreateSession: (id, socket?, rebind?, options?) =>
980
+ this.getOrCreateSession(id, socket, rebind, options),
981
+ touchSession: (id) => this.evictor.touch(id),
982
+ };
983
+ }
984
+
985
+ private dispatchMessage(msg: ClientMessage, socket: net.Socket): void {
986
+ if (msg.type !== 'ping') {
987
+ const now = Date.now();
988
+ if (now - this.lastConfigRefreshTime >= DaemonServer.CONFIG_REFRESH_INTERVAL_MS) {
989
+ try {
990
+ this.refreshConfigFromSources();
991
+ this.lastConfigRefreshTime = now;
992
+ } catch (err) {
993
+ log.warn({ err }, 'Failed to refresh config from secure sources before handling IPC message');
994
+ }
995
+ }
996
+ }
997
+ handleMessage(msg, socket, this.handlerContext());
998
+ }
999
+
1000
+ /**
1001
+ * Persist a user message and start the agent loop in the background.
1002
+ * Returns the messageId immediately without waiting for the agent loop
1003
+ * to complete. Used by the HTTP sendMessage endpoint so the response
1004
+ * is not blocked for the duration of the agent loop.
1005
+ */
1006
+ async persistAndProcessMessage(
1007
+ conversationId: string,
1008
+ content: string,
1009
+ attachmentIds?: string[],
1010
+ options?: SessionCreateOptions,
1011
+ sourceChannel?: string,
1012
+ ): Promise<{ messageId: string }> {
1013
+ // Block inbound content that contains secrets — mirrors the IPC check in sessions.ts
1014
+ const ingressCheck = checkIngressForSecrets(content);
1015
+ if (ingressCheck.blocked) {
1016
+ throw new IngressBlockedError(ingressCheck.userNotice!, ingressCheck.detectedTypes);
1017
+ }
1018
+
1019
+ const session = await this.getOrCreateSession(conversationId, undefined, true, options);
1020
+
1021
+ // Reject concurrent requests upfront. The HTTP path should never use
1022
+ // the message queue — it returns 409 to the caller instead.
1023
+ if (session.isProcessing()) {
1024
+ throw new Error('Session is already processing a message');
1025
+ }
1026
+
1027
+ session.setChannelCapabilities(resolveChannelCapabilities(sourceChannel));
1028
+
1029
+ // Resolve attachment IDs to full attachment data for the session
1030
+ const attachments = attachmentIds
1031
+ ? attachmentsStore.getAttachmentsByIds(attachmentIds).map((a) => ({
1032
+ id: a.id,
1033
+ filename: a.originalFilename,
1034
+ mimeType: a.mimeType,
1035
+ data: a.dataBase64,
1036
+ }))
1037
+ : [];
1038
+
1039
+ // Persist the user message immediately after the isProcessing() guard.
1040
+ // This synchronously sets session.processing = true, closing the race
1041
+ // window that previously existed between the guard and the async bridge
1042
+ // check (two concurrent requests could both pass isProcessing() and
1043
+ // race into message handling).
1044
+ const requestId = crypto.randomUUID();
1045
+ const messageId = session.persistUserMessage(content, attachments, requestId);
1046
+
1047
+ // Now that the processing lock is held, check the call-answer bridge.
1048
+ let bridgeHandled = false;
1049
+ try {
1050
+ const bridgeResult = await tryRouteCallMessage(conversationId, content, messageId);
1051
+ bridgeHandled = bridgeResult.handled;
1052
+ } catch (err) {
1053
+ log.warn({ err, conversationId }, 'Call bridge check failed (non-fatal), proceeding with agent loop');
1054
+ }
1055
+
1056
+ if (bridgeHandled) {
1057
+ // The message was consumed by the call system. Release the session.
1058
+ resetSessionProcessingState(session);
1059
+ // Drain any queued messages that arrived while processing was true.
1060
+ session.drainQueue('loop_complete');
1061
+ log.info({ conversationId, messageId }, 'User message consumed by call bridge, skipping agent loop');
1062
+ return { messageId };
1063
+ }
1064
+
1065
+ // Fire-and-forget the agent loop. Errors are logged but do not
1066
+ // affect the HTTP response (the client polls GET /messages).
1067
+ session.runAgentLoop(content, messageId, () => {}).catch((err) => {
1068
+ log.error({ err, conversationId }, 'Background agent loop failed');
1069
+ });
1070
+
1071
+ return { messageId };
1072
+ }
1073
+
1074
+ /**
1075
+ * Process a message from the HTTP runtime API (blocking).
1076
+ * Gets or creates a session and runs the full agent loop before returning.
1077
+ * Used by the channel inbound endpoint which needs the assistant reply.
1078
+ */
1079
+ async processMessage(
1080
+ conversationId: string,
1081
+ content: string,
1082
+ attachmentIds?: string[],
1083
+ options?: SessionCreateOptions,
1084
+ sourceChannel?: string,
1085
+ ): Promise<{ messageId: string }> {
1086
+ // Block inbound content that contains secrets — mirrors the IPC check in sessions.ts
1087
+ const ingressCheck = checkIngressForSecrets(content);
1088
+ if (ingressCheck.blocked) {
1089
+ throw new IngressBlockedError(ingressCheck.userNotice!, ingressCheck.detectedTypes);
1090
+ }
1091
+
1092
+ const session = await this.getOrCreateSession(conversationId, undefined, true, options);
1093
+
1094
+ if (session.isProcessing()) {
1095
+ throw new Error('Session is already processing a message');
1096
+ }
1097
+
1098
+ session.setChannelCapabilities(resolveChannelCapabilities(sourceChannel));
1099
+
1100
+ // Resolve attachment IDs to full attachment data for the session
1101
+ const attachments = attachmentIds
1102
+ ? attachmentsStore.getAttachmentsByIds(attachmentIds).map((a) => ({
1103
+ id: a.id,
1104
+ filename: a.originalFilename,
1105
+ mimeType: a.mimeType,
1106
+ data: a.dataBase64,
1107
+ }))
1108
+ : [];
1109
+
1110
+ // Resolve slash commands before persistence (synchronous — no race window).
1111
+ const slashResult = resolveSlash(content);
1112
+
1113
+ // Unknown slash command — persist the exchange (user + assistant) and
1114
+ // return immediately. This path doesn't set processing=true since no
1115
+ // agent loop runs, so there is no race concern.
1116
+ if (slashResult.kind === 'unknown') {
1117
+ const userMsg = createUserMessage(content, attachments);
1118
+ const persisted = conversationStore.addMessage(
1119
+ conversationId,
1120
+ 'user',
1121
+ JSON.stringify(userMsg.content),
1122
+ );
1123
+ session.getMessages().push(userMsg);
1124
+
1125
+ const assistantMsg = createAssistantMessage(slashResult.message);
1126
+ conversationStore.addMessage(
1127
+ conversationId,
1128
+ 'assistant',
1129
+ JSON.stringify(assistantMsg.content),
1130
+ );
1131
+ session.getMessages().push(assistantMsg);
1132
+ return { messageId: persisted.id };
1133
+ }
1134
+
1135
+ const resolvedContent = slashResult.content;
1136
+
1137
+ // Preactivate skill tools when slash resolution identifies a known skill
1138
+ if (slashResult.kind === 'rewritten') {
1139
+ (session as unknown as { preactivatedSkillIds?: string[] }).preactivatedSkillIds = [slashResult.skillId];
1140
+ }
1141
+
1142
+ // Persist the user message immediately after the isProcessing() guard.
1143
+ // This synchronously sets session.processing = true, closing the race
1144
+ // window that previously existed between the guard and the async bridge
1145
+ // check.
1146
+ const requestId = crypto.randomUUID();
1147
+ let messageId: string;
1148
+ try {
1149
+ messageId = session.persistUserMessage(resolvedContent, attachments, requestId);
1150
+ } catch (err) {
1151
+ // runAgentLoop never ran, so its finally block won't clear this
1152
+ (session as unknown as { preactivatedSkillIds?: string[] }).preactivatedSkillIds = undefined;
1153
+ throw err;
1154
+ }
1155
+
1156
+ // Now that the processing lock is held, check the call-answer bridge.
1157
+ let bridgeHandled = false;
1158
+ try {
1159
+ const bridgeResult = await tryRouteCallMessage(conversationId, resolvedContent, messageId);
1160
+ bridgeHandled = bridgeResult.handled;
1161
+ } catch (err) {
1162
+ log.warn({ err, conversationId }, 'Call bridge check failed (non-fatal), proceeding with agent loop');
1163
+ }
1164
+
1165
+ if (bridgeHandled) {
1166
+ // The message was consumed by the call system. Release the session.
1167
+ (session as unknown as { preactivatedSkillIds?: string[] }).preactivatedSkillIds = undefined;
1168
+ resetSessionProcessingState(session);
1169
+ // Drain any queued messages that arrived while processing was true.
1170
+ session.drainQueue('loop_complete');
1171
+ log.info({ conversationId, messageId }, 'User message consumed by call bridge, skipping agent loop');
1172
+ return { messageId };
1173
+ }
1174
+
1175
+ // Run the agent loop (blocking — the channel inbound endpoint needs the reply).
1176
+ await session.runAgentLoop(resolvedContent, messageId, () => {});
1177
+
1178
+ return { messageId };
1179
+ }
1180
+
1181
+ /**
1182
+ * Create a RunOrchestrator wired to this server's session management.
1183
+ */
1184
+ createRunOrchestrator(): RunOrchestrator {
1185
+ return new RunOrchestrator({
1186
+ getOrCreateSession: (conversationId) =>
1187
+ this.getOrCreateSession(conversationId),
1188
+ resolveAttachments: (attachmentIds) =>
1189
+ attachmentsStore.getAttachmentsByIds(attachmentIds).map((a) => ({
1190
+ id: a.id,
1191
+ filename: a.originalFilename,
1192
+ mimeType: a.mimeType,
1193
+ data: a.dataBase64,
1194
+ })),
1195
+ deriveDefaultStrictSideEffects: (conversationId) =>
1196
+ this.deriveMemoryPolicy(conversationId).strictSideEffects,
1197
+ });
1198
+ }
1199
+
1200
+ }
1201
+
1202
+ /**
1203
+ * Reset the processing state set by `persistUserMessage` when the agent loop
1204
+ * is intentionally skipped (e.g. call-answer bridge consumed the message).
1205
+ */
1206
+ function resetSessionProcessingState(session: Session): void {
1207
+ const s = session as unknown as {
1208
+ processing: boolean;
1209
+ abortController: AbortController | null;
1210
+ currentRequestId: string | undefined;
1211
+ };
1212
+ s.processing = false;
1213
+ s.abortController = null;
1214
+ s.currentRequestId = undefined;
1215
+ }