@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
@@ -36,6 +36,7 @@ import {
36
36
  revokeBinding,
37
37
  createChallenge,
38
38
  findPendingChallengeByHash,
39
+ findPendingChallengeForChannel,
39
40
  consumeChallenge,
40
41
  createApprovalRequest,
41
42
  getPendingApprovalForRun,
@@ -51,6 +52,7 @@ import {
51
52
  getGuardianBinding,
52
53
  isGuardian,
53
54
  revokeBinding as serviceRevokeBinding,
55
+ getPendingChallenge,
54
56
  } from '../runtime/channel-guardian-service.js';
55
57
  import { handleGuardianVerification } from '../daemon/handlers/config.js';
56
58
  import type { GuardianVerificationRequest, GuardianVerificationResponse } from '../daemon/ipc-contract.js';
@@ -1424,4 +1426,558 @@ describe('IPC handler channel-aware guardian status', () => {
1424
1426
  expect(resp!.guardianDeliveryChatId).toBeUndefined();
1425
1427
  expect(resp!.guardianExternalUserId).toBeUndefined();
1426
1428
  });
1429
+
1430
+ test('status action includes hasPendingChallenge when challenge exists', () => {
1431
+ createVerificationChallenge('self', 'voice');
1432
+
1433
+ const { ctx, lastResponse } = createMockCtx();
1434
+ const msg: GuardianVerificationRequest = {
1435
+ type: 'guardian_verification',
1436
+ action: 'status',
1437
+ channel: 'voice',
1438
+ assistantId: 'self',
1439
+ };
1440
+
1441
+ handleGuardianVerification(msg, mockSocket, ctx);
1442
+
1443
+ const resp = lastResponse();
1444
+ expect(resp).not.toBeNull();
1445
+ expect(resp!.success).toBe(true);
1446
+ expect(resp!.hasPendingChallenge).toBe(true);
1447
+ });
1448
+
1449
+ test('status action hasPendingChallenge is false when no challenge exists', () => {
1450
+ const { ctx, lastResponse } = createMockCtx();
1451
+ const msg: GuardianVerificationRequest = {
1452
+ type: 'guardian_verification',
1453
+ action: 'status',
1454
+ channel: 'voice',
1455
+ assistantId: 'self',
1456
+ };
1457
+
1458
+ handleGuardianVerification(msg, mockSocket, ctx);
1459
+
1460
+ const resp = lastResponse();
1461
+ expect(resp).not.toBeNull();
1462
+ expect(resp!.success).toBe(true);
1463
+ expect(resp!.hasPendingChallenge).toBe(false);
1464
+ });
1465
+ });
1466
+
1467
+ // ═══════════════════════════════════════════════════════════════════════════
1468
+ // 11. Voice Guardian Challenge — Six-Digit Secret Generation
1469
+ // ═══════════════════════════════════════════════════════════════════════════
1470
+
1471
+ describe('voice guardian challenge generation', () => {
1472
+ beforeEach(() => {
1473
+ resetTables();
1474
+ });
1475
+
1476
+ test('createVerificationChallenge for voice returns a six-digit numeric secret', () => {
1477
+ const result = createVerificationChallenge('asst-1', 'voice');
1478
+
1479
+ expect(result.challengeId).toBeDefined();
1480
+ expect(result.secret).toBeDefined();
1481
+ expect(result.secret).toMatch(/^\d{6}$/);
1482
+ const num = parseInt(result.secret, 10);
1483
+ expect(num).toBeGreaterThanOrEqual(100000);
1484
+ expect(num).toBeLessThanOrEqual(999999);
1485
+ });
1486
+
1487
+ test('createVerificationChallenge for non-voice returns 64-char hex secret', () => {
1488
+ const result = createVerificationChallenge('asst-1', 'telegram');
1489
+
1490
+ expect(result.secret.length).toBe(64);
1491
+ expect(result.secret).toMatch(/^[a-f0-9]{64}$/);
1492
+ });
1493
+
1494
+ test('voice challenge verifyCommand contains the six-digit secret', () => {
1495
+ const result = createVerificationChallenge('asst-1', 'voice');
1496
+
1497
+ expect(result.verifyCommand).toBe(`/guardian_verify ${result.secret}`);
1498
+ });
1499
+
1500
+ test('voice challenge instruction contains voice-specific copy', () => {
1501
+ const result = createVerificationChallenge('asst-1', 'voice');
1502
+
1503
+ expect(result.instruction).toContain('six-digit code');
1504
+ expect(result.instruction).toContain(result.secret);
1505
+ expect(result.instruction).toContain('minutes');
1506
+ });
1507
+
1508
+ test('voice challenge secrets are different across calls', () => {
1509
+ const result1 = createVerificationChallenge('asst-1', 'voice');
1510
+ const result2 = createVerificationChallenge('asst-2', 'voice');
1511
+
1512
+ // While technically they could collide, the probability is ~1/900000
1513
+ // so this is a reasonable smoke test for randomness
1514
+ expect(result1.secret).toMatch(/^\d{6}$/);
1515
+ expect(result2.secret).toMatch(/^\d{6}$/);
1516
+ });
1517
+
1518
+ test('voice ttlSeconds is 600 (10 minutes)', () => {
1519
+ const result = createVerificationChallenge('asst-1', 'voice');
1520
+ expect(result.ttlSeconds).toBe(600);
1521
+ });
1522
+ });
1523
+
1524
+ // ═══════════════════════════════════════════════════════════════════════════
1525
+ // 12. Voice Guardian Challenge Validation
1526
+ // ═══════════════════════════════════════════════════════════════════════════
1527
+
1528
+ describe('voice guardian challenge validation', () => {
1529
+ beforeEach(() => {
1530
+ resetTables();
1531
+ });
1532
+
1533
+ test('validateAndConsumeChallenge succeeds with correct voice secret', () => {
1534
+ const { secret } = createVerificationChallenge('asst-1', 'voice');
1535
+
1536
+ const result = validateAndConsumeChallenge(
1537
+ 'asst-1',
1538
+ 'voice',
1539
+ secret,
1540
+ 'voice-user-1',
1541
+ 'voice-chat-1',
1542
+ );
1543
+
1544
+ expect(result.success).toBe(true);
1545
+ if (result.success) {
1546
+ expect(result.bindingId).toBeDefined();
1547
+ }
1548
+ });
1549
+
1550
+ test('validateAndConsumeChallenge creates a guardian binding for voice', () => {
1551
+ const { secret } = createVerificationChallenge('asst-1', 'voice');
1552
+
1553
+ validateAndConsumeChallenge('asst-1', 'voice', secret, 'voice-user-1', 'voice-chat-1');
1554
+
1555
+ const binding = getActiveBinding('asst-1', 'voice');
1556
+ expect(binding).not.toBeNull();
1557
+ expect(binding!.guardianExternalUserId).toBe('voice-user-1');
1558
+ expect(binding!.guardianDeliveryChatId).toBe('voice-chat-1');
1559
+ expect(binding!.channel).toBe('voice');
1560
+ expect(binding!.verifiedVia).toBe('challenge');
1561
+ });
1562
+
1563
+ test('validateAndConsumeChallenge fails with wrong voice secret', () => {
1564
+ createVerificationChallenge('asst-1', 'voice');
1565
+
1566
+ const result = validateAndConsumeChallenge(
1567
+ 'asst-1',
1568
+ 'voice',
1569
+ '000000',
1570
+ 'voice-user-1',
1571
+ 'voice-chat-1',
1572
+ );
1573
+
1574
+ expect(result.success).toBe(false);
1575
+ if (!result.success) {
1576
+ expect(result.reason).toBeDefined();
1577
+ expect(result.reason.length).toBeGreaterThan(0);
1578
+ expect(result.reason.toLowerCase()).toContain('failed');
1579
+ }
1580
+ });
1581
+
1582
+ test('voice and telegram guardian challenges are independent', () => {
1583
+ const voiceChallenge = createVerificationChallenge('asst-1', 'voice');
1584
+ const telegramChallenge = createVerificationChallenge('asst-1', 'telegram');
1585
+
1586
+ // Voice secret against telegram channel should fail
1587
+ const crossResult = validateAndConsumeChallenge(
1588
+ 'asst-1',
1589
+ 'telegram',
1590
+ voiceChallenge.secret,
1591
+ 'user-1',
1592
+ 'chat-1',
1593
+ );
1594
+ expect(crossResult.success).toBe(false);
1595
+
1596
+ // Voice secret against correct channel should succeed
1597
+ const voiceResult = validateAndConsumeChallenge(
1598
+ 'asst-1',
1599
+ 'voice',
1600
+ voiceChallenge.secret,
1601
+ 'voice-user-1',
1602
+ 'voice-chat-1',
1603
+ );
1604
+ expect(voiceResult.success).toBe(true);
1605
+
1606
+ // Telegram challenge should still be valid
1607
+ const telegramResult = validateAndConsumeChallenge(
1608
+ 'asst-1',
1609
+ 'telegram',
1610
+ telegramChallenge.secret,
1611
+ 'user-2',
1612
+ 'chat-2',
1613
+ );
1614
+ expect(telegramResult.success).toBe(true);
1615
+ });
1616
+
1617
+ test('consumed voice challenge cannot be reused', () => {
1618
+ const { secret } = createVerificationChallenge('asst-1', 'voice');
1619
+
1620
+ const result1 = validateAndConsumeChallenge(
1621
+ 'asst-1', 'voice', secret, 'voice-user-1', 'voice-chat-1',
1622
+ );
1623
+ expect(result1.success).toBe(true);
1624
+
1625
+ const result2 = validateAndConsumeChallenge(
1626
+ 'asst-1', 'voice', secret, 'voice-user-2', 'voice-chat-2',
1627
+ );
1628
+ expect(result2.success).toBe(false);
1629
+ });
1630
+
1631
+ test('validateAndConsumeChallenge revokes existing voice binding before creating new one', () => {
1632
+ createBinding({
1633
+ assistantId: 'asst-1',
1634
+ channel: 'voice',
1635
+ guardianExternalUserId: 'old-voice-user',
1636
+ guardianDeliveryChatId: 'old-voice-chat',
1637
+ });
1638
+
1639
+ const oldBinding = getActiveBinding('asst-1', 'voice');
1640
+ expect(oldBinding).not.toBeNull();
1641
+ expect(oldBinding!.guardianExternalUserId).toBe('old-voice-user');
1642
+
1643
+ const { secret } = createVerificationChallenge('asst-1', 'voice');
1644
+ validateAndConsumeChallenge('asst-1', 'voice', secret, 'new-voice-user', 'new-voice-chat');
1645
+
1646
+ const newBinding = getActiveBinding('asst-1', 'voice');
1647
+ expect(newBinding).not.toBeNull();
1648
+ expect(newBinding!.guardianExternalUserId).toBe('new-voice-user');
1649
+ expect(newBinding!.guardianDeliveryChatId).toBe('new-voice-chat');
1650
+ });
1651
+ });
1652
+
1653
+ // ═══════════════════════════════════════════════════════════════════════════
1654
+ // 13. Voice Guardian Identity and Revocation
1655
+ // ═══════════════════════════════════════════════════════════════════════════
1656
+
1657
+ describe('voice guardian identity and revocation', () => {
1658
+ beforeEach(() => {
1659
+ resetTables();
1660
+ });
1661
+
1662
+ test('isGuardian works for voice channel', () => {
1663
+ createBinding({
1664
+ assistantId: 'asst-1',
1665
+ channel: 'voice',
1666
+ guardianExternalUserId: 'voice-user-1',
1667
+ guardianDeliveryChatId: 'voice-chat-1',
1668
+ });
1669
+
1670
+ expect(isGuardian('asst-1', 'voice', 'voice-user-1')).toBe(true);
1671
+ expect(isGuardian('asst-1', 'voice', 'voice-user-2')).toBe(false);
1672
+ // Voice guardian should not match telegram channel
1673
+ expect(isGuardian('asst-1', 'telegram', 'voice-user-1')).toBe(false);
1674
+ });
1675
+
1676
+ test('getGuardianBinding returns voice binding', () => {
1677
+ createBinding({
1678
+ assistantId: 'asst-1',
1679
+ channel: 'voice',
1680
+ guardianExternalUserId: 'voice-user-1',
1681
+ guardianDeliveryChatId: 'voice-chat-1',
1682
+ });
1683
+
1684
+ const binding = getGuardianBinding('asst-1', 'voice');
1685
+ expect(binding).not.toBeNull();
1686
+ expect(binding!.channel).toBe('voice');
1687
+ expect(binding!.guardianExternalUserId).toBe('voice-user-1');
1688
+ });
1689
+
1690
+ test('revokeBinding clears active voice guardian binding', () => {
1691
+ createBinding({
1692
+ assistantId: 'asst-1',
1693
+ channel: 'voice',
1694
+ guardianExternalUserId: 'voice-user-1',
1695
+ guardianDeliveryChatId: 'voice-chat-1',
1696
+ });
1697
+
1698
+ const result = serviceRevokeBinding('asst-1', 'voice');
1699
+ expect(result).toBe(true);
1700
+ expect(getGuardianBinding('asst-1', 'voice')).toBeNull();
1701
+ });
1702
+
1703
+ test('revokeBinding for voice does not affect telegram binding', () => {
1704
+ createBinding({
1705
+ assistantId: 'asst-1',
1706
+ channel: 'voice',
1707
+ guardianExternalUserId: 'voice-user-1',
1708
+ guardianDeliveryChatId: 'voice-chat-1',
1709
+ });
1710
+ createBinding({
1711
+ assistantId: 'asst-1',
1712
+ channel: 'telegram',
1713
+ guardianExternalUserId: 'tg-user-1',
1714
+ guardianDeliveryChatId: 'tg-chat-1',
1715
+ });
1716
+
1717
+ serviceRevokeBinding('asst-1', 'voice');
1718
+
1719
+ expect(getGuardianBinding('asst-1', 'voice')).toBeNull();
1720
+ expect(getGuardianBinding('asst-1', 'telegram')).not.toBeNull();
1721
+ });
1722
+ });
1723
+
1724
+ // ═══════════════════════════════════════════════════════════════════════════
1725
+ // 14. Voice Guardian Rate Limiting
1726
+ // ═══════════════════════════════════════════════════════════════════════════
1727
+
1728
+ describe('voice guardian rate limiting', () => {
1729
+ beforeEach(() => {
1730
+ resetTables();
1731
+ });
1732
+
1733
+ test('repeated invalid voice submissions hit rate limit', () => {
1734
+ createVerificationChallenge('asst-1', 'voice');
1735
+
1736
+ for (let i = 0; i < 5; i++) {
1737
+ const result = validateAndConsumeChallenge(
1738
+ 'asst-1', 'voice', `${100000 + i}`, 'voice-user-1', 'voice-chat-1',
1739
+ );
1740
+ expect(result.success).toBe(false);
1741
+ }
1742
+
1743
+ // The 6th attempt should be rate-limited
1744
+ const result = validateAndConsumeChallenge(
1745
+ 'asst-1', 'voice', '999999', 'voice-user-1', 'voice-chat-1',
1746
+ );
1747
+ expect(result.success).toBe(false);
1748
+
1749
+ const rl = getRateLimit('asst-1', 'voice', 'voice-user-1', 'voice-chat-1');
1750
+ expect(rl).not.toBeNull();
1751
+ expect(rl!.lockedUntil).not.toBeNull();
1752
+ });
1753
+
1754
+ test('voice rate limit does not affect telegram rate limit', () => {
1755
+ createVerificationChallenge('asst-1', 'voice');
1756
+ for (let i = 0; i < 5; i++) {
1757
+ validateAndConsumeChallenge('asst-1', 'voice', `${100000 + i}`, 'user-1', 'chat-1');
1758
+ }
1759
+
1760
+ const voiceRl = getRateLimit('asst-1', 'voice', 'user-1', 'chat-1');
1761
+ expect(voiceRl).not.toBeNull();
1762
+ expect(voiceRl!.lockedUntil).not.toBeNull();
1763
+
1764
+ // Telegram should be unaffected
1765
+ const telegramRl = getRateLimit('asst-1', 'telegram', 'user-1', 'chat-1');
1766
+ expect(telegramRl).toBeNull();
1767
+ });
1768
+
1769
+ test('successful voice verification resets rate limit', () => {
1770
+ const { secret: _s } = createVerificationChallenge('asst-1', 'voice');
1771
+ validateAndConsumeChallenge('asst-1', 'voice', '000000', 'voice-user-1', 'voice-chat-1');
1772
+ validateAndConsumeChallenge('asst-1', 'voice', '111111', 'voice-user-1', 'voice-chat-1');
1773
+
1774
+ // Valid attempt should succeed (under the 5-attempt threshold)
1775
+ const { secret } = createVerificationChallenge('asst-1', 'voice');
1776
+ const result = validateAndConsumeChallenge(
1777
+ 'asst-1', 'voice', secret, 'voice-user-1', 'voice-chat-1',
1778
+ );
1779
+ expect(result.success).toBe(true);
1780
+
1781
+ const rl = getRateLimit('asst-1', 'voice', 'voice-user-1', 'voice-chat-1');
1782
+ expect(rl).not.toBeNull();
1783
+ expect(rl!.invalidAttempts).toBe(0);
1784
+ expect(rl!.lockedUntil).toBeNull();
1785
+ });
1786
+ });
1787
+
1788
+ // ═══════════════════════════════════════════════════════════════════════════
1789
+ // 15. Pending Challenge Lookup (Store + Service)
1790
+ // ═══════════════════════════════════════════════════════════════════════════
1791
+
1792
+ describe('pending challenge lookup', () => {
1793
+ beforeEach(() => {
1794
+ resetTables();
1795
+ });
1796
+
1797
+ test('findPendingChallengeForChannel returns pending challenge', () => {
1798
+ createVerificationChallenge('asst-1', 'voice');
1799
+
1800
+ const pending = findPendingChallengeForChannel('asst-1', 'voice');
1801
+ expect(pending).not.toBeNull();
1802
+ expect(pending!.channel).toBe('voice');
1803
+ expect(pending!.status).toBe('pending');
1804
+ });
1805
+
1806
+ test('findPendingChallengeForChannel returns null when no challenge exists', () => {
1807
+ const pending = findPendingChallengeForChannel('asst-1', 'voice');
1808
+ expect(pending).toBeNull();
1809
+ });
1810
+
1811
+ test('findPendingChallengeForChannel returns null for different channel', () => {
1812
+ createVerificationChallenge('asst-1', 'telegram');
1813
+
1814
+ const pending = findPendingChallengeForChannel('asst-1', 'voice');
1815
+ expect(pending).toBeNull();
1816
+ });
1817
+
1818
+ test('findPendingChallengeForChannel returns null after challenge is consumed', () => {
1819
+ const { secret } = createVerificationChallenge('asst-1', 'voice');
1820
+ validateAndConsumeChallenge('asst-1', 'voice', secret, 'voice-user-1', 'voice-chat-1');
1821
+
1822
+ const pending = findPendingChallengeForChannel('asst-1', 'voice');
1823
+ expect(pending).toBeNull();
1824
+ });
1825
+
1826
+ test('getPendingChallenge service helper returns pending voice challenge', () => {
1827
+ createVerificationChallenge('asst-1', 'voice');
1828
+
1829
+ const pending = getPendingChallenge('asst-1', 'voice');
1830
+ expect(pending).not.toBeNull();
1831
+ expect(pending!.channel).toBe('voice');
1832
+ });
1833
+
1834
+ test('getPendingChallenge returns null when no challenge exists', () => {
1835
+ const pending = getPendingChallenge('asst-1', 'voice');
1836
+ expect(pending).toBeNull();
1837
+ });
1838
+
1839
+ test('creating a new challenge revokes prior pending challenges', () => {
1840
+ createVerificationChallenge('asst-1', 'voice');
1841
+ const pending1 = findPendingChallengeForChannel('asst-1', 'voice');
1842
+ expect(pending1).not.toBeNull();
1843
+ const firstId = pending1!.id;
1844
+
1845
+ // Creating a second challenge should revoke the first
1846
+ createVerificationChallenge('asst-1', 'voice');
1847
+ const pending2 = findPendingChallengeForChannel('asst-1', 'voice');
1848
+ expect(pending2).not.toBeNull();
1849
+ expect(pending2!.id).not.toBe(firstId);
1850
+ });
1851
+ });
1852
+
1853
+ // ═══════════════════════════════════════════════════════════════════════════
1854
+ // 16. IPC handler — voice guardian verification
1855
+ // ═══════════════════════════════════════════════════════════════════════════
1856
+
1857
+ describe('IPC handler voice guardian verification', () => {
1858
+ beforeEach(() => {
1859
+ resetTables();
1860
+ });
1861
+
1862
+ test('create_challenge for voice returns a six-digit secret', () => {
1863
+ const { ctx, lastResponse } = createMockCtx();
1864
+ const msg: GuardianVerificationRequest = {
1865
+ type: 'guardian_verification',
1866
+ action: 'create_challenge',
1867
+ channel: 'voice',
1868
+ assistantId: 'self',
1869
+ };
1870
+
1871
+ handleGuardianVerification(msg, mockSocket, ctx);
1872
+
1873
+ const resp = lastResponse();
1874
+ expect(resp).not.toBeNull();
1875
+ expect(resp!.success).toBe(true);
1876
+ expect(resp!.secret).toBeDefined();
1877
+ expect(resp!.secret).toMatch(/^\d{6}$/);
1878
+ expect(resp!.instruction).toBeDefined();
1879
+ expect(resp!.instruction).toContain('six-digit code');
1880
+ expect(resp!.channel).toBe('voice');
1881
+ });
1882
+
1883
+ test('status for voice reflects unbound state', () => {
1884
+ const { ctx, lastResponse } = createMockCtx();
1885
+ const msg: GuardianVerificationRequest = {
1886
+ type: 'guardian_verification',
1887
+ action: 'status',
1888
+ channel: 'voice',
1889
+ assistantId: 'self',
1890
+ };
1891
+
1892
+ handleGuardianVerification(msg, mockSocket, ctx);
1893
+
1894
+ const resp = lastResponse();
1895
+ expect(resp).not.toBeNull();
1896
+ expect(resp!.success).toBe(true);
1897
+ expect(resp!.channel).toBe('voice');
1898
+ expect(resp!.bound).toBe(false);
1899
+ expect(resp!.guardianExternalUserId).toBeUndefined();
1900
+ });
1901
+
1902
+ test('status for voice reflects bound state', () => {
1903
+ createBinding({
1904
+ assistantId: 'self',
1905
+ channel: 'voice',
1906
+ guardianExternalUserId: 'voice-user-1',
1907
+ guardianDeliveryChatId: 'voice-chat-1',
1908
+ });
1909
+
1910
+ const { ctx, lastResponse } = createMockCtx();
1911
+ const msg: GuardianVerificationRequest = {
1912
+ type: 'guardian_verification',
1913
+ action: 'status',
1914
+ channel: 'voice',
1915
+ assistantId: 'self',
1916
+ };
1917
+
1918
+ handleGuardianVerification(msg, mockSocket, ctx);
1919
+
1920
+ const resp = lastResponse();
1921
+ expect(resp).not.toBeNull();
1922
+ expect(resp!.success).toBe(true);
1923
+ expect(resp!.bound).toBe(true);
1924
+ expect(resp!.guardianExternalUserId).toBe('voice-user-1');
1925
+ expect(resp!.guardianDeliveryChatId).toBe('voice-chat-1');
1926
+ expect(resp!.channel).toBe('voice');
1927
+ });
1928
+
1929
+ test('revoke for voice clears active binding', () => {
1930
+ createBinding({
1931
+ assistantId: 'self',
1932
+ channel: 'voice',
1933
+ guardianExternalUserId: 'voice-user-1',
1934
+ guardianDeliveryChatId: 'voice-chat-1',
1935
+ });
1936
+
1937
+ const { ctx, lastResponse } = createMockCtx();
1938
+ const msg: GuardianVerificationRequest = {
1939
+ type: 'guardian_verification',
1940
+ action: 'revoke',
1941
+ channel: 'voice',
1942
+ assistantId: 'self',
1943
+ };
1944
+
1945
+ handleGuardianVerification(msg, mockSocket, ctx);
1946
+
1947
+ const resp = lastResponse();
1948
+ expect(resp).not.toBeNull();
1949
+ expect(resp!.success).toBe(true);
1950
+ expect(resp!.bound).toBe(false);
1951
+
1952
+ // Verify binding is actually revoked
1953
+ expect(getGuardianBinding('self', 'voice')).toBeNull();
1954
+ });
1955
+
1956
+ test('revoke for voice does not affect telegram binding', () => {
1957
+ createBinding({
1958
+ assistantId: 'self',
1959
+ channel: 'voice',
1960
+ guardianExternalUserId: 'voice-user-1',
1961
+ guardianDeliveryChatId: 'voice-chat-1',
1962
+ });
1963
+ createBinding({
1964
+ assistantId: 'self',
1965
+ channel: 'telegram',
1966
+ guardianExternalUserId: 'tg-user-1',
1967
+ guardianDeliveryChatId: 'tg-chat-1',
1968
+ });
1969
+
1970
+ const { ctx } = createMockCtx();
1971
+ const msg: GuardianVerificationRequest = {
1972
+ type: 'guardian_verification',
1973
+ action: 'revoke',
1974
+ channel: 'voice',
1975
+ assistantId: 'self',
1976
+ };
1977
+
1978
+ handleGuardianVerification(msg, mockSocket, ctx);
1979
+
1980
+ expect(getGuardianBinding('self', 'voice')).toBeNull();
1981
+ expect(getGuardianBinding('self', 'telegram')).not.toBeNull();
1982
+ });
1427
1983
  });
@@ -1,11 +1,12 @@
1
- import { describe, test, expect, beforeEach, mock } from 'bun:test';
1
+ import { describe, test, expect, beforeEach } from 'bun:test';
2
2
  import { ChannelReadinessService, REMOTE_TTL_MS } from '../runtime/channel-readiness-service.js';
3
+ import type { ChannelId } from '../channels/types.js';
3
4
  import type { ChannelProbe, ReadinessCheckResult } from '../runtime/channel-readiness-types.js';
4
5
 
5
6
  // ── Test helpers ────────────────────────────────────────────────────────────
6
7
 
7
8
  function makeProbe(
8
- channel: string,
9
+ channel: ChannelId,
9
10
  localResults: ReadinessCheckResult[],
10
11
  remoteResults?: ReadinessCheckResult[],
11
12
  ): ChannelProbe & { localCallCount: number; remoteCallCount: number } {
@@ -114,7 +115,7 @@ describe('ChannelReadinessService', () => {
114
115
  expect(probe.remoteCallCount).toBe(1);
115
116
 
116
117
  // Manually age the cached snapshot beyond TTL
117
- const cached = (service as unknown as { snapshots: Map<string, { checkedAt: number }> }).snapshots.get('sms')!;
118
+ const cached = (service as unknown as { snapshots: Map<string, { checkedAt: number }> }).snapshots.get('sms::__default__')!;
118
119
  cached.checkedAt = Date.now() - REMOTE_TTL_MS - 1;
119
120
 
120
121
  // Second call should re-run remote checks
@@ -166,9 +167,10 @@ describe('ChannelReadinessService', () => {
166
167
  });
167
168
 
168
169
  test('unknown channel returns unsupported_channel reason', async () => {
169
- const [snapshot] = await service.getReadiness('carrier_pigeon');
170
+ // Cast to exercise runtime handling of an unrecognized channel value
171
+ const [snapshot] = await service.getReadiness('carrier_pigeon' as ChannelId);
170
172
 
171
- expect(snapshot.channel).toBe('carrier_pigeon');
173
+ expect(snapshot.channel).toBe('carrier_pigeon' as ChannelId);
172
174
  expect(snapshot.ready).toBe(false);
173
175
  expect(snapshot.reasons).toEqual([
174
176
  { code: 'unsupported_channel', text: 'Channel carrier_pigeon is not supported' },
@@ -177,13 +179,13 @@ describe('ChannelReadinessService', () => {
177
179
  });
178
180
 
179
181
  test('all checks passing yields ready=true', async () => {
180
- const probe = makeProbe('test', [
182
+ const probe = makeProbe('telegram', [
181
183
  { name: 'a', passed: true, message: 'ok' },
182
184
  { name: 'b', passed: true, message: 'ok' },
183
185
  ]);
184
186
  service.registerProbe(probe);
185
187
 
186
- const [snapshot] = await service.getReadiness('test');
188
+ const [snapshot] = await service.getReadiness('telegram');
187
189
 
188
190
  expect(snapshot.ready).toBe(true);
189
191
  expect(snapshot.reasons).toHaveLength(0);
@@ -254,4 +256,69 @@ describe('ChannelReadinessService', () => {
254
256
  { code: 'api_check', text: 'API unreachable' },
255
257
  ]);
256
258
  });
259
+
260
+ test('fresh cached remote failures do not affect local-only readiness', async () => {
261
+ const probe = makeProbe(
262
+ 'sms',
263
+ [{ name: 'creds', passed: true, message: 'ok' }],
264
+ [{ name: 'api_check', passed: false, message: 'API unreachable' }],
265
+ );
266
+ service.registerProbe(probe);
267
+
268
+ // Prime remote cache with a failing check
269
+ await service.getReadiness('sms', true);
270
+
271
+ // Immediately call with includeRemote=false (cache is still fresh within TTL).
272
+ // The cached remote failure should be surfaced for visibility but must NOT
273
+ // affect readiness when the caller explicitly opted out of remote checks.
274
+ const [snapshot] = await service.getReadiness('sms', false);
275
+ expect(snapshot.ready).toBe(true);
276
+ expect(snapshot.reasons).toEqual([]);
277
+ // Remote checks are still visible for informational purposes
278
+ expect(snapshot.remoteChecks).toHaveLength(1);
279
+ expect(snapshot.remoteChecks![0].passed).toBe(false);
280
+ });
281
+
282
+ test('stale cached remote failures do not affect local-only readiness', async () => {
283
+ const probe = makeProbe(
284
+ 'sms',
285
+ [{ name: 'creds', passed: true, message: 'ok' }],
286
+ [{ name: 'api_check', passed: false, message: 'API unreachable' }],
287
+ );
288
+ service.registerProbe(probe);
289
+
290
+ // Prime remote cache with a failing check
291
+ await service.getReadiness('sms', true);
292
+
293
+ // Age snapshot beyond TTL so remote checks are stale
294
+ const cached = (service as unknown as { snapshots: Map<string, { checkedAt: number }> }).snapshots.get('sms::__default__')!;
295
+ cached.checkedAt = Date.now() - REMOTE_TTL_MS - 1;
296
+
297
+ // Local-only call should not be blocked by stale remote failure
298
+ const [snapshot] = await service.getReadiness('sms', false);
299
+ expect(snapshot.stale).toBe(true);
300
+ expect(snapshot.ready).toBe(true);
301
+ expect(snapshot.reasons).toEqual([]);
302
+ });
303
+
304
+ test('remote cache is scoped per assistantId', async () => {
305
+ const remoteCalls: Record<string, number> = {};
306
+ const probe: ChannelProbe = {
307
+ channel: 'sms',
308
+ runLocalChecks: () => [{ name: 'local', passed: true, message: 'ok' }],
309
+ async runRemoteChecks(context) {
310
+ const key = context?.assistantId ?? '__default__';
311
+ remoteCalls[key] = (remoteCalls[key] ?? 0) + 1;
312
+ return [{ name: 'remote', passed: true, message: `ok-${key}` }];
313
+ },
314
+ };
315
+ service.registerProbe(probe);
316
+
317
+ await service.getReadiness('sms', true, 'ast-alpha');
318
+ await service.getReadiness('sms', true, 'ast-beta');
319
+ await service.getReadiness('sms', true, 'ast-alpha');
320
+
321
+ expect(remoteCalls['ast-alpha']).toBe(1);
322
+ expect(remoteCalls['ast-beta']).toBe(1);
323
+ });
257
324
  });