@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
@@ -5,9 +5,18 @@ import { getLogger } from '../../util/logger.js';
5
5
  import { checkBrowserRuntime } from './runtime-check.js';
6
6
  import { authSessionCache } from './auth-cache.js';
7
7
  import type { ExtractedCredential } from './network-recording-types.js';
8
+ import { silentlyWithLog } from '../../util/silently.js';
8
9
 
9
10
  const log = getLogger('browser-manager');
10
11
 
12
+ function getDownloadsDir(): string {
13
+ const dir = join(getDataDir(), 'browser-downloads');
14
+ mkdirSync(dir, { recursive: true });
15
+ return dir;
16
+ }
17
+
18
+ export type DownloadInfo = { path: string; filename: string };
19
+
11
20
  type BrowserContext = {
12
21
  newPage(): Promise<Page>;
13
22
  close(): Promise<void>;
@@ -51,7 +60,7 @@ export type Page = {
51
60
  move(x: number, y: number): Promise<void>;
52
61
  wheel(deltaX: number, deltaY: number): Promise<void>;
53
62
  };
54
- bringToFront(): Promise<void>;
63
+ on(event: string, handler: (...args: unknown[]) => void): void;
55
64
  };
56
65
 
57
66
  type ScreencastFrameMetadata = {
@@ -108,6 +117,8 @@ class BrowserManager {
108
117
  private interactiveModeSessions = new Set<string>();
109
118
  private handoffResolvers = new Map<string, () => void>();
110
119
  private sessionSenders = new Map<string, (msg: { type: string; sessionId: string }) => void>();
120
+ private downloads = new Map<string, DownloadInfo[]>();
121
+ private pendingDownloads = new Map<string, { resolve: (info: DownloadInfo) => void; reject: (err: Error) => void }[]>();
111
122
 
112
123
  get browserMode(): 'headless' | 'cdp' {
113
124
  return this._browserMode;
@@ -193,7 +204,7 @@ class BrowserManager {
193
204
  this.contextCreating = (async () => {
194
205
  // Deterministic test mode: when launch is injected via setLaunchFn,
195
206
  // bypass ambient CDP probing/negotiation and use the injected launcher.
196
- const hasInjectedLaunchFn = launchPersistentContext !== null;
207
+ const hasInjectedLaunchFn = launchPersistentContext != null;
197
208
 
198
209
  if (!hasInjectedLaunchFn) {
199
210
  // Try to detect or negotiate CDP before falling back to headless.
@@ -339,6 +350,11 @@ class BrowserManager {
339
350
  this.cdpSessions.clear();
340
351
  this.screencastCallbacks.clear();
341
352
  this.snapshotMaps.clear();
353
+ this.downloads.clear();
354
+ for (const pending of this.pendingDownloads.values()) {
355
+ for (const waiter of pending) waiter.reject(new Error('Browser closed'));
356
+ }
357
+ this.pendingDownloads.clear();
342
358
  };
343
359
  rawCtx.on('close', this.contextCloseHandler);
344
360
  }
@@ -365,6 +381,9 @@ class BrowserManager {
365
381
  this.pages.set(sessionId, page);
366
382
  this.rawPages.set(sessionId, page);
367
383
 
384
+ // Track downloads for this page
385
+ this.setupDownloadTracking(sessionId, page);
386
+
368
387
  // In CDP mode, keep the window minimized unless we're in an active handoff.
369
388
  if (this._browserMode === 'cdp' && !this.interactiveModeSessions.has(sessionId)) {
370
389
  await this.moveWindowOffscreen();
@@ -390,6 +409,13 @@ class BrowserManager {
390
409
  this.pages.delete(sessionId);
391
410
  this.rawPages.delete(sessionId);
392
411
  this.snapshotMaps.delete(sessionId);
412
+ this.downloads.delete(sessionId);
413
+ // Reject any pending download waiters
414
+ const pending = this.pendingDownloads.get(sessionId);
415
+ if (pending) {
416
+ for (const waiter of pending) waiter.reject(new Error('Session closed'));
417
+ this.pendingDownloads.delete(sessionId);
418
+ }
393
419
  log.debug({ sessionId }, 'Session page closed');
394
420
  }
395
421
 
@@ -415,6 +441,11 @@ class BrowserManager {
415
441
  this.pages.clear();
416
442
  this.rawPages.clear();
417
443
  this.snapshotMaps.clear();
444
+ this.downloads.clear();
445
+ for (const pending of this.pendingDownloads.values()) {
446
+ for (const waiter of pending) waiter.reject(new Error('Browser closed'));
447
+ }
448
+ this.pendingDownloads.clear();
418
449
 
419
450
  if (this.context) {
420
451
  // Remove the close listener before intentional close to avoid
@@ -477,7 +508,7 @@ class BrowserManager {
477
508
 
478
509
  cdp.on('Page.screencastFrame', (params) => {
479
510
  onFrame({ data: params.data as string, metadata: params.metadata as ScreencastFrameMetadata });
480
- cdp.send('Page.screencastFrameAck', { sessionId: params.sessionId }).catch(() => {});
511
+ silentlyWithLog(cdp.send('Page.screencastFrameAck', { sessionId: params.sessionId }), 'screencast frame ack');
481
512
  });
482
513
 
483
514
  await cdp.send('Page.startScreencast', {
@@ -709,8 +740,92 @@ class BrowserManager {
709
740
  }
710
741
  }
711
742
 
743
+ private setupDownloadTracking(sessionId: string, page: Page): void {
744
+ page.on('download', async (download: unknown) => {
745
+ const dl = download as {
746
+ suggestedFilename(): string;
747
+ path(): Promise<string | null>;
748
+ saveAs(path: string): Promise<void>;
749
+ failure(): Promise<string | null>;
750
+ };
751
+ try {
752
+ const filename = dl.suggestedFilename();
753
+ const destPath = join(getDownloadsDir(), `${Date.now()}-${filename}`);
754
+ await dl.saveAs(destPath);
755
+ const info: DownloadInfo = { path: destPath, filename };
756
+
757
+ // Resolve a pending waiter if one exists, otherwise store for later retrieval
758
+ const pending = this.pendingDownloads.get(sessionId);
759
+ if (pending && pending.length > 0) {
760
+ const waiter = pending.shift()!;
761
+ waiter.resolve(info);
762
+ if (pending.length === 0) this.pendingDownloads.delete(sessionId);
763
+ } else {
764
+ const list = this.downloads.get(sessionId) ?? [];
765
+ list.push(info);
766
+ this.downloads.set(sessionId, list);
767
+ }
768
+
769
+ log.info({ sessionId, filename, path: destPath }, 'Download completed');
770
+ } catch (err) {
771
+ const failure = await dl.failure();
772
+ log.warn({ err, failure, sessionId }, 'Download failed');
773
+
774
+ // Reject any pending waiters
775
+ const pending = this.pendingDownloads.get(sessionId);
776
+ if (pending && pending.length > 0) {
777
+ const waiter = pending.shift()!;
778
+ waiter.reject(new Error(`Download failed: ${failure ?? String(err)}`));
779
+ if (pending.length === 0) this.pendingDownloads.delete(sessionId);
780
+ }
781
+ }
782
+ });
783
+ }
784
+
785
+ getLastDownload(sessionId: string): DownloadInfo | null {
786
+ const list = this.downloads.get(sessionId);
787
+ if (!list || list.length === 0) return null;
788
+ return list[list.length - 1];
789
+ }
790
+
791
+ waitForDownload(sessionId: string, timeoutMs: number = 30_000): Promise<DownloadInfo> {
792
+ // Check if an unconsumed download already completed for this session
793
+ const existing = this.downloads.get(sessionId);
794
+ if (existing && existing.length > 0) {
795
+ const info = existing.pop()!;
796
+ if (existing.length === 0) this.downloads.delete(sessionId);
797
+ return Promise.resolve(info);
798
+ }
799
+
800
+ return new Promise<DownloadInfo>((resolve, reject) => {
801
+ const timer = setTimeout(() => {
802
+ // Remove this waiter from the pending list
803
+ const pending = this.pendingDownloads.get(sessionId);
804
+ if (pending) {
805
+ const idx = pending.findIndex(w => w.resolve === wrappedResolve);
806
+ if (idx >= 0) pending.splice(idx, 1);
807
+ if (pending.length === 0) this.pendingDownloads.delete(sessionId);
808
+ }
809
+ reject(new Error(`Download timed out after ${timeoutMs}ms`));
810
+ }, timeoutMs);
811
+
812
+ const wrappedResolve = (info: DownloadInfo) => {
813
+ clearTimeout(timer);
814
+ resolve(info);
815
+ };
816
+ const wrappedReject = (err: Error) => {
817
+ clearTimeout(timer);
818
+ reject(err);
819
+ };
820
+
821
+ const pending = this.pendingDownloads.get(sessionId) ?? [];
822
+ pending.push({ resolve: wrappedResolve, reject: wrappedReject });
823
+ this.pendingDownloads.set(sessionId, pending);
824
+ });
825
+ }
826
+
712
827
  hasContext(): boolean {
713
- return this.context !== null;
828
+ return this.context != null;
714
829
  }
715
830
  }
716
831
 
@@ -106,6 +106,11 @@ export class NetworkRecorder {
106
106
  /** URL patterns that indicate a successful login (checked via `includes`). */
107
107
  loginSignals: string[] = [];
108
108
 
109
+ /** Number of network entries recorded so far. */
110
+ get entryCount(): number {
111
+ return this.entries.size;
112
+ }
113
+
109
114
  constructor(targetDomain?: string) {
110
115
  this.targetDomain = targetDomain;
111
116
  }
@@ -54,6 +54,7 @@ class CallStartTool implements Tool {
54
54
  task: input.task as string,
55
55
  context: input.context as string | undefined,
56
56
  conversationId: context.conversationId,
57
+ assistantId: context.assistantId,
57
58
  callerIdentityMode: input.caller_identity_mode as 'assistant_number' | 'user_number' | undefined,
58
59
  });
59
60
 
@@ -20,6 +20,9 @@ import { getLogger } from '../../util/logger.js';
20
20
 
21
21
  const log = getLogger('credential-broker');
22
22
 
23
+ /** Tokens expire after 5 minutes to limit the window for using stale/revoked credentials. */
24
+ const TOKEN_TTL_MS = 5 * 60 * 1000;
25
+
23
26
  /**
24
27
  * Credential broker that issues single-use tokens for policy-checked credential access.
25
28
  *
@@ -101,6 +104,11 @@ export class CredentialBroker {
101
104
  if (token.consumed) {
102
105
  return { success: false, reason: 'Token already consumed' };
103
106
  }
107
+ if (Date.now() - token.createdAt > TOKEN_TTL_MS) {
108
+ this.tokens.delete(tokenId);
109
+ log.info({ tokenId }, 'Token expired (TTL exceeded)');
110
+ return { success: false, reason: 'Token expired' };
111
+ }
104
112
 
105
113
  token.consumed = true;
106
114
  const storageKey = `credential:${token.service}:${token.field}`;
@@ -358,11 +366,12 @@ export class CredentialBroker {
358
366
  };
359
367
  }
360
368
 
361
- /** Return the number of active (non-consumed, non-revoked) tokens. */
369
+ /** Return the number of active (non-consumed, non-revoked, non-expired) tokens. */
362
370
  get activeTokenCount(): number {
371
+ const now = Date.now();
363
372
  let count = 0;
364
373
  for (const token of this.tokens.values()) {
365
- if (!token.consumed) count++;
374
+ if (!token.consumed && now - token.createdAt <= TOKEN_TTL_MS) count++;
366
375
  }
367
376
  return count;
368
377
  }
@@ -6,9 +6,10 @@
6
6
  * in the secure key backend only.
7
7
  */
8
8
 
9
- import { readFileSync, writeFileSync, mkdirSync, existsSync, renameSync } from 'node:fs';
9
+ import { writeFileSync, renameSync } from 'node:fs';
10
10
  import { join, dirname } from 'node:path';
11
11
  import { getDataDir } from '../../util/platform.js';
12
+ import { ensureDir, readTextFileSync } from '../../util/fs.js';
12
13
  import { randomUUID } from 'node:crypto';
13
14
  import type { CredentialInjectionTemplate } from './policy-types.js';
14
15
 
@@ -28,6 +29,8 @@ export interface CredentialMetadata {
28
29
  oauth2ClientId?: string;
29
30
  /** OAuth2 client secret — for providers that require it (e.g. Slack). Stored in metadata for autonomous refresh. */
30
31
  oauth2ClientSecret?: string;
32
+ /** How the client authenticates at the token endpoint (client_secret_basic or client_secret_post). */
33
+ oauth2TokenEndpointAuthMethod?: string;
31
34
  /** Human-friendly name for this credential (e.g. "fal-primary"). */
32
35
  alias?: string;
33
36
  /** Templates describing how to inject this credential into proxied requests. */
@@ -71,7 +74,7 @@ function isUnknownVersion(r: LoadResult): r is UnknownVersionResult {
71
74
  * Filters out corrupted or incomplete entries during migration.
72
75
  */
73
76
  function isValidCredentialRecord(record: unknown): record is Record<string, unknown> {
74
- if (typeof record !== 'object' || record === null) return false;
77
+ if (typeof record !== 'object' || record == null) return false;
75
78
  const r = record as Record<string, unknown>;
76
79
  return (
77
80
  typeof r.credentialId === 'string' &&
@@ -99,6 +102,7 @@ function migrateRecordV1toV2(record: Record<string, unknown>): CredentialMetadat
99
102
  oauth2TokenUrl: typeof record.oauth2TokenUrl === 'string' ? record.oauth2TokenUrl : undefined,
100
103
  oauth2ClientId: typeof record.oauth2ClientId === 'string' ? record.oauth2ClientId : undefined,
101
104
  oauth2ClientSecret: typeof record.oauth2ClientSecret === 'string' ? record.oauth2ClientSecret : undefined,
105
+ oauth2TokenEndpointAuthMethod: typeof record.oauth2TokenEndpointAuthMethod === 'string' ? record.oauth2TokenEndpointAuthMethod : undefined,
102
106
  alias: typeof record.alias === 'string' ? record.alias : undefined,
103
107
  injectionTemplates: Array.isArray(record.injectionTemplates)
104
108
  ? (record.injectionTemplates as CredentialInjectionTemplate[])
@@ -109,14 +113,13 @@ function migrateRecordV1toV2(record: Record<string, unknown>): CredentialMetadat
109
113
  }
110
114
 
111
115
  function loadFile(): LoadResult {
112
- const path = getMetadataPath();
113
- if (!existsSync(path)) {
116
+ const raw = readTextFileSync(getMetadataPath());
117
+ if (raw == null) {
114
118
  return { version: CURRENT_VERSION, credentials: [] };
115
119
  }
116
120
  try {
117
- const raw = readFileSync(path, 'utf-8');
118
121
  const data = JSON.parse(raw);
119
- if (typeof data !== 'object' || data === null) {
122
+ if (typeof data !== 'object' || data == null) {
120
123
  return { version: CURRENT_VERSION, credentials: [] };
121
124
  }
122
125
  const fileVersion = typeof data.version === 'number' ? data.version : 1;
@@ -146,9 +149,7 @@ function loadFile(): LoadResult {
146
149
  function saveFile(data: MetadataFile): void {
147
150
  const path = getMetadataPath();
148
151
  const dir = dirname(path);
149
- if (!existsSync(dir)) {
150
- mkdirSync(dir, { recursive: true });
151
- }
152
+ ensureDir(dir);
152
153
  const tmpPath = join(dir, `.tmp-${randomUUID()}`);
153
154
  writeFileSync(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
154
155
  renameSync(tmpPath, path);
@@ -186,6 +187,7 @@ export function upsertCredentialMetadata(
186
187
  oauth2ClientId?: string;
187
188
  /** Pass `null` to explicitly clear a previously-set client secret. */
188
189
  oauth2ClientSecret?: string | null;
190
+ oauth2TokenEndpointAuthMethod?: string;
189
191
  /** Pass `null` to explicitly clear a previously-set alias. */
190
192
  alias?: string | null;
191
193
  /** Pass `null` to explicitly clear injection templates. */
@@ -208,7 +210,7 @@ export function upsertCredentialMetadata(
208
210
  if (policy?.allowedDomains !== undefined) existing.allowedDomains = policy.allowedDomains;
209
211
  if (policy?.usageDescription !== undefined) existing.usageDescription = policy.usageDescription;
210
212
  if (policy?.expiresAt !== undefined) {
211
- if (policy.expiresAt === null) {
213
+ if (policy.expiresAt == null) {
212
214
  delete existing.expiresAt;
213
215
  } else {
214
216
  existing.expiresAt = policy.expiresAt;
@@ -216,7 +218,7 @@ export function upsertCredentialMetadata(
216
218
  }
217
219
  if (policy?.grantedScopes !== undefined) existing.grantedScopes = policy.grantedScopes;
218
220
  if (policy?.accountInfo !== undefined) {
219
- if (policy.accountInfo === null) {
221
+ if (policy.accountInfo == null) {
220
222
  delete existing.accountInfo;
221
223
  } else {
222
224
  existing.accountInfo = policy.accountInfo;
@@ -225,21 +227,22 @@ export function upsertCredentialMetadata(
225
227
  if (policy?.oauth2TokenUrl !== undefined) existing.oauth2TokenUrl = policy.oauth2TokenUrl;
226
228
  if (policy?.oauth2ClientId !== undefined) existing.oauth2ClientId = policy.oauth2ClientId;
227
229
  if (policy?.oauth2ClientSecret !== undefined) {
228
- if (policy.oauth2ClientSecret === null) {
230
+ if (policy.oauth2ClientSecret == null) {
229
231
  delete existing.oauth2ClientSecret;
230
232
  } else {
231
233
  existing.oauth2ClientSecret = policy.oauth2ClientSecret;
232
234
  }
233
235
  }
236
+ if (policy?.oauth2TokenEndpointAuthMethod !== undefined) existing.oauth2TokenEndpointAuthMethod = policy.oauth2TokenEndpointAuthMethod;
234
237
  if (policy?.alias !== undefined) {
235
- if (policy.alias === null) {
238
+ if (policy.alias == null) {
236
239
  delete existing.alias;
237
240
  } else {
238
241
  existing.alias = policy.alias;
239
242
  }
240
243
  }
241
244
  if (policy?.injectionTemplates !== undefined) {
242
- if (policy.injectionTemplates === null) {
245
+ if (policy.injectionTemplates == null) {
243
246
  delete existing.injectionTemplates;
244
247
  } else {
245
248
  existing.injectionTemplates = policy.injectionTemplates;
@@ -263,6 +266,7 @@ export function upsertCredentialMetadata(
263
266
  oauth2TokenUrl: policy?.oauth2TokenUrl,
264
267
  oauth2ClientId: policy?.oauth2ClientId,
265
268
  oauth2ClientSecret: policy?.oauth2ClientSecret ?? undefined,
269
+ oauth2TokenEndpointAuthMethod: policy?.oauth2TokenEndpointAuthMethod,
266
270
  alias: policy?.alias ?? undefined,
267
271
  injectionTemplates: policy?.injectionTemplates ?? undefined,
268
272
  createdAt: now,
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Post-connect hooks for OAuth2 services.
3
+ *
4
+ * This module decouples provider-specific post-connection side effects
5
+ * (e.g. sending a welcome DM after Slack OAuth) from the generic vault
6
+ * OAuth2 flow. Each hook is keyed by canonical service name and receives
7
+ * the raw token response so it can perform provider-specific actions.
8
+ */
9
+
10
+ import { authTest, conversationsOpen, postMessage } from '../../messaging/providers/slack/client.js';
11
+ import { getLogger } from '../../util/logger.js';
12
+
13
+ const log = getLogger('post-connect-hooks');
14
+
15
+ export interface PostConnectHookContext {
16
+ service: string;
17
+ rawTokenResponse: Record<string, unknown>;
18
+ }
19
+
20
+ type PostConnectHook = (ctx: PostConnectHookContext) => Promise<void>;
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Provider-specific hooks
24
+ // ---------------------------------------------------------------------------
25
+
26
+ async function slackWelcomeDM(ctx: PostConnectHookContext): Promise<void> {
27
+ const botToken = ctx.rawTokenResponse.access_token as string | undefined;
28
+ const authedUser = ctx.rawTokenResponse.authed_user as Record<string, unknown> | undefined;
29
+ const installingUserId = authedUser?.id as string | undefined;
30
+ if (!botToken || !installingUserId) return;
31
+
32
+ const identity = await authTest(botToken);
33
+ const dmChannel = await conversationsOpen(botToken, installingUserId);
34
+ const welcomeMsg =
35
+ `You have installed ${identity.user}, an AI Assistant, on ${identity.team}. ` +
36
+ `You can manage the assistant experience for this workspace by chatting with the assistant or from the Settings page.`;
37
+ await postMessage(botToken, dmChannel.channel.id, welcomeMsg);
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Registry
42
+ // ---------------------------------------------------------------------------
43
+
44
+ const POST_CONNECT_HOOKS: Record<string, PostConnectHook> = {
45
+ 'integration:slack': slackWelcomeDM,
46
+ };
47
+
48
+ /**
49
+ * Run the post-connect hook for a service, if one is registered.
50
+ * Failures are logged but never propagated -- they must not break the OAuth flow.
51
+ */
52
+ export async function runPostConnectHook(ctx: PostConnectHookContext): Promise<void> {
53
+ const hook = POST_CONNECT_HOOKS[ctx.service];
54
+ if (!hook) return;
55
+
56
+ try {
57
+ await hook(ctx);
58
+ } catch (err) {
59
+ log.warn({ err, service: ctx.service }, 'Post-connect hook failed (non-fatal)');
60
+ }
61
+ }
@@ -13,8 +13,8 @@ import { upsertCredentialMetadata, deleteCredentialMetadata, getCredentialMetada
13
13
  import { validatePolicyInput, toPolicyFromInput } from './policy-validate.js';
14
14
  import type { CredentialPolicyInput, CredentialInjectionTemplate } from './policy-types.js';
15
15
  import { credentialBroker } from './broker.js';
16
- import { startOAuth2Flow } from '../../security/oauth2.js';
17
- import { authTest, conversationsOpen, postMessage } from '../../messaging/providers/slack/client.js';
16
+ import { startOAuth2Flow, type TokenEndpointAuthMethod } from '../../security/oauth2.js';
17
+ import { runPostConnectHook } from './post-connect-hooks.js';
18
18
  import { getConfig } from '../../config/loader.js';
19
19
  import { getLogger } from '../../util/logger.js';
20
20
 
@@ -32,6 +32,10 @@ interface WellKnownOAuthConfig {
32
32
  scopes: string[];
33
33
  userinfoUrl?: string;
34
34
  extraParams?: Record<string, string>;
35
+ /** How to send client credentials at the token endpoint. Defaults to client_secret_post. */
36
+ tokenEndpointAuthMethod?: TokenEndpointAuthMethod;
37
+ /** Injection templates auto-applied to the access_token credential after a successful OAuth2 connect. */
38
+ injectionTemplates?: CredentialInjectionTemplate[];
35
39
  }
36
40
 
37
41
  const WELL_KNOWN_OAUTH: Record<string, WellKnownOAuthConfig> = {
@@ -64,12 +68,34 @@ const WELL_KNOWN_OAUTH: Record<string, WellKnownOAuthConfig> = {
64
68
  user_scope: 'channels:read,channels:history,groups:read,groups:history,im:read,im:history,mpim:read,mpim:history,users:read,chat:write,search:read,reactions:write',
65
69
  },
66
70
  },
71
+ // Notion uses a simple OAuth2 flow with client_secret_basic auth at the token endpoint.
72
+ // The access token is long-lived (no expiry) and scopes are configured per-integration in Notion
73
+ // (the authorization URL accepts owner=user but there are no traditional scope strings to request).
74
+ 'integration:notion': {
75
+ authUrl: 'https://api.notion.com/v1/oauth/authorize',
76
+ tokenUrl: 'https://api.notion.com/v1/oauth/token',
77
+ scopes: [],
78
+ extraParams: { owner: 'user' },
79
+ // Notion requires HTTP Basic Auth (base64 of client_id:client_secret) at the token endpoint,
80
+ // not the default client_secret_post form-body approach.
81
+ tokenEndpointAuthMethod: 'client_secret_basic',
82
+ // Auto-inject the Bearer token for all Notion API calls made through the sandbox proxy.
83
+ injectionTemplates: [
84
+ {
85
+ hostPattern: 'api.notion.com',
86
+ injectionType: 'header',
87
+ headerName: 'Authorization',
88
+ valuePrefix: 'Bearer ',
89
+ },
90
+ ],
91
+ },
67
92
  };
68
93
 
69
94
  /** Map shorthand aliases to canonical service names. */
70
95
  const SERVICE_ALIASES: Record<string, string> = {
71
96
  gmail: 'integration:gmail',
72
97
  slack: 'integration:slack',
98
+ notion: 'integration:notion',
73
99
  };
74
100
 
75
101
  /** Resolve a service name through aliases. */
@@ -198,6 +224,11 @@ class CredentialStoreTool implements Tool {
198
224
  type: 'string',
199
225
  description: 'OAuth2 client secret for providers that require it (e.g. Google, Slack). If omitted, looked up from previously stored credentials; if still absent, PKCE-only is used (only for oauth2_connect action)',
200
226
  },
227
+ token_endpoint_auth_method: {
228
+ type: 'string',
229
+ enum: ['client_secret_basic', 'client_secret_post'],
230
+ description: 'How to send client credentials at the token endpoint: "client_secret_post" (default, in POST body) or "client_secret_basic" (HTTP Basic Auth header). Only for oauth2_connect action.',
231
+ },
201
232
  alias: {
202
233
  type: 'string',
203
234
  description: 'Human-friendly name for this credential (only for store action), e.g. "fal-primary"',
@@ -269,7 +300,7 @@ class CredentialStoreTool implements Tool {
269
300
  injectionTemplates = [];
270
301
  for (let i = 0; i < rawTemplates.length; i++) {
271
302
  const t = rawTemplates[i] as Record<string, unknown>;
272
- if (typeof t !== 'object' || t === null) {
303
+ if (typeof t !== 'object' || t == null) {
273
304
  templateErrors.push(`injection_templates[${i}] must be an object`);
274
305
  continue;
275
306
  }
@@ -540,10 +571,15 @@ class CredentialStoreTool implements Tool {
540
571
  ?? findStoredOAuthField(service, ['client_id', 'oauth_client_id']);
541
572
  const clientSecret = (input.client_secret as string | undefined)
542
573
  ?? findStoredOAuthField(service, ['client_secret', 'oauth_client_secret']);
574
+ const tokenEndpointAuthMethod = (input.token_endpoint_auth_method as TokenEndpointAuthMethod | undefined)
575
+ ?? wellKnown?.tokenEndpointAuthMethod;
543
576
 
544
577
  if (!authUrl) return { content: 'Error: auth_url is required for oauth2_connect action (no well-known config for this service)', isError: true };
545
578
  if (!tokenUrl) return { content: 'Error: token_url is required for oauth2_connect action (no well-known config for this service)', isError: true };
546
- if (!scopes || scopes.length === 0) return { content: 'Error: scopes is required for oauth2_connect action (no well-known config for this service)', isError: true };
579
+ // Scopes are optional some providers (e.g. Notion) configure authorization at the integration
580
+ // level and don't use traditional scope strings. Reject only when scopes is entirely absent (not
581
+ // provided and no well-known config), not when it is an empty array.
582
+ if (!scopes) return { content: 'Error: scopes is required for oauth2_connect action (no well-known config for this service)', isError: true };
547
583
  if (!clientId) return { content: 'Error: client_id is required for oauth2_connect action. Provide it directly or store it first with credential_store.', isError: true };
548
584
 
549
585
  if (!context.isInteractive) {
@@ -560,7 +596,7 @@ class CredentialStoreTool implements Tool {
560
596
  const allowedTools = input.allowed_tools as string[] | undefined;
561
597
 
562
598
  const { tokens, grantedScopes, rawTokenResponse } = await startOAuth2Flow(
563
- { authUrl, tokenUrl, scopes, clientId, clientSecret, extraParams, userinfoUrl },
599
+ { authUrl, tokenUrl, scopes, clientId, clientSecret, extraParams, userinfoUrl, tokenEndpointAuthMethod },
564
600
  {
565
601
  openUrl: (url) => {
566
602
  context.sendToClient?.({ type: 'open_url', url, title: `Connect ${service}` });
@@ -602,6 +638,10 @@ class CredentialStoreTool implements Tool {
602
638
  }
603
639
  }
604
640
 
641
+ // Well-known configs may supply injection templates (e.g. auto-inject Bearer header for
642
+ // api.notion.com) so that bash/network_mode=proxied works without manual setup.
643
+ const wellKnownInjectionTemplates = wellKnown?.injectionTemplates;
644
+
605
645
  upsertCredentialMetadata(service, 'access_token', {
606
646
  allowedTools: allowedTools ?? [],
607
647
  expiresAt,
@@ -610,6 +650,8 @@ class CredentialStoreTool implements Tool {
610
650
  oauth2TokenUrl: tokenUrl,
611
651
  oauth2ClientId: clientId,
612
652
  ...(clientSecret ? { oauth2ClientSecret: clientSecret } : {}),
653
+ ...(tokenEndpointAuthMethod ? { oauth2TokenEndpointAuthMethod: tokenEndpointAuthMethod } : {}),
654
+ ...(wellKnownInjectionTemplates ? { injectionTemplates: wellKnownInjectionTemplates } : {}),
613
655
  });
614
656
 
615
657
  if (tokens.refreshToken) {
@@ -619,24 +661,8 @@ class CredentialStoreTool implements Tool {
619
661
  }
620
662
  }
621
663
 
622
- // Send a welcome DM for Slack connections
623
- if (service === 'integration:slack') {
624
- try {
625
- const botToken = rawTokenResponse.access_token as string | undefined;
626
- const authedUser = rawTokenResponse.authed_user as Record<string, unknown> | undefined;
627
- const installingUserId = authedUser?.id as string | undefined;
628
- if (botToken && installingUserId) {
629
- const identity = await authTest(botToken);
630
- const dmChannel = await conversationsOpen(botToken, installingUserId);
631
- const welcomeMsg =
632
- `You have installed ${identity.user}, an AI Assistant, on ${identity.team}. ` +
633
- `Manage the assistant experience for this workspace in the workspace settings page.`;
634
- await postMessage(botToken, dmChannel.channel.id, welcomeMsg);
635
- }
636
- } catch (err) {
637
- log.warn({ err }, 'Failed to send Slack welcome DM (non-fatal)');
638
- }
639
- }
664
+ // Run any provider-specific post-connect actions (e.g. Slack welcome DM)
665
+ await runPostConnectHook({ service, rawTokenResponse });
640
666
 
641
667
  return {
642
668
  content: `Successfully connected "${service}"${accountInfo ? ` as ${accountInfo}` : ''}. The service is now ready to use.`,
@@ -1,7 +1,12 @@
1
1
  import type { ExecutionTarget } from './types.js';
2
2
  import { getTool } from './registry.js';
3
3
 
4
- export function resolveExecutionTarget(toolName: string): ExecutionTarget {
4
+ export interface ManifestOverride {
5
+ risk: 'low' | 'medium' | 'high';
6
+ execution_target: 'host' | 'sandbox';
7
+ }
8
+
9
+ export function resolveExecutionTarget(toolName: string, manifestOverride?: ManifestOverride): ExecutionTarget {
5
10
  const tool = getTool(toolName);
6
11
  // Manifest-declared execution target is authoritative — check it first so
7
12
  // skill tools with host_/computer_use_ prefixes aren't mis-classified.
@@ -13,6 +18,11 @@ export function resolveExecutionTarget(toolName: string): ExecutionTarget {
13
18
  if (tool?.executionMode === 'proxy') {
14
19
  return 'host';
15
20
  }
21
+ // Use manifest metadata for unregistered skill tools so the Permission
22
+ // Simulator shows accurate execution targets instead of defaulting to sandbox.
23
+ if (!tool && manifestOverride) {
24
+ return manifestOverride.execution_target;
25
+ }
16
26
  // Prefix heuristics for core tools that don't declare an explicit target.
17
27
  if (toolName.startsWith('host_') || toolName.startsWith('computer_use_')) {
18
28
  return 'host';