@vellumai/assistant 0.3.4 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/Dockerfile +2 -0
  2. package/README.md +88 -2
  3. package/eslint.config.mjs +31 -0
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  6. package/scripts/ipc/generate-swift.ts +31 -2
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
  8. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  9. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
  10. package/src/__tests__/approval-message-composer.test.ts +253 -0
  11. package/src/__tests__/browser-manager.test.ts +1 -0
  12. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  13. package/src/__tests__/call-domain.test.ts +12 -2
  14. package/src/__tests__/call-orchestrator.test.ts +799 -249
  15. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  16. package/src/__tests__/call-recovery.test.ts +3 -0
  17. package/src/__tests__/call-routes-http.test.ts +32 -2
  18. package/src/__tests__/call-store.test.ts +3 -0
  19. package/src/__tests__/channel-approval-routes.test.ts +1277 -98
  20. package/src/__tests__/channel-approval.test.ts +37 -0
  21. package/src/__tests__/channel-approvals.test.ts +36 -50
  22. package/src/__tests__/channel-guardian.test.ts +630 -22
  23. package/src/__tests__/channel-readiness-service.test.ts +324 -0
  24. package/src/__tests__/checker.test.ts +14 -7
  25. package/src/__tests__/clarification-resolver.test.ts +44 -24
  26. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  27. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  28. package/src/__tests__/config-schema.test.ts +14 -8
  29. package/src/__tests__/context-window-manager.test.ts +30 -2
  30. package/src/__tests__/contradiction-checker.test.ts +20 -5
  31. package/src/__tests__/credential-security-invariants.test.ts +7 -2
  32. package/src/__tests__/daemon-lifecycle.test.ts +13 -12
  33. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  34. package/src/__tests__/dictation-mode-detection.test.ts +63 -0
  35. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  36. package/src/__tests__/entity-search.test.ts +615 -0
  37. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  38. package/src/__tests__/guardian-action-store.test.ts +123 -0
  39. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  40. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  41. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  42. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  43. package/src/__tests__/handlers-twilio-config.test.ts +533 -0
  44. package/src/__tests__/intent-routing.test.ts +2 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +291 -1
  46. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  47. package/src/__tests__/messaging-send-tool.test.ts +65 -0
  48. package/src/__tests__/model-intents.test.ts +96 -0
  49. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  50. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  51. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  52. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  53. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  54. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  55. package/src/__tests__/qdrant-manager.test.ts +27 -20
  56. package/src/__tests__/relay-server.test.ts +779 -40
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
  58. package/src/__tests__/run-orchestrator.test.ts +42 -4
  59. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  60. package/src/__tests__/runtime-runs.test.ts +16 -0
  61. package/src/__tests__/schedule-store.test.ts +18 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  63. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  64. package/src/__tests__/session-agent-loop.test.ts +857 -0
  65. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  66. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  67. package/src/__tests__/session-profile-injection.test.ts +6 -0
  68. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  69. package/src/__tests__/session-queue.test.ts +6 -0
  70. package/src/__tests__/session-runtime-assembly.test.ts +321 -13
  71. package/src/__tests__/session-slash-known.test.ts +6 -0
  72. package/src/__tests__/session-slash-queue.test.ts +6 -0
  73. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  74. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  75. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  76. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  77. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  78. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  79. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  80. package/src/__tests__/skills.test.ts +2 -0
  81. package/src/__tests__/sms-messaging-provider.test.ts +126 -0
  82. package/src/__tests__/starter-task-flow.test.ts +2 -0
  83. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  84. package/src/__tests__/system-prompt.test.ts +2 -0
  85. package/src/__tests__/task-management-tools.test.ts +2 -2
  86. package/src/__tests__/task-runner.test.ts +14 -4
  87. package/src/__tests__/terminal-tools.test.ts +25 -19
  88. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  90. package/src/__tests__/tool-executor.test.ts +23 -24
  91. package/src/__tests__/trust-store.test.ts +3 -3
  92. package/src/__tests__/twilio-rest.test.ts +29 -0
  93. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  94. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  95. package/src/__tests__/twilio-routes.test.ts +167 -11
  96. package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
  97. package/src/__tests__/user-reference.test.ts +2 -0
  98. package/src/__tests__/voice-quality.test.ts +222 -0
  99. package/src/__tests__/web-search.test.ts +46 -30
  100. package/src/__tests__/work-item-output.test.ts +110 -0
  101. package/src/agent/loop.ts +1 -1
  102. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  103. package/src/amazon/client.ts +1418 -0
  104. package/src/amazon/request-extractor.ts +135 -0
  105. package/src/amazon/session.ts +109 -0
  106. package/src/autonomy/autonomy-store.ts +5 -5
  107. package/src/browser-extension-relay/client.ts +124 -0
  108. package/src/browser-extension-relay/protocol.ts +63 -0
  109. package/src/browser-extension-relay/server.ts +177 -0
  110. package/src/bundler/app-bundler.ts +3 -3
  111. package/src/bundler/bundle-signer.ts +1 -1
  112. package/src/bundler/signature-verifier.ts +1 -1
  113. package/src/calls/call-conversation-messages.ts +33 -0
  114. package/src/calls/call-domain.ts +114 -10
  115. package/src/calls/call-orchestrator.ts +268 -59
  116. package/src/calls/call-pointer-messages.ts +53 -0
  117. package/src/calls/call-recovery.ts +3 -8
  118. package/src/calls/call-store.ts +69 -87
  119. package/src/calls/elevenlabs-config.ts +3 -2
  120. package/src/calls/guardian-action-sweep.ts +105 -0
  121. package/src/calls/guardian-dispatch.ts +203 -0
  122. package/src/calls/guardian-question-copy.ts +133 -0
  123. package/src/calls/relay-server.ts +466 -8
  124. package/src/calls/speaker-identification.ts +1 -1
  125. package/src/calls/twilio-config.ts +22 -14
  126. package/src/calls/twilio-provider.ts +6 -4
  127. package/src/calls/twilio-rest.ts +308 -7
  128. package/src/calls/twilio-routes.ts +65 -12
  129. package/src/calls/types.ts +3 -1
  130. package/src/channels/types.ts +25 -0
  131. package/src/cli/amazon.ts +815 -0
  132. package/src/cli/config-commands.ts +2 -2
  133. package/src/cli/core-commands.ts +4 -3
  134. package/src/cli/influencer.ts +244 -0
  135. package/src/cli/map.ts +89 -6
  136. package/src/cli.ts +1 -1
  137. package/src/config/agent-schema.ts +171 -0
  138. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  139. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  140. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  141. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  142. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  143. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  144. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  145. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  146. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  147. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  148. package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
  149. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
  150. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
  151. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  152. package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
  153. package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
  154. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  155. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  156. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  157. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  158. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  159. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  160. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  161. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
  162. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  163. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
  164. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
  165. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
  166. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
  167. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
  168. package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
  169. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
  170. package/src/config/bundled-skills/messaging/SKILL.md +33 -8
  171. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  172. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  174. package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
  175. package/src/config/bundled-skills/twitter/SKILL.md +19 -3
  176. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  177. package/src/config/bundled-tool-registry.ts +310 -0
  178. package/src/config/calls-schema.ts +181 -0
  179. package/src/config/core-schema.ts +309 -0
  180. package/src/config/defaults.ts +28 -3
  181. package/src/config/env-registry.ts +162 -0
  182. package/src/config/env.ts +175 -0
  183. package/src/config/loader.ts +6 -6
  184. package/src/config/memory-schema.ts +528 -0
  185. package/src/config/sandbox-schema.ts +55 -0
  186. package/src/config/schema.ts +158 -1133
  187. package/src/config/skill-state.ts +1 -1
  188. package/src/config/skills-schema.ts +32 -0
  189. package/src/config/skills.ts +35 -24
  190. package/src/config/system-prompt.ts +131 -56
  191. package/src/config/templates/IDENTITY.md +2 -2
  192. package/src/config/templates/SOUL.md +1 -1
  193. package/src/config/types.ts +1 -0
  194. package/src/config/user-reference.ts +4 -9
  195. package/src/config/vellum-skills/catalog.json +6 -7
  196. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  197. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
  198. package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
  199. package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
  200. package/src/context/window-manager.ts +27 -7
  201. package/src/daemon/approval-generators.ts +186 -0
  202. package/src/daemon/approved-devices-store.ts +140 -0
  203. package/src/daemon/assistant-attachments.ts +1 -1
  204. package/src/daemon/classifier.ts +35 -32
  205. package/src/daemon/config-watcher.ts +1 -1
  206. package/src/daemon/daemon-control.ts +217 -0
  207. package/src/daemon/handlers/apps.ts +2 -3
  208. package/src/daemon/handlers/config-channels.ts +158 -0
  209. package/src/daemon/handlers/config-inbox.ts +540 -0
  210. package/src/daemon/handlers/config-ingress.ts +231 -0
  211. package/src/daemon/handlers/config-integrations.ts +258 -0
  212. package/src/daemon/handlers/config-model.ts +143 -0
  213. package/src/daemon/handlers/config-parental.ts +163 -0
  214. package/src/daemon/handlers/config-scheduling.ts +172 -0
  215. package/src/daemon/handlers/config-slack.ts +92 -0
  216. package/src/daemon/handlers/config-telegram.ts +301 -0
  217. package/src/daemon/handlers/config-tools.ts +177 -0
  218. package/src/daemon/handlers/config-trust.ts +104 -0
  219. package/src/daemon/handlers/config-twilio.ts +1080 -0
  220. package/src/daemon/handlers/config.ts +53 -1689
  221. package/src/daemon/handlers/diagnostics.ts +1 -1
  222. package/src/daemon/handlers/dictation.ts +180 -0
  223. package/src/daemon/handlers/documents.ts +18 -32
  224. package/src/daemon/handlers/identity.ts +14 -23
  225. package/src/daemon/handlers/index.ts +11 -0
  226. package/src/daemon/handlers/misc.ts +3 -5
  227. package/src/daemon/handlers/pairing.ts +98 -0
  228. package/src/daemon/handlers/sessions.ts +56 -5
  229. package/src/daemon/handlers/shared.ts +6 -1
  230. package/src/daemon/handlers/skills.ts +1 -1
  231. package/src/daemon/handlers/twitter-auth.ts +2 -0
  232. package/src/daemon/handlers/work-items.ts +17 -9
  233. package/src/daemon/handlers/workspace-files.ts +4 -3
  234. package/src/daemon/install-cli-launchers.ts +113 -0
  235. package/src/daemon/ipc-contract/apps.ts +356 -0
  236. package/src/daemon/ipc-contract/browser.ts +74 -0
  237. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  238. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  239. package/src/daemon/ipc-contract/documents.ts +74 -0
  240. package/src/daemon/ipc-contract/inbox.ts +209 -0
  241. package/src/daemon/ipc-contract/integrations.ts +284 -0
  242. package/src/daemon/ipc-contract/memory.ts +48 -0
  243. package/src/daemon/ipc-contract/messages.ts +211 -0
  244. package/src/daemon/ipc-contract/pairing.ts +45 -0
  245. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  246. package/src/daemon/ipc-contract/schedules.ts +97 -0
  247. package/src/daemon/ipc-contract/sessions.ts +315 -0
  248. package/src/daemon/ipc-contract/shared.ts +42 -0
  249. package/src/daemon/ipc-contract/skills.ts +120 -0
  250. package/src/daemon/ipc-contract/subagents.ts +58 -0
  251. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  252. package/src/daemon/ipc-contract/trust.ts +60 -0
  253. package/src/daemon/ipc-contract/work-items.ts +225 -0
  254. package/src/daemon/ipc-contract/workspace.ts +113 -0
  255. package/src/daemon/ipc-contract-inventory.json +70 -0
  256. package/src/daemon/ipc-contract-inventory.ts +55 -29
  257. package/src/daemon/ipc-contract.ts +229 -2426
  258. package/src/daemon/ipc-protocol.ts +1 -1
  259. package/src/daemon/ipc-validate.ts +7 -0
  260. package/src/daemon/lifecycle.ts +97 -377
  261. package/src/daemon/pairing-store.ts +177 -0
  262. package/src/daemon/providers-setup.ts +43 -0
  263. package/src/daemon/ride-shotgun-handler.ts +68 -3
  264. package/src/daemon/server.ts +66 -46
  265. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  266. package/src/daemon/session-agent-loop.ts +117 -275
  267. package/src/daemon/session-dynamic-profile.ts +1 -1
  268. package/src/daemon/session-history.ts +1 -1
  269. package/src/daemon/session-media-retry.ts +1 -1
  270. package/src/daemon/session-messaging.ts +37 -2
  271. package/src/daemon/session-notifiers.ts +5 -25
  272. package/src/daemon/session-process.ts +99 -59
  273. package/src/daemon/session-queue-manager.ts +96 -4
  274. package/src/daemon/session-runtime-assembly.ts +199 -10
  275. package/src/daemon/session-surfaces.ts +19 -4
  276. package/src/daemon/session-tool-setup.ts +30 -30
  277. package/src/daemon/session-workspace.ts +1 -1
  278. package/src/daemon/session.ts +35 -2
  279. package/src/daemon/shutdown-handlers.ts +122 -0
  280. package/src/daemon/trace-emitter.ts +1 -1
  281. package/src/daemon/watch-handler.ts +36 -33
  282. package/src/doordash/cart-queries.ts +787 -0
  283. package/src/doordash/client.ts +144 -127
  284. package/src/doordash/order-queries.ts +85 -0
  285. package/src/doordash/queries.ts +10 -1308
  286. package/src/doordash/search-queries.ts +203 -0
  287. package/src/doordash/session.ts +3 -2
  288. package/src/doordash/store-queries.ts +246 -0
  289. package/src/doordash/types.ts +367 -0
  290. package/src/email/providers/agentmail.ts +2 -1
  291. package/src/email/providers/index.ts +3 -2
  292. package/src/email/service.ts +3 -2
  293. package/src/errors.ts +43 -0
  294. package/src/home-base/prebuilt/seed.ts +1 -1
  295. package/src/hooks/cli.ts +6 -5
  296. package/src/hooks/config.ts +6 -8
  297. package/src/hooks/discovery.ts +6 -5
  298. package/src/hooks/manager.ts +4 -3
  299. package/src/hooks/runner.ts +2 -2
  300. package/src/hooks/templates.ts +5 -5
  301. package/src/inbound/public-ingress-urls.ts +6 -4
  302. package/src/index.ts +4 -2
  303. package/src/influencer/client.ts +1104 -0
  304. package/src/instrument.ts +4 -3
  305. package/src/logfire.ts +4 -3
  306. package/src/memory/admin.ts +25 -35
  307. package/src/memory/attachments-store.ts +4 -7
  308. package/src/memory/channel-delivery-store.ts +30 -1
  309. package/src/memory/channel-guardian-store.ts +202 -2
  310. package/src/memory/clarification-resolver.ts +37 -33
  311. package/src/memory/conflict-store.ts +67 -61
  312. package/src/memory/contradiction-checker.ts +141 -117
  313. package/src/memory/conversation-store.ts +335 -51
  314. package/src/memory/db-connection.ts +27 -4
  315. package/src/memory/db-init.ts +265 -4
  316. package/src/memory/db.ts +14 -1
  317. package/src/memory/embedding-backend.ts +27 -5
  318. package/src/memory/embedding-ollama.ts +2 -1
  319. package/src/memory/entity-extractor.ts +38 -35
  320. package/src/memory/guardian-action-store.ts +430 -0
  321. package/src/memory/inbox-escalation-projection.ts +59 -0
  322. package/src/memory/inbox-thread-store.ts +218 -0
  323. package/src/memory/ingress-invite-store.ts +338 -0
  324. package/src/memory/ingress-member-store.ts +350 -0
  325. package/src/memory/items-extractor.ts +91 -97
  326. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  327. package/src/memory/job-handlers/media-processing.ts +69 -0
  328. package/src/memory/job-handlers/summarization.ts +32 -26
  329. package/src/memory/job-utils.ts +3 -10
  330. package/src/memory/jobs-store.ts +8 -10
  331. package/src/memory/jobs-worker.ts +55 -36
  332. package/src/memory/media-store.ts +759 -0
  333. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  334. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  335. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  336. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  337. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  338. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  339. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  340. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  341. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  342. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  343. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  344. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  345. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  346. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  347. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  348. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  349. package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
  350. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  351. package/src/memory/migrations/index.ts +24 -0
  352. package/src/memory/migrations/registry.ts +79 -0
  353. package/src/memory/migrations/validate-migration-state.ts +69 -0
  354. package/src/memory/qdrant-manager.ts +49 -8
  355. package/src/memory/query-builder.ts +1 -1
  356. package/src/memory/raw-query.ts +119 -0
  357. package/src/memory/recall-cache.ts +4 -1
  358. package/src/memory/retriever.ts +165 -47
  359. package/src/memory/schema-migration.ts +25 -984
  360. package/src/memory/schema.ts +228 -7
  361. package/src/memory/search/entity.ts +205 -31
  362. package/src/memory/search/lexical.ts +81 -52
  363. package/src/memory/search/ranking.ts +27 -23
  364. package/src/memory/search/semantic.ts +157 -19
  365. package/src/memory/search/types.ts +24 -0
  366. package/src/memory/shared-app-links-store.ts +4 -5
  367. package/src/memory/validation.ts +19 -0
  368. package/src/messaging/draft-store.ts +5 -6
  369. package/src/messaging/provider-types.ts +2 -0
  370. package/src/messaging/providers/sms/adapter.ts +201 -0
  371. package/src/messaging/providers/sms/client.ts +93 -0
  372. package/src/messaging/providers/sms/types.ts +7 -0
  373. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  374. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  375. package/src/messaging/providers/whatsapp/client.ts +67 -0
  376. package/src/messaging/style-analyzer.ts +5 -4
  377. package/src/messaging/thread-summarizer.ts +61 -69
  378. package/src/messaging/triage-engine.ts +62 -71
  379. package/src/migrations/config-merge.ts +53 -0
  380. package/src/migrations/data-layout.ts +68 -0
  381. package/src/migrations/data-merge.ts +33 -0
  382. package/src/migrations/hooks-merge.ts +90 -0
  383. package/src/migrations/index.ts +6 -0
  384. package/src/migrations/log.ts +23 -0
  385. package/src/migrations/skills-merge.ts +33 -0
  386. package/src/migrations/workspace-layout.ts +79 -0
  387. package/src/permissions/checker.ts +133 -11
  388. package/src/permissions/prompter.ts +14 -0
  389. package/src/permissions/shell-identity.ts +31 -1
  390. package/src/permissions/trust-store.ts +21 -1
  391. package/src/providers/anthropic/client.ts +4 -4
  392. package/src/providers/failover.ts +2 -2
  393. package/src/providers/model-intents.ts +70 -0
  394. package/src/providers/ollama/client.ts +2 -1
  395. package/src/providers/provider-send-message.ts +176 -0
  396. package/src/providers/registry.ts +71 -30
  397. package/src/providers/retry.ts +35 -1
  398. package/src/providers/types.ts +12 -1
  399. package/src/runtime/approval-conversation-turn.ts +97 -0
  400. package/src/runtime/approval-message-composer.ts +253 -0
  401. package/src/runtime/channel-approval-parser.ts +36 -2
  402. package/src/runtime/channel-approvals.ts +11 -24
  403. package/src/runtime/channel-guardian-service.ts +88 -21
  404. package/src/runtime/channel-readiness-service.ts +418 -0
  405. package/src/runtime/channel-readiness-types.ts +35 -0
  406. package/src/runtime/channel-retry-sweep.ts +184 -0
  407. package/src/runtime/guardian-context-resolver.ts +108 -0
  408. package/src/runtime/http-server.ts +275 -717
  409. package/src/runtime/http-types.ts +59 -3
  410. package/src/runtime/middleware/auth.ts +116 -0
  411. package/src/runtime/middleware/error-handler.ts +33 -0
  412. package/src/runtime/middleware/twilio-validation.ts +127 -0
  413. package/src/runtime/routes/app-routes.ts +1 -1
  414. package/src/runtime/routes/call-routes.ts +51 -7
  415. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  416. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  417. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  418. package/src/runtime/routes/channel-route-shared.ts +144 -0
  419. package/src/runtime/routes/channel-routes.ts +32 -1588
  420. package/src/runtime/routes/conversation-routes.ts +50 -7
  421. package/src/runtime/routes/events-routes.ts +2 -2
  422. package/src/runtime/routes/identity-routes.ts +126 -0
  423. package/src/runtime/routes/pairing-routes.ts +143 -0
  424. package/src/runtime/routes/run-routes.ts +15 -1
  425. package/src/runtime/run-orchestrator.ts +86 -35
  426. package/src/schedule/schedule-store.ts +36 -32
  427. package/src/schedule/scheduler.ts +3 -3
  428. package/src/security/encrypted-store.ts +5 -7
  429. package/src/security/oauth2.ts +45 -15
  430. package/src/security/parental-control-store.ts +183 -0
  431. package/src/security/secret-allowlist.ts +4 -3
  432. package/src/security/secret-scanner.ts +5 -5
  433. package/src/security/secure-keys.ts +1 -1
  434. package/src/security/token-manager.ts +3 -2
  435. package/src/services/vercel-deploy.ts +6 -2
  436. package/src/skills/tool-manifest.ts +3 -3
  437. package/src/skills/vellum-catalog-remote.ts +75 -16
  438. package/src/slack/slack-webhook.ts +2 -1
  439. package/src/swarm/orchestrator.ts +92 -1
  440. package/src/swarm/router-planner.ts +6 -9
  441. package/src/swarm/worker-prompts.ts +9 -12
  442. package/src/tasks/task-compiler.ts +19 -28
  443. package/src/tasks/task-runner.ts +1 -1
  444. package/src/tools/assets/materialize.ts +2 -2
  445. package/src/tools/assets/search.ts +15 -14
  446. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  447. package/src/tools/browser/auto-navigate.ts +1 -0
  448. package/src/tools/browser/browser-execution.ts +10 -1
  449. package/src/tools/browser/browser-manager.ts +119 -4
  450. package/src/tools/browser/network-recorder.ts +5 -0
  451. package/src/tools/calls/call-start.ts +1 -0
  452. package/src/tools/credentials/broker.ts +11 -2
  453. package/src/tools/credentials/metadata-store.ts +18 -14
  454. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  455. package/src/tools/credentials/vault.ts +49 -23
  456. package/src/tools/execution-target.ts +11 -1
  457. package/src/tools/executor.ts +68 -9
  458. package/src/tools/host-terminal/cli-discover.ts +1 -1
  459. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  460. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  461. package/src/tools/network/script-proxy/server.ts +1 -1
  462. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  463. package/src/tools/network/web-fetch.ts +18 -2
  464. package/src/tools/network/web-search.ts +8 -4
  465. package/src/tools/reminder/reminder-store.ts +14 -15
  466. package/src/tools/schedule/create.ts +1 -0
  467. package/src/tools/schedule/list.ts +2 -1
  468. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  469. package/src/tools/skills/skill-script-runner.ts +24 -9
  470. package/src/tools/skills/skill-tool-factory.ts +1 -0
  471. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  472. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  473. package/src/tools/terminal/parser.ts +50 -0
  474. package/src/tools/types.ts +2 -0
  475. package/src/tools/watcher/delete.ts +6 -0
  476. package/src/tools/weather/service.ts +1 -1
  477. package/src/twitter/client.ts +190 -24
  478. package/src/twitter/router.ts +1 -1
  479. package/src/twitter/session.ts +4 -3
  480. package/src/util/clipboard.ts +1 -1
  481. package/src/util/errors.ts +65 -8
  482. package/src/util/fs.ts +40 -0
  483. package/src/util/json.ts +10 -0
  484. package/src/util/log-redact.ts +189 -0
  485. package/src/util/logger.ts +19 -17
  486. package/src/util/object.ts +3 -0
  487. package/src/util/platform.ts +105 -363
  488. package/src/util/pricing.ts +1 -1
  489. package/src/util/promise-guard.ts +1 -1
  490. package/src/util/retry.ts +19 -0
  491. package/src/util/row-mapper.ts +79 -0
  492. package/src/util/silently.ts +21 -0
  493. package/src/watcher/engine.ts +5 -1
  494. package/src/watcher/provider-types.ts +20 -0
  495. package/src/watcher/providers/github.ts +156 -0
  496. package/src/watcher/providers/gmail.ts +1 -0
  497. package/src/watcher/providers/google-calendar.ts +1 -0
  498. package/src/watcher/providers/linear.ts +460 -0
  499. package/src/watcher/providers/slack.ts +1 -0
  500. package/src/work-items/work-item-runner.ts +1 -1
  501. package/src/workspace/git-service.ts +1 -1
  502. package/src/workspace/provider-commit-message-generator.ts +51 -22
  503. package/src/__tests__/call-bridge.test.ts +0 -517
  504. package/src/__tests__/session-process-bridge.test.ts +0 -244
  505. package/src/calls/call-bridge.ts +0 -168
  506. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Layered approval message composition system.
3
+ *
4
+ * Generates approval prompt text through a priority chain:
5
+ * 1. Assistant preface (macOS parity — reuse existing assistant text)
6
+ * 2. Generator-produced rewrite of deterministic fallback text (when provided by daemon)
7
+ * 3. Deterministic fallback templates (natural, scenario-specific messages)
8
+ */
9
+ import { getLogger } from '../util/logger.js';
10
+ import type { ApprovalCopyGenerator } from './http-types.js';
11
+
12
+ const log = getLogger('approval-message-composer');
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Types
16
+ // ---------------------------------------------------------------------------
17
+
18
+ export type ApprovalMessageScenario =
19
+ | 'standard_prompt'
20
+ | 'guardian_prompt'
21
+ | 'reminder_prompt'
22
+ | 'guardian_delivery_failed'
23
+ | 'guardian_request_forwarded'
24
+ | 'guardian_disambiguation'
25
+ | 'guardian_identity_mismatch'
26
+ | 'request_pending_guardian'
27
+ | 'guardian_decision_outcome'
28
+ | 'guardian_expired_requester'
29
+ | 'guardian_expired_guardian'
30
+ | 'guardian_verify_success'
31
+ | 'guardian_verify_failed'
32
+ | 'guardian_verify_challenge_setup'
33
+ | 'guardian_verify_status_bound'
34
+ | 'guardian_verify_status_unbound'
35
+ | 'guardian_deny_no_identity'
36
+ | 'guardian_deny_no_binding'
37
+ | 'requester_cancel'
38
+ | 'approval_already_resolved';
39
+
40
+ export interface ApprovalMessageContext {
41
+ scenario: ApprovalMessageScenario;
42
+ channel?: string;
43
+ toolName?: string;
44
+ requesterIdentifier?: string;
45
+ guardianIdentifier?: string;
46
+ pendingCount?: number;
47
+ decision?: 'approved' | 'denied';
48
+ richUi?: boolean;
49
+ /** Pre-existing assistant text to reuse (macOS parity). */
50
+ assistantPreface?: string;
51
+ verifyCommand?: string;
52
+ ttlSeconds?: number;
53
+ failureReason?: string;
54
+ }
55
+
56
+ export interface ComposeApprovalMessageGenerativeOptions {
57
+ /**
58
+ * Optional fallback message to use when generation fails. If omitted,
59
+ * the deterministic scenario fallback is used.
60
+ */
61
+ fallbackText?: string;
62
+ /**
63
+ * Require these standalone words in the generated output (case-insensitive).
64
+ * Useful for plain-text decision flows where parser-compatible keywords
65
+ * like yes/no/always must be present.
66
+ */
67
+ requiredKeywords?: string[];
68
+ timeoutMs?: number;
69
+ maxTokens?: number;
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Public API
74
+ // ---------------------------------------------------------------------------
75
+
76
+ /**
77
+ * Compose an approval message using layered source selection:
78
+ * 1. If an assistant preface is provided and non-empty, return it directly.
79
+ * 2. Otherwise fall back to a deterministic scenario-specific template.
80
+ */
81
+ export function composeApprovalMessage(context: ApprovalMessageContext): string {
82
+ if (context.assistantPreface && context.assistantPreface.trim().length > 0) {
83
+ return context.assistantPreface;
84
+ }
85
+
86
+ return getFallbackMessage(context);
87
+ }
88
+
89
+ /** @internal Exported for use by the daemon-injected generator implementation. */
90
+ export function escapeRegExp(input: string): string {
91
+ return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
92
+ }
93
+
94
+ /** @internal Exported for use by the daemon-injected generator implementation. */
95
+ export function includesRequiredKeywords(text: string, requiredKeywords: string[] | undefined): boolean {
96
+ if (!requiredKeywords || requiredKeywords.length === 0) return true;
97
+ return requiredKeywords.every((keyword) => {
98
+ const re = new RegExp(`\\b${escapeRegExp(keyword)}\\b`, 'i');
99
+ return re.test(text);
100
+ });
101
+ }
102
+
103
+ /** @internal Exported for use by the daemon-injected generator implementation. */
104
+ export function buildGenerationPrompt(
105
+ context: ApprovalMessageContext,
106
+ fallbackText: string,
107
+ requiredKeywords: string[] | undefined,
108
+ ): string {
109
+ const keywordClause = requiredKeywords && requiredKeywords.length > 0
110
+ ? `Required words to include (as standalone words): ${requiredKeywords.join(', ')}.\n`
111
+ : '';
112
+ return [
113
+ 'Rewrite the following approval/guardian message as a natural assistant reply to the user.',
114
+ 'Keep the same concrete facts and next-step guidance.',
115
+ keywordClause,
116
+ `Context JSON: ${JSON.stringify(context)}`,
117
+ `Fallback message: ${fallbackText}`,
118
+ ].filter(Boolean).join('\n\n');
119
+ }
120
+
121
+ /** Constants for the generator implementation (moved to exports for daemon lifecycle). */
122
+ export const APPROVAL_COPY_TIMEOUT_MS = 4_000;
123
+ export const APPROVAL_COPY_MAX_TOKENS = 180;
124
+ export const APPROVAL_COPY_SYSTEM_PROMPT =
125
+ 'You are an assistant writing one user-facing message about permissions/approval state. '
126
+ + 'Keep it concise, natural, and actionable. Preserve factual details exactly. '
127
+ + 'Do not mention internal systems, scenario IDs, or policy engine details. '
128
+ + 'Return plain text only.';
129
+
130
+ /**
131
+ * Compose user-facing approval copy using the daemon-injected generator when
132
+ * available, with deterministic fallback for reliability.
133
+ *
134
+ * The generator parameter is the daemon-provided function that knows about
135
+ * providers. When absent (or in test env), only the deterministic fallback
136
+ * is used.
137
+ */
138
+ export async function composeApprovalMessageGenerative(
139
+ context: ApprovalMessageContext,
140
+ options: ComposeApprovalMessageGenerativeOptions = {},
141
+ generator?: ApprovalCopyGenerator,
142
+ ): Promise<string> {
143
+ if (context.assistantPreface && context.assistantPreface.trim().length > 0) {
144
+ return context.assistantPreface;
145
+ }
146
+
147
+ const fallbackText = options.fallbackText?.trim() || getFallbackMessage(context);
148
+
149
+ if (process.env.NODE_ENV === 'test') {
150
+ return fallbackText;
151
+ }
152
+
153
+ if (generator) {
154
+ try {
155
+ const generated = await generator(context, options);
156
+ if (generated) return generated;
157
+ } catch (err) {
158
+ log.warn({ err, scenario: context.scenario }, 'Failed to generate approval copy, using fallback');
159
+ }
160
+ }
161
+
162
+ return fallbackText;
163
+ }
164
+
165
+ // ---------------------------------------------------------------------------
166
+ // Deterministic fallback templates
167
+ // ---------------------------------------------------------------------------
168
+
169
+ /**
170
+ * Return a scenario-specific deterministic fallback message.
171
+ *
172
+ * Each template is slightly more conversational than the old hard-coded
173
+ * strings while preserving all required semantic content (tool name,
174
+ * who must approve, next action, etc.).
175
+ */
176
+ export function getFallbackMessage(context: ApprovalMessageContext): string {
177
+ switch (context.scenario) {
178
+ case 'standard_prompt':
179
+ return `I'd like to use the tool "${context.toolName ?? 'unknown'}". Would you like to allow this?`;
180
+
181
+ case 'guardian_prompt':
182
+ return `${context.requesterIdentifier ?? 'A user'} is requesting to use "${context.toolName ?? 'unknown'}". Please approve or deny this request.`;
183
+
184
+ case 'reminder_prompt':
185
+ return 'There is a pending approval request. Ask a follow-up question or say approve/deny when you are ready.';
186
+
187
+ case 'guardian_delivery_failed':
188
+ return context.toolName
189
+ ? `Your request to run "${context.toolName}" could not be sent to the guardian for approval. The request has been denied for safety.`
190
+ : "I wasn't able to reach the guardian to request approval. The request has been denied for safety.";
191
+
192
+ case 'guardian_request_forwarded':
193
+ return `Your request to use "${context.toolName ?? 'unknown'}" has been forwarded to the guardian for approval. I'll let you know once they decide.`;
194
+
195
+ case 'guardian_disambiguation':
196
+ return `There are ${context.pendingCount ?? 'multiple'} pending approval requests. Please use the approval buttons to specify which request you're responding to.`;
197
+
198
+ case 'guardian_identity_mismatch':
199
+ return 'This approval request can only be handled by the designated guardian.';
200
+
201
+ case 'request_pending_guardian':
202
+ return 'Your request is pending guardian approval. Please wait for the guardian to respond.';
203
+
204
+ case 'guardian_decision_outcome':
205
+ return `The guardian has ${context.decision ?? 'decided on'} your request to use "${context.toolName ?? 'unknown'}".`;
206
+
207
+ case 'guardian_expired_requester':
208
+ return `The approval request for "${context.toolName ?? 'unknown'}" has expired without a guardian response. The request has been denied.`;
209
+
210
+ case 'guardian_expired_guardian':
211
+ return `The approval request from ${context.requesterIdentifier ?? 'the requester'} for "${context.toolName ?? 'unknown'}" has expired.`;
212
+
213
+ case 'guardian_verify_success':
214
+ return 'Guardian verification successful! You are now set as the guardian for this channel.';
215
+
216
+ case 'guardian_verify_failed':
217
+ return `Verification failed. ${context.failureReason ?? 'Please try again.'}`;
218
+
219
+ case 'guardian_verify_challenge_setup':
220
+ if (context.channel === 'voice') {
221
+ // Voice challenges use a six-digit numeric code that can be spoken aloud
222
+ const code = context.verifyCommand?.replace('/guardian_verify ', '') ?? 'the verification code';
223
+ return `To complete guardian verification, speak or enter the six-digit code: ${code}. This code expires in ${Math.round((context.ttlSeconds ?? 600) / 60)} minutes.`;
224
+ }
225
+ return `To complete guardian verification, send ${context.verifyCommand ?? 'the verification command'} within ${context.ttlSeconds ?? 60} seconds.`;
226
+
227
+ case 'guardian_verify_status_bound':
228
+ return 'A guardian is currently active for this channel.';
229
+
230
+ case 'guardian_verify_status_unbound':
231
+ return 'No guardian is currently configured for this channel.';
232
+
233
+ case 'guardian_deny_no_identity':
234
+ return 'This action requires approval, but your identity could not be verified. The request has been denied for safety.';
235
+
236
+ case 'guardian_deny_no_binding':
237
+ return 'This action requires guardian approval, but no guardian has been configured for this channel. The request has been denied for safety.';
238
+
239
+ case 'requester_cancel':
240
+ return context.toolName
241
+ ? `Your request to use "${context.toolName}" has been cancelled.`
242
+ : 'Your pending request has been cancelled.';
243
+
244
+ case 'approval_already_resolved':
245
+ return 'This approval request has already been resolved.';
246
+
247
+ default: {
248
+ // Exhaustive check — TypeScript will flag if a scenario is missing.
249
+ const _exhaustive: never = context.scenario;
250
+ return `Approval required. ${String(_exhaustive)}`;
251
+ }
252
+ }
253
+ }
@@ -4,6 +4,11 @@
4
4
  * Parses inbound user text to determine whether it matches an approval,
5
5
  * rejection, or "approve always" intent. This module is transport-agnostic
6
6
  * and can be used by any channel adapter (Telegram, SMS, etc.).
7
+ *
8
+ * Both the standard and guardian approval flows now use the conversational
9
+ * approval engine as the primary classifier. This deterministic parser is
10
+ * retained only as a legacy fallback for when the conversational engine is
11
+ * not injected (i.e. approvalConversationGenerator is undefined).
7
12
  */
8
13
 
9
14
  import type { ApprovalAction, ApprovalDecisionResult } from './channel-approval-types.js';
@@ -44,6 +49,30 @@ const PHRASE_MAP = buildPhraseMap();
44
49
  // Public API
45
50
  // ---------------------------------------------------------------------------
46
51
 
52
+ // ---------------------------------------------------------------------------
53
+ // Run-reference tag extraction
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * Pattern matching a `[ref:<runId>]` disambiguation tag appended to
58
+ * plain-text approval prompts. Guardians can include this tag in their
59
+ * reply so that `handleApprovalInterception` can resolve the correct
60
+ * pending approval when multiple approvals target the same chat.
61
+ */
62
+ const REF_TAG_RE = /\[ref:([^\]]+)\]/i;
63
+
64
+ /**
65
+ * Extract a run-reference tag from the text and return the cleaned
66
+ * decision text plus the extracted runId (if any).
67
+ */
68
+ function extractRefTag(text: string): { cleaned: string; runId?: string } {
69
+ const match = REF_TAG_RE.exec(text);
70
+ if (!match) return { cleaned: text };
71
+ const runId = match[1].trim();
72
+ const cleaned = text.replace(REF_TAG_RE, '').trim();
73
+ return { cleaned, runId: runId || undefined };
74
+ }
75
+
47
76
  /**
48
77
  * Parse a plain-text message into an approval decision.
49
78
  *
@@ -51,10 +80,15 @@ const PHRASE_MAP = buildPhraseMap();
51
80
  * of the known intent phrases, or `null` if it does not match.
52
81
  *
53
82
  * Matching is case-insensitive with leading/trailing whitespace trimmed.
83
+ *
84
+ * When the text contains a `[ref:<runId>]` tag (appended by the
85
+ * plain-text fallback path), the extracted runId is included in the
86
+ * result so the caller can disambiguate among multiple pending approvals.
54
87
  */
55
88
  export function parseApprovalDecision(text: string): ApprovalDecisionResult | null {
56
- const normalised = text.trim().toLowerCase();
89
+ const { cleaned, runId } = extractRefTag(text);
90
+ const normalised = cleaned.trim().toLowerCase();
57
91
  const action = PHRASE_MAP.get(normalised);
58
92
  if (!action) return null;
59
- return { action, source: 'plain_text' };
93
+ return { action, source: 'plain_text', ...(runId ? { runId } : {}) };
60
94
  }
@@ -7,7 +7,6 @@
7
7
  * 1. Detect pending confirmations for a conversation
8
8
  * 2. Build human-readable approval prompts with action buttons
9
9
  * 3. Consume user decisions and apply them to the underlying run
10
- * 4. Build reminder prompts when non-decision messages arrive
11
10
  */
12
11
 
13
12
  import { getPendingConfirmationsByConversation, getRun } from '../memory/runs-store.js';
@@ -21,6 +20,7 @@ import type {
21
20
  ApprovalUIMetadata,
22
21
  ApprovalDecisionResult,
23
22
  } from './channel-approval-types.js';
23
+ import { composeApprovalMessage } from './approval-message-composer.js';
24
24
 
25
25
  // ---------------------------------------------------------------------------
26
26
  // 1. Detect pending confirmations and build prompt
@@ -47,13 +47,17 @@ export function getChannelApprovalPrompt(
47
47
  * Internal helper: turn a PendingRunInfo into a ChannelApprovalPrompt.
48
48
  */
49
49
  function buildPromptFromRunInfo(info: PendingRunInfo): ChannelApprovalPrompt {
50
- const promptText = `The assistant wants to use the tool "${info.toolName}". Do you want to allow this?`;
50
+ const promptText = composeApprovalMessage({
51
+ scenario: 'standard_prompt',
52
+ toolName: info.toolName,
53
+ });
51
54
 
52
55
  // Hide "approve always" when persistent trust rules are disallowed for this invocation.
53
56
  const actions = info.persistentDecisionsAllowed === false
54
57
  ? DEFAULT_APPROVAL_ACTIONS.filter((a) => a.id !== 'approve_always')
55
58
  : [...DEFAULT_APPROVAL_ACTIONS];
56
59
 
60
+ // Plain-text fallback must remain parser-compatible (contains "yes"/"always"/"no" keywords).
57
61
  const plainTextFallback = info.persistentDecisionsAllowed === false
58
62
  ? `${promptText}\n\nReply "yes" to approve or "no" to reject.`
59
63
  : `${promptText}\n\nReply "yes" to approve once, "always" to approve always, or "no" to reject.`;
@@ -166,8 +170,11 @@ export function buildGuardianApprovalPrompt(
166
170
  info: PendingRunInfo,
167
171
  requesterIdentifier: string,
168
172
  ): ChannelApprovalPrompt {
169
- const promptText =
170
- `User ${requesterIdentifier} is requesting to run "${info.toolName}". Approve or deny?`;
173
+ const promptText = composeApprovalMessage({
174
+ scenario: 'guardian_prompt',
175
+ toolName: info.toolName,
176
+ requesterIdentifier,
177
+ });
171
178
 
172
179
  // Guardian approvals are always one-time decisions — "approve always"
173
180
  // doesn't make sense when the guardian is approving on behalf of someone else.
@@ -198,23 +205,3 @@ const RICH_APPROVAL_CHANNELS: ReadonlySet<string> = new Set(['telegram']);
198
205
  export function channelSupportsRichApprovalUI(channel: string): boolean {
199
206
  return RICH_APPROVAL_CHANNELS.has(channel);
200
207
  }
201
-
202
- // ---------------------------------------------------------------------------
203
- // 6. Reminder prompt for non-decision messages
204
- // ---------------------------------------------------------------------------
205
-
206
- /**
207
- * Build a reminder prompt when the user sends a non-decision message while
208
- * an approval is pending. Reuses the original actions and fallback text
209
- * but prefixes the prompt text with a reminder.
210
- */
211
- export function buildReminderPrompt(
212
- pendingPrompt: ChannelApprovalPrompt,
213
- ): ChannelApprovalPrompt {
214
- const reminderPrefix = "I'm still waiting for your decision on the previous request.";
215
- return {
216
- promptText: `${reminderPrefix}\n\n${pendingPrompt.promptText}`,
217
- actions: pendingPrompt.actions,
218
- plainTextFallback: `${reminderPrefix}\n\n${pendingPrompt.plainTextFallback}`,
219
- };
220
- }
@@ -12,14 +12,17 @@ import {
12
12
  createBinding,
13
13
  getActiveBinding,
14
14
  revokeBinding as storeRevokeBinding,
15
+ revokePendingChallenges as storeRevokePendingChallenges,
15
16
  createChallenge,
16
17
  findPendingChallengeByHash,
18
+ findPendingChallengeForChannel,
17
19
  consumeChallenge,
18
20
  getRateLimit,
19
21
  recordInvalidAttempt,
20
22
  resetRateLimit,
21
23
  } from '../memory/channel-guardian-store.js';
22
- import type { GuardianBinding } from '../memory/channel-guardian-store.js';
24
+ import type { GuardianBinding, VerificationChallenge } from '../memory/channel-guardian-store.js';
25
+ import { composeApprovalMessage } from './approval-message-composer.js';
23
26
 
24
27
  // ---------------------------------------------------------------------------
25
28
  // Constants
@@ -44,6 +47,8 @@ const RATE_LIMIT_LOCKOUT_MS = 30 * 60 * 1000;
44
47
  export interface CreateChallengeResult {
45
48
  challengeId: string;
46
49
  secret: string;
50
+ verifyCommand: string;
51
+ ttlSeconds: number;
47
52
  instruction: string;
48
53
  }
49
54
 
@@ -64,31 +69,32 @@ function hashSecret(secret: string): string {
64
69
  // ---------------------------------------------------------------------------
65
70
 
66
71
  /**
67
- * Build a human-readable channel label for use in guardian challenge
68
- * instructions. Avoids hardcoding "Telegram" so SMS and future
69
- * channels get appropriate wording.
72
+ * Generate a six-digit numeric secret for voice channel challenges.
73
+ * Uses cryptographic randomness to pick a number in [100000, 999999].
70
74
  */
71
- function channelLabel(channel: string): string {
72
- switch (channel) {
73
- case 'telegram': return 'Telegram';
74
- case 'sms': return 'SMS';
75
- default: return channel;
76
- }
75
+ function generateVoiceSecret(): string {
76
+ const buf = randomBytes(4);
77
+ const num = buf.readUInt32BE(0);
78
+ // Map to the range [100000, 999999] (900000 possible values)
79
+ return String(100000 + (num % 900000));
77
80
  }
78
81
 
79
82
  /**
80
83
  * Create a new verification challenge for a guardian candidate.
81
84
  *
82
- * Generates a random secret, hashes it (SHA-256), and stores the
83
- * challenge record with a 10-minute TTL. The raw secret is returned
84
- * so it can be displayed to the user; only the hash is persisted.
85
+ * For voice channels, generates a six-digit numeric secret that can be
86
+ * spoken aloud. For all other channels, generates a 32-byte hex secret.
87
+ *
88
+ * Hashes the secret (SHA-256) and stores the challenge record with a
89
+ * 10-minute TTL. The raw secret is returned so it can be displayed to
90
+ * the user; only the hash is persisted.
85
91
  */
86
92
  export function createVerificationChallenge(
87
93
  assistantId: string,
88
94
  channel: string,
89
95
  sessionId?: string,
90
96
  ): CreateChallengeResult {
91
- const secret = randomBytes(32).toString('hex');
97
+ const secret = channel === 'voice' ? generateVoiceSecret() : randomBytes(32).toString('hex');
92
98
  const challengeHash = hashSecret(secret);
93
99
  const challengeId = uuid();
94
100
  const expiresAt = Date.now() + CHALLENGE_TTL_MS;
@@ -102,12 +108,20 @@ export function createVerificationChallenge(
102
108
  createdBySessionId: sessionId,
103
109
  });
104
110
 
105
- const label = channelLabel(channel);
111
+ const verifyCommand = `/guardian_verify ${secret}`;
112
+ const ttlSeconds = CHALLENGE_TTL_MS / 1000;
106
113
 
107
114
  return {
108
115
  challengeId,
109
116
  secret,
110
- instruction: `Send \`/guardian_verify ${secret}\` to your bot via ${label} within 10 minutes.`,
117
+ verifyCommand,
118
+ ttlSeconds,
119
+ instruction: composeApprovalMessage({
120
+ scenario: 'guardian_verify_challenge_setup',
121
+ channel,
122
+ verifyCommand,
123
+ ttlSeconds,
124
+ }),
111
125
  };
112
126
  }
113
127
 
@@ -129,13 +143,21 @@ export function validateAndConsumeChallenge(
129
143
  secret: string,
130
144
  actorExternalUserId: string,
131
145
  actorChatId: string,
146
+ actorUsername?: string,
147
+ actorDisplayName?: string,
132
148
  ): ValidateChallengeResult {
133
149
  // ── Rate-limit check ──
134
150
  const existing = getRateLimit(assistantId, channel, actorExternalUserId, actorChatId);
135
- if (existing && existing.lockedUntil !== null && Date.now() < existing.lockedUntil) {
151
+ if (existing && existing.lockedUntil != null && Date.now() < existing.lockedUntil) {
136
152
  // Use the same generic failure message to avoid leaking whether the
137
153
  // actor is rate-limited vs. the code is genuinely wrong.
138
- return { success: false, reason: 'Verification failed. Please try again later.' };
154
+ return {
155
+ success: false,
156
+ reason: composeApprovalMessage({
157
+ scenario: 'guardian_verify_failed',
158
+ failureReason: 'The verification code is invalid or has expired.',
159
+ }),
160
+ };
139
161
  }
140
162
 
141
163
  const challengeHash = hashSecret(secret);
@@ -146,7 +168,13 @@ export function validateAndConsumeChallenge(
146
168
  assistantId, channel, actorExternalUserId, actorChatId,
147
169
  RATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX_ATTEMPTS, RATE_LIMIT_LOCKOUT_MS,
148
170
  );
149
- return { success: false, reason: 'Verification failed. Please try again later.' };
171
+ return {
172
+ success: false,
173
+ reason: composeApprovalMessage({
174
+ scenario: 'guardian_verify_failed',
175
+ failureReason: 'The verification code is invalid or has expired.',
176
+ }),
177
+ };
150
178
  }
151
179
 
152
180
  if (Date.now() > challenge.expiresAt) {
@@ -154,7 +182,13 @@ export function validateAndConsumeChallenge(
154
182
  assistantId, channel, actorExternalUserId, actorChatId,
155
183
  RATE_LIMIT_WINDOW_MS, RATE_LIMIT_MAX_ATTEMPTS, RATE_LIMIT_LOCKOUT_MS,
156
184
  );
157
- return { success: false, reason: 'Verification failed. Please try again later.' };
185
+ return {
186
+ success: false,
187
+ reason: composeApprovalMessage({
188
+ scenario: 'guardian_verify_failed',
189
+ failureReason: 'The verification code is invalid or has expired.',
190
+ }),
191
+ };
158
192
  }
159
193
 
160
194
  // Consume the challenge so it cannot be reused
@@ -166,6 +200,14 @@ export function validateAndConsumeChallenge(
166
200
  // Revoke any existing active binding before creating a new one
167
201
  storeRevokeBinding(assistantId, channel);
168
202
 
203
+ const metadata: Record<string, string> = {};
204
+ if (actorUsername && actorUsername.trim().length > 0) {
205
+ metadata.username = actorUsername.trim();
206
+ }
207
+ if (actorDisplayName && actorDisplayName.trim().length > 0) {
208
+ metadata.displayName = actorDisplayName.trim();
209
+ }
210
+
169
211
  // Create the new guardian binding
170
212
  const binding = createBinding({
171
213
  assistantId,
@@ -173,6 +215,7 @@ export function validateAndConsumeChallenge(
173
215
  guardianExternalUserId: actorExternalUserId,
174
216
  guardianDeliveryChatId: actorChatId,
175
217
  verifiedVia: 'challenge',
218
+ metadataJson: Object.keys(metadata).length > 0 ? JSON.stringify(metadata) : null,
176
219
  });
177
220
 
178
221
  return { success: true, bindingId: binding.id };
@@ -198,7 +241,7 @@ export function isGuardian(
198
241
  externalUserId: string,
199
242
  ): boolean {
200
243
  const binding = getActiveBinding(assistantId, channel);
201
- return binding !== null && binding.guardianExternalUserId === externalUserId;
244
+ return binding != null && binding.guardianExternalUserId === externalUserId;
202
245
  }
203
246
 
204
247
  /**
@@ -210,3 +253,27 @@ export function revokeBinding(
210
253
  ): boolean {
211
254
  return storeRevokeBinding(assistantId, channel);
212
255
  }
256
+
257
+ /**
258
+ * Revoke all pending challenges for a given assistant and channel.
259
+ * Called when the user cancels verification so that stale challenges
260
+ * don't gate inbound calls.
261
+ */
262
+ export function revokePendingChallenges(
263
+ assistantId: string,
264
+ channel: string,
265
+ ): void {
266
+ storeRevokePendingChallenges(assistantId, channel);
267
+ }
268
+
269
+ /**
270
+ * Look up a pending (non-expired) verification challenge for a given
271
+ * assistant and channel. Used by relay setup to detect whether an active
272
+ * voice verification session exists.
273
+ */
274
+ export function getPendingChallenge(
275
+ assistantId: string,
276
+ channel: string,
277
+ ): VerificationChallenge | null {
278
+ return findPendingChallengeForChannel(assistantId, channel);
279
+ }