@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
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Persistent store for always-allowed paired devices.
3
+ *
4
+ * Persisted to ~/.vellum/protected/approved-devices.json using the
5
+ * atomic-write pattern from trust-store.ts (write .tmp → rename → chmod).
6
+ */
7
+
8
+ import { existsSync, readFileSync, writeFileSync, renameSync, chmodSync, mkdirSync } from 'node:fs';
9
+ import { join, dirname } from 'node:path';
10
+ import { createHash } from 'node:crypto';
11
+ import { getRootDir } from '../util/platform.js';
12
+ import { getLogger } from '../util/logger.js';
13
+
14
+ const log = getLogger('approved-devices-store');
15
+
16
+ export interface ApprovedDevice {
17
+ hashedDeviceId: string;
18
+ deviceName: string;
19
+ lastPairedAt: number;
20
+ }
21
+
22
+ interface ApprovedDevicesFile {
23
+ version: 1;
24
+ devices: ApprovedDevice[];
25
+ }
26
+
27
+ function getStorePath(): string {
28
+ return join(getRootDir(), 'protected', 'approved-devices.json');
29
+ }
30
+
31
+ /** Hash a raw deviceId for storage. */
32
+ export function hashDeviceId(deviceId: string): string {
33
+ return createHash('sha256').update(deviceId).digest('hex');
34
+ }
35
+
36
+ let cachedDevices: Map<string, ApprovedDevice> | null = null;
37
+
38
+ function loadFromDisk(): Map<string, ApprovedDevice> {
39
+ const path = getStorePath();
40
+ if (!existsSync(path)) {
41
+ return new Map();
42
+ }
43
+ try {
44
+ const raw = readFileSync(path, 'utf-8');
45
+ const data = JSON.parse(raw) as ApprovedDevicesFile;
46
+ if (data.version !== 1 || !Array.isArray(data.devices)) {
47
+ log.warn('Invalid approved-devices.json format, starting fresh');
48
+ return new Map();
49
+ }
50
+ const map = new Map<string, ApprovedDevice>();
51
+ for (const device of data.devices) {
52
+ map.set(device.hashedDeviceId, device);
53
+ }
54
+ return map;
55
+ } catch (err) {
56
+ log.error({ err }, 'Failed to load approved-devices.json');
57
+ return new Map();
58
+ }
59
+ }
60
+
61
+ function saveToDisk(devices: Map<string, ApprovedDevice>): void {
62
+ const path = getStorePath();
63
+ const dir = dirname(path);
64
+ if (!existsSync(dir)) {
65
+ mkdirSync(dir, { recursive: true });
66
+ }
67
+ const data: ApprovedDevicesFile = {
68
+ version: 1,
69
+ devices: Array.from(devices.values()),
70
+ };
71
+ const tmpPath = path + '.tmp.' + process.pid;
72
+ writeFileSync(tmpPath, JSON.stringify(data, null, 2), { mode: 0o600 });
73
+ renameSync(tmpPath, path);
74
+ chmodSync(path, 0o600);
75
+ }
76
+
77
+ function getDevices(): Map<string, ApprovedDevice> {
78
+ if (cachedDevices == null) {
79
+ cachedDevices = loadFromDisk();
80
+ }
81
+ return cachedDevices;
82
+ }
83
+
84
+ /** Check if a hashed device ID is in the allowlist. */
85
+ export function isDeviceApproved(hashedDeviceId: string): boolean {
86
+ return getDevices().has(hashedDeviceId);
87
+ }
88
+
89
+ /** Add or update a device in the allowlist. */
90
+ export function approveDevice(hashedDeviceId: string, deviceName: string): void {
91
+ const devices = getDevices();
92
+ devices.set(hashedDeviceId, {
93
+ hashedDeviceId,
94
+ deviceName,
95
+ lastPairedAt: Date.now(),
96
+ });
97
+ saveToDisk(devices);
98
+ log.info({ hashedDeviceId }, 'Device approved and saved to allowlist');
99
+ }
100
+
101
+ /** Update lastPairedAt and deviceName for an existing device (auto-approve refresh). */
102
+ export function refreshDevice(hashedDeviceId: string, deviceName: string): void {
103
+ const devices = getDevices();
104
+ const existing = devices.get(hashedDeviceId);
105
+ if (existing) {
106
+ existing.deviceName = deviceName;
107
+ existing.lastPairedAt = Date.now();
108
+ saveToDisk(devices);
109
+ log.info({ hashedDeviceId }, 'Device metadata refreshed');
110
+ }
111
+ }
112
+
113
+ /** Remove a device from the allowlist. Returns true if removed. */
114
+ export function removeDevice(hashedDeviceId: string): boolean {
115
+ const devices = getDevices();
116
+ const removed = devices.delete(hashedDeviceId);
117
+ if (removed) {
118
+ saveToDisk(devices);
119
+ log.info({ hashedDeviceId }, 'Device removed from allowlist');
120
+ }
121
+ return removed;
122
+ }
123
+
124
+ /** Clear all approved devices. */
125
+ export function clearAllDevices(): void {
126
+ const devices = getDevices();
127
+ devices.clear();
128
+ saveToDisk(devices);
129
+ log.info('All approved devices cleared');
130
+ }
131
+
132
+ /** List all approved devices. */
133
+ export function listDevices(): ApprovedDevice[] {
134
+ return Array.from(getDevices().values());
135
+ }
136
+
137
+ /** Reset the in-memory cache (for testing). */
138
+ export function resetCache(): void {
139
+ cachedDevices = null;
140
+ }
@@ -191,7 +191,7 @@ const ATTR_RE = /(\w+)\s*=\s*"([^"]*)"|(\w+)\s*=\s*'([^']*)'/g;
191
191
  function parseAttributes(raw: string): Record<string, string> {
192
192
  const attrs: Record<string, string> = {};
193
193
  let m: RegExpExecArray | null;
194
- while ((m = ATTR_RE.exec(raw)) !== null) {
194
+ while ((m = ATTR_RE.exec(raw)) != null) {
195
195
  const key = m[1] ?? m[3];
196
196
  const value = m[2] ?? m[4];
197
197
  attrs[key] = value;
@@ -1,5 +1,4 @@
1
- import Anthropic from '@anthropic-ai/sdk';
2
- import { getConfig } from '../config/loader.js';
1
+ import { getConfiguredProvider, createTimeout, extractToolUse, userMessage } from '../providers/provider-send-message.js';
3
2
  import { getLogger } from '../util/logger.js';
4
3
 
5
4
  const log = getLogger('classifier');
@@ -9,7 +8,7 @@ const CLASSIFICATION_TIMEOUT_MS = 5000;
9
8
  export type InteractionType = 'computer_use' | 'text_qa';
10
9
 
11
10
  /**
12
- * Classify a user task as computer_use or text_qa using a Haiku tool-use call,
11
+ * Classify a user task as computer_use or text_qa using an LLM tool-use call,
13
12
  * falling back to a heuristic if the API call fails or no API key is available.
14
13
  */
15
14
  export async function classifyInteraction(task: string, source?: 'voice' | 'text'): Promise<InteractionType> {
@@ -18,21 +17,18 @@ export async function classifyInteraction(task: string, source?: 'voice' | 'text
18
17
  return 'text_qa';
19
18
  }
20
19
 
21
- const config = getConfig();
22
- const apiKey = config.apiKeys.anthropic ?? process.env.ANTHROPIC_API_KEY;
23
- if (!apiKey) {
24
- log.warn('No API key available, falling back to heuristic classification');
20
+ const provider = getConfiguredProvider();
21
+ if (!provider) {
22
+ log.warn('No configured provider available, falling back to heuristic classification');
25
23
  return classifyHeuristic(task);
26
24
  }
27
25
 
28
26
  try {
29
- const client = new Anthropic({ apiKey });
30
- const response = await Promise.race([
31
- client.messages.create({
32
- model: 'claude-haiku-4-5-20251001',
33
- max_tokens: 128,
34
- system: 'You are a classifier. Determine whether the user\'s request requires computer use (controlling the GUI — clicking, scrolling, typing into app windows, navigating between apps) or can be handled with local tools (answering questions, running terminal commands, creating/editing/reading files, web searches, writing code). GUI tasks → computer_use. Everything else → text_qa.',
35
- tools: [{
27
+ const { signal, cleanup } = createTimeout(CLASSIFICATION_TIMEOUT_MS);
28
+ try {
29
+ const response = await provider.sendMessage(
30
+ [userMessage(task)],
31
+ [{
36
32
  name: 'classify_interaction',
37
33
  description: 'Classify the user interaction type',
38
34
  input_schema: {
@@ -51,34 +47,41 @@ export async function classifyInteraction(task: string, source?: 'voice' | 'text
51
47
  required: ['interaction_type', 'reasoning'],
52
48
  },
53
49
  }],
54
- tool_choice: { type: 'tool' as const, name: 'classify_interaction' },
55
- messages: [{ role: 'user' as const, content: task }],
56
- }),
57
- new Promise<never>((_, reject) =>
58
- setTimeout(() => reject(new Error('Classification timeout')), CLASSIFICATION_TIMEOUT_MS),
59
- ),
60
- ]);
50
+ 'You are a classifier. Determine whether the user\'s request requires computer use (controlling the GUI — clicking, scrolling, typing into app windows, navigating between apps) or can be handled with local tools (answering questions, running terminal commands, creating/editing/reading files, web searches, writing code). GUI tasks → computer_use. Everything else → text_qa.',
51
+ {
52
+ config: {
53
+ modelIntent: 'latency-optimized',
54
+ max_tokens: 128,
55
+ tool_choice: { type: 'tool' as const, name: 'classify_interaction' },
56
+ },
57
+ signal,
58
+ },
59
+ );
60
+ cleanup();
61
61
 
62
- const toolBlock = response.content.find((b) => b.type === 'tool_use');
63
- if (toolBlock && toolBlock.type === 'tool_use') {
64
- const input = toolBlock.input as { interaction_type?: string; reasoning?: string };
65
- const result = input.interaction_type === 'text_qa' ? 'text_qa' : 'computer_use';
66
- log.info({ result, reasoning: input.reasoning }, 'Haiku classification');
67
- return result;
68
- }
62
+ const toolBlock = extractToolUse(response);
63
+ if (toolBlock) {
64
+ const input = toolBlock.input as { interaction_type?: string; reasoning?: string };
65
+ const result = input.interaction_type === 'text_qa' ? 'text_qa' : 'computer_use';
66
+ log.info({ result, reasoning: input.reasoning }, 'LLM classification');
67
+ return result;
68
+ }
69
69
 
70
- log.warn('No tool_use block in classification response, falling back to heuristic');
71
- return classifyHeuristic(task);
70
+ log.warn('No tool_use block in classification response, falling back to heuristic');
71
+ return classifyHeuristic(task);
72
+ } finally {
73
+ cleanup();
74
+ }
72
75
  } catch (err) {
73
76
  const message = err instanceof Error ? err.message : String(err);
74
- log.warn({ err: message }, 'Haiku classification failed, falling back to heuristic');
77
+ log.warn({ err: message }, 'LLM classification failed, falling back to heuristic');
75
78
  return classifyHeuristic(task);
76
79
  }
77
80
  }
78
81
 
79
82
  /**
80
83
  * Heuristic classifier — direct port of the Swift client's logic.
81
- * Used as fallback when the Haiku API call is unavailable or fails.
84
+ * Used as fallback when the LLM API call is unavailable or fails.
82
85
  */
83
86
  export function classifyHeuristic(task: string): InteractionType {
84
87
  const lower = task.toLowerCase().trim();
@@ -24,7 +24,7 @@ export class ConfigWatcher {
24
24
  protectedKeyPrefix: '__',
25
25
  });
26
26
  private suppressReload = false;
27
- private lastFingerprint = '';
27
+ lastFingerprint = '';
28
28
  private lastRefreshTime = 0;
29
29
 
30
30
  static readonly REFRESH_INTERVAL_MS = 30_000;
@@ -0,0 +1,217 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { mkdirSync, readFileSync, writeFileSync, unlinkSync, existsSync, openSync, closeSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import {
5
+ getSocketPath,
6
+ getPidPath,
7
+ getRootDir,
8
+ removeSocketFile,
9
+ } from '../util/platform.js';
10
+ import { getLogger } from '../util/logger.js';
11
+ import { DaemonError } from '../util/errors.js';
12
+ import { getConfig } from '../config/loader.js';
13
+
14
+ const log = getLogger('lifecycle');
15
+
16
+ function isProcessRunning(pid: number): boolean {
17
+ try {
18
+ process.kill(pid, 0);
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
25
+ function readPid(): number | null {
26
+ const pidPath = getPidPath();
27
+ if (!existsSync(pidPath)) return null;
28
+ try {
29
+ const pid = parseInt(readFileSync(pidPath, 'utf-8').trim(), 10);
30
+ return isNaN(pid) ? null : pid;
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ export function writePid(pid: number): void {
37
+ writeFileSync(getPidPath(), String(pid));
38
+ }
39
+
40
+ export function cleanupPidFile(): void {
41
+ const pidPath = getPidPath();
42
+ if (existsSync(pidPath)) {
43
+ unlinkSync(pidPath);
44
+ }
45
+ }
46
+
47
+ export function isDaemonRunning(): boolean {
48
+ const pid = readPid();
49
+ if (pid == null) return false;
50
+ if (!isProcessRunning(pid)) {
51
+ cleanupPidFile();
52
+ return false;
53
+ }
54
+ return true;
55
+ }
56
+
57
+ export function getDaemonStatus(): { running: boolean; pid?: number } {
58
+ const pid = readPid();
59
+ if (pid == null) return { running: false };
60
+ if (!isProcessRunning(pid)) {
61
+ cleanupPidFile();
62
+ return { running: false };
63
+ }
64
+ return { running: true, pid };
65
+ }
66
+
67
+ export async function startDaemon(): Promise<{
68
+ pid: number;
69
+ alreadyRunning: boolean;
70
+ }> {
71
+ const status = getDaemonStatus();
72
+ if (status.running && status.pid) {
73
+ return { pid: status.pid, alreadyRunning: true };
74
+ }
75
+
76
+ // Only create the root dir for socket/PID — the daemon process itself
77
+ // handles migration + full ensureDataDir() in runDaemon(). Calling
78
+ // ensureDataDir() here would pre-create workspace destination dirs
79
+ // and cause migration moves to no-op.
80
+ const rootDir = getRootDir();
81
+ if (!existsSync(rootDir)) {
82
+ mkdirSync(rootDir, { recursive: true });
83
+ }
84
+
85
+ // Clean up stale socket (only if it's actually a Unix socket)
86
+ const socketPath = getSocketPath();
87
+ removeSocketFile(socketPath);
88
+
89
+ // Spawn the daemon as a detached child process
90
+ const mainPath = resolve(
91
+ import.meta.dirname ?? __dirname,
92
+ 'main.ts',
93
+ );
94
+
95
+ // Redirect the child's stderr to a file instead of piping it back to the
96
+ // parent. A pipe's read end is destroyed when the parent exits, leaving
97
+ // fd 2 broken in the child. Bun (unlike Node.js) does not ignore SIGPIPE,
98
+ // so any later stderr write would silently kill the daemon.
99
+ const stderrPath = join(rootDir, 'daemon-stderr.log');
100
+ const stderrFd = openSync(stderrPath, 'w');
101
+
102
+ const child = spawn('bun', ['run', mainPath], {
103
+ detached: true,
104
+ stdio: ['ignore', 'ignore', stderrFd],
105
+ env: { ...process.env },
106
+ });
107
+
108
+ // The child inherited the fd; close the parent's copy.
109
+ closeSync(stderrFd);
110
+
111
+ let childExited = false;
112
+ let childExitCode: number | null = null;
113
+ child.on('exit', (code) => {
114
+ childExited = true;
115
+ childExitCode = code;
116
+ });
117
+
118
+ child.unref();
119
+
120
+ const pid = child.pid;
121
+ if (!pid) {
122
+ throw new DaemonError('Failed to start daemon: no PID returned');
123
+ }
124
+
125
+ writePid(pid);
126
+
127
+ // Wait for socket to appear
128
+ const config = getConfig();
129
+ const maxWait = config.daemon.startupSocketWaitMs;
130
+ const interval = 100;
131
+ let waited = 0;
132
+ while (waited < maxWait) {
133
+ if (existsSync(socketPath)) {
134
+ return { pid, alreadyRunning: false };
135
+ }
136
+ if (childExited) {
137
+ cleanupPidFile();
138
+ const stderr = readFileSync(stderrPath, 'utf-8').trim();
139
+ const detail = stderr
140
+ ? `\n${stderr}`
141
+ : `\nCheck logs at ~/.vellum/workspace/data/logs/ for details.`;
142
+ throw new DaemonError(
143
+ `Daemon exited immediately (code ${childExitCode ?? 'unknown'}).${detail}`,
144
+ );
145
+ }
146
+ await new Promise((r) => setTimeout(r, interval));
147
+ waited += interval;
148
+ }
149
+
150
+ throw new DaemonError(
151
+ `Daemon started but socket not available after ${maxWait}ms`,
152
+ );
153
+ }
154
+
155
+ export type StopResult =
156
+ | { stopped: true }
157
+ | { stopped: false; reason: 'not_running' | 'stop_failed' };
158
+
159
+ export async function stopDaemon(): Promise<StopResult> {
160
+ const pid = readPid();
161
+ if (pid == null || !isProcessRunning(pid)) {
162
+ cleanupPidFile();
163
+ return { stopped: false, reason: 'not_running' };
164
+ }
165
+
166
+ process.kill(pid, 'SIGTERM');
167
+
168
+ const config = getConfig();
169
+
170
+ // Wait for process to exit
171
+ const maxWait = config.daemon.stopTimeoutMs;
172
+ const interval = 100;
173
+ let waited = 0;
174
+ while (waited < maxWait) {
175
+ if (!isProcessRunning(pid)) {
176
+ cleanupPidFile();
177
+ return { stopped: true };
178
+ }
179
+ await new Promise((r) => setTimeout(r, interval));
180
+ waited += interval;
181
+ }
182
+
183
+ // Force kill
184
+ try {
185
+ process.kill(pid, 'SIGKILL');
186
+ } catch (err) {
187
+ log.debug({ err, pid }, 'SIGKILL failed, process already exited');
188
+ }
189
+
190
+ // Wait for the process to actually die after SIGKILL. Without this,
191
+ // startDaemon() can race with the dying process's shutdown handler,
192
+ // which removes the socket file and bricks the new daemon.
193
+ const killMaxWait = config.daemon.sigkillGracePeriodMs;
194
+ let killWaited = 0;
195
+ while (killWaited < killMaxWait && isProcessRunning(pid)) {
196
+ await new Promise((r) => setTimeout(r, 100));
197
+ killWaited += 100;
198
+ }
199
+
200
+ // Only clean up if the process has actually exited.
201
+ // If it's still alive after SIGKILL + timeout, preserve both socket
202
+ // and PID file so isDaemonRunning() still reports true and prevents
203
+ // a duplicate daemon from being spawned.
204
+ if (!isProcessRunning(pid)) {
205
+ removeSocketFile(getSocketPath());
206
+ cleanupPidFile();
207
+ return { stopped: true };
208
+ }
209
+
210
+ log.warn({ pid }, 'Daemon process still running after SIGKILL + timeout, leaving socket and PID file intact');
211
+ return { stopped: false, reason: 'stop_failed' };
212
+ }
213
+
214
+ export async function ensureDaemonRunning(): Promise<void> {
215
+ if (isDaemonRunning()) return;
216
+ await startDaemon();
217
+ }
@@ -3,6 +3,7 @@ import { v4 as uuid } from 'uuid';
3
3
  import { existsSync, rmSync, readdirSync, readFileSync } from 'node:fs';
4
4
  import { homedir } from 'node:os';
5
5
  import { join } from 'node:path';
6
+ import { getRuntimeHttpPort } from '../../config/env.js';
6
7
  import { queryAppRecords, createAppRecord, updateAppRecord, deleteAppRecord, listApps, getApp, getAppPreview, createApp, updateApp } from '../../memory/app-store.js';
7
8
  import { computeContentId } from '../../util/content-id.js';
8
9
  import { packageApp } from '../../bundler/app-bundler.js';
@@ -365,9 +366,7 @@ export async function handleShareAppCloud(
365
366
  const bundleData = readFileSync(result.bundlePath);
366
367
  const { shareToken } = createSharedAppLink(bundleData, result.manifest);
367
368
 
368
- const port = process.env.RUNTIME_HTTP_PORT
369
- ? parseInt(process.env.RUNTIME_HTTP_PORT, 10)
370
- : 7821;
369
+ const port = getRuntimeHttpPort() ?? 7821;
371
370
  const shareUrl = `http://localhost:${port}/v1/apps/shared/${shareToken}`;
372
371
 
373
372
  ctx.send(socket, {
@@ -0,0 +1,158 @@
1
+ import * as net from 'node:net';
2
+ import { createVerificationChallenge, getGuardianBinding, getPendingChallenge, revokeBinding as revokeGuardianBinding, revokePendingChallenges } from '../../runtime/channel-guardian-service.js';
3
+ import { createReadinessService, type ChannelReadinessService } from '../../runtime/channel-readiness-service.js';
4
+ import * as externalConversationStore from '../../memory/external-conversation-store.js';
5
+ import type {
6
+ GuardianVerificationRequest,
7
+ ChannelReadinessRequest,
8
+ } from '../ipc-protocol.js';
9
+ import { normalizeAssistantId } from '../../util/platform.js';
10
+ import { log, defineHandlers, type HandlerContext } from './shared.js';
11
+
12
+ // Lazy singleton — created on first use so module-load stays lightweight.
13
+ let _readinessService: ChannelReadinessService | undefined;
14
+ export function getReadinessService(): ChannelReadinessService {
15
+ if (!_readinessService) {
16
+ _readinessService = createReadinessService();
17
+ }
18
+ return _readinessService;
19
+ }
20
+
21
+ export function handleGuardianVerification(
22
+ msg: GuardianVerificationRequest,
23
+ socket: net.Socket,
24
+ ctx: HandlerContext,
25
+ ): void {
26
+ // Normalize the assistant ID so challenges are always stored under the
27
+ // same key the inbound-call path will use for lookups (typically "self").
28
+ const assistantId = normalizeAssistantId(msg.assistantId ?? 'self');
29
+ const channel = msg.channel ?? 'telegram';
30
+
31
+ try {
32
+ if (msg.action === 'create_challenge') {
33
+ const result = createVerificationChallenge(assistantId, channel, msg.sessionId);
34
+
35
+ ctx.send(socket, {
36
+ type: 'guardian_verification_response',
37
+ success: true,
38
+ secret: result.secret,
39
+ instruction: result.instruction,
40
+ channel,
41
+ });
42
+ } else if (msg.action === 'status') {
43
+ const binding = getGuardianBinding(assistantId, channel);
44
+ let guardianUsername: string | undefined;
45
+ let guardianDisplayName: string | undefined;
46
+ if (binding?.metadataJson) {
47
+ try {
48
+ const parsed = JSON.parse(binding.metadataJson) as Record<string, unknown>;
49
+ if (typeof parsed.username === 'string' && parsed.username.trim().length > 0) {
50
+ guardianUsername = parsed.username.trim();
51
+ }
52
+ if (typeof parsed.displayName === 'string' && parsed.displayName.trim().length > 0) {
53
+ guardianDisplayName = parsed.displayName.trim();
54
+ }
55
+ } catch {
56
+ // ignore malformed metadata
57
+ }
58
+ }
59
+ if (binding?.guardianDeliveryChatId && (!guardianUsername || !guardianDisplayName)) {
60
+ const ext = externalConversationStore.getBindingByChannelChat(
61
+ channel,
62
+ binding.guardianDeliveryChatId,
63
+ );
64
+ if (!guardianUsername && ext?.username) {
65
+ guardianUsername = ext.username;
66
+ }
67
+ if (!guardianDisplayName && ext?.displayName) {
68
+ guardianDisplayName = ext.displayName;
69
+ }
70
+ }
71
+ const hasPendingChallenge = getPendingChallenge(assistantId, channel) != null;
72
+ ctx.send(socket, {
73
+ type: 'guardian_verification_response',
74
+ success: true,
75
+ bound: binding != null,
76
+ guardianExternalUserId: binding?.guardianExternalUserId,
77
+ guardianUsername,
78
+ guardianDisplayName,
79
+ channel,
80
+ assistantId,
81
+ guardianDeliveryChatId: binding?.guardianDeliveryChatId,
82
+ hasPendingChallenge,
83
+ });
84
+ } else if (msg.action === 'revoke') {
85
+ revokeGuardianBinding(assistantId, channel);
86
+ revokePendingChallenges(assistantId, channel);
87
+ ctx.send(socket, {
88
+ type: 'guardian_verification_response',
89
+ success: true,
90
+ bound: false,
91
+ channel,
92
+ });
93
+ } else {
94
+ ctx.send(socket, {
95
+ type: 'guardian_verification_response',
96
+ success: false,
97
+ error: `Unknown action: ${String(msg.action)}`,
98
+ channel,
99
+ });
100
+ }
101
+ } catch (err) {
102
+ const message = err instanceof Error ? err.message : String(err);
103
+ log.error({ err }, 'Failed to handle guardian verification');
104
+ ctx.send(socket, {
105
+ type: 'guardian_verification_response',
106
+ success: false,
107
+ error: message,
108
+ channel,
109
+ });
110
+ }
111
+ }
112
+
113
+ export async function handleChannelReadiness(
114
+ msg: ChannelReadinessRequest,
115
+ socket: net.Socket,
116
+ ctx: HandlerContext,
117
+ ): Promise<void> {
118
+ try {
119
+ const service = getReadinessService();
120
+
121
+ if (msg.action === 'refresh') {
122
+ if (msg.channel) {
123
+ service.invalidateChannel(msg.channel, msg.assistantId);
124
+ } else {
125
+ service.invalidateAll();
126
+ }
127
+ }
128
+
129
+ const snapshots = await service.getReadiness(msg.channel, msg.includeRemote, msg.assistantId);
130
+
131
+ ctx.send(socket, {
132
+ type: 'channel_readiness_response',
133
+ success: true,
134
+ snapshots: snapshots.map((s) => ({
135
+ channel: s.channel,
136
+ ready: s.ready,
137
+ checkedAt: s.checkedAt,
138
+ stale: s.stale,
139
+ reasons: s.reasons,
140
+ localChecks: s.localChecks,
141
+ remoteChecks: s.remoteChecks,
142
+ })),
143
+ });
144
+ } catch (err) {
145
+ const message = err instanceof Error ? err.message : String(err);
146
+ log.error({ err }, 'Failed to handle channel readiness');
147
+ ctx.send(socket, {
148
+ type: 'channel_readiness_response',
149
+ success: false,
150
+ error: message,
151
+ });
152
+ }
153
+ }
154
+
155
+ export const channelHandlers = defineHandlers({
156
+ channel_readiness: handleChannelReadiness,
157
+ guardian_verification: handleGuardianVerification,
158
+ });