@vellumai/assistant 0.3.5 → 0.3.7

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