@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
@@ -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';
@@ -311,28 +313,32 @@ describe('guardian service challenge validation', () => {
311
313
  resetTables();
312
314
  });
313
315
 
314
- test('createVerificationChallenge returns a secret and instruction', () => {
316
+ test('createVerificationChallenge returns a secret, verifyCommand, ttlSeconds, and instruction', () => {
315
317
  const result = createVerificationChallenge('asst-1', 'telegram');
316
318
 
317
319
  expect(result.challengeId).toBeDefined();
318
320
  expect(result.secret).toBeDefined();
319
321
  expect(result.secret.length).toBe(64); // 32 bytes hex-encoded
322
+ expect(result.verifyCommand).toBe(`/guardian_verify ${result.secret}`);
323
+ expect(result.ttlSeconds).toBe(600);
324
+ expect(result.instruction).toBeDefined();
325
+ expect(result.instruction.length).toBeGreaterThan(0);
320
326
  expect(result.instruction).toContain('/guardian_verify');
321
- expect(result.instruction).toContain(result.secret);
322
327
  });
323
328
 
324
- test('createVerificationChallenge instruction mentions Telegram for telegram channel', () => {
329
+ test('createVerificationChallenge produces a non-empty instruction for telegram channel', () => {
325
330
  const result = createVerificationChallenge('asst-1', 'telegram');
326
- expect(result.instruction).toContain('via Telegram');
327
- expect(result.instruction).not.toContain('via SMS');
331
+ expect(result.instruction).toBeDefined();
332
+ expect(result.instruction.length).toBeGreaterThan(0);
333
+ expect(result.instruction).toContain(result.verifyCommand);
328
334
  });
329
335
 
330
- test('createVerificationChallenge instruction mentions SMS for sms channel', () => {
336
+ test('createVerificationChallenge produces a non-empty instruction for sms channel', () => {
331
337
  const result = createVerificationChallenge('asst-1', 'sms');
332
- expect(result.instruction).toContain('via SMS');
333
- expect(result.instruction).not.toContain('via Telegram');
338
+ expect(result.instruction).toBeDefined();
339
+ expect(result.instruction.length).toBeGreaterThan(0);
334
340
  expect(result.instruction).toContain('/guardian_verify');
335
- expect(result.instruction).toContain(result.secret);
341
+ expect(result.instruction).toContain(result.verifyCommand);
336
342
  });
337
343
 
338
344
  test('validateAndConsumeChallenge succeeds with correct secret', () => {
@@ -377,8 +383,10 @@ describe('guardian service challenge validation', () => {
377
383
 
378
384
  expect(result.success).toBe(false);
379
385
  if (!result.success) {
380
- // Generic message to prevent oracle leakage
381
- expect(result.reason).toContain('try again later');
386
+ // Composed failure message check it is non-empty and contains "failed"
387
+ expect(result.reason).toBeDefined();
388
+ expect(result.reason.length).toBeGreaterThan(0);
389
+ expect(result.reason.toLowerCase()).toContain('failed');
382
390
  }
383
391
  });
384
392
 
@@ -404,8 +412,10 @@ describe('guardian service challenge validation', () => {
404
412
 
405
413
  expect(result.success).toBe(false);
406
414
  if (!result.success) {
407
- // Generic message to prevent oracle leakage
408
- expect(result.reason).toContain('try again later');
415
+ // Composed failure message check it is non-empty and contains "failed"
416
+ expect(result.reason).toBeDefined();
417
+ expect(result.reason.length).toBeGreaterThan(0);
418
+ expect(result.reason.toLowerCase()).toContain('failed');
409
419
  }
410
420
  });
411
421
 
@@ -943,7 +953,9 @@ describe('guardian service rate limiting', () => {
943
953
  'asst-1', 'telegram', 'another-wrong', 'user-42', 'chat-42',
944
954
  );
945
955
  expect(result.success).toBe(false);
946
- expect((result as { reason: string }).reason).toContain('try again later');
956
+ expect((result as { reason: string }).reason).toBeDefined();
957
+ expect((result as { reason: string }).reason.length).toBeGreaterThan(0);
958
+ expect((result as { reason: string }).reason.toLowerCase()).toContain('failed');
947
959
 
948
960
  // Verify the rate limit record
949
961
  const rl = getRateLimit('asst-1', 'telegram', 'user-42', 'chat-42');
@@ -975,21 +987,38 @@ describe('guardian service rate limiting', () => {
975
987
  test('rate-limit uses generic failure message (no oracle leakage)', () => {
976
988
  createVerificationChallenge('asst-1', 'telegram');
977
989
 
978
- // Trigger rate limit
979
- for (let i = 0; i < 5; i++) {
990
+ // Capture a normal invalid-code failure response
991
+ const normalFailure = validateAndConsumeChallenge(
992
+ 'asst-1', 'telegram', 'wrong-first', 'user-42', 'chat-42',
993
+ );
994
+ expect(normalFailure.success).toBe(false);
995
+ const normalReason = (normalFailure as { reason: string }).reason;
996
+
997
+ // Trigger rate limit (4 more attempts to reach 5 total)
998
+ for (let i = 0; i < 4; i++) {
980
999
  validateAndConsumeChallenge(
981
1000
  'asst-1', 'telegram', `wrong-${i}`, 'user-42', 'chat-42',
982
1001
  );
983
1002
  }
984
1003
 
985
- const result = validateAndConsumeChallenge(
1004
+ // Verify lockout is actually active before testing the rate-limited response
1005
+ const rl = getRateLimit('asst-1', 'telegram', 'user-42', 'chat-42');
1006
+ expect(rl).not.toBeNull();
1007
+ expect(rl!.lockedUntil).toBeGreaterThan(Date.now());
1008
+
1009
+ // The rate-limited response should be indistinguishable from normal failure
1010
+ const rateLimitedResult = validateAndConsumeChallenge(
986
1011
  'asst-1', 'telegram', 'anything', 'user-42', 'chat-42',
987
1012
  );
988
- expect(result.success).toBe(false);
989
- // Must NOT reveal "invalid", "expired", or "rate limit" specifically
990
- expect((result as { reason: string }).reason).not.toContain('Invalid');
991
- expect((result as { reason: string }).reason).not.toContain('expired');
992
- expect((result as { reason: string }).reason).not.toContain('rate limit');
1013
+ expect(rateLimitedResult.success).toBe(false);
1014
+ const rateLimitedReason = (rateLimitedResult as { reason: string }).reason;
1015
+
1016
+ // Anti-oracle: both responses must be identical
1017
+ expect(rateLimitedReason).toBe(normalReason);
1018
+
1019
+ // Neither should reveal rate-limiting info
1020
+ expect(rateLimitedReason).not.toContain('rate limit');
1021
+ expect(normalReason).not.toContain('rate limit');
993
1022
  });
994
1023
 
995
1024
  test('rate limit does not affect different actors', () => {
@@ -1295,6 +1324,31 @@ describe('IPC handler channel-aware guardian status', () => {
1295
1324
  expect(resp!.assistantId).toBe('self');
1296
1325
  });
1297
1326
 
1327
+ test('status action returns guardian username/displayName from binding metadata', () => {
1328
+ createBinding({
1329
+ assistantId: 'self',
1330
+ channel: 'telegram',
1331
+ guardianExternalUserId: 'user-43',
1332
+ guardianDeliveryChatId: 'chat-43',
1333
+ metadataJson: JSON.stringify({ username: 'guardian_handle', displayName: 'Guardian Name' }),
1334
+ });
1335
+
1336
+ const { ctx, lastResponse } = createMockCtx();
1337
+ const msg: GuardianVerificationRequest = {
1338
+ type: 'guardian_verification',
1339
+ action: 'status',
1340
+ channel: 'telegram',
1341
+ assistantId: 'self',
1342
+ };
1343
+
1344
+ handleGuardianVerification(msg, mockSocket, ctx);
1345
+
1346
+ const resp = lastResponse();
1347
+ expect(resp).not.toBeNull();
1348
+ expect(resp!.guardianUsername).toBe('guardian_handle');
1349
+ expect(resp!.guardianDisplayName).toBe('Guardian Name');
1350
+ });
1351
+
1298
1352
  test('status action defaults channel to telegram when omitted (backward compat)', () => {
1299
1353
  const { ctx, lastResponse } = createMockCtx();
1300
1354
  const msg: GuardianVerificationRequest = {
@@ -1372,4 +1426,558 @@ describe('IPC handler channel-aware guardian status', () => {
1372
1426
  expect(resp!.guardianDeliveryChatId).toBeUndefined();
1373
1427
  expect(resp!.guardianExternalUserId).toBeUndefined();
1374
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
+ });
1375
1983
  });