@vellumai/assistant 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (487) hide show
  1. package/README.md +51 -0
  2. package/eslint.config.mjs +31 -0
  3. package/package.json +1 -1
  4. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  5. package/scripts/ipc/generate-swift.ts +18 -2
  6. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +338 -1
  7. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  8. package/src/__tests__/browser-manager.test.ts +1 -0
  9. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  10. package/src/__tests__/call-orchestrator.test.ts +752 -271
  11. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  12. package/src/__tests__/call-recovery.test.ts +3 -0
  13. package/src/__tests__/call-routes-http.test.ts +5 -0
  14. package/src/__tests__/call-store.test.ts +3 -0
  15. package/src/__tests__/channel-approval-routes.test.ts +1260 -85
  16. package/src/__tests__/channel-approval.test.ts +37 -0
  17. package/src/__tests__/channel-approvals.test.ts +4 -65
  18. package/src/__tests__/channel-guardian.test.ts +556 -0
  19. package/src/__tests__/channel-readiness-service.test.ts +74 -7
  20. package/src/__tests__/checker.test.ts +14 -7
  21. package/src/__tests__/clarification-resolver.test.ts +44 -24
  22. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  23. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  24. package/src/__tests__/config-schema.test.ts +12 -7
  25. package/src/__tests__/context-window-manager.test.ts +30 -2
  26. package/src/__tests__/contradiction-checker.test.ts +20 -5
  27. package/src/__tests__/credential-security-invariants.test.ts +6 -2
  28. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  29. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  30. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  31. package/src/__tests__/guardian-action-store.test.ts +123 -0
  32. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  33. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  34. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  35. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  36. package/src/__tests__/handlers-twilio-config.test.ts +126 -0
  37. package/src/__tests__/intent-routing.test.ts +2 -0
  38. package/src/__tests__/ipc-snapshot.test.ts +228 -1
  39. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  40. package/src/__tests__/model-intents.test.ts +96 -0
  41. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  42. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  43. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  44. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  45. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  46. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  47. package/src/__tests__/qdrant-manager.test.ts +27 -20
  48. package/src/__tests__/relay-server.test.ts +779 -40
  49. package/src/__tests__/run-orchestrator-assistant-events.test.ts +2 -0
  50. package/src/__tests__/run-orchestrator.test.ts +20 -4
  51. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  52. package/src/__tests__/runtime-runs.test.ts +16 -0
  53. package/src/__tests__/schedule-store.test.ts +18 -4
  54. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  55. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  56. package/src/__tests__/session-agent-loop.test.ts +857 -0
  57. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  58. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  59. package/src/__tests__/session-profile-injection.test.ts +6 -0
  60. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  61. package/src/__tests__/session-queue.test.ts +6 -0
  62. package/src/__tests__/session-runtime-assembly.test.ts +237 -13
  63. package/src/__tests__/session-slash-known.test.ts +6 -0
  64. package/src/__tests__/session-slash-queue.test.ts +6 -0
  65. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  66. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  67. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  68. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  69. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  70. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  71. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  72. package/src/__tests__/skills.test.ts +2 -0
  73. package/src/__tests__/sms-messaging-provider.test.ts +2 -1
  74. package/src/__tests__/starter-task-flow.test.ts +2 -0
  75. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  76. package/src/__tests__/system-prompt.test.ts +2 -0
  77. package/src/__tests__/task-management-tools.test.ts +2 -2
  78. package/src/__tests__/task-runner.test.ts +14 -4
  79. package/src/__tests__/terminal-tools.test.ts +25 -19
  80. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  81. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  82. package/src/__tests__/tool-executor.test.ts +23 -24
  83. package/src/__tests__/trust-store.test.ts +3 -3
  84. package/src/__tests__/twilio-rest.test.ts +29 -0
  85. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  86. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  87. package/src/__tests__/twilio-routes.test.ts +141 -21
  88. package/src/__tests__/user-reference.test.ts +2 -0
  89. package/src/__tests__/voice-quality.test.ts +222 -0
  90. package/src/__tests__/web-search.test.ts +45 -29
  91. package/src/agent/loop.ts +1 -1
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  93. package/src/amazon/client.ts +1418 -0
  94. package/src/amazon/request-extractor.ts +135 -0
  95. package/src/amazon/session.ts +109 -0
  96. package/src/autonomy/autonomy-store.ts +5 -5
  97. package/src/browser-extension-relay/client.ts +124 -0
  98. package/src/browser-extension-relay/protocol.ts +63 -0
  99. package/src/browser-extension-relay/server.ts +177 -0
  100. package/src/bundler/app-bundler.ts +3 -3
  101. package/src/bundler/bundle-signer.ts +1 -1
  102. package/src/bundler/signature-verifier.ts +1 -1
  103. package/src/calls/call-conversation-messages.ts +33 -0
  104. package/src/calls/call-domain.ts +106 -5
  105. package/src/calls/call-orchestrator.ts +252 -54
  106. package/src/calls/call-pointer-messages.ts +53 -0
  107. package/src/calls/call-recovery.ts +3 -8
  108. package/src/calls/call-store.ts +69 -87
  109. package/src/calls/elevenlabs-config.ts +3 -2
  110. package/src/calls/guardian-action-sweep.ts +105 -0
  111. package/src/calls/guardian-dispatch.ts +203 -0
  112. package/src/calls/guardian-question-copy.ts +133 -0
  113. package/src/calls/relay-server.ts +466 -8
  114. package/src/calls/speaker-identification.ts +1 -1
  115. package/src/calls/twilio-config.ts +7 -5
  116. package/src/calls/twilio-provider.ts +6 -4
  117. package/src/calls/twilio-rest.ts +40 -15
  118. package/src/calls/twilio-routes.ts +60 -45
  119. package/src/calls/types.ts +3 -1
  120. package/src/channels/types.ts +25 -0
  121. package/src/cli/amazon.ts +815 -0
  122. package/src/cli/config-commands.ts +2 -2
  123. package/src/cli/core-commands.ts +4 -3
  124. package/src/cli/influencer.ts +244 -0
  125. package/src/cli/map.ts +89 -6
  126. package/src/cli.ts +1 -1
  127. package/src/config/agent-schema.ts +171 -0
  128. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  129. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  130. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  131. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  132. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  133. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  134. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  135. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  136. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  137. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  138. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  139. package/src/config/bundled-skills/media-processing/SKILL.md +72 -95
  140. package/src/config/bundled-skills/media-processing/TOOLS.json +57 -147
  141. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  142. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  143. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  144. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  145. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  146. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  147. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  148. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +7 -9
  149. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  150. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +88 -253
  151. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +22 -153
  152. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  153. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +28 -51
  154. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +35 -270
  155. package/src/config/bundled-skills/messaging/SKILL.md +12 -2
  156. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  157. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  158. package/src/config/bundled-skills/phone-calls/SKILL.md +86 -21
  159. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  160. package/src/config/bundled-tool-registry.ts +310 -0
  161. package/src/config/calls-schema.ts +181 -0
  162. package/src/config/core-schema.ts +309 -0
  163. package/src/config/defaults.ts +27 -3
  164. package/src/config/env-registry.ts +169 -0
  165. package/src/config/env.ts +175 -0
  166. package/src/config/loader.ts +6 -6
  167. package/src/config/memory-schema.ts +528 -0
  168. package/src/config/sandbox-schema.ts +55 -0
  169. package/src/config/schema.ts +157 -1138
  170. package/src/config/skill-state.ts +1 -1
  171. package/src/config/skills-schema.ts +32 -0
  172. package/src/config/skills.ts +35 -24
  173. package/src/config/system-prompt.ts +107 -56
  174. package/src/config/templates/SOUL.md +1 -1
  175. package/src/config/types.ts +1 -0
  176. package/src/config/user-reference.ts +4 -9
  177. package/src/config/vellum-skills/catalog.json +0 -7
  178. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  179. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +1 -0
  180. package/src/config/vellum-skills/sms-setup/SKILL.md +112 -14
  181. package/src/context/window-manager.ts +27 -7
  182. package/src/daemon/approval-generators.ts +186 -0
  183. package/src/daemon/approved-devices-store.ts +140 -0
  184. package/src/daemon/assistant-attachments.ts +1 -1
  185. package/src/daemon/classifier.ts +35 -32
  186. package/src/daemon/config-watcher.ts +1 -1
  187. package/src/daemon/daemon-control.ts +254 -0
  188. package/src/daemon/handlers/apps.ts +2 -3
  189. package/src/daemon/handlers/config-channels.ts +158 -0
  190. package/src/daemon/handlers/config-inbox.ts +540 -0
  191. package/src/daemon/handlers/config-ingress.ts +231 -0
  192. package/src/daemon/handlers/config-integrations.ts +258 -0
  193. package/src/daemon/handlers/config-model.ts +143 -0
  194. package/src/daemon/handlers/config-parental.ts +163 -0
  195. package/src/daemon/handlers/config-scheduling.ts +172 -0
  196. package/src/daemon/handlers/config-slack.ts +92 -0
  197. package/src/daemon/handlers/config-telegram.ts +301 -0
  198. package/src/daemon/handlers/config-tools.ts +177 -0
  199. package/src/daemon/handlers/config-trust.ts +104 -0
  200. package/src/daemon/handlers/config-twilio.ts +1080 -0
  201. package/src/daemon/handlers/config.ts +53 -2463
  202. package/src/daemon/handlers/diagnostics.ts +1 -1
  203. package/src/daemon/handlers/dictation.ts +4 -6
  204. package/src/daemon/handlers/documents.ts +18 -32
  205. package/src/daemon/handlers/index.ts +9 -0
  206. package/src/daemon/handlers/misc.ts +3 -5
  207. package/src/daemon/handlers/pairing.ts +98 -0
  208. package/src/daemon/handlers/sessions.ts +74 -5
  209. package/src/daemon/handlers/shared.ts +3 -1
  210. package/src/daemon/handlers/skills.ts +1 -1
  211. package/src/daemon/handlers/twitter-auth.ts +2 -0
  212. package/src/daemon/handlers/work-items.ts +2 -2
  213. package/src/daemon/handlers/workspace-files.ts +4 -3
  214. package/src/daemon/install-cli-launchers.ts +113 -0
  215. package/src/daemon/ipc-contract/apps.ts +356 -0
  216. package/src/daemon/ipc-contract/browser.ts +74 -0
  217. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  218. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  219. package/src/daemon/ipc-contract/documents.ts +74 -0
  220. package/src/daemon/ipc-contract/inbox.ts +209 -0
  221. package/src/daemon/ipc-contract/integrations.ts +284 -0
  222. package/src/daemon/ipc-contract/memory.ts +48 -0
  223. package/src/daemon/ipc-contract/messages.ts +211 -0
  224. package/src/daemon/ipc-contract/pairing.ts +45 -0
  225. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  226. package/src/daemon/ipc-contract/schedules.ts +97 -0
  227. package/src/daemon/ipc-contract/sessions.ts +321 -0
  228. package/src/daemon/ipc-contract/shared.ts +42 -0
  229. package/src/daemon/ipc-contract/skills.ts +120 -0
  230. package/src/daemon/ipc-contract/subagents.ts +58 -0
  231. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  232. package/src/daemon/ipc-contract/trust.ts +60 -0
  233. package/src/daemon/ipc-contract/work-items.ts +225 -0
  234. package/src/daemon/ipc-contract/workspace.ts +113 -0
  235. package/src/daemon/ipc-contract-inventory.json +62 -0
  236. package/src/daemon/ipc-contract-inventory.ts +55 -29
  237. package/src/daemon/ipc-contract.ts +227 -2527
  238. package/src/daemon/ipc-protocol.ts +1 -1
  239. package/src/daemon/ipc-validate.ts +7 -0
  240. package/src/daemon/lifecycle.ts +97 -379
  241. package/src/daemon/pairing-store.ts +177 -0
  242. package/src/daemon/providers-setup.ts +43 -0
  243. package/src/daemon/ride-shotgun-handler.ts +67 -2
  244. package/src/daemon/server.ts +60 -44
  245. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  246. package/src/daemon/session-agent-loop.ts +113 -275
  247. package/src/daemon/session-dynamic-profile.ts +1 -1
  248. package/src/daemon/session-history.ts +1 -1
  249. package/src/daemon/session-media-retry.ts +1 -1
  250. package/src/daemon/session-messaging.ts +37 -2
  251. package/src/daemon/session-notifiers.ts +5 -25
  252. package/src/daemon/session-process.ts +99 -59
  253. package/src/daemon/session-queue-manager.ts +98 -4
  254. package/src/daemon/session-runtime-assembly.ts +149 -15
  255. package/src/daemon/session-surfaces.ts +26 -4
  256. package/src/daemon/session-tool-setup.ts +28 -30
  257. package/src/daemon/session-workspace.ts +1 -1
  258. package/src/daemon/session.ts +24 -1
  259. package/src/daemon/shutdown-handlers.ts +122 -0
  260. package/src/daemon/trace-emitter.ts +1 -1
  261. package/src/daemon/watch-handler.ts +36 -33
  262. package/src/doordash/cart-queries.ts +787 -0
  263. package/src/doordash/client.ts +144 -127
  264. package/src/doordash/order-queries.ts +85 -0
  265. package/src/doordash/queries.ts +10 -1308
  266. package/src/doordash/search-queries.ts +203 -0
  267. package/src/doordash/session.ts +3 -2
  268. package/src/doordash/store-queries.ts +246 -0
  269. package/src/doordash/types.ts +367 -0
  270. package/src/email/providers/agentmail.ts +2 -1
  271. package/src/email/providers/index.ts +3 -2
  272. package/src/email/service.ts +3 -2
  273. package/src/errors.ts +43 -0
  274. package/src/home-base/prebuilt/seed.ts +1 -1
  275. package/src/hooks/cli.ts +6 -5
  276. package/src/hooks/config.ts +6 -8
  277. package/src/hooks/discovery.ts +6 -5
  278. package/src/hooks/manager.ts +4 -3
  279. package/src/hooks/runner.ts +2 -2
  280. package/src/hooks/templates.ts +5 -5
  281. package/src/inbound/public-ingress-urls.ts +3 -1
  282. package/src/index.ts +4 -2
  283. package/src/influencer/client.ts +1104 -0
  284. package/src/instrument.ts +4 -3
  285. package/src/logfire.ts +4 -3
  286. package/src/memory/admin.ts +25 -35
  287. package/src/memory/attachments-store.ts +4 -7
  288. package/src/memory/channel-delivery-store.ts +30 -1
  289. package/src/memory/channel-guardian-store.ts +200 -1
  290. package/src/memory/clarification-resolver.ts +37 -33
  291. package/src/memory/conflict-store.ts +67 -61
  292. package/src/memory/contradiction-checker.ts +141 -117
  293. package/src/memory/conversation-store.ts +335 -51
  294. package/src/memory/db-connection.ts +27 -4
  295. package/src/memory/db-init.ts +121 -4
  296. package/src/memory/db.ts +14 -1
  297. package/src/memory/embedding-backend.ts +27 -5
  298. package/src/memory/embedding-ollama.ts +2 -1
  299. package/src/memory/entity-extractor.ts +38 -35
  300. package/src/memory/guardian-action-store.ts +430 -0
  301. package/src/memory/inbox-escalation-projection.ts +59 -0
  302. package/src/memory/inbox-thread-store.ts +218 -0
  303. package/src/memory/ingress-invite-store.ts +338 -0
  304. package/src/memory/ingress-member-store.ts +350 -0
  305. package/src/memory/items-extractor.ts +91 -97
  306. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  307. package/src/memory/job-handlers/media-processing.ts +11 -42
  308. package/src/memory/job-handlers/summarization.ts +32 -26
  309. package/src/memory/job-utils.ts +3 -10
  310. package/src/memory/jobs-store.ts +6 -9
  311. package/src/memory/jobs-worker.ts +51 -36
  312. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  313. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  314. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  315. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  316. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  317. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  318. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  319. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  320. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  321. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  322. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  323. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  324. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  325. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  326. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  327. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  328. package/src/memory/migrations/017-memory-items-indexes.ts +12 -0
  329. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  330. package/src/memory/migrations/index.ts +24 -0
  331. package/src/memory/migrations/registry.ts +79 -0
  332. package/src/memory/migrations/validate-migration-state.ts +69 -0
  333. package/src/memory/qdrant-manager.ts +49 -8
  334. package/src/memory/query-builder.ts +1 -1
  335. package/src/memory/raw-query.ts +119 -0
  336. package/src/memory/recall-cache.ts +4 -1
  337. package/src/memory/retriever.ts +163 -47
  338. package/src/memory/schema-migration.ts +25 -984
  339. package/src/memory/schema.ts +130 -7
  340. package/src/memory/search/entity.ts +10 -19
  341. package/src/memory/search/lexical.ts +81 -52
  342. package/src/memory/search/ranking.ts +21 -22
  343. package/src/memory/search/semantic.ts +157 -19
  344. package/src/memory/shared-app-links-store.ts +4 -5
  345. package/src/memory/validation.ts +19 -0
  346. package/src/messaging/draft-store.ts +5 -6
  347. package/src/messaging/providers/sms/adapter.ts +3 -6
  348. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  349. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  350. package/src/messaging/providers/whatsapp/client.ts +67 -0
  351. package/src/messaging/style-analyzer.ts +5 -4
  352. package/src/messaging/thread-summarizer.ts +61 -69
  353. package/src/messaging/triage-engine.ts +62 -71
  354. package/src/migrations/config-merge.ts +53 -0
  355. package/src/migrations/data-layout.ts +68 -0
  356. package/src/migrations/data-merge.ts +33 -0
  357. package/src/migrations/hooks-merge.ts +90 -0
  358. package/src/migrations/index.ts +6 -0
  359. package/src/migrations/log.ts +23 -0
  360. package/src/migrations/skills-merge.ts +33 -0
  361. package/src/migrations/workspace-layout.ts +79 -0
  362. package/src/permissions/checker.ts +126 -11
  363. package/src/permissions/prompter.ts +14 -0
  364. package/src/permissions/shell-identity.ts +31 -1
  365. package/src/permissions/trust-store.ts +21 -1
  366. package/src/providers/anthropic/client.ts +4 -4
  367. package/src/providers/failover.ts +2 -2
  368. package/src/providers/model-intents.ts +70 -0
  369. package/src/providers/ollama/client.ts +2 -1
  370. package/src/providers/provider-send-message.ts +176 -0
  371. package/src/providers/registry.ts +71 -30
  372. package/src/providers/retry.ts +35 -1
  373. package/src/providers/types.ts +12 -1
  374. package/src/runtime/approval-conversation-turn.ts +97 -0
  375. package/src/runtime/approval-message-composer.ts +115 -5
  376. package/src/runtime/assistant-event-hub.ts +3 -1
  377. package/src/runtime/channel-approval-parser.ts +36 -2
  378. package/src/runtime/channel-approvals.ts +0 -21
  379. package/src/runtime/channel-guardian-service.ts +48 -7
  380. package/src/runtime/channel-readiness-service.ts +160 -34
  381. package/src/runtime/channel-readiness-types.ts +10 -4
  382. package/src/runtime/channel-retry-sweep.ts +184 -0
  383. package/src/runtime/guardian-context-resolver.ts +108 -0
  384. package/src/runtime/http-server.ts +289 -745
  385. package/src/runtime/http-types.ts +56 -3
  386. package/src/runtime/middleware/auth.ts +116 -0
  387. package/src/runtime/middleware/error-handler.ts +33 -0
  388. package/src/runtime/middleware/twilio-validation.ts +127 -0
  389. package/src/runtime/routes/app-routes.ts +1 -1
  390. package/src/runtime/routes/call-routes.ts +49 -6
  391. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  392. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  393. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  394. package/src/runtime/routes/channel-route-shared.ts +144 -0
  395. package/src/runtime/routes/channel-routes.ts +32 -1634
  396. package/src/runtime/routes/conversation-routes.ts +50 -7
  397. package/src/runtime/routes/events-routes.ts +2 -2
  398. package/src/runtime/routes/identity-routes.ts +126 -0
  399. package/src/runtime/routes/pairing-routes.ts +144 -0
  400. package/src/runtime/routes/run-routes.ts +15 -1
  401. package/src/runtime/run-orchestrator.ts +52 -34
  402. package/src/schedule/schedule-store.ts +36 -32
  403. package/src/schedule/scheduler.ts +3 -3
  404. package/src/security/encrypted-store.ts +5 -7
  405. package/src/security/oauth2.ts +45 -15
  406. package/src/security/parental-control-store.ts +183 -0
  407. package/src/security/secret-allowlist.ts +4 -3
  408. package/src/security/secret-scanner.ts +5 -5
  409. package/src/security/secure-keys.ts +1 -1
  410. package/src/security/token-manager.ts +3 -2
  411. package/src/services/vercel-deploy.ts +6 -2
  412. package/src/skills/tool-manifest.ts +3 -3
  413. package/src/skills/vellum-catalog-remote.ts +75 -16
  414. package/src/slack/slack-webhook.ts +2 -1
  415. package/src/swarm/orchestrator.ts +92 -1
  416. package/src/swarm/router-planner.ts +6 -9
  417. package/src/swarm/worker-prompts.ts +9 -12
  418. package/src/tasks/task-compiler.ts +19 -28
  419. package/src/tasks/task-runner.ts +1 -1
  420. package/src/tools/assets/search.ts +15 -14
  421. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  422. package/src/tools/browser/auto-navigate.ts +1 -0
  423. package/src/tools/browser/browser-execution.ts +13 -1
  424. package/src/tools/browser/browser-manager.ts +119 -4
  425. package/src/tools/browser/network-recorder.ts +5 -0
  426. package/src/tools/credentials/broker.ts +11 -2
  427. package/src/tools/credentials/metadata-store.ts +18 -14
  428. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  429. package/src/tools/credentials/vault.ts +49 -23
  430. package/src/tools/executor.ts +80 -18
  431. package/src/tools/host-terminal/cli-discover.ts +1 -1
  432. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  433. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  434. package/src/tools/network/script-proxy/server.ts +1 -1
  435. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  436. package/src/tools/network/web-fetch.ts +18 -2
  437. package/src/tools/network/web-search.ts +7 -3
  438. package/src/tools/reminder/reminder-store.ts +14 -15
  439. package/src/tools/schedule/create.ts +1 -0
  440. package/src/tools/schedule/list.ts +2 -1
  441. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  442. package/src/tools/skills/skill-script-runner.ts +24 -9
  443. package/src/tools/skills/skill-tool-factory.ts +1 -0
  444. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  445. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  446. package/src/tools/terminal/parser.ts +50 -0
  447. package/src/tools/watcher/delete.ts +6 -0
  448. package/src/tools/weather/service.ts +1 -1
  449. package/src/twitter/client.ts +190 -24
  450. package/src/twitter/session.ts +4 -3
  451. package/src/util/clipboard.ts +1 -1
  452. package/src/util/errors.ts +65 -8
  453. package/src/util/fs.ts +40 -0
  454. package/src/util/json.ts +10 -0
  455. package/src/util/log-redact.ts +189 -0
  456. package/src/util/logger.ts +25 -18
  457. package/src/util/object.ts +3 -0
  458. package/src/util/platform.ts +72 -365
  459. package/src/util/pricing.ts +1 -1
  460. package/src/util/promise-guard.ts +1 -1
  461. package/src/util/retry.ts +19 -0
  462. package/src/util/row-mapper.ts +79 -0
  463. package/src/util/silently.ts +21 -0
  464. package/src/watcher/engine.ts +5 -1
  465. package/src/watcher/provider-types.ts +20 -0
  466. package/src/watcher/providers/github.ts +156 -0
  467. package/src/watcher/providers/gmail.ts +1 -0
  468. package/src/watcher/providers/google-calendar.ts +1 -0
  469. package/src/watcher/providers/linear.ts +460 -0
  470. package/src/watcher/providers/slack.ts +1 -0
  471. package/src/work-items/work-item-runner.ts +1 -1
  472. package/src/workspace/git-service.ts +1 -1
  473. package/src/workspace/provider-commit-message-generator.ts +51 -22
  474. package/src/__tests__/call-bridge.test.ts +0 -517
  475. package/src/__tests__/session-process-bridge.test.ts +0 -244
  476. package/src/calls/call-bridge.ts +0 -168
  477. package/src/config/bundled-skills/media-processing/services/capability-registry.ts +0 -137
  478. package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +0 -280
  479. package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +0 -144
  480. package/src/config/bundled-skills/media-processing/services/feedback-store.ts +0 -136
  481. package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +0 -95
  482. package/src/config/bundled-skills/media-processing/services/timeline-service.ts +0 -267
  483. package/src/config/bundled-skills/media-processing/tools/detect-events.ts +0 -110
  484. package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +0 -235
  485. package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +0 -142
  486. package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +0 -150
  487. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Helper utilities for provider callsites that should stay decoupled from
3
+ * provider SDK details. Includes provider resolution, timeout utilities,
4
+ * and response extraction helpers.
5
+ */
6
+
7
+ import type { Provider, ProviderResponse, Message, ContentBlock, ToolUseContent } from './types.js';
8
+ import { getFailoverProvider, listProviders, initializeProviders, resolveProviderSelection } from './registry.js';
9
+ import { getConfig } from '../config/loader.js';
10
+ import { getLogger } from '../util/logger.js';
11
+
12
+ export interface ConfiguredProviderResult {
13
+ provider: Provider;
14
+ configuredProviderName: string;
15
+ selectedProviderName: string;
16
+ usedFallbackPrimary: boolean;
17
+ }
18
+
19
+ const providerSelectionLog = getLogger('provider-selection');
20
+ let fallbackWarningLogged = false;
21
+
22
+ /**
23
+ * Resolve the configured provider with full selection metadata.
24
+ * If providers haven't been initialized yet (e.g. non-daemon code paths),
25
+ * performs a one-shot `initializeProviders(getConfig())`.
26
+ *
27
+ * Uses fail-open selection: if `config.provider` is unavailable but
28
+ * alternates from `config.providerOrder` exist, selects the first available.
29
+ *
30
+ * Returns `null` when no providers are available at all.
31
+ */
32
+ export function resolveConfiguredProvider(): ConfiguredProviderResult | null {
33
+ const config = getConfig();
34
+
35
+ if (listProviders().length === 0) {
36
+ try {
37
+ initializeProviders(config);
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+
43
+ const providerOrder = Array.isArray(config.providerOrder) ? config.providerOrder : [];
44
+ const selection = resolveProviderSelection(config.provider, providerOrder);
45
+
46
+ if (!selection.selectedPrimary) {
47
+ return null;
48
+ }
49
+
50
+ if (selection.usedFallbackPrimary) {
51
+ const level = fallbackWarningLogged ? 'debug' : 'warn';
52
+ providerSelectionLog[level](
53
+ { configured: config.provider, selected: selection.selectedPrimary },
54
+ 'Configured provider unavailable, using fallback',
55
+ );
56
+ fallbackWarningLogged = true;
57
+ }
58
+
59
+ try {
60
+ const provider = getFailoverProvider(config.provider, providerOrder);
61
+ return {
62
+ provider,
63
+ configuredProviderName: config.provider,
64
+ selectedProviderName: selection.selectedPrimary,
65
+ usedFallbackPrimary: selection.usedFallbackPrimary,
66
+ };
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Resolve the configured provider through the registry/failover path.
74
+ * Thin wrapper around `resolveConfiguredProvider()` for callsites
75
+ * that only need the Provider instance.
76
+ *
77
+ * Returns `null` when no providers are available.
78
+ */
79
+ export function getConfiguredProvider(): Provider | null {
80
+ const result = resolveConfiguredProvider();
81
+ return result?.provider ?? null;
82
+ }
83
+
84
+ /**
85
+ * Create an AbortSignal that fires after `ms` milliseconds.
86
+ * Returns the signal and a cleanup function to clear the timer.
87
+ */
88
+ export function createTimeout(ms: number): { signal: AbortSignal; cleanup: () => void } {
89
+ const controller = new AbortController();
90
+ const timer = setTimeout(() => controller.abort(), ms);
91
+ return {
92
+ signal: controller.signal,
93
+ cleanup: () => clearTimeout(timer),
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Extract the first text block's text from a ProviderResponse.
99
+ * Returns empty string if no text block is found.
100
+ */
101
+ export function extractText(response: ProviderResponse): string {
102
+ const block = response.content.find((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text');
103
+ return block?.text?.trim() ?? '';
104
+ }
105
+
106
+ /**
107
+ * Extract all text blocks from a ProviderResponse and join them.
108
+ */
109
+ export function extractAllText(response: ProviderResponse): string {
110
+ return response.content
111
+ .filter((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text')
112
+ .map((b) => b.text)
113
+ .join('');
114
+ }
115
+
116
+ /**
117
+ * Find the first tool_use block in a ProviderResponse.
118
+ */
119
+ export function extractToolUse(response: ProviderResponse): ToolUseContent | undefined {
120
+ return response.content.find((b): b is ToolUseContent => b.type === 'tool_use');
121
+ }
122
+
123
+ /**
124
+ * Build a single user message in the provider Message format.
125
+ */
126
+ export function userMessage(text: string): Message {
127
+ return { role: 'user', content: [{ type: 'text', text }] };
128
+ }
129
+
130
+ /**
131
+ * Build a single user message with image + text content.
132
+ */
133
+ export function userMessageWithImage(
134
+ imageBase64: string,
135
+ mediaType: string,
136
+ text: string,
137
+ ): Message {
138
+ return {
139
+ role: 'user',
140
+ content: [
141
+ {
142
+ type: 'image',
143
+ source: {
144
+ type: 'base64',
145
+ media_type: mediaType,
146
+ data: imageBase64,
147
+ },
148
+ },
149
+ { type: 'text', text },
150
+ ],
151
+ };
152
+ }
153
+
154
+ /**
155
+ * Build a single user message with multiple images followed by a text block.
156
+ * Each image becomes its own content block; the text block comes last.
157
+ */
158
+ export function userMessageWithImages(
159
+ images: Array<{ base64: string; mediaType: string }>,
160
+ text: string,
161
+ ): Message {
162
+ return {
163
+ role: 'user',
164
+ content: [
165
+ ...images.map((img) => ({
166
+ type: 'image' as const,
167
+ source: {
168
+ type: 'base64' as const,
169
+ media_type: img.mediaType,
170
+ data: img.base64,
171
+ },
172
+ })),
173
+ { type: 'text' as const, text },
174
+ ],
175
+ };
176
+ }
@@ -9,15 +9,7 @@ import { RetryProvider } from "./retry.js";
9
9
  import { FailoverProvider } from "./failover.js";
10
10
  import { wrapWithLogfire } from "../logfire.js";
11
11
  import { ConfigError } from "../util/errors.js";
12
-
13
- const DEFAULT_MODELS: Record<string, string> = {
14
- anthropic: 'claude-opus-4-6',
15
- openai: 'gpt-5.2',
16
- gemini: 'gemini-3-flash',
17
- ollama: 'llama3.2',
18
- fireworks: 'accounts/fireworks/models/kimi-k2p5',
19
- openrouter: 'x-ai/grok-4',
20
- };
12
+ import { getProviderDefaultModel } from "./model-intents.js";
21
13
 
22
14
  const providers = new Map<string, Provider>();
23
15
  let cachedFailoverProvider: FailoverProvider | null = null;
@@ -37,33 +29,74 @@ export function getProvider(name: string): Provider {
37
29
  return provider;
38
30
  }
39
31
 
32
+ export interface ProviderSelection {
33
+ /** Ordered list of available provider names */
34
+ availableProviders: string[];
35
+ /** The selected (effective) primary provider name, or null if none available */
36
+ selectedPrimary: string | null;
37
+ /** Whether the effective primary differs from the requested primary */
38
+ usedFallbackPrimary: boolean;
39
+ }
40
+
40
41
  /**
41
- * Build a provider that tries the primary provider first, then falls back to
42
- * others in the configured order. If providerOrder is empty or only contains
43
- * the primary, returns the primary provider directly (no wrapper overhead).
42
+ * Resolve provider selection from requested primary and provider order.
43
+ * Dedupes [requestedPrimary, ...providerOrder], filtered to initialized providers.
44
+ * Returns null selectedPrimary when no providers are available.
45
+ */
46
+ export function resolveProviderSelection(
47
+ requestedPrimary: string,
48
+ providerOrder: string[],
49
+ ): ProviderSelection {
50
+ const ordered: string[] = [];
51
+ const seen = new Set<string>();
52
+
53
+ for (const name of [requestedPrimary, ...providerOrder]) {
54
+ if (seen.has(name)) continue;
55
+ seen.add(name);
56
+ if (providers.has(name)) {
57
+ ordered.push(name);
58
+ }
59
+ }
60
+
61
+ if (ordered.length === 0) {
62
+ return { availableProviders: [], selectedPrimary: null, usedFallbackPrimary: false };
63
+ }
64
+
65
+ return {
66
+ availableProviders: ordered,
67
+ selectedPrimary: ordered[0],
68
+ usedFallbackPrimary: ordered[0] !== requestedPrimary,
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Build a provider that tries the effective primary provider first, then falls
74
+ * back to others in the configured order. If the requested primary is not
75
+ * available, automatically selects the first available provider from the
76
+ * deduped [primaryName, ...providerOrder] list (fail-open).
77
+ *
78
+ * Throws ConfigError only when NO providers are available at all.
44
79
  * Caches the FailoverProvider instance so health state persists across calls.
45
80
  */
46
81
  export function getFailoverProvider(primaryName: string, providerOrder: string[]): Provider {
47
- const primary = getProvider(primaryName);
48
-
49
- // Build the ordered list: primary first, then remaining from providerOrder
50
- const orderedProviders: Provider[] = [primary];
51
- const seen = new Set<string>([primaryName]);
82
+ const selection = resolveProviderSelection(primaryName, providerOrder);
52
83
 
53
- for (const name of providerOrder) {
54
- if (seen.has(name)) continue;
55
- const p = providers.get(name);
56
- if (p) {
57
- orderedProviders.push(p);
58
- seen.add(name);
59
- }
84
+ if (!selection.selectedPrimary) {
85
+ throw new ConfigError(
86
+ `No providers available. Requested: "${primaryName}". Registered: ${listProviders().join(", ") || "none"}`,
87
+ );
60
88
  }
61
89
 
90
+ const orderedProviders: Provider[] = selection.availableProviders.map(
91
+ (name) => providers.get(name)!,
92
+ );
93
+
62
94
  if (orderedProviders.length === 1) {
63
- return primary;
95
+ return orderedProviders[0];
64
96
  }
65
97
 
66
- const cacheKey = `${primaryName}:${providerOrder.join(',')}`;
98
+ // Cache key from effective ordered providers (not raw input strings)
99
+ const cacheKey = selection.availableProviders.join(',');
67
100
  if (cachedFailoverProvider && cachedFailoverKey === cacheKey) {
68
101
  return cachedFailoverProvider;
69
102
  }
@@ -77,6 +110,14 @@ export function listProviders(): string[] {
77
110
  return Array.from(providers.keys());
78
111
  }
79
112
 
113
+ /**
114
+ * Return the default model for a given provider name.
115
+ * Falls back to the Anthropic default if the provider name is unknown.
116
+ */
117
+ export function getDefaultModel(providerName: string): string {
118
+ return getProviderDefaultModel(providerName);
119
+ }
120
+
80
121
  export interface ProvidersConfig {
81
122
  apiKeys: Record<string, string>;
82
123
  provider: string;
@@ -85,16 +126,16 @@ export interface ProvidersConfig {
85
126
  timeouts?: { providerStreamTimeoutSec?: number };
86
127
  }
87
128
 
88
- function resolveModel(config: ProvidersConfig, providerName: keyof typeof DEFAULT_MODELS): string {
129
+ function resolveModel(config: ProvidersConfig, providerName: string): string {
89
130
  if (config.provider === providerName) {
90
131
  // If a non-Anthropic provider is selected with the untouched global default
91
132
  // model, use a provider-appropriate fallback instead.
92
- if (providerName !== 'anthropic' && config.model === DEFAULT_MODELS.anthropic) {
93
- return DEFAULT_MODELS[providerName];
133
+ if (providerName !== 'anthropic' && config.model === getProviderDefaultModel('anthropic')) {
134
+ return getProviderDefaultModel(providerName);
94
135
  }
95
136
  return config.model;
96
137
  }
97
- return DEFAULT_MODELS[providerName];
138
+ return getProviderDefaultModel(providerName);
98
139
  }
99
140
 
100
141
  export function initializeProviders(config: ProvidersConfig): void {
@@ -1,6 +1,7 @@
1
1
  import type { Provider, ProviderResponse, SendMessageOptions, Message, ToolDefinition } from './types.js';
2
2
  import { ProviderError } from '../util/errors.js';
3
3
  import { getLogger, isDebug } from '../util/logger.js';
4
+ import { isModelIntent, resolveModelIntent } from './model-intents.js';
4
5
  import {
5
6
  computeRetryDelay,
6
7
  isRetryableNetworkError,
@@ -18,6 +19,37 @@ function isRetryableError(error: unknown): boolean {
18
19
  return isRetryableNetworkError(error);
19
20
  }
20
21
 
22
+ function normalizeSendMessageOptions(providerName: string, options?: SendMessageOptions): SendMessageOptions | undefined {
23
+ const config = options?.config;
24
+ if (!config) return options;
25
+
26
+ const explicitModel = typeof config.model === 'string' && config.model.trim().length > 0
27
+ ? config.model.trim()
28
+ : undefined;
29
+ const intent = isModelIntent(config.modelIntent) ? config.modelIntent : undefined;
30
+ const hasIntent = config.modelIntent !== undefined;
31
+
32
+ if (!hasIntent && explicitModel === config.model) {
33
+ return options;
34
+ }
35
+
36
+ const nextConfig: Record<string, unknown> = { ...config };
37
+ delete nextConfig.modelIntent;
38
+
39
+ if (explicitModel) {
40
+ nextConfig.model = explicitModel;
41
+ } else if (intent) {
42
+ nextConfig.model = resolveModelIntent(providerName, intent);
43
+ } else {
44
+ delete nextConfig.model;
45
+ }
46
+
47
+ return {
48
+ ...options,
49
+ config: nextConfig,
50
+ };
51
+ }
52
+
21
53
  export class RetryProvider implements Provider {
22
54
  public readonly name: string;
23
55
 
@@ -42,10 +74,12 @@ export class RetryProvider implements Provider {
42
74
  }, 'Provider sendMessage start');
43
75
  }
44
76
 
77
+ const normalizedOptions = normalizeSendMessageOptions(this.name, options);
78
+
45
79
  for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
46
80
  try {
47
81
  const start = Date.now();
48
- const result = await this.inner.sendMessage(messages, tools, systemPrompt, options);
82
+ const result = await this.inner.sendMessage(messages, tools, systemPrompt, normalizedOptions);
49
83
  if (debug) {
50
84
  log.debug({
51
85
  provider: this.name,
@@ -70,6 +70,11 @@ export interface ToolDefinition {
70
70
  input_schema: object;
71
71
  }
72
72
 
73
+ export type ModelIntent =
74
+ | 'latency-optimized'
75
+ | 'quality-optimized'
76
+ | 'vision-optimized';
77
+
73
78
  export interface ProviderResponse {
74
79
  content: ContentBlock[];
75
80
  model: string;
@@ -92,8 +97,14 @@ export type ProviderEvent =
92
97
  | { type: 'thinking_delta'; thinking: string }
93
98
  | { type: 'input_json_delta'; toolName: string; accumulatedJson: string };
94
99
 
100
+ export interface SendMessageConfig {
101
+ model?: string;
102
+ modelIntent?: ModelIntent;
103
+ [key: string]: unknown;
104
+ }
105
+
95
106
  export interface SendMessageOptions {
96
- config?: object;
107
+ config?: SendMessageConfig;
97
108
  onEvent?: (event: ProviderEvent) => void;
98
109
  signal?: AbortSignal;
99
110
  }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Approval conversation turn engine.
3
+ *
4
+ * Processes a single turn of the conversational approval flow by delegating
5
+ * to a generator function (typically backed by a language model) and
6
+ * validating the structured result. Fails closed on any error — returning
7
+ * a safe keep_pending disposition — so that a broken model call never
8
+ * silently approves or rejects a request.
9
+ */
10
+
11
+ // Hook point: a deterministic classifier could be inserted here as an
12
+ // alternative to model-based inference
13
+
14
+ import type {
15
+ ApprovalConversationContext,
16
+ ApprovalConversationGenerator,
17
+ ApprovalConversationResult,
18
+ ApprovalConversationDisposition,
19
+ } from './http-types.js';
20
+
21
+ const VALID_DISPOSITIONS: ReadonlySet<ApprovalConversationDisposition> = new Set([
22
+ 'keep_pending',
23
+ 'approve_once',
24
+ 'approve_always',
25
+ 'reject',
26
+ ]);
27
+
28
+ /** Dispositions that represent an actual decision (not just "keep waiting"). */
29
+ const DECISION_BEARING_DISPOSITIONS: ReadonlySet<ApprovalConversationDisposition> = new Set([
30
+ 'approve_once',
31
+ 'approve_always',
32
+ 'reject',
33
+ ]);
34
+
35
+ const FAIL_CLOSED_REPLY =
36
+ "I couldn't process that. Please reply with approve, deny, or cancel to decide on the pending request.";
37
+
38
+ function failClosed(): ApprovalConversationResult {
39
+ return { disposition: 'keep_pending', replyText: FAIL_CLOSED_REPLY };
40
+ }
41
+
42
+ function isValidResult(value: unknown): value is ApprovalConversationResult {
43
+ if (!value || typeof value !== 'object') return false;
44
+ const obj = value as Record<string, unknown>;
45
+ if (typeof obj.disposition !== 'string') return false;
46
+ if (!VALID_DISPOSITIONS.has(obj.disposition as ApprovalConversationDisposition)) return false;
47
+ if (typeof obj.replyText !== 'string' || obj.replyText.trim().length === 0) return false;
48
+ if (obj.targetRunId !== undefined && typeof obj.targetRunId !== 'string') return false;
49
+ return true;
50
+ }
51
+
52
+ /**
53
+ * Run one turn of the approval conversation engine.
54
+ *
55
+ * Calls the provided generator, validates the result, and returns a
56
+ * structured decision. On ANY failure (timeout, malformed output,
57
+ * exception) the function returns a safe keep_pending fallback.
58
+ */
59
+ export async function runApprovalConversationTurn(
60
+ context: ApprovalConversationContext,
61
+ generator: ApprovalConversationGenerator,
62
+ ): Promise<ApprovalConversationResult> {
63
+ let result: ApprovalConversationResult;
64
+
65
+ try {
66
+ result = await generator(context);
67
+ } catch {
68
+ return failClosed();
69
+ }
70
+
71
+ if (!isValidResult(result)) {
72
+ return failClosed();
73
+ }
74
+
75
+ // Enforce allowed-actions policy: the model must not return a disposition
76
+ // that the caller did not offer (keep_pending is always acceptable).
77
+ if (
78
+ result.disposition !== 'keep_pending'
79
+ && !context.allowedActions.includes(result.disposition)
80
+ ) {
81
+ return failClosed();
82
+ }
83
+
84
+ // Validate targetRunId for decision-bearing dispositions:
85
+ // 1. When multiple approvals are pending, targetRunId is required.
86
+ // 2. When targetRunId is present, it must match a known pending approval
87
+ // regardless of how many approvals are pending.
88
+ if (DECISION_BEARING_DISPOSITIONS.has(result.disposition)) {
89
+ if (context.pendingApprovals.length > 1 && !result.targetRunId) return failClosed();
90
+ if (result.targetRunId) {
91
+ const validRunIds = new Set(context.pendingApprovals.map((p) => p.runId));
92
+ if (!validRunIds.has(result.targetRunId)) return failClosed();
93
+ }
94
+ }
95
+
96
+ return result;
97
+ }
@@ -3,10 +3,13 @@
3
3
  *
4
4
  * Generates approval prompt text through a priority chain:
5
5
  * 1. Assistant preface (macOS parity — reuse existing assistant text)
6
- * 2. Deterministic fallback templates (natural, scenario-specific messages)
7
- *
8
- * A provider-backed generation layer can be inserted between 1 and 2 later.
6
+ * 2. Generator-produced rewrite of deterministic fallback text (when provided by daemon)
7
+ * 3. Deterministic fallback templates (natural, scenario-specific messages)
9
8
  */
9
+ import { getLogger } from '../util/logger.js';
10
+ import type { ApprovalCopyGenerator } from './http-types.js';
11
+
12
+ const log = getLogger('approval-message-composer');
10
13
 
11
14
  // ---------------------------------------------------------------------------
12
15
  // Types
@@ -30,7 +33,9 @@ export type ApprovalMessageScenario =
30
33
  | 'guardian_verify_status_bound'
31
34
  | 'guardian_verify_status_unbound'
32
35
  | 'guardian_deny_no_identity'
33
- | 'guardian_deny_no_binding';
36
+ | 'guardian_deny_no_binding'
37
+ | 'requester_cancel'
38
+ | 'approval_already_resolved';
34
39
 
35
40
  export interface ApprovalMessageContext {
36
41
  scenario: ApprovalMessageScenario;
@@ -48,6 +53,22 @@ export interface ApprovalMessageContext {
48
53
  failureReason?: string;
49
54
  }
50
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
+
51
72
  // ---------------------------------------------------------------------------
52
73
  // Public API
53
74
  // ---------------------------------------------------------------------------
@@ -65,6 +86,82 @@ export function composeApprovalMessage(context: ApprovalMessageContext): string
65
86
  return getFallbackMessage(context);
66
87
  }
67
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
+
68
165
  // ---------------------------------------------------------------------------
69
166
  // Deterministic fallback templates
70
167
  // ---------------------------------------------------------------------------
@@ -85,7 +182,7 @@ export function getFallbackMessage(context: ApprovalMessageContext): string {
85
182
  return `${context.requesterIdentifier ?? 'A user'} is requesting to use "${context.toolName ?? 'unknown'}". Please approve or deny this request.`;
86
183
 
87
184
  case 'reminder_prompt':
88
- return "I'm still waiting for your decision on the pending approval request.";
185
+ return 'There is a pending approval request. Ask a follow-up question or say approve/deny when you are ready.';
89
186
 
90
187
  case 'guardian_delivery_failed':
91
188
  return context.toolName
@@ -120,6 +217,11 @@ export function getFallbackMessage(context: ApprovalMessageContext): string {
120
217
  return `Verification failed. ${context.failureReason ?? 'Please try again.'}`;
121
218
 
122
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
+ }
123
225
  return `To complete guardian verification, send ${context.verifyCommand ?? 'the verification command'} within ${context.ttlSeconds ?? 60} seconds.`;
124
226
 
125
227
  case 'guardian_verify_status_bound':
@@ -134,6 +236,14 @@ export function getFallbackMessage(context: ApprovalMessageContext): string {
134
236
  case 'guardian_deny_no_binding':
135
237
  return 'This action requires guardian approval, but no guardian has been configured for this channel. The request has been denied for safety.';
136
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
+
137
247
  default: {
138
248
  // Exhaustive check — TypeScript will flag if a scenario is missing.
139
249
  const _exhaustive: never = context.scenario;
@@ -123,7 +123,9 @@ export class AssistantEventHub {
123
123
  for (const entry of snapshot) {
124
124
  if (!entry.active) continue;
125
125
  if (entry.filter.assistantId !== event.assistantId) continue;
126
- if (entry.filter.sessionId != null && entry.filter.sessionId !== event.sessionId) continue;
126
+ // System events (no sessionId) match all subscribers; scoped events
127
+ // must match the subscriber's sessionId filter when present.
128
+ if (event.sessionId != null && entry.filter.sessionId != null && entry.filter.sessionId !== event.sessionId) continue;
127
129
  try {
128
130
  await entry.callback(event);
129
131
  } catch (err) {