@vellumai/assistant 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/Dockerfile +2 -0
  2. package/README.md +88 -2
  3. package/eslint.config.mjs +31 -0
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  6. package/scripts/ipc/generate-swift.ts +31 -2
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
  8. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  9. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
  10. package/src/__tests__/approval-message-composer.test.ts +253 -0
  11. package/src/__tests__/browser-manager.test.ts +1 -0
  12. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  13. package/src/__tests__/call-domain.test.ts +12 -2
  14. package/src/__tests__/call-orchestrator.test.ts +799 -249
  15. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  16. package/src/__tests__/call-recovery.test.ts +3 -0
  17. package/src/__tests__/call-routes-http.test.ts +32 -2
  18. package/src/__tests__/call-store.test.ts +3 -0
  19. package/src/__tests__/channel-approval-routes.test.ts +1277 -98
  20. package/src/__tests__/channel-approval.test.ts +37 -0
  21. package/src/__tests__/channel-approvals.test.ts +36 -50
  22. package/src/__tests__/channel-guardian.test.ts +630 -22
  23. package/src/__tests__/channel-readiness-service.test.ts +324 -0
  24. package/src/__tests__/checker.test.ts +14 -7
  25. package/src/__tests__/clarification-resolver.test.ts +44 -24
  26. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  27. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  28. package/src/__tests__/config-schema.test.ts +14 -8
  29. package/src/__tests__/context-window-manager.test.ts +30 -2
  30. package/src/__tests__/contradiction-checker.test.ts +20 -5
  31. package/src/__tests__/credential-security-invariants.test.ts +7 -2
  32. package/src/__tests__/daemon-lifecycle.test.ts +13 -12
  33. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  34. package/src/__tests__/dictation-mode-detection.test.ts +63 -0
  35. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  36. package/src/__tests__/entity-search.test.ts +615 -0
  37. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  38. package/src/__tests__/guardian-action-store.test.ts +123 -0
  39. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  40. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  41. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  42. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  43. package/src/__tests__/handlers-twilio-config.test.ts +533 -0
  44. package/src/__tests__/intent-routing.test.ts +2 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +291 -1
  46. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  47. package/src/__tests__/messaging-send-tool.test.ts +65 -0
  48. package/src/__tests__/model-intents.test.ts +96 -0
  49. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  50. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  51. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  52. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  53. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  54. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  55. package/src/__tests__/qdrant-manager.test.ts +27 -20
  56. package/src/__tests__/relay-server.test.ts +779 -40
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
  58. package/src/__tests__/run-orchestrator.test.ts +42 -4
  59. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  60. package/src/__tests__/runtime-runs.test.ts +16 -0
  61. package/src/__tests__/schedule-store.test.ts +18 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  63. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  64. package/src/__tests__/session-agent-loop.test.ts +857 -0
  65. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  66. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  67. package/src/__tests__/session-profile-injection.test.ts +6 -0
  68. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  69. package/src/__tests__/session-queue.test.ts +6 -0
  70. package/src/__tests__/session-runtime-assembly.test.ts +321 -13
  71. package/src/__tests__/session-slash-known.test.ts +6 -0
  72. package/src/__tests__/session-slash-queue.test.ts +6 -0
  73. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  74. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  75. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  76. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  77. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  78. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  79. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  80. package/src/__tests__/skills.test.ts +2 -0
  81. package/src/__tests__/sms-messaging-provider.test.ts +126 -0
  82. package/src/__tests__/starter-task-flow.test.ts +2 -0
  83. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  84. package/src/__tests__/system-prompt.test.ts +2 -0
  85. package/src/__tests__/task-management-tools.test.ts +2 -2
  86. package/src/__tests__/task-runner.test.ts +14 -4
  87. package/src/__tests__/terminal-tools.test.ts +25 -19
  88. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  90. package/src/__tests__/tool-executor.test.ts +23 -24
  91. package/src/__tests__/trust-store.test.ts +3 -3
  92. package/src/__tests__/twilio-rest.test.ts +29 -0
  93. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  94. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  95. package/src/__tests__/twilio-routes.test.ts +167 -11
  96. package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
  97. package/src/__tests__/user-reference.test.ts +2 -0
  98. package/src/__tests__/voice-quality.test.ts +222 -0
  99. package/src/__tests__/web-search.test.ts +46 -30
  100. package/src/__tests__/work-item-output.test.ts +110 -0
  101. package/src/agent/loop.ts +1 -1
  102. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  103. package/src/amazon/client.ts +1418 -0
  104. package/src/amazon/request-extractor.ts +135 -0
  105. package/src/amazon/session.ts +109 -0
  106. package/src/autonomy/autonomy-store.ts +5 -5
  107. package/src/browser-extension-relay/client.ts +124 -0
  108. package/src/browser-extension-relay/protocol.ts +63 -0
  109. package/src/browser-extension-relay/server.ts +177 -0
  110. package/src/bundler/app-bundler.ts +3 -3
  111. package/src/bundler/bundle-signer.ts +1 -1
  112. package/src/bundler/signature-verifier.ts +1 -1
  113. package/src/calls/call-conversation-messages.ts +33 -0
  114. package/src/calls/call-domain.ts +114 -10
  115. package/src/calls/call-orchestrator.ts +268 -59
  116. package/src/calls/call-pointer-messages.ts +53 -0
  117. package/src/calls/call-recovery.ts +3 -8
  118. package/src/calls/call-store.ts +69 -87
  119. package/src/calls/elevenlabs-config.ts +3 -2
  120. package/src/calls/guardian-action-sweep.ts +105 -0
  121. package/src/calls/guardian-dispatch.ts +203 -0
  122. package/src/calls/guardian-question-copy.ts +133 -0
  123. package/src/calls/relay-server.ts +466 -8
  124. package/src/calls/speaker-identification.ts +1 -1
  125. package/src/calls/twilio-config.ts +22 -14
  126. package/src/calls/twilio-provider.ts +6 -4
  127. package/src/calls/twilio-rest.ts +308 -7
  128. package/src/calls/twilio-routes.ts +65 -12
  129. package/src/calls/types.ts +3 -1
  130. package/src/channels/types.ts +25 -0
  131. package/src/cli/amazon.ts +815 -0
  132. package/src/cli/config-commands.ts +2 -2
  133. package/src/cli/core-commands.ts +4 -3
  134. package/src/cli/influencer.ts +244 -0
  135. package/src/cli/map.ts +89 -6
  136. package/src/cli.ts +1 -1
  137. package/src/config/agent-schema.ts +171 -0
  138. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  139. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  140. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  141. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  142. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  143. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  144. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  145. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  146. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  147. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  148. package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
  149. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
  150. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
  151. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  152. package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
  153. package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
  154. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  155. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  156. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  157. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  158. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  159. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  160. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  161. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
  162. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  163. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
  164. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
  165. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
  166. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
  167. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
  168. package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
  169. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
  170. package/src/config/bundled-skills/messaging/SKILL.md +33 -8
  171. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  172. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  174. package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
  175. package/src/config/bundled-skills/twitter/SKILL.md +19 -3
  176. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  177. package/src/config/bundled-tool-registry.ts +310 -0
  178. package/src/config/calls-schema.ts +181 -0
  179. package/src/config/core-schema.ts +309 -0
  180. package/src/config/defaults.ts +28 -3
  181. package/src/config/env-registry.ts +162 -0
  182. package/src/config/env.ts +175 -0
  183. package/src/config/loader.ts +6 -6
  184. package/src/config/memory-schema.ts +528 -0
  185. package/src/config/sandbox-schema.ts +55 -0
  186. package/src/config/schema.ts +158 -1133
  187. package/src/config/skill-state.ts +1 -1
  188. package/src/config/skills-schema.ts +32 -0
  189. package/src/config/skills.ts +35 -24
  190. package/src/config/system-prompt.ts +131 -56
  191. package/src/config/templates/IDENTITY.md +2 -2
  192. package/src/config/templates/SOUL.md +1 -1
  193. package/src/config/types.ts +1 -0
  194. package/src/config/user-reference.ts +4 -9
  195. package/src/config/vellum-skills/catalog.json +6 -7
  196. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  197. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
  198. package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
  199. package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
  200. package/src/context/window-manager.ts +27 -7
  201. package/src/daemon/approval-generators.ts +186 -0
  202. package/src/daemon/approved-devices-store.ts +140 -0
  203. package/src/daemon/assistant-attachments.ts +1 -1
  204. package/src/daemon/classifier.ts +35 -32
  205. package/src/daemon/config-watcher.ts +1 -1
  206. package/src/daemon/daemon-control.ts +217 -0
  207. package/src/daemon/handlers/apps.ts +2 -3
  208. package/src/daemon/handlers/config-channels.ts +158 -0
  209. package/src/daemon/handlers/config-inbox.ts +540 -0
  210. package/src/daemon/handlers/config-ingress.ts +231 -0
  211. package/src/daemon/handlers/config-integrations.ts +258 -0
  212. package/src/daemon/handlers/config-model.ts +143 -0
  213. package/src/daemon/handlers/config-parental.ts +163 -0
  214. package/src/daemon/handlers/config-scheduling.ts +172 -0
  215. package/src/daemon/handlers/config-slack.ts +92 -0
  216. package/src/daemon/handlers/config-telegram.ts +301 -0
  217. package/src/daemon/handlers/config-tools.ts +177 -0
  218. package/src/daemon/handlers/config-trust.ts +104 -0
  219. package/src/daemon/handlers/config-twilio.ts +1080 -0
  220. package/src/daemon/handlers/config.ts +53 -1689
  221. package/src/daemon/handlers/diagnostics.ts +1 -1
  222. package/src/daemon/handlers/dictation.ts +180 -0
  223. package/src/daemon/handlers/documents.ts +18 -32
  224. package/src/daemon/handlers/identity.ts +14 -23
  225. package/src/daemon/handlers/index.ts +11 -0
  226. package/src/daemon/handlers/misc.ts +3 -5
  227. package/src/daemon/handlers/pairing.ts +98 -0
  228. package/src/daemon/handlers/sessions.ts +56 -5
  229. package/src/daemon/handlers/shared.ts +6 -1
  230. package/src/daemon/handlers/skills.ts +1 -1
  231. package/src/daemon/handlers/twitter-auth.ts +2 -0
  232. package/src/daemon/handlers/work-items.ts +17 -9
  233. package/src/daemon/handlers/workspace-files.ts +4 -3
  234. package/src/daemon/install-cli-launchers.ts +113 -0
  235. package/src/daemon/ipc-contract/apps.ts +356 -0
  236. package/src/daemon/ipc-contract/browser.ts +74 -0
  237. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  238. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  239. package/src/daemon/ipc-contract/documents.ts +74 -0
  240. package/src/daemon/ipc-contract/inbox.ts +209 -0
  241. package/src/daemon/ipc-contract/integrations.ts +284 -0
  242. package/src/daemon/ipc-contract/memory.ts +48 -0
  243. package/src/daemon/ipc-contract/messages.ts +211 -0
  244. package/src/daemon/ipc-contract/pairing.ts +45 -0
  245. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  246. package/src/daemon/ipc-contract/schedules.ts +97 -0
  247. package/src/daemon/ipc-contract/sessions.ts +315 -0
  248. package/src/daemon/ipc-contract/shared.ts +42 -0
  249. package/src/daemon/ipc-contract/skills.ts +120 -0
  250. package/src/daemon/ipc-contract/subagents.ts +58 -0
  251. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  252. package/src/daemon/ipc-contract/trust.ts +60 -0
  253. package/src/daemon/ipc-contract/work-items.ts +225 -0
  254. package/src/daemon/ipc-contract/workspace.ts +113 -0
  255. package/src/daemon/ipc-contract-inventory.json +70 -0
  256. package/src/daemon/ipc-contract-inventory.ts +55 -29
  257. package/src/daemon/ipc-contract.ts +229 -2426
  258. package/src/daemon/ipc-protocol.ts +1 -1
  259. package/src/daemon/ipc-validate.ts +7 -0
  260. package/src/daemon/lifecycle.ts +97 -377
  261. package/src/daemon/pairing-store.ts +177 -0
  262. package/src/daemon/providers-setup.ts +43 -0
  263. package/src/daemon/ride-shotgun-handler.ts +68 -3
  264. package/src/daemon/server.ts +66 -46
  265. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  266. package/src/daemon/session-agent-loop.ts +117 -275
  267. package/src/daemon/session-dynamic-profile.ts +1 -1
  268. package/src/daemon/session-history.ts +1 -1
  269. package/src/daemon/session-media-retry.ts +1 -1
  270. package/src/daemon/session-messaging.ts +37 -2
  271. package/src/daemon/session-notifiers.ts +5 -25
  272. package/src/daemon/session-process.ts +99 -59
  273. package/src/daemon/session-queue-manager.ts +96 -4
  274. package/src/daemon/session-runtime-assembly.ts +199 -10
  275. package/src/daemon/session-surfaces.ts +19 -4
  276. package/src/daemon/session-tool-setup.ts +30 -30
  277. package/src/daemon/session-workspace.ts +1 -1
  278. package/src/daemon/session.ts +35 -2
  279. package/src/daemon/shutdown-handlers.ts +122 -0
  280. package/src/daemon/trace-emitter.ts +1 -1
  281. package/src/daemon/watch-handler.ts +36 -33
  282. package/src/doordash/cart-queries.ts +787 -0
  283. package/src/doordash/client.ts +144 -127
  284. package/src/doordash/order-queries.ts +85 -0
  285. package/src/doordash/queries.ts +10 -1308
  286. package/src/doordash/search-queries.ts +203 -0
  287. package/src/doordash/session.ts +3 -2
  288. package/src/doordash/store-queries.ts +246 -0
  289. package/src/doordash/types.ts +367 -0
  290. package/src/email/providers/agentmail.ts +2 -1
  291. package/src/email/providers/index.ts +3 -2
  292. package/src/email/service.ts +3 -2
  293. package/src/errors.ts +43 -0
  294. package/src/home-base/prebuilt/seed.ts +1 -1
  295. package/src/hooks/cli.ts +6 -5
  296. package/src/hooks/config.ts +6 -8
  297. package/src/hooks/discovery.ts +6 -5
  298. package/src/hooks/manager.ts +4 -3
  299. package/src/hooks/runner.ts +2 -2
  300. package/src/hooks/templates.ts +5 -5
  301. package/src/inbound/public-ingress-urls.ts +6 -4
  302. package/src/index.ts +4 -2
  303. package/src/influencer/client.ts +1104 -0
  304. package/src/instrument.ts +4 -3
  305. package/src/logfire.ts +4 -3
  306. package/src/memory/admin.ts +25 -35
  307. package/src/memory/attachments-store.ts +4 -7
  308. package/src/memory/channel-delivery-store.ts +30 -1
  309. package/src/memory/channel-guardian-store.ts +202 -2
  310. package/src/memory/clarification-resolver.ts +37 -33
  311. package/src/memory/conflict-store.ts +67 -61
  312. package/src/memory/contradiction-checker.ts +141 -117
  313. package/src/memory/conversation-store.ts +335 -51
  314. package/src/memory/db-connection.ts +27 -4
  315. package/src/memory/db-init.ts +265 -4
  316. package/src/memory/db.ts +14 -1
  317. package/src/memory/embedding-backend.ts +27 -5
  318. package/src/memory/embedding-ollama.ts +2 -1
  319. package/src/memory/entity-extractor.ts +38 -35
  320. package/src/memory/guardian-action-store.ts +430 -0
  321. package/src/memory/inbox-escalation-projection.ts +59 -0
  322. package/src/memory/inbox-thread-store.ts +218 -0
  323. package/src/memory/ingress-invite-store.ts +338 -0
  324. package/src/memory/ingress-member-store.ts +350 -0
  325. package/src/memory/items-extractor.ts +91 -97
  326. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  327. package/src/memory/job-handlers/media-processing.ts +69 -0
  328. package/src/memory/job-handlers/summarization.ts +32 -26
  329. package/src/memory/job-utils.ts +3 -10
  330. package/src/memory/jobs-store.ts +8 -10
  331. package/src/memory/jobs-worker.ts +55 -36
  332. package/src/memory/media-store.ts +759 -0
  333. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  334. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  335. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  336. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  337. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  338. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  339. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  340. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  341. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  342. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  343. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  344. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  345. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  346. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  347. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  348. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  349. package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
  350. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  351. package/src/memory/migrations/index.ts +24 -0
  352. package/src/memory/migrations/registry.ts +79 -0
  353. package/src/memory/migrations/validate-migration-state.ts +69 -0
  354. package/src/memory/qdrant-manager.ts +49 -8
  355. package/src/memory/query-builder.ts +1 -1
  356. package/src/memory/raw-query.ts +119 -0
  357. package/src/memory/recall-cache.ts +4 -1
  358. package/src/memory/retriever.ts +165 -47
  359. package/src/memory/schema-migration.ts +25 -984
  360. package/src/memory/schema.ts +228 -7
  361. package/src/memory/search/entity.ts +205 -31
  362. package/src/memory/search/lexical.ts +81 -52
  363. package/src/memory/search/ranking.ts +27 -23
  364. package/src/memory/search/semantic.ts +157 -19
  365. package/src/memory/search/types.ts +24 -0
  366. package/src/memory/shared-app-links-store.ts +4 -5
  367. package/src/memory/validation.ts +19 -0
  368. package/src/messaging/draft-store.ts +5 -6
  369. package/src/messaging/provider-types.ts +2 -0
  370. package/src/messaging/providers/sms/adapter.ts +201 -0
  371. package/src/messaging/providers/sms/client.ts +93 -0
  372. package/src/messaging/providers/sms/types.ts +7 -0
  373. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  374. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  375. package/src/messaging/providers/whatsapp/client.ts +67 -0
  376. package/src/messaging/style-analyzer.ts +5 -4
  377. package/src/messaging/thread-summarizer.ts +61 -69
  378. package/src/messaging/triage-engine.ts +62 -71
  379. package/src/migrations/config-merge.ts +53 -0
  380. package/src/migrations/data-layout.ts +68 -0
  381. package/src/migrations/data-merge.ts +33 -0
  382. package/src/migrations/hooks-merge.ts +90 -0
  383. package/src/migrations/index.ts +6 -0
  384. package/src/migrations/log.ts +23 -0
  385. package/src/migrations/skills-merge.ts +33 -0
  386. package/src/migrations/workspace-layout.ts +79 -0
  387. package/src/permissions/checker.ts +133 -11
  388. package/src/permissions/prompter.ts +14 -0
  389. package/src/permissions/shell-identity.ts +31 -1
  390. package/src/permissions/trust-store.ts +21 -1
  391. package/src/providers/anthropic/client.ts +4 -4
  392. package/src/providers/failover.ts +2 -2
  393. package/src/providers/model-intents.ts +70 -0
  394. package/src/providers/ollama/client.ts +2 -1
  395. package/src/providers/provider-send-message.ts +176 -0
  396. package/src/providers/registry.ts +71 -30
  397. package/src/providers/retry.ts +35 -1
  398. package/src/providers/types.ts +12 -1
  399. package/src/runtime/approval-conversation-turn.ts +97 -0
  400. package/src/runtime/approval-message-composer.ts +253 -0
  401. package/src/runtime/channel-approval-parser.ts +36 -2
  402. package/src/runtime/channel-approvals.ts +11 -24
  403. package/src/runtime/channel-guardian-service.ts +88 -21
  404. package/src/runtime/channel-readiness-service.ts +418 -0
  405. package/src/runtime/channel-readiness-types.ts +35 -0
  406. package/src/runtime/channel-retry-sweep.ts +184 -0
  407. package/src/runtime/guardian-context-resolver.ts +108 -0
  408. package/src/runtime/http-server.ts +275 -717
  409. package/src/runtime/http-types.ts +59 -3
  410. package/src/runtime/middleware/auth.ts +116 -0
  411. package/src/runtime/middleware/error-handler.ts +33 -0
  412. package/src/runtime/middleware/twilio-validation.ts +127 -0
  413. package/src/runtime/routes/app-routes.ts +1 -1
  414. package/src/runtime/routes/call-routes.ts +51 -7
  415. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  416. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  417. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  418. package/src/runtime/routes/channel-route-shared.ts +144 -0
  419. package/src/runtime/routes/channel-routes.ts +32 -1588
  420. package/src/runtime/routes/conversation-routes.ts +50 -7
  421. package/src/runtime/routes/events-routes.ts +2 -2
  422. package/src/runtime/routes/identity-routes.ts +126 -0
  423. package/src/runtime/routes/pairing-routes.ts +143 -0
  424. package/src/runtime/routes/run-routes.ts +15 -1
  425. package/src/runtime/run-orchestrator.ts +86 -35
  426. package/src/schedule/schedule-store.ts +36 -32
  427. package/src/schedule/scheduler.ts +3 -3
  428. package/src/security/encrypted-store.ts +5 -7
  429. package/src/security/oauth2.ts +45 -15
  430. package/src/security/parental-control-store.ts +183 -0
  431. package/src/security/secret-allowlist.ts +4 -3
  432. package/src/security/secret-scanner.ts +5 -5
  433. package/src/security/secure-keys.ts +1 -1
  434. package/src/security/token-manager.ts +3 -2
  435. package/src/services/vercel-deploy.ts +6 -2
  436. package/src/skills/tool-manifest.ts +3 -3
  437. package/src/skills/vellum-catalog-remote.ts +75 -16
  438. package/src/slack/slack-webhook.ts +2 -1
  439. package/src/swarm/orchestrator.ts +92 -1
  440. package/src/swarm/router-planner.ts +6 -9
  441. package/src/swarm/worker-prompts.ts +9 -12
  442. package/src/tasks/task-compiler.ts +19 -28
  443. package/src/tasks/task-runner.ts +1 -1
  444. package/src/tools/assets/materialize.ts +2 -2
  445. package/src/tools/assets/search.ts +15 -14
  446. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  447. package/src/tools/browser/auto-navigate.ts +1 -0
  448. package/src/tools/browser/browser-execution.ts +10 -1
  449. package/src/tools/browser/browser-manager.ts +119 -4
  450. package/src/tools/browser/network-recorder.ts +5 -0
  451. package/src/tools/calls/call-start.ts +1 -0
  452. package/src/tools/credentials/broker.ts +11 -2
  453. package/src/tools/credentials/metadata-store.ts +18 -14
  454. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  455. package/src/tools/credentials/vault.ts +49 -23
  456. package/src/tools/execution-target.ts +11 -1
  457. package/src/tools/executor.ts +68 -9
  458. package/src/tools/host-terminal/cli-discover.ts +1 -1
  459. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  460. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  461. package/src/tools/network/script-proxy/server.ts +1 -1
  462. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  463. package/src/tools/network/web-fetch.ts +18 -2
  464. package/src/tools/network/web-search.ts +8 -4
  465. package/src/tools/reminder/reminder-store.ts +14 -15
  466. package/src/tools/schedule/create.ts +1 -0
  467. package/src/tools/schedule/list.ts +2 -1
  468. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  469. package/src/tools/skills/skill-script-runner.ts +24 -9
  470. package/src/tools/skills/skill-tool-factory.ts +1 -0
  471. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  472. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  473. package/src/tools/terminal/parser.ts +50 -0
  474. package/src/tools/types.ts +2 -0
  475. package/src/tools/watcher/delete.ts +6 -0
  476. package/src/tools/weather/service.ts +1 -1
  477. package/src/twitter/client.ts +190 -24
  478. package/src/twitter/router.ts +1 -1
  479. package/src/twitter/session.ts +4 -3
  480. package/src/util/clipboard.ts +1 -1
  481. package/src/util/errors.ts +65 -8
  482. package/src/util/fs.ts +40 -0
  483. package/src/util/json.ts +10 -0
  484. package/src/util/log-redact.ts +189 -0
  485. package/src/util/logger.ts +19 -17
  486. package/src/util/object.ts +3 -0
  487. package/src/util/platform.ts +105 -363
  488. package/src/util/pricing.ts +1 -1
  489. package/src/util/promise-guard.ts +1 -1
  490. package/src/util/retry.ts +19 -0
  491. package/src/util/row-mapper.ts +79 -0
  492. package/src/util/silently.ts +21 -0
  493. package/src/watcher/engine.ts +5 -1
  494. package/src/watcher/provider-types.ts +20 -0
  495. package/src/watcher/providers/github.ts +156 -0
  496. package/src/watcher/providers/gmail.ts +1 -0
  497. package/src/watcher/providers/google-calendar.ts +1 -0
  498. package/src/watcher/providers/linear.ts +460 -0
  499. package/src/watcher/providers/slack.ts +1 -0
  500. package/src/work-items/work-item-runner.ts +1 -1
  501. package/src/workspace/git-service.ts +1 -1
  502. package/src/workspace/provider-commit-message-generator.ts +51 -22
  503. package/src/__tests__/call-bridge.test.ts +0 -517
  504. package/src/__tests__/session-process-bridge.test.ts +0 -244
  505. package/src/calls/call-bridge.ts +0 -168
  506. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -42,7 +42,7 @@ export function createMessageParser(options?: { maxLineSize?: number }) {
42
42
  try {
43
43
  const msg = JSON.parse(trimmed);
44
44
  const entry: ParsedMessage = { msg };
45
- if (typeof msg === 'object' && msg !== null && msg.type === 'cu_observation') {
45
+ if (typeof msg === 'object' && msg != null && msg.type === 'cu_observation') {
46
46
  entry.rawByteLength = Buffer.byteLength(trimmed, 'utf8');
47
47
  }
48
48
  results.push(entry);
@@ -1,3 +1,4 @@
1
+ import { isChannelId } from '../channels/types.js';
1
2
  import type { ClientMessage } from './ipc-contract.js';
2
3
  import inventory from './ipc-contract-inventory.json' with { type: 'json' };
3
4
 
@@ -82,6 +83,9 @@ const HIGH_RISK_VALIDATORS: Record<string, PropertyValidator> = {
82
83
  if (obj.activeSurfaceId !== undefined && typeof obj.activeSurfaceId !== 'string') {
83
84
  return 'user_message "activeSurfaceId" must be a string when present';
84
85
  }
86
+ if (obj.channel !== undefined && !isChannelId(obj.channel)) {
87
+ return 'user_message "channel" must be a valid channel ID when present';
88
+ }
85
89
  return null;
86
90
  },
87
91
 
@@ -100,6 +104,9 @@ const HIGH_RISK_VALIDATORS: Record<string, PropertyValidator> = {
100
104
  if (typeof transport.channelId !== 'string' || transport.channelId.trim().length === 0) {
101
105
  return 'session_create "transport.channelId" must be a non-empty string';
102
106
  }
107
+ if (!isChannelId(transport.channelId)) {
108
+ return 'session_create "transport.channelId" must be a valid channel ID';
109
+ }
103
110
  if (transport.uxBrief !== undefined && typeof transport.uxBrief !== 'string') {
104
111
  return 'session_create "transport.uxBrief" must be a string when present';
105
112
  }
@@ -1,260 +1,64 @@
1
- import { spawn } from 'node:child_process';
2
1
  import { randomBytes } from 'node:crypto';
3
- import { mkdirSync, readFileSync, writeFileSync, unlinkSync, existsSync, openSync, closeSync, chmodSync } from 'node:fs';
2
+ import { mkdirSync, readFileSync, writeFileSync, existsSync, chmodSync } from 'node:fs';
4
3
  import { createRequire } from 'node:module';
5
- import { dirname, join, resolve } from 'node:path';
4
+ import { dirname, join } from 'node:path';
6
5
  import { config as dotenvConfig } from 'dotenv';
7
- import * as Sentry from '@sentry/node';
8
6
  import {
9
7
  getInterfacesDir,
10
8
  getSocketPath,
11
- getPidPath,
12
9
  getHttpTokenPath,
13
10
  getRootDir,
14
11
  ensureDataDir,
15
- migrateToDataLayout,
16
- migrateToWorkspaceLayout,
17
- removeSocketFile,
18
12
  } from '../util/platform.js';
13
+ import { migrateToDataLayout } from '../migrations/data-layout.js';
14
+ import { migrateToWorkspaceLayout } from '../migrations/workspace-layout.js';
19
15
  import { initializeDb } from '../memory/db.js';
20
16
  import { rotateToolInvocations } from '../memory/tool-usage-store.js';
21
- import { initializeProviders } from '../providers/registry.js';
22
- import { initializeTools } from '../tools/registry.js';
23
17
  import { loadConfig } from '../config/loader.js';
18
+ import {
19
+ getQdrantUrlEnv,
20
+ getRuntimeHttpPort,
21
+ getRuntimeProxyBearerToken,
22
+ getRuntimeHttpHost,
23
+ validateEnv,
24
+ } from '../config/env.js';
24
25
  import { ensurePromptFiles } from '../config/system-prompt.js';
25
26
  import { loadPrebuiltHtml } from '../home-base/prebuilt/seed.js';
26
27
  import { DaemonServer } from './server.js';
28
+ import { setRelayBroadcast } from '../calls/relay-server.js';
27
29
  import { listWorkItems, updateWorkItem } from '../work-items/work-item-store.js';
28
30
  import { getLogger, initLogger } from '../util/logger.js';
29
- import { DaemonError } from '../util/errors.js';
30
31
  import { initSentry } from '../instrument.js';
31
32
  import { initLogfire } from '../logfire.js';
32
33
  import { startMemoryJobsWorker } from '../memory/jobs-worker.js';
33
34
  import { QdrantManager } from '../memory/qdrant-manager.js';
34
35
  import { initQdrantClient } from '../memory/qdrant-client.js';
35
36
  import { startScheduler } from '../schedule/scheduler.js';
36
- import { initWatcherEngine } from '../watcher/engine.js';
37
- import { registerWatcherProvider } from '../watcher/provider-registry.js';
38
- import { gmailProvider } from '../watcher/providers/gmail.js';
39
- import { googleCalendarProvider } from '../watcher/providers/google-calendar.js';
40
- import { slackProvider as slackWatcherProvider } from '../watcher/providers/slack.js';
41
- import { registerMessagingProvider } from '../messaging/registry.js';
42
- import { slackProvider as slackMessagingProvider } from '../messaging/providers/slack/adapter.js';
43
- import { gmailMessagingProvider } from '../messaging/providers/gmail/adapter.js';
44
- import { telegramBotMessagingProvider } from '../messaging/providers/telegram-bot/adapter.js';
45
- import { browserManager } from '../tools/browser/browser-manager.js';
46
37
  import { RuntimeHttpServer } from '../runtime/http-server.js';
47
38
  import { getHookManager } from '../hooks/manager.js';
48
39
  import { installTemplates } from '../hooks/templates.js';
40
+ import { installCliLaunchers } from './install-cli-launchers.js';
49
41
  import { HeartbeatService } from '../workspace/heartbeat-service.js';
50
42
  import { AgentHeartbeatService } from '../agent-heartbeat/agent-heartbeat-service.js';
51
- import { getEnrichmentService } from '../workspace/commit-message-enrichment-service.js';
52
43
  import { reconcileCallsOnStartup } from '../calls/call-recovery.js';
53
44
  import { TwilioConversationRelayProvider } from '../calls/twilio-provider.js';
45
+ import { createApprovalCopyGenerator, createApprovalConversationGenerator } from './approval-generators.js';
46
+ import { initializeProvidersAndTools, registerWatcherProviders, registerMessagingProviders } from './providers-setup.js';
47
+ import { installShutdownHandlers } from './shutdown-handlers.js';
48
+ import { writePid, cleanupPidFile } from './daemon-control.js';
49
+
50
+ // Re-export public API so existing consumers don't need to change imports
51
+ export {
52
+ isDaemonRunning,
53
+ getDaemonStatus,
54
+ startDaemon,
55
+ stopDaemon,
56
+ ensureDaemonRunning,
57
+ } from './daemon-control.js';
58
+ export type { StopResult } from './daemon-control.js';
54
59
 
55
60
  const log = getLogger('lifecycle');
56
61
 
57
- function isProcessRunning(pid: number): boolean {
58
- try {
59
- process.kill(pid, 0);
60
- return true;
61
- } catch {
62
- return false;
63
- }
64
- }
65
-
66
- function readPid(): number | null {
67
- const pidPath = getPidPath();
68
- if (!existsSync(pidPath)) return null;
69
- try {
70
- const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
71
- return isNaN(pid) ? null : pid;
72
- } catch {
73
- return null;
74
- }
75
- }
76
-
77
- function writePid(pid: number): void {
78
- writeFileSync(getPidPath(), String(pid));
79
- }
80
-
81
- function cleanupPidFile(): void {
82
- const pidPath = getPidPath();
83
- if (existsSync(pidPath)) {
84
- unlinkSync(pidPath);
85
- }
86
- }
87
-
88
- export function isDaemonRunning(): boolean {
89
- const pid = readPid();
90
- if (pid === null) return false;
91
- if (!isProcessRunning(pid)) {
92
- // Stale PID file
93
- cleanupPidFile();
94
- return false;
95
- }
96
- return true;
97
- }
98
-
99
- export function getDaemonStatus(): { running: boolean; pid?: number } {
100
- const pid = readPid();
101
- if (pid === null) return { running: false };
102
- if (!isProcessRunning(pid)) {
103
- cleanupPidFile();
104
- return { running: false };
105
- }
106
- return { running: true, pid };
107
- }
108
-
109
- export async function startDaemon(): Promise<{
110
- pid: number;
111
- alreadyRunning: boolean;
112
- }> {
113
- const status = getDaemonStatus();
114
- if (status.running && status.pid) {
115
- return { pid: status.pid, alreadyRunning: true };
116
- }
117
-
118
- // Only create the root dir for socket/PID — the daemon process itself
119
- // handles migration + full ensureDataDir() in runDaemon(). Calling
120
- // ensureDataDir() here would pre-create workspace destination dirs
121
- // and cause migration moves to no-op.
122
- const rootDir = getRootDir();
123
- if (!existsSync(rootDir)) {
124
- mkdirSync(rootDir, { recursive: true });
125
- }
126
-
127
- // Clean up stale socket (only if it's actually a Unix socket)
128
- const socketPath = getSocketPath();
129
- removeSocketFile(socketPath);
130
-
131
- // Spawn the daemon as a detached child process
132
- const mainPath = resolve(
133
- import.meta.dirname ?? __dirname,
134
- 'main.ts',
135
- );
136
-
137
- // Redirect the child's stderr to a file instead of piping it back to the
138
- // parent. A pipe's read end is destroyed when the parent exits, leaving
139
- // fd 2 broken in the child. Bun (unlike Node.js) does not ignore SIGPIPE,
140
- // so any later stderr write would silently kill the daemon.
141
- const stderrPath = join(rootDir, 'daemon-stderr.log');
142
- const stderrFd = openSync(stderrPath, 'w');
143
-
144
- const child = spawn('bun', ['run', mainPath], {
145
- detached: true,
146
- stdio: ['ignore', 'ignore', stderrFd],
147
- env: { ...process.env },
148
- });
149
-
150
- // The child inherited the fd; close the parent's copy.
151
- closeSync(stderrFd);
152
-
153
- let childExited = false;
154
- let childExitCode: number | null = null;
155
- child.on('exit', (code) => {
156
- childExited = true;
157
- childExitCode = code;
158
- });
159
-
160
- child.unref();
161
-
162
- const pid = child.pid;
163
- if (!pid) {
164
- throw new DaemonError('Failed to start daemon: no PID returned');
165
- }
166
-
167
- writePid(pid);
168
-
169
- // Wait for socket to appear
170
- const maxWait = 5000;
171
- const interval = 100;
172
- let waited = 0;
173
- while (waited < maxWait) {
174
- if (existsSync(socketPath)) {
175
- return { pid, alreadyRunning: false };
176
- }
177
- if (childExited) {
178
- cleanupPidFile();
179
- const stderr = readFileSync(stderrPath, 'utf-8').trim();
180
- const detail = stderr
181
- ? `\n${stderr}`
182
- : `\nCheck logs at ~/.vellum/workspace/data/logs/ for details.`;
183
- throw new DaemonError(
184
- `Daemon exited immediately (code ${childExitCode ?? 'unknown'}).${detail}`,
185
- );
186
- }
187
- await new Promise((r) => setTimeout(r, interval));
188
- waited += interval;
189
- }
190
-
191
- throw new DaemonError(
192
- 'Daemon started but socket not available after 5 seconds',
193
- );
194
- }
195
-
196
- export type StopResult =
197
- | { stopped: true }
198
- | { stopped: false; reason: 'not_running' | 'stop_failed' };
199
-
200
- export async function stopDaemon(): Promise<StopResult> {
201
- const pid = readPid();
202
- if (pid === null || !isProcessRunning(pid)) {
203
- cleanupPidFile();
204
- return { stopped: false, reason: 'not_running' };
205
- }
206
-
207
- process.kill(pid, 'SIGTERM');
208
-
209
- // Wait for process to exit
210
- const maxWait = 5000;
211
- const interval = 100;
212
- let waited = 0;
213
- while (waited < maxWait) {
214
- if (!isProcessRunning(pid)) {
215
- cleanupPidFile();
216
- return { stopped: true };
217
- }
218
- await new Promise((r) => setTimeout(r, interval));
219
- waited += interval;
220
- }
221
-
222
- // Force kill
223
- try {
224
- process.kill(pid, 'SIGKILL');
225
- } catch (err) {
226
- log.debug({ err, pid }, 'SIGKILL failed, process already exited');
227
- }
228
-
229
- // Wait for the process to actually die after SIGKILL. Without this,
230
- // startDaemon() can race with the dying process's shutdown handler,
231
- // which removes the socket file and bricks the new daemon.
232
- const killMaxWait = 2000;
233
- let killWaited = 0;
234
- while (killWaited < killMaxWait && isProcessRunning(pid)) {
235
- await new Promise((r) => setTimeout(r, 100));
236
- killWaited += 100;
237
- }
238
-
239
- // Only clean up if the process has actually exited.
240
- // If it's still alive after SIGKILL + timeout, preserve both socket
241
- // and PID file so isDaemonRunning() still reports true and prevents
242
- // a duplicate daemon from being spawned.
243
- if (!isProcessRunning(pid)) {
244
- removeSocketFile(getSocketPath());
245
- cleanupPidFile();
246
- return { stopped: true };
247
- }
248
-
249
- log.warn({ pid }, 'Daemon process still running after SIGKILL + timeout, leaving socket and PID file intact');
250
- return { stopped: false, reason: 'stop_failed' };
251
- }
252
-
253
- export async function ensureDaemonRunning(): Promise<void> {
254
- if (isDaemonRunning()) return;
255
- await startDaemon();
256
- }
257
-
258
62
  function loadDotEnv(): void {
259
63
  dotenvConfig({ path: join(getRootDir(), '.env'), quiet: true });
260
64
  }
@@ -262,6 +66,7 @@ function loadDotEnv(): void {
262
66
  // Entry point for the daemon process itself
263
67
  export async function runDaemon(): Promise<void> {
264
68
  loadDotEnv();
69
+ validateEnv();
265
70
  initSentry();
266
71
  await initLogfire();
267
72
 
@@ -311,6 +116,12 @@ export async function runDaemon(): Promise<void> {
311
116
  log.info('Daemon startup: installing templates and initializing DB');
312
117
  installTemplates();
313
118
  ensurePromptFiles();
119
+
120
+ try {
121
+ installCliLaunchers();
122
+ } catch (err) {
123
+ log.warn({ err }, 'CLI launcher installation failed — continuing startup');
124
+ }
314
125
  initializeDb();
315
126
  log.info('Daemon startup: DB initialized');
316
127
 
@@ -325,8 +136,6 @@ export async function runDaemon(): Promise<void> {
325
136
  log.info({ count: orphanedRunning.length }, 'Recovered orphaned running work items');
326
137
  }
327
138
 
328
- // Reconcile in-flight calls that were left in non-terminal states
329
- // after a daemon crash or restart.
330
139
  try {
331
140
  const twilioProvider = new TwilioConversationRelayProvider();
332
141
  await reconcileCallsOnStartup(twilioProvider, log);
@@ -341,10 +150,7 @@ export async function runDaemon(): Promise<void> {
341
150
  initLogger({ dir: config.logFile.dir, retentionDays: config.logFile.retentionDays });
342
151
  }
343
152
 
344
- log.info('Daemon startup: initializing providers and tools');
345
- initializeProviders(config);
346
- await initializeTools();
347
- log.info('Daemon startup: providers and tools initialized');
153
+ await initializeProvidersAndTools(config);
348
154
 
349
155
  // Start the IPC socket BEFORE Qdrant so that clients can connect
350
156
  // immediately. Qdrant startup can take 30+ seconds (binary download,
@@ -355,11 +161,9 @@ export async function runDaemon(): Promise<void> {
355
161
  log.info('Daemon startup: DaemonServer started');
356
162
 
357
163
  // Initialize Qdrant vector store — non-fatal so the daemon stays up without it
358
- const qdrantUrl = process.env.QDRANT_URL?.trim() || config.memory.qdrant.url;
164
+ const qdrantUrl = getQdrantUrlEnv() || config.memory.qdrant.url;
359
165
  log.info({ qdrantUrl }, 'Daemon startup: initializing Qdrant');
360
- const qdrantManager = new QdrantManager({
361
- url: qdrantUrl,
362
- });
166
+ const qdrantManager = new QdrantManager({ url: qdrantUrl });
363
167
  try {
364
168
  await qdrantManager.start();
365
169
  initQdrantClient({
@@ -376,16 +180,9 @@ export async function runDaemon(): Promise<void> {
376
180
 
377
181
  log.info('Daemon startup: starting memory worker');
378
182
  const memoryWorker = startMemoryJobsWorker();
379
- // Initialize watcher engine and register providers
380
- registerWatcherProvider(gmailProvider);
381
- registerWatcherProvider(googleCalendarProvider);
382
- registerWatcherProvider(slackWatcherProvider);
383
- initWatcherEngine();
384
183
 
385
- // Register messaging providers
386
- registerMessagingProvider(slackMessagingProvider);
387
- registerMessagingProvider(gmailMessagingProvider);
388
- registerMessagingProvider(telegramBotMessagingProvider);
184
+ registerWatcherProviders();
185
+ registerMessagingProviders();
389
186
 
390
187
  const scheduler = startScheduler(
391
188
  async (conversationId, message) => {
@@ -422,57 +219,57 @@ export async function runDaemon(): Promise<void> {
422
219
  },
423
220
  );
424
221
 
425
- // Start optional runtime HTTP server when RUNTIME_HTTP_PORT is set
222
+ // Start the runtime HTTP server. Required for iOS pairing (gateway proxies
223
+ // to it) and optional REST API access. Defaults to port 7821.
426
224
  let runtimeHttp: RuntimeHttpServer | null = null;
427
- const httpPortEnv = process.env.RUNTIME_HTTP_PORT;
428
- log.info({ httpPortEnv }, 'Daemon startup: checking RUNTIME_HTTP_PORT');
429
- if (httpPortEnv) {
430
- const port = parseInt(httpPortEnv, 10);
431
- if (!isNaN(port) && port > 0) {
432
- // Resolve the bearer token in priority order:
433
- // 1. Explicit env var (e.g. cloud deploys)
434
- // 2. Existing token file on disk (preserves QR-paired iOS devices across restarts)
435
- // 3. Fresh random token (first-time startup)
436
- const httpTokenPath = getHttpTokenPath();
437
- let bearerToken = process.env.RUNTIME_PROXY_BEARER_TOKEN;
438
- if (!bearerToken) {
439
- try {
440
- const existing = readFileSync(httpTokenPath, 'utf-8').trim();
441
- if (existing) bearerToken = existing;
442
- } catch {
443
- // File doesn't exist or can't be read — will generate below
444
- }
445
- }
446
- if (!bearerToken) {
447
- bearerToken = randomBytes(32).toString('hex');
448
- }
449
- writeFileSync(httpTokenPath, bearerToken, { mode: 0o600 });
450
- chmodSync(httpTokenPath, 0o600);
451
-
452
- const hostname = process.env.RUNTIME_HTTP_HOST?.trim() || '127.0.0.1';
453
-
454
- runtimeHttp = new RuntimeHttpServer({
455
- port,
456
- hostname,
457
- bearerToken,
458
- processMessage: (conversationId, content, attachmentIds, options, sourceChannel) =>
459
- server.processMessage(conversationId, content, attachmentIds, options, sourceChannel),
460
- persistAndProcessMessage: (conversationId, content, attachmentIds, options, sourceChannel) =>
461
- server.persistAndProcessMessage(conversationId, content, attachmentIds, options, sourceChannel),
462
- runOrchestrator: server.createRunOrchestrator(),
463
- interfacesDir: getInterfacesDir(),
464
- });
465
- try {
466
- log.info({ port, hostname }, 'Daemon startup: starting runtime HTTP server');
467
- await runtimeHttp.start();
468
- server.setHttpPort(port);
469
- log.info({ port, hostname }, 'Daemon startup: runtime HTTP server listening');
470
- } catch (err) {
471
- log.warn({ err, port }, 'Failed to start runtime HTTP server, continuing without it');
472
- runtimeHttp = null;
473
- }
225
+ const httpPort = getRuntimeHttpPort();
226
+ log.info({ httpPort }, 'Daemon startup: starting runtime HTTP server');
227
+
228
+ // Resolve the bearer token in priority order:
229
+ // 1. Explicit env var (e.g. cloud deploys)
230
+ // 2. Existing token file on disk (preserves QR-paired iOS devices across restarts)
231
+ // 3. Fresh random token (first-time startup)
232
+ const httpTokenPath = getHttpTokenPath();
233
+ let bearerToken = getRuntimeProxyBearerToken();
234
+ if (!bearerToken) {
235
+ try {
236
+ const existing = readFileSync(httpTokenPath, 'utf-8').trim();
237
+ if (existing) bearerToken = existing;
238
+ } catch {
239
+ // File doesn't exist or can't be read — will generate below
474
240
  }
475
241
  }
242
+ if (!bearerToken) {
243
+ bearerToken = randomBytes(32).toString('hex');
244
+ }
245
+ writeFileSync(httpTokenPath, bearerToken, { mode: 0o600 });
246
+ chmodSync(httpTokenPath, 0o600);
247
+
248
+ const hostname = getRuntimeHttpHost();
249
+
250
+ runtimeHttp = new RuntimeHttpServer({
251
+ port: httpPort,
252
+ hostname,
253
+ bearerToken,
254
+ processMessage: (conversationId, content, attachmentIds, options, sourceChannel) =>
255
+ server.processMessage(conversationId, content, attachmentIds, options, sourceChannel),
256
+ persistAndProcessMessage: (conversationId, content, attachmentIds, options, sourceChannel) =>
257
+ server.persistAndProcessMessage(conversationId, content, attachmentIds, options, sourceChannel),
258
+ runOrchestrator: server.createRunOrchestrator(),
259
+ interfacesDir: getInterfacesDir(),
260
+ approvalCopyGenerator: createApprovalCopyGenerator(),
261
+ approvalConversationGenerator: createApprovalConversationGenerator(),
262
+ });
263
+ try {
264
+ await runtimeHttp.start();
265
+ setRelayBroadcast((msg) => server.broadcast(msg));
266
+ runtimeHttp.setPairingBroadcast((msg) => server.broadcast(msg));
267
+ server.setHttpPort(httpPort);
268
+ log.info({ port: httpPort, hostname }, 'Daemon startup: runtime HTTP server listening');
269
+ } catch (err) {
270
+ log.warn({ err, port: httpPort }, 'Failed to start runtime HTTP server, continuing without it');
271
+ runtimeHttp = null;
272
+ }
476
273
 
477
274
  writePid(process.pid);
478
275
  log.info({ pid: process.pid }, 'Daemon started');
@@ -485,9 +282,6 @@ export async function runDaemon(): Promise<void> {
485
282
  socketPath: getSocketPath(),
486
283
  });
487
284
 
488
- // Rotate old audit log entries after startup handshake is complete.
489
- // This runs after the socket is listening so it won't block the 5s
490
- // readiness window in startDaemon().
491
285
  if (config.auditLog.retentionDays > 0) {
492
286
  try {
493
287
  rotateToolInvocations(config.auditLog.retentionDays);
@@ -496,15 +290,9 @@ export async function runDaemon(): Promise<void> {
496
290
  }
497
291
  }
498
292
 
499
- // Start workspace heartbeat service. This periodically checks all
500
- // tracked workspaces for uncommitted changes and auto-commits when
501
- // thresholds are exceeded (age > 5 min OR > 20 files changed).
502
- // Acts as a safety net for long-running operations or background
503
- // processes that modify workspace files between turn-boundary commits.
504
293
  const heartbeat = new HeartbeatService();
505
294
  heartbeat.start();
506
295
 
507
- // Start model-driven heartbeat service (opt-in via config).
508
296
  const agentHeartbeat = new AgentHeartbeatService({
509
297
  processMessage: (conversationId, content) =>
510
298
  server.processMessage(conversationId, content),
@@ -512,83 +300,15 @@ export async function runDaemon(): Promise<void> {
512
300
  });
513
301
  agentHeartbeat.start();
514
302
 
515
- // Graceful shutdown
516
- let shuttingDown = false;
517
- const shutdown = async () => {
518
- if (shuttingDown) return; // Prevent re-entrant shutdown
519
- shuttingDown = true;
520
- log.info('Shutting down daemon...');
521
-
522
- hookManager.stopWatching();
523
-
524
- // Force exit if graceful shutdown takes too long.
525
- // Set this BEFORE awaiting heartbeat stop and triggering daemon-stop hooks
526
- // so it covers all potentially-blocking async shutdown work.
527
- const forceTimer = setTimeout(() => {
528
- log.warn('Graceful shutdown timed out, forcing exit');
529
- cleanupPidFile();
530
- process.exit(1);
531
- }, 10_000);
532
- forceTimer.unref();
533
-
534
- await heartbeat.stop();
535
- await agentHeartbeat.stop();
536
-
537
- try {
538
- await hookManager.trigger('daemon-stop', { pid: process.pid });
539
- } catch {
540
- // Don't let hook failures block shutdown
541
- }
542
-
543
- // Commit any uncommitted workspace changes before stopping the server.
544
- // This ensures no workspace state is lost during graceful shutdown.
545
- try {
546
- log.info({ phase: 'pre_stop' }, 'Committing pending workspace changes');
547
- await heartbeat.commitAllPending();
548
- } catch (err) {
549
- log.warn({ err, phase: 'pre_stop' }, 'Shutdown workspace commit failed');
550
- }
551
-
552
- await server.stop();
553
-
554
- // Final commit sweep: catch any writes that occurred during server.stop()
555
- // (e.g. in-flight tool executions completing during drain).
556
- try {
557
- log.info({ phase: 'post_stop' }, 'Final workspace commit sweep');
558
- await heartbeat.commitAllPending();
559
- } catch (err) {
560
- log.warn({ err, phase: 'post_stop' }, 'Post-stop workspace commit failed');
561
- }
562
-
563
- // Flush in-flight enrichment jobs so shutdown commit notes are not dropped.
564
- // The enrichment service's shutdown() drains active jobs and discards pending ones.
565
- try {
566
- await getEnrichmentService().shutdown();
567
- } catch (err) {
568
- log.warn({ err }, 'Enrichment service shutdown failed (non-fatal)');
569
- }
570
-
571
- if (runtimeHttp) await runtimeHttp.stop();
572
- await browserManager.closeAllPages();
573
- scheduler.stop();
574
- memoryWorker.stop();
575
- await qdrantManager.stop();
576
- await Sentry.flush(2000);
577
- clearTimeout(forceTimer);
578
- cleanupPidFile();
579
- process.exit(0);
580
- };
581
-
582
- process.on('SIGTERM', shutdown);
583
- process.on('SIGINT', shutdown);
584
-
585
- process.on('unhandledRejection', (reason) => {
586
- log.error({ err: reason }, 'Unhandled promise rejection');
587
- Sentry.captureException(reason);
588
- });
589
-
590
- process.on('uncaughtException', (err) => {
591
- log.error({ err }, 'Uncaught exception');
592
- Sentry.captureException(err);
303
+ installShutdownHandlers({
304
+ server,
305
+ heartbeat,
306
+ agentHeartbeat,
307
+ hookManager,
308
+ runtimeHttp,
309
+ scheduler,
310
+ memoryWorker,
311
+ qdrantManager,
312
+ cleanupPidFile,
593
313
  });
594
314
  }