@vellumai/assistant 0.4.52 → 0.4.54

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 (380) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/bun.lock +62 -349
  3. package/docs/architecture/integrations.md +1 -1
  4. package/docs/architecture/keychain-broker.md +91 -40
  5. package/docs/architecture/memory.md +3 -3
  6. package/docs/architecture/security.md +2 -2
  7. package/knip.json +7 -29
  8. package/package.json +2 -9
  9. package/src/__tests__/agent-loop.test.ts +1 -1
  10. package/src/__tests__/app-git-history.test.ts +0 -2
  11. package/src/__tests__/app-git-service.test.ts +1 -6
  12. package/src/__tests__/approval-cascade.test.ts +3 -2
  13. package/src/__tests__/approval-routes-http.test.ts +0 -1
  14. package/src/__tests__/asset-materialize-tool.test.ts +0 -1
  15. package/src/__tests__/asset-search-tool.test.ts +0 -1
  16. package/src/__tests__/assistant-events-sse-hardening.test.ts +0 -1
  17. package/src/__tests__/attachments-store.test.ts +0 -1
  18. package/src/__tests__/avatar-e2e.test.ts +5 -1
  19. package/src/__tests__/browser-fill-credential.test.ts +4 -6
  20. package/src/__tests__/btw-routes.test.ts +39 -0
  21. package/src/__tests__/call-controller.test.ts +0 -1
  22. package/src/__tests__/call-domain.test.ts +1 -1
  23. package/src/__tests__/call-routes-http.test.ts +1 -3
  24. package/src/__tests__/canonical-guardian-store.test.ts +33 -2
  25. package/src/__tests__/channel-guardian.test.ts +4 -4
  26. package/src/__tests__/channel-readiness-routes.test.ts +0 -1
  27. package/src/__tests__/channel-readiness-service.test.ts +1 -1
  28. package/src/__tests__/checker.test.ts +13 -11
  29. package/src/__tests__/claude-code-skill-regression.test.ts +5 -2
  30. package/src/__tests__/claude-code-tool-profiles.test.ts +7 -3
  31. package/src/__tests__/config-loader-backfill.test.ts +1 -5
  32. package/src/__tests__/config-schema.test.ts +9 -46
  33. package/src/__tests__/config-watcher.test.ts +11 -3
  34. package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -1
  35. package/src/__tests__/credential-broker-browser-fill.test.ts +27 -24
  36. package/src/__tests__/credential-broker-server-use.test.ts +76 -40
  37. package/src/__tests__/credential-security-e2e.test.ts +1 -6
  38. package/src/__tests__/credential-security-invariants.test.ts +27 -8
  39. package/src/__tests__/credential-vault-unit.test.ts +32 -16
  40. package/src/__tests__/credential-vault.test.ts +40 -28
  41. package/src/__tests__/credentials-cli.test.ts +1 -21
  42. package/src/__tests__/email-invite-adapter.test.ts +0 -1
  43. package/src/__tests__/error-handler-friendly-messages.test.ts +4 -5
  44. package/src/__tests__/fixtures/credential-security-fixtures.ts +3 -3
  45. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -79
  46. package/src/__tests__/gateway-only-enforcement.test.ts +1 -23
  47. package/src/__tests__/guardian-action-conversation-turn.test.ts +8 -8
  48. package/src/__tests__/guardian-action-late-reply.test.ts +13 -14
  49. package/src/__tests__/guardian-action-store.test.ts +0 -57
  50. package/src/__tests__/guardian-outbound-http.test.ts +1 -1
  51. package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -3
  52. package/src/__tests__/hooks-blocking.test.ts +1 -1
  53. package/src/__tests__/hooks-config.test.ts +5 -29
  54. package/src/__tests__/hooks-discovery.test.ts +1 -1
  55. package/src/__tests__/hooks-integration.test.ts +1 -1
  56. package/src/__tests__/hooks-manager.test.ts +1 -1
  57. package/src/__tests__/hooks-runner.test.ts +1 -23
  58. package/src/__tests__/hooks-settings.test.ts +1 -1
  59. package/src/__tests__/hooks-templates.test.ts +1 -1
  60. package/src/__tests__/host-shell-tool.test.ts +0 -1
  61. package/src/__tests__/http-user-message-parity.test.ts +19 -0
  62. package/src/__tests__/integration-status.test.ts +0 -1
  63. package/src/__tests__/invite-routes-http.test.ts +0 -3
  64. package/src/__tests__/list-messages-attachments.test.ts +0 -1
  65. package/src/__tests__/llm-usage-store.test.ts +50 -0
  66. package/src/__tests__/log-export-workspace.test.ts +233 -0
  67. package/src/__tests__/managed-proxy-context.test.ts +41 -41
  68. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
  69. package/src/__tests__/media-generate-image.test.ts +9 -4
  70. package/src/__tests__/media-reuse-story.e2e.test.ts +1 -7
  71. package/src/__tests__/memory-regressions.experimental.test.ts +4 -4
  72. package/src/__tests__/memory-regressions.test.ts +27 -28
  73. package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
  74. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -4
  75. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1
  76. package/src/__tests__/migration-export-http.test.ts +0 -1
  77. package/src/__tests__/migration-import-commit-http.test.ts +0 -1
  78. package/src/__tests__/migration-import-preflight-http.test.ts +0 -1
  79. package/src/__tests__/migration-validate-http.test.ts +0 -1
  80. package/src/__tests__/notification-decision-fallback.test.ts +1 -1
  81. package/src/__tests__/notification-schedule-dedup.test.ts +237 -0
  82. package/src/__tests__/oauth-cli.test.ts +2 -14
  83. package/src/__tests__/oauth-store.test.ts +3 -7
  84. package/src/__tests__/oauth2-gateway-transport.test.ts +5 -4
  85. package/src/__tests__/onboarding-starter-tasks.test.ts +1 -1
  86. package/src/__tests__/onboarding-template-contract.test.ts +1 -2
  87. package/src/__tests__/openai-provider.test.ts +7 -7
  88. package/src/__tests__/platform.test.ts +14 -4
  89. package/src/__tests__/pricing.test.ts +0 -234
  90. package/src/__tests__/provider-commit-message-generator.test.ts +19 -15
  91. package/src/__tests__/provider-fail-open-selection.test.ts +67 -62
  92. package/src/__tests__/provider-managed-proxy-integration.test.ts +88 -85
  93. package/src/__tests__/provider-registry-ollama.test.ts +10 -4
  94. package/src/__tests__/public-ingress-urls.test.ts +1 -1
  95. package/src/__tests__/recording-handler.test.ts +0 -1
  96. package/src/__tests__/registry.test.ts +3 -103
  97. package/src/__tests__/relay-server.test.ts +0 -1
  98. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  99. package/src/__tests__/runtime-events-sse-parity.test.ts +0 -1
  100. package/src/__tests__/runtime-events-sse.test.ts +0 -1
  101. package/src/__tests__/script-proxy-injection-runtime.test.ts +2 -7
  102. package/src/__tests__/secret-onetime-send.test.ts +1 -6
  103. package/src/__tests__/secret-routes-managed-proxy.test.ts +6 -14
  104. package/src/__tests__/secret-scanner-executor.test.ts +0 -1
  105. package/src/__tests__/secure-keys.test.ts +241 -229
  106. package/src/__tests__/send-endpoint-busy.test.ts +0 -1
  107. package/src/__tests__/session-abort-tool-results.test.ts +3 -2
  108. package/src/__tests__/session-agent-loop-overflow.test.ts +1012 -838
  109. package/src/__tests__/session-agent-loop.test.ts +2 -2
  110. package/src/__tests__/session-confirmation-signals.test.ts +3 -2
  111. package/src/__tests__/session-error.test.ts +5 -4
  112. package/src/__tests__/session-history-web-search.test.ts +34 -9
  113. package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -7
  114. package/src/__tests__/session-pre-run-repair.test.ts +3 -2
  115. package/src/__tests__/session-provider-retry-repair.test.ts +31 -27
  116. package/src/__tests__/session-queue.test.ts +5 -5
  117. package/src/__tests__/session-runtime-assembly.test.ts +118 -0
  118. package/src/__tests__/session-slash-known.test.ts +31 -14
  119. package/src/__tests__/session-slash-queue.test.ts +3 -2
  120. package/src/__tests__/session-slash-unknown.test.ts +3 -2
  121. package/src/__tests__/session-workspace-cache-state.test.ts +3 -1
  122. package/src/__tests__/session-workspace-injection.test.ts +3 -2
  123. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -2
  124. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
  125. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  126. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
  127. package/src/__tests__/skillssh-registry.test.ts +21 -0
  128. package/src/__tests__/slack-channel-config.test.ts +1 -7
  129. package/src/__tests__/slack-share-routes.test.ts +1 -1
  130. package/src/__tests__/swarm-recursion.test.ts +4 -1
  131. package/src/__tests__/swarm-session-integration.test.ts +24 -14
  132. package/src/__tests__/swarm-tool.test.ts +4 -2
  133. package/src/__tests__/task-compiler.test.ts +1 -1
  134. package/src/__tests__/telegram-bot-username-resolution.test.ts +2 -4
  135. package/src/__tests__/test-support/browser-skill-harness.ts +0 -18
  136. package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -23
  137. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1521 -0
  138. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
  139. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  140. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  141. package/src/__tests__/tool-executor.test.ts +1 -2
  142. package/src/__tests__/trust-store.test.ts +8 -83
  143. package/src/__tests__/twilio-config.test.ts +0 -1
  144. package/src/__tests__/twilio-provider.test.ts +0 -5
  145. package/src/__tests__/twilio-routes.test.ts +2 -3
  146. package/src/__tests__/usage-cache-backfill-migration.test.ts +10 -10
  147. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  148. package/src/__tests__/voice-quality.test.ts +2 -1
  149. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  150. package/src/__tests__/web-search.test.ts +1 -1
  151. package/src/agent/loop.ts +17 -1
  152. package/src/bundler/app-bundler.ts +40 -24
  153. package/src/calls/call-controller.ts +16 -0
  154. package/src/calls/guardian-question-copy.ts +1 -1
  155. package/src/calls/relay-server.ts +29 -13
  156. package/src/calls/voice-control-protocol.ts +1 -0
  157. package/src/calls/voice-quality.ts +1 -1
  158. package/src/calls/voice-session-bridge.ts +9 -3
  159. package/src/channels/types.ts +16 -0
  160. package/src/cli/commands/bash.ts +173 -0
  161. package/src/cli/commands/doctor.ts +15 -57
  162. package/src/cli/commands/memory.ts +3 -5
  163. package/src/cli/commands/oauth/connections.ts +4 -2
  164. package/src/cli/commands/oauth/providers.ts +1 -13
  165. package/src/cli/commands/sessions.ts +1 -1
  166. package/src/cli/commands/usage.ts +359 -0
  167. package/src/cli/http-client.ts +22 -12
  168. package/src/cli/program.ts +4 -0
  169. package/src/cli/reference.ts +2 -0
  170. package/src/cli.ts +251 -181
  171. package/src/config/assistant-feature-flags.ts +0 -7
  172. package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
  173. package/src/config/bundled-skills/claude-code/SKILL.md +1 -1
  174. package/src/config/bundled-skills/claude-code/TOOLS.json +1 -1
  175. package/src/config/bundled-skills/gmail/SKILL.md +0 -1
  176. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +4 -3
  177. package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
  178. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +3 -5
  179. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -3
  180. package/src/config/bundled-skills/messaging/SKILL.md +0 -1
  181. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +1 -1
  182. package/src/config/bundled-skills/sequences/SKILL.md +0 -1
  183. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -6
  184. package/src/config/env.ts +13 -0
  185. package/src/config/feature-flag-registry.json +15 -39
  186. package/src/config/loader.ts +7 -135
  187. package/src/config/schema.ts +0 -6
  188. package/src/config/schemas/channels.ts +1 -0
  189. package/src/config/schemas/elevenlabs.ts +2 -2
  190. package/src/config/schemas/security.ts +1 -2
  191. package/src/config/skills.ts +1 -1
  192. package/src/contacts/contact-store.ts +21 -75
  193. package/src/contacts/contacts-write.ts +6 -6
  194. package/src/contacts/types.ts +2 -0
  195. package/src/context/token-estimator.ts +35 -2
  196. package/src/context/window-manager.ts +16 -2
  197. package/src/daemon/approved-devices-store.ts +0 -44
  198. package/src/daemon/classifier.ts +1 -1
  199. package/src/daemon/config-watcher.ts +35 -11
  200. package/src/daemon/context-overflow-reducer.ts +13 -2
  201. package/src/daemon/handlers/config-ingress.ts +25 -8
  202. package/src/daemon/handlers/config-model.ts +22 -16
  203. package/src/daemon/handlers/config-telegram.ts +18 -6
  204. package/src/daemon/handlers/dictation.ts +0 -429
  205. package/src/daemon/handlers/sessions.ts +4 -116
  206. package/src/daemon/handlers/skills.ts +2 -201
  207. package/src/daemon/lifecycle.ts +21 -20
  208. package/src/daemon/message-types/contacts.ts +2 -0
  209. package/src/daemon/message-types/integrations.ts +1 -0
  210. package/src/daemon/message-types/sessions.ts +2 -0
  211. package/src/daemon/parse-actual-tokens-from-error.test.ts +75 -0
  212. package/src/daemon/providers-setup.ts +1 -1
  213. package/src/daemon/server.ts +42 -5
  214. package/src/daemon/session-agent-loop-handlers.ts +1 -1
  215. package/src/daemon/session-agent-loop.ts +27 -79
  216. package/src/daemon/session-error.ts +5 -4
  217. package/src/daemon/session-process.ts +17 -10
  218. package/src/daemon/session-runtime-assembly.ts +50 -0
  219. package/src/daemon/session-slash.ts +34 -22
  220. package/src/daemon/session.ts +1 -0
  221. package/src/daemon/shutdown-handlers.ts +15 -0
  222. package/src/daemon/watch-handler.ts +2 -2
  223. package/src/email/guardrails.ts +1 -1
  224. package/src/email/service.ts +0 -5
  225. package/src/events/domain-events.ts +1 -0
  226. package/src/hooks/templates.ts +1 -1
  227. package/src/media/app-icon-generator.ts +4 -3
  228. package/src/media/avatar-router.ts +5 -4
  229. package/src/media/gemini-image-service.ts +5 -5
  230. package/src/memory/admin.ts +2 -2
  231. package/src/memory/app-git-service.ts +0 -7
  232. package/src/memory/canonical-guardian-store.ts +25 -3
  233. package/src/memory/conversation-crud.ts +1 -1
  234. package/src/memory/conversation-title-service.ts +2 -2
  235. package/src/memory/db-init.ts +12 -0
  236. package/src/memory/embedding-backend.ts +46 -33
  237. package/src/memory/external-conversation-store.ts +0 -30
  238. package/src/memory/guardian-action-store.ts +0 -31
  239. package/src/memory/guardian-approvals.ts +1 -56
  240. package/src/memory/indexer.ts +4 -3
  241. package/src/memory/items-extractor.ts +1 -1
  242. package/src/memory/job-handlers/backfill.ts +5 -2
  243. package/src/memory/job-handlers/index-maintenance.ts +2 -2
  244. package/src/memory/job-handlers/media-processing.ts +2 -2
  245. package/src/memory/job-handlers/summarization.ts +1 -1
  246. package/src/memory/job-utils.ts +1 -2
  247. package/src/memory/jobs-worker.ts +2 -2
  248. package/src/memory/llm-usage-store.ts +57 -11
  249. package/src/memory/media-store.ts +4 -535
  250. package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +2 -2
  251. package/src/memory/migrations/110-channel-guardian.ts +0 -1
  252. package/src/memory/migrations/158-channel-interaction-columns.ts +18 -0
  253. package/src/memory/migrations/159-drop-contact-interaction-columns.ts +16 -0
  254. package/src/memory/migrations/160-drop-loopback-port-column.ts +13 -0
  255. package/src/memory/migrations/index.ts +3 -0
  256. package/src/memory/published-pages-store.ts +0 -83
  257. package/src/memory/qdrant-circuit-breaker.ts +0 -8
  258. package/src/memory/retriever.test.ts +19 -12
  259. package/src/memory/retriever.ts +1 -1
  260. package/src/memory/schema/contacts.ts +2 -2
  261. package/src/memory/schema/oauth.ts +0 -1
  262. package/src/memory/search/semantic.ts +1 -8
  263. package/src/memory/shared-app-links-store.ts +0 -15
  264. package/src/messaging/registry.ts +0 -5
  265. package/src/messaging/style-analyzer.ts +1 -1
  266. package/src/notifications/copy-composer.ts +5 -13
  267. package/src/notifications/decision-engine.ts +2 -2
  268. package/src/notifications/deliveries-store.ts +0 -39
  269. package/src/notifications/guardian-question-mode.ts +6 -10
  270. package/src/notifications/preference-extractor.ts +1 -1
  271. package/src/oauth/byo-connection.test.ts +29 -20
  272. package/src/oauth/connect-orchestrator.ts +5 -3
  273. package/src/oauth/connect-types.ts +9 -2
  274. package/src/oauth/manual-token-connection.ts +9 -7
  275. package/src/oauth/oauth-store.ts +2 -8
  276. package/src/oauth/provider-behaviors.ts +11 -1
  277. package/src/oauth/seed-providers.ts +13 -5
  278. package/src/permissions/checker.ts +21 -2
  279. package/src/permissions/shell-identity.ts +0 -5
  280. package/src/permissions/trust-store.ts +0 -37
  281. package/src/prompts/__tests__/build-cli-reference-section.test.ts +1 -1
  282. package/src/prompts/system-prompt.ts +5 -14
  283. package/src/prompts/templates/BOOTSTRAP.md +1 -3
  284. package/src/providers/anthropic/client.ts +16 -8
  285. package/src/providers/managed-proxy/constants.ts +9 -11
  286. package/src/providers/managed-proxy/context.ts +14 -9
  287. package/src/providers/provider-send-message.ts +4 -52
  288. package/src/providers/registry.ts +29 -57
  289. package/src/providers/types.ts +1 -1
  290. package/src/runtime/actor-token-store.ts +0 -23
  291. package/src/runtime/auth/route-policy.ts +4 -0
  292. package/src/runtime/channel-invite-transports/telegram.ts +12 -6
  293. package/src/runtime/channel-retry-sweep.ts +6 -0
  294. package/src/runtime/http-router.ts +5 -1
  295. package/src/runtime/http-server.ts +101 -4
  296. package/src/runtime/http-types.ts +1 -0
  297. package/src/runtime/invite-instruction-generator.ts +25 -51
  298. package/src/runtime/invite-service.ts +0 -20
  299. package/src/runtime/middleware/error-handler.ts +1 -2
  300. package/src/runtime/routes/app-management-routes.ts +1 -0
  301. package/src/runtime/routes/attachment-routes.ts +1 -1
  302. package/src/runtime/routes/brain-graph-routes.ts +1 -1
  303. package/src/runtime/routes/btw-routes.ts +20 -1
  304. package/src/runtime/routes/call-routes.ts +1 -1
  305. package/src/runtime/routes/conversation-routes.ts +64 -24
  306. package/src/runtime/routes/debug-routes.ts +1 -1
  307. package/src/runtime/routes/diagnostics-routes.ts +2 -2
  308. package/src/runtime/routes/documents-routes.ts +3 -3
  309. package/src/runtime/routes/global-search-routes.ts +1 -1
  310. package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -20
  311. package/src/runtime/routes/guardian-refresh-routes.ts +0 -20
  312. package/src/runtime/routes/inbound-message-handler.ts +10 -2
  313. package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
  314. package/src/runtime/routes/inbound-stages/edit-intercept.ts +5 -5
  315. package/src/runtime/routes/integrations/slack/share.ts +5 -5
  316. package/src/runtime/routes/log-export-routes.ts +122 -10
  317. package/src/runtime/routes/secret-routes.ts +4 -4
  318. package/src/runtime/routes/session-query-routes.ts +3 -3
  319. package/src/runtime/routes/settings-routes.ts +53 -0
  320. package/src/runtime/routes/trust-rules-routes.ts +1 -1
  321. package/src/runtime/routes/workspace-routes.ts +3 -0
  322. package/src/runtime/verification-templates.ts +1 -1
  323. package/src/security/credential-backend.ts +148 -0
  324. package/src/security/oauth2.ts +5 -5
  325. package/src/security/secret-allowlist.ts +1 -1
  326. package/src/security/secure-keys.ts +98 -160
  327. package/src/security/token-manager.ts +0 -7
  328. package/src/sequence/guardrails.ts +0 -4
  329. package/src/sequence/store.ts +1 -20
  330. package/src/sequence/types.ts +1 -36
  331. package/src/signals/bash.ts +157 -0
  332. package/src/signals/cancel.ts +69 -0
  333. package/src/signals/conversation-undo.ts +127 -0
  334. package/src/signals/trust-rule.ts +174 -0
  335. package/src/skills/clawhub.ts +5 -5
  336. package/src/skills/managed-store.ts +4 -4
  337. package/src/skills/skillssh-registry.ts +6 -1
  338. package/src/swarm/backend-claude-code.ts +6 -6
  339. package/src/swarm/worker-backend.ts +1 -1
  340. package/src/swarm/worker-runner.ts +1 -1
  341. package/src/telegram/bot-username.ts +11 -0
  342. package/src/telemetry/usage-telemetry-reporter.test.ts +366 -0
  343. package/src/telemetry/usage-telemetry-reporter.ts +181 -0
  344. package/src/tools/claude-code/claude-code.ts +6 -6
  345. package/src/tools/credentials/broker.ts +7 -5
  346. package/src/tools/credentials/vault.ts +11 -6
  347. package/src/tools/memory/handlers.test.ts +24 -26
  348. package/src/tools/memory/handlers.ts +1 -13
  349. package/src/tools/network/__tests__/web-search.test.ts +18 -86
  350. package/src/tools/network/web-search.ts +9 -15
  351. package/src/tools/registry.ts +5 -100
  352. package/src/tools/terminal/parser.ts +34 -4
  353. package/src/tools/tool-manifest.ts +0 -10
  354. package/src/usage/actors.ts +0 -12
  355. package/src/util/canonicalize-identity.ts +0 -9
  356. package/src/util/errors.ts +0 -3
  357. package/src/util/platform.ts +31 -8
  358. package/src/util/pricing.ts +0 -39
  359. package/src/watcher/constants.ts +0 -7
  360. package/src/watcher/providers/linear.ts +1 -1
  361. package/src/work-items/work-item-store.ts +4 -4
  362. package/src/workspace/commit-message-provider.ts +1 -1
  363. package/src/workspace/git-service.ts +44 -1
  364. package/src/workspace/provider-commit-message-generator.ts +11 -7
  365. package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
  366. package/src/browser-extension-relay/client.ts +0 -155
  367. package/src/contacts/index.ts +0 -18
  368. package/src/daemon/tls-certs.ts +0 -270
  369. package/src/errors.ts +0 -41
  370. package/src/events/index.ts +0 -18
  371. package/src/followups/index.ts +0 -10
  372. package/src/playbooks/index.ts +0 -10
  373. package/src/runtime/auth/index.ts +0 -44
  374. package/src/tasks/candidate-store.ts +0 -95
  375. package/src/tools/browser/api-map.ts +0 -313
  376. package/src/tools/browser/auto-navigate.ts +0 -469
  377. package/src/tools/browser/headless-browser.ts +0 -590
  378. package/src/tools/browser/recording-store.ts +0 -75
  379. package/src/tools/computer-use/registry.ts +0 -21
  380. package/src/tools/tasks/index.ts +0 -27
@@ -22,89 +22,6 @@ export interface PublishedPageRecord {
22
22
  projectSlug: string | null;
23
23
  }
24
24
 
25
- export function createPublishedPage(record: {
26
- id: string;
27
- deploymentId: string;
28
- publicUrl: string;
29
- pageTitle?: string;
30
- htmlHash: string;
31
- appId?: string;
32
- projectSlug?: string;
33
- }): void {
34
- const db = getDb();
35
- db.insert(publishedPages)
36
- .values({
37
- id: record.id,
38
- deploymentId: record.deploymentId,
39
- publicUrl: record.publicUrl,
40
- pageTitle: record.pageTitle ?? null,
41
- htmlHash: record.htmlHash,
42
- publishedAt: Date.now(),
43
- status: "active",
44
- appId: record.appId ?? null,
45
- projectSlug: record.projectSlug ?? null,
46
- })
47
- .run();
48
- }
49
-
50
- export function getPublishedPageByDeploymentId(
51
- deploymentId: string,
52
- ): PublishedPageRecord | null {
53
- const db = getDb();
54
- const row = db
55
- .select()
56
- .from(publishedPages)
57
- .where(eq(publishedPages.deploymentId, deploymentId))
58
- .get();
59
-
60
- return row ?? null;
61
- }
62
-
63
- export function getPublishedPageByHash(
64
- hash: string,
65
- ): PublishedPageRecord | null {
66
- const db = getDb();
67
- const row = db
68
- .select()
69
- .from(publishedPages)
70
- .where(
71
- and(
72
- eq(publishedPages.htmlHash, hash),
73
- eq(publishedPages.status, "active"),
74
- ),
75
- )
76
- .get();
77
-
78
- return row ?? null;
79
- }
80
-
81
- export function listPublishedPages(): PublishedPageRecord[] {
82
- const db = getDb();
83
- return db
84
- .select()
85
- .from(publishedPages)
86
- .where(eq(publishedPages.status, "active"))
87
- .all();
88
- }
89
-
90
- export function markDeleted(id: string): boolean {
91
- const db = getDb();
92
- const existing = db
93
- .select({ id: publishedPages.id })
94
- .from(publishedPages)
95
- .where(eq(publishedPages.id, id))
96
- .get();
97
-
98
- if (!existing) return false;
99
-
100
- db.update(publishedPages)
101
- .set({ status: "deleted" })
102
- .where(eq(publishedPages.id, id))
103
- .run();
104
-
105
- return true;
106
- }
107
-
108
25
  export function getActivePublishedPageByAppId(
109
26
  appId: string,
110
27
  ): PublishedPageRecord | null {
@@ -108,11 +108,3 @@ export function _resetQdrantBreaker(): void {
108
108
  openedAt = 0;
109
109
  halfOpenProbeInFlight = false;
110
110
  }
111
-
112
- /** @internal Test-only: get breaker state */
113
- export function _getQdrantBreakerState(): {
114
- state: BreakerState;
115
- consecutiveFailures: number;
116
- } {
117
- return { state: breakerState, consecutiveFailures };
118
- }
@@ -374,7 +374,14 @@ describe("Memory Retriever Pipeline", () => {
374
374
  const convId = "conv-superseded";
375
375
 
376
376
  insertConversation(db, convId, now - 60_000);
377
- insertMessage(db, "msg-s1", convId, "user", "test superseded", now - 50_000);
377
+ insertMessage(
378
+ db,
379
+ "msg-s1",
380
+ convId,
381
+ "user",
382
+ "test superseded",
383
+ now - 50_000,
384
+ );
378
385
 
379
386
  insertSegment(
380
387
  db,
@@ -535,12 +542,6 @@ describe("Memory Retriever Pipeline", () => {
535
542
 
536
543
  const requiredEmbedConfig: AssistantConfig = {
537
544
  ...TEST_CONFIG,
538
- apiKeys: {
539
- ...TEST_CONFIG.apiKeys,
540
- openai: "",
541
- gemini: "",
542
- ollama: "",
543
- },
544
545
  memory: {
545
546
  ...TEST_CONFIG.memory,
546
547
  embeddings: {
@@ -595,7 +596,8 @@ describe("Memory Retriever Pipeline", () => {
595
596
  role: "user" | "assistant";
596
597
  content: Array<{ type: string; text?: string }>;
597
598
  };
598
- const recallText = "<memory_context>\n\n<relevant_context>\nsome context\n</relevant_context>\n\n</memory_context>";
599
+ const recallText =
600
+ "<memory_context>\n\n<relevant_context>\nsome context\n</relevant_context>\n\n</memory_context>";
599
601
 
600
602
  const msgs: Msg[] = [
601
603
  {
@@ -615,7 +617,9 @@ describe("Memory Retriever Pipeline", () => {
615
617
  const cleaned = stripMemoryRecallMessages(msgs, recallText);
616
618
  expect(cleaned).toHaveLength(1);
617
619
  expect(cleaned[0].role).toBe("user");
618
- expect(cleaned[0].content[0].text).toBe("Hello, what do you know about me?");
620
+ expect(cleaned[0].content[0].text).toBe(
621
+ "Hello, what do you know about me?",
622
+ );
619
623
  });
620
624
 
621
625
  test("stripMemoryRecallMessages: handles <memory_context> with slightly different content", () => {
@@ -623,8 +627,10 @@ describe("Memory Retriever Pipeline", () => {
623
627
  role: "user" | "assistant";
624
628
  content: Array<{ type: string; text?: string }>;
625
629
  };
626
- const originalRecall = "<memory_context>\n\n<relevant_context>\noriginal\n</relevant_context>\n\n</memory_context>";
627
- const actualRecall = "<memory_context>\n\n<relevant_context>\nslightly different\n</relevant_context>\n\n</memory_context>";
630
+ const originalRecall =
631
+ "<memory_context>\n\n<relevant_context>\noriginal\n</relevant_context>\n\n</memory_context>";
632
+ const actualRecall =
633
+ "<memory_context>\n\n<relevant_context>\nslightly different\n</relevant_context>\n\n</memory_context>";
628
634
 
629
635
  const msgs: Msg[] = [
630
636
  {
@@ -663,7 +669,8 @@ describe("Memory Retriever Pipeline", () => {
663
669
  },
664
670
  ];
665
671
 
666
- const recallText = "<memory_context>\n\n<relevant_context>\ntest\n</relevant_context>\n\n</memory_context>";
672
+ const recallText =
673
+ "<memory_context>\n\n<relevant_context>\ntest\n</relevant_context>\n\n</memory_context>";
667
674
  const result = injectMemoryRecallAsSeparateMessage(msgs, recallText);
668
675
 
669
676
  expect(result).toHaveLength(3);
@@ -166,7 +166,7 @@ async function generateQueryEmbedding(
166
166
  signal: AbortSignal | undefined,
167
167
  start: number,
168
168
  ): Promise<EmbeddingResult | { earlyExit: MemoryRecallResult }> {
169
- const backendStatus = getMemoryBackendStatus(config);
169
+ const backendStatus = await getMemoryBackendStatus(config);
170
170
  let queryVector: number[] | null = null;
171
171
  let provider: string | undefined;
172
172
  let model: string | undefined;
@@ -6,8 +6,6 @@ export const contacts = sqliteTable("contacts", {
6
6
  id: text("id").primaryKey(),
7
7
  displayName: text("display_name").notNull(),
8
8
  notes: text("notes"),
9
- lastInteraction: integer("last_interaction"), // epoch ms
10
- interactionCount: integer("interaction_count").notNull().default(0),
11
9
  createdAt: integer("created_at").notNull(),
12
10
  updatedAt: integer("updated_at").notNull(),
13
11
  role: text("role").notNull().default("contact"), // 'guardian' | 'contact'
@@ -37,6 +35,8 @@ export const contactChannels = sqliteTable(
37
35
  revokedReason: text("revoked_reason"),
38
36
  blockedReason: text("blocked_reason"),
39
37
  lastSeenAt: integer("last_seen_at"), // epoch ms
38
+ interactionCount: integer("interaction_count").notNull().default(0),
39
+ lastInteraction: integer("last_interaction"),
40
40
  updatedAt: integer("updated_at"), // epoch ms
41
41
  createdAt: integer("created_at").notNull(),
42
42
  },
@@ -17,7 +17,6 @@ export const oauthProviders = sqliteTable("oauth_providers", {
17
17
  scopePolicy: text("scope_policy").notNull().default("{}"),
18
18
  extraParams: text("extra_params"),
19
19
  callbackTransport: text("callback_transport"),
20
- loopbackPort: integer("loopback_port"),
21
20
  pingUrl: text("ping_url"),
22
21
  createdAt: integer("created_at").notNull(),
23
22
  updatedAt: integer("updated_at").notNull(),
@@ -2,11 +2,7 @@ import { inArray } from "drizzle-orm";
2
2
 
3
3
  import { getLogger } from "../../util/logger.js";
4
4
  import { getDb } from "../db.js";
5
- import {
6
- _getQdrantBreakerState,
7
- _resetQdrantBreaker,
8
- withQdrantBreaker,
9
- } from "../qdrant-circuit-breaker.js";
5
+ import { withQdrantBreaker } from "../qdrant-circuit-breaker.js";
10
6
  import type {
11
7
  QdrantSearchResult,
12
8
  QdrantSparseVector,
@@ -24,9 +20,6 @@ import type { Candidate } from "./types.js";
24
20
 
25
21
  const _log = getLogger("semantic-search");
26
22
 
27
- // Re-export for tests that depend on these from this module
28
- export { _getQdrantBreakerState, _resetQdrantBreaker };
29
-
30
23
  export async function semanticSearch(
31
24
  queryVector: number[],
32
25
  _provider: string,
@@ -104,21 +104,6 @@ export function getSharedAppLink(
104
104
  };
105
105
  }
106
106
 
107
- export function deleteSharedAppLink(id: string): boolean {
108
- const db = getDb();
109
- const existing = db
110
- .select({ id: sharedAppLinks.id })
111
- .from(sharedAppLinks)
112
- .where(eq(sharedAppLinks.id, id))
113
- .get();
114
-
115
- if (!existing) return false;
116
-
117
- db.delete(sharedAppLinks).where(eq(sharedAppLinks.id, id)).run();
118
-
119
- return true;
120
- }
121
-
122
107
  export function deleteSharedAppLinkByToken(shareToken: string): boolean {
123
108
  const db = getDb();
124
109
  const existing = db
@@ -33,8 +33,3 @@ export async function getConnectedProviders(): Promise<MessagingProvider[]> {
33
33
  }
34
34
  return results;
35
35
  }
36
-
37
- /** Return all registered provider IDs. */
38
- export function getRegisteredProviderIds(): string[] {
39
- return Array.from(providers.keys());
40
- }
@@ -127,7 +127,7 @@ export async function extractStylePatterns(
127
127
  .map((e, i) => `--- Message ${i + 1} ---\n${e}`)
128
128
  .join("\n\n");
129
129
 
130
- const provider = getConfiguredProvider();
130
+ const provider = await getConfiguredProvider();
131
131
  if (!provider) {
132
132
  return { stylePatterns: [], contactObservations: [] };
133
133
  }
@@ -117,21 +117,10 @@ export function buildAccessRequestIdentityLine(
117
117
  return `${parts.join(" ")} is requesting access to the assistant.`;
118
118
  }
119
119
 
120
- export function buildAccessRequestDecisionDirective(
121
- requestCode: string,
122
- ): string {
123
- const code = requestCode.toUpperCase();
124
- return `Reply "${code} approve" to grant access or "${code} reject" to deny.`;
125
- }
126
-
127
120
  export function buildAccessRequestInviteDirective(): string {
128
121
  return 'Reply "open invite flow" to start Trusted Contacts invite flow.';
129
122
  }
130
123
 
131
- export function buildAccessRequestRevokedNote(): string {
132
- return "Note: this user was previously revoked.";
133
- }
134
-
135
124
  /**
136
125
  * Normalize text before running directive-matching regexes.
137
126
  *
@@ -222,10 +211,13 @@ export function buildAccessRequestContractText(
222
211
  const lines: string[] = [];
223
212
  lines.push(buildAccessRequestIdentityLine(payload));
224
213
  if (previousMemberStatus === "revoked") {
225
- lines.push(buildAccessRequestRevokedNote());
214
+ lines.push("Note: this user was previously revoked.");
226
215
  }
227
216
  if (requestCode) {
228
- lines.push(buildAccessRequestDecisionDirective(requestCode));
217
+ const code = requestCode.toUpperCase();
218
+ lines.push(
219
+ `Reply "${code} approve" to grant access or "${code} reject" to deny.`,
220
+ );
229
221
  }
230
222
  lines.push(buildAccessRequestInviteDirective());
231
223
  return lines.join("\n");
@@ -711,7 +711,7 @@ export async function evaluateSignal(
711
711
  );
712
712
  }
713
713
 
714
- const provider = getConfiguredProvider();
714
+ const provider = await getConfiguredProvider();
715
715
  if (!provider) {
716
716
  log.warn(
717
717
  "Configured provider unavailable for notification decision, using fallback",
@@ -767,7 +767,7 @@ async function classifyWithLLM(
767
767
  modelIntent: ModelIntent,
768
768
  candidateSet?: ThreadCandidateSet,
769
769
  ): Promise<NotificationDecision> {
770
- const provider = getConfiguredProvider()!;
770
+ const provider = (await getConfiguredProvider())!;
771
771
  const { signal: abortSignal, cleanup } = createTimeout(DECISION_TIMEOUT_MS);
772
772
 
773
773
  const candidateContext = candidateSet
@@ -159,45 +159,6 @@ export function updateDeliveryStatus(
159
159
  return rawChanges() > 0;
160
160
  }
161
161
 
162
- /**
163
- * Update a delivery record with the client-side outcome of posting the
164
- * notification via UNUserNotificationCenter.add().
165
- *
166
- * Returns true if a row was updated, false otherwise (e.g. unknown deliveryId).
167
- */
168
- export function updateDeliveryClientOutcome(
169
- deliveryId: string,
170
- success: boolean,
171
- error?: { code?: string; message?: string },
172
- ): boolean {
173
- const db = getDb();
174
- const now = Date.now();
175
-
176
- const updates: Record<string, unknown> = {
177
- clientDeliveryStatus: success ? "delivered" : "client_failed",
178
- clientDeliveryAt: now,
179
- updatedAt: now,
180
- };
181
-
182
- if (success) {
183
- // Clear any stale error from previous failed attempts
184
- updates.clientDeliveryError = null;
185
- } else if (error?.message) {
186
- updates.clientDeliveryError = error.code
187
- ? `[${error.code}] ${error.message}`
188
- : error.message;
189
- } else if (error?.code) {
190
- updates.clientDeliveryError = error.code;
191
- }
192
-
193
- db.update(notificationDeliveries)
194
- .set(updates)
195
- .where(eq(notificationDeliveries.id, deliveryId))
196
- .run();
197
-
198
- return rawChanges() > 0;
199
- }
200
-
201
162
  /** Check whether a delivery already exists for a given decision+channel pair. */
202
163
  export function findDeliveryByDecisionAndChannel(
203
164
  decisionId: string,
@@ -6,15 +6,11 @@
6
6
  * fields like `toolName`.
7
7
  */
8
8
 
9
- export const GUARDIAN_QUESTION_REQUEST_KINDS = {
10
- pending_question: "pending_question",
11
- tool_approval: "tool_approval",
12
- tool_grant_request: "tool_grant_request",
13
- access_request: "access_request",
14
- } as const;
15
-
16
9
  export type GuardianQuestionRequestKind =
17
- keyof typeof GUARDIAN_QUESTION_REQUEST_KINDS;
10
+ | "pending_question"
11
+ | "tool_approval"
12
+ | "tool_grant_request"
13
+ | "access_request";
18
14
  export type GuardianQuestionInstructionMode = "approval" | "answer";
19
15
 
20
16
  interface GuardianRequestKindModeConfig {
@@ -151,7 +147,7 @@ function nonEmptyString(value: unknown): string | null {
151
147
  return trimmed.length > 0 ? trimmed : null;
152
148
  }
153
149
 
154
- export function parseGuardianQuestionRequestKind(
150
+ function parseGuardianQuestionRequestKind(
155
151
  payload: Record<string, unknown>,
156
152
  ): GuardianQuestionRequestKind | null {
157
153
  const raw = nonEmptyString(payload.requestKind);
@@ -241,7 +237,7 @@ export function parseGuardianQuestionPayload(
241
237
  }
242
238
  }
243
239
 
244
- export function resolveGuardianInstructionModeForRequestKind(
240
+ function resolveGuardianInstructionModeForRequestKind(
245
241
  requestKind: GuardianQuestionRequestKind,
246
242
  toolName?: string | null,
247
243
  ): GuardianQuestionInstructionMode {
@@ -140,7 +140,7 @@ const EXTRACTION_TOOL = {
140
140
  export async function extractPreferences(
141
141
  message: string,
142
142
  ): Promise<ExtractionResult> {
143
- const provider = getConfiguredProvider();
143
+ const provider = await getConfiguredProvider();
144
144
  if (!provider) {
145
145
  log.debug("No provider available for preference extraction");
146
146
  return { detected: false, preferences: [] };
@@ -117,7 +117,7 @@ mock.module("./oauth-store.js", () => ({
117
117
  // Imports (after mocks)
118
118
  // ---------------------------------------------------------------------------
119
119
 
120
- import { setSecureKey } from "../security/secure-keys.js";
120
+ import { setSecureKeyAsync } from "../security/secure-keys.js";
121
121
  import {
122
122
  _resetInflightRefreshes,
123
123
  _resetRefreshBreakers,
@@ -183,7 +183,7 @@ afterEach(() => {
183
183
  }
184
184
  });
185
185
 
186
- function setupCredential(
186
+ async function setupCredential(
187
187
  service: string,
188
188
  opts?: { expiresAt?: number; grantedScopes?: string[] },
189
189
  ) {
@@ -214,13 +214,19 @@ function setupCredential(
214
214
  accountInfo: null,
215
215
  });
216
216
  // Store access token in oauth-store key format
217
- setSecureKey(`oauth_connection/${connId}/access_token`, "test-access-token");
217
+ await setSecureKeyAsync(
218
+ `oauth_connection/${connId}/access_token`,
219
+ "test-access-token",
220
+ );
218
221
  // Store refresh token and client_secret in secure keys (token-manager reads them)
219
- setSecureKey(
222
+ await setSecureKeyAsync(
220
223
  `oauth_connection/${connId}/refresh_token`,
221
224
  "test-refresh-token",
222
225
  );
223
- setSecureKey(`oauth_app/${appId}/client_secret`, "test-client-secret");
226
+ await setSecureKeyAsync(
227
+ `oauth_app/${appId}/client_secret`,
228
+ "test-client-secret",
229
+ );
224
230
  upsertCredentialMetadata(service, "access_token", {});
225
231
  }
226
232
 
@@ -242,7 +248,7 @@ function createConnection(service = "integration:google"): BYOOAuthConnection {
242
248
  describe("BYOOAuthConnection", () => {
243
249
  describe("request()", () => {
244
250
  test("makes authenticated request with Bearer token", async () => {
245
- setupCredential("integration:google");
251
+ await setupCredential("integration:google");
246
252
  const conn = createConnection();
247
253
 
248
254
  const result = await conn.request({
@@ -266,7 +272,7 @@ describe("BYOOAuthConnection", () => {
266
272
  });
267
273
 
268
274
  test("appends query parameters", async () => {
269
- setupCredential("integration:google");
275
+ await setupCredential("integration:google");
270
276
  const conn = createConnection();
271
277
 
272
278
  await conn.request({
@@ -282,7 +288,7 @@ describe("BYOOAuthConnection", () => {
282
288
  });
283
289
 
284
290
  test("uses per-request baseUrl override", async () => {
285
- setupCredential("integration:google");
291
+ await setupCredential("integration:google");
286
292
  const conn = createConnection();
287
293
 
288
294
  await conn.request({
@@ -296,7 +302,7 @@ describe("BYOOAuthConnection", () => {
296
302
  });
297
303
 
298
304
  test("sends JSON body for POST requests", async () => {
299
- setupCredential("integration:google");
305
+ await setupCredential("integration:google");
300
306
  const conn = createConnection();
301
307
 
302
308
  await conn.request({
@@ -316,7 +322,7 @@ describe("BYOOAuthConnection", () => {
316
322
  });
317
323
 
318
324
  test("retries once on 401 response", async () => {
319
- setupCredential("integration:google");
325
+ await setupCredential("integration:google");
320
326
  const conn = createConnection();
321
327
 
322
328
  // First call returns 401, second returns 200
@@ -344,7 +350,7 @@ describe("BYOOAuthConnection", () => {
344
350
  });
345
351
 
346
352
  test("handles empty response body", async () => {
347
- setupCredential("integration:google");
353
+ await setupCredential("integration:google");
348
354
  const conn = createConnection();
349
355
 
350
356
  globalThis.fetch = mock(() =>
@@ -361,7 +367,7 @@ describe("BYOOAuthConnection", () => {
361
367
  });
362
368
 
363
369
  test("handles non-JSON response body", async () => {
364
- setupCredential("integration:google");
370
+ await setupCredential("integration:google");
365
371
  const conn = createConnection();
366
372
 
367
373
  globalThis.fetch = mock(() =>
@@ -378,7 +384,7 @@ describe("BYOOAuthConnection", () => {
378
384
  });
379
385
 
380
386
  test("returns response headers", async () => {
381
- setupCredential("integration:google");
387
+ await setupCredential("integration:google");
382
388
  const conn = createConnection();
383
389
 
384
390
  globalThis.fetch = mock(() =>
@@ -402,7 +408,7 @@ describe("BYOOAuthConnection", () => {
402
408
  });
403
409
 
404
410
  test("includes custom request headers", async () => {
405
- setupCredential("integration:google");
411
+ await setupCredential("integration:google");
406
412
  const conn = createConnection();
407
413
 
408
414
  await conn.request({
@@ -421,7 +427,7 @@ describe("BYOOAuthConnection", () => {
421
427
  describe("proactive token refresh", () => {
422
428
  test("refreshes token when near expiry (within 5-minute buffer)", async () => {
423
429
  // Set token to expire in 2 minutes (within 5-min buffer)
424
- setupCredential("integration:google", {
430
+ await setupCredential("integration:google", {
425
431
  expiresAt: Date.now() + 2 * 60 * 1000,
426
432
  });
427
433
  const conn = createConnection();
@@ -445,7 +451,7 @@ describe("BYOOAuthConnection", () => {
445
451
 
446
452
  describe("withToken()", () => {
447
453
  test("provides valid token to callback", async () => {
448
- setupCredential("integration:google");
454
+ await setupCredential("integration:google");
449
455
  const conn = createConnection();
450
456
 
451
457
  const result = await conn.withToken(async (token) => {
@@ -456,7 +462,7 @@ describe("BYOOAuthConnection", () => {
456
462
  });
457
463
 
458
464
  test("retries callback on 401 error", async () => {
459
- setupCredential("integration:google");
465
+ await setupCredential("integration:google");
460
466
  const conn = createConnection();
461
467
 
462
468
  let callCount = 0;
@@ -489,7 +495,7 @@ describe("BYOOAuthConnection", () => {
489
495
 
490
496
  describe("resolveOAuthConnection", () => {
491
497
  test("returns a BYOOAuthConnection for valid credential", async () => {
492
- setupCredential("integration:google");
498
+ await setupCredential("integration:google");
493
499
  const conn = await resolveOAuthConnection("integration:google");
494
500
 
495
501
  expect(conn).toBeInstanceOf(BYOOAuthConnection);
@@ -504,7 +510,7 @@ describe("resolveOAuthConnection", () => {
504
510
  });
505
511
 
506
512
  test("throws when no base URL configured", async () => {
507
- setupCredential("integration:custom-service");
513
+ await setupCredential("integration:custom-service");
508
514
  await expect(
509
515
  resolveOAuthConnection("integration:custom-service"),
510
516
  ).rejects.toThrow(
@@ -541,7 +547,10 @@ describe("resolveOAuthConnection", () => {
541
547
  grantedScopes: JSON.stringify(["repo"]),
542
548
  accountInfo: null,
543
549
  });
544
- setSecureKey(`oauth_connection/${connId}/access_token`, "ghp-test-token");
550
+ await setSecureKeyAsync(
551
+ `oauth_connection/${connId}/access_token`,
552
+ "ghp-test-token",
553
+ );
545
554
 
546
555
  const conn = await resolveOAuthConnection("integration:github-work");
547
556
 
@@ -48,6 +48,7 @@ function resolveBehavior(providerKey: string): {
48
48
  setup?: OAuthProviderBehavior["setup"];
49
49
  setupSkillId?: string;
50
50
  postConnectHookId?: string;
51
+ loopbackPort?: number;
51
52
  } {
52
53
  const behavior = getProviderBehavior(providerKey);
53
54
  if (!behavior) return {};
@@ -56,6 +57,7 @@ function resolveBehavior(providerKey: string): {
56
57
  setup: behavior.setup,
57
58
  setupSkillId: behavior.setupSkillId,
58
59
  postConnectHookId: behavior.postConnectHookId,
60
+ loopbackPort: behavior.loopbackPort,
59
61
  };
60
62
  }
61
63
 
@@ -161,7 +163,7 @@ export async function orchestrateOAuthConnect(
161
163
  const callbackTransport =
162
164
  (providerRow.callbackTransport as "loopback" | "gateway" | null) ??
163
165
  "loopback";
164
- const loopbackPort = providerRow.loopbackPort;
166
+ const loopbackPort = behavior.loopbackPort;
165
167
 
166
168
  // Resolve scopes via the scope policy engine
167
169
  const scopeProfile = {
@@ -240,7 +242,7 @@ export async function orchestrateOAuthConnect(
240
242
  const prepared = await prepareOAuth2Flow(
241
243
  oauthConfig,
242
244
  callbackTransport === "loopback"
243
- ? { callbackTransport, loopbackPort: loopbackPort ?? undefined }
245
+ ? { callbackTransport, loopbackPort }
244
246
  : callbackTransport === "gateway"
245
247
  ? { callbackTransport }
246
248
  : undefined,
@@ -345,7 +347,7 @@ export async function orchestrateOAuthConnect(
345
347
  },
346
348
  },
347
349
  callbackTransport === "loopback"
348
- ? { callbackTransport, loopbackPort: loopbackPort ?? undefined }
350
+ ? { callbackTransport, loopbackPort }
349
351
  : callbackTransport === "gateway"
350
352
  ? { callbackTransport }
351
353
  : undefined,
@@ -34,8 +34,8 @@ export interface OAuthScopePolicy {
34
34
  * Code-side behavioral configuration for a well-known OAuth provider.
35
35
  *
36
36
  * Protocol-level fields (authUrl, tokenUrl, defaultScopes, scopePolicy,
37
- * tokenEndpointAuthMethod, callbackTransport, loopbackPort, userinfoUrl,
38
- * extraParams) are stored in the `oauth_providers` DB table. This
37
+ * tokenEndpointAuthMethod, callbackTransport, userinfoUrl, extraParams)
38
+ * are stored in the `oauth_providers` DB table. This
39
39
  * interface contains only fields that require code references (functions,
40
40
  * templates, skill IDs) and cannot be serialised to a DB row.
41
41
  */
@@ -74,6 +74,13 @@ export interface OAuthProviderBehavior {
74
74
  * agent to load this skill rather than embedding instructions inline.
75
75
  */
76
76
  setupSkillId?: string;
77
+ /**
78
+ * Fixed port for the loopback OAuth callback server. When set, the
79
+ * server binds to this port instead of an OS-assigned random port.
80
+ * Required for providers that need pre-registered redirect URIs
81
+ * (e.g. Slack, Notion).
82
+ */
83
+ loopbackPort?: number;
77
84
  }
78
85
 
79
86
  // ---------------------------------------------------------------------------