@vellumai/assistant 0.5.1 → 0.5.2

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 (338) hide show
  1. package/ARCHITECTURE.md +54 -54
  2. package/docs/architecture/integrations.md +62 -67
  3. package/docs/credential-execution-service.md +3 -3
  4. package/package.json +1 -1
  5. package/src/__tests__/agent-loop.test.ts +111 -0
  6. package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
  7. package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
  8. package/src/__tests__/app-dir-path-guard.test.ts +78 -0
  9. package/src/__tests__/app-executors.test.ts +1 -291
  10. package/src/__tests__/app-git-history.test.ts +4 -4
  11. package/src/__tests__/app-routes-csp.test.ts +1 -0
  12. package/src/__tests__/app-store-dir-names.test.ts +426 -0
  13. package/src/__tests__/attachments-store.test.ts +169 -21
  14. package/src/__tests__/attachments.test.ts +115 -1
  15. package/src/__tests__/btw-routes.test.ts +1 -0
  16. package/src/__tests__/canonical-guardian-store.test.ts +38 -0
  17. package/src/__tests__/channel-reply-delivery.test.ts +55 -0
  18. package/src/__tests__/checker.test.ts +54 -0
  19. package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
  20. package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
  21. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  22. package/src/__tests__/config-schema-cmd.test.ts +68 -21
  23. package/src/__tests__/config-schema.test.ts +1 -1
  24. package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
  25. package/src/__tests__/conversation-agent-loop.test.ts +290 -2
  26. package/src/__tests__/conversation-attachments.test.ts +17 -19
  27. package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
  28. package/src/__tests__/conversation-disk-view.test.ts +810 -0
  29. package/src/__tests__/conversation-error.test.ts +1 -1
  30. package/src/__tests__/conversation-fork-crud.test.ts +551 -0
  31. package/src/__tests__/conversation-fork-route.test.ts +386 -0
  32. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  33. package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
  34. package/src/__tests__/conversation-media-retry.test.ts +8 -2
  35. package/src/__tests__/conversation-queue.test.ts +36 -1
  36. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  37. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  38. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  39. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  40. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  41. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  42. package/src/__tests__/conversation-store.test.ts +24 -21
  43. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  44. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  45. package/src/__tests__/conversation-title-service.test.ts +137 -0
  46. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  47. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  48. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  49. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  50. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  51. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  52. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  53. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  54. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  55. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  56. package/src/__tests__/diagnostics-export.test.ts +70 -1
  57. package/src/__tests__/first-greeting.test.ts +80 -0
  58. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  59. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  60. package/src/__tests__/history-repair.test.ts +32 -10
  61. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  62. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  63. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  64. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  65. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  66. package/src/__tests__/media-generate-image.test.ts +47 -94
  67. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  68. package/src/__tests__/memory-recall-quality.test.ts +5 -5
  69. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  70. package/src/__tests__/migration-export-http.test.ts +3 -1
  71. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  72. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  73. package/src/__tests__/mime-builder.test.ts +3 -2
  74. package/src/__tests__/non-member-access-request.test.ts +12 -1
  75. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  76. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  77. package/src/__tests__/oauth-store.test.ts +115 -0
  78. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  79. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  80. package/src/__tests__/recording-handler.test.ts +17 -0
  81. package/src/__tests__/registry.test.ts +3 -8
  82. package/src/__tests__/relay-server.test.ts +1 -1
  83. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  84. package/src/__tests__/schema-transforms.test.ts +165 -5
  85. package/src/__tests__/server-history-render.test.ts +2 -2
  86. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  87. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  88. package/src/__tests__/starter-task-flow.test.ts +1 -0
  89. package/src/__tests__/suggestion-routes.test.ts +443 -0
  90. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  91. package/src/__tests__/swarm-recursion.test.ts +1 -0
  92. package/src/__tests__/swarm-tool.test.ts +1 -0
  93. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  94. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  95. package/src/__tests__/top-level-renderer.test.ts +22 -0
  96. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  97. package/src/__tests__/web-fetch.test.ts +6 -2
  98. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  99. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  100. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  101. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  102. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  103. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  104. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  105. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  106. package/src/agent/attachments.ts +27 -1
  107. package/src/agent/loop.ts +29 -1
  108. package/src/avatar/traits-png-sync.ts +80 -25
  109. package/src/bundler/app-bundler.ts +4 -4
  110. package/src/calls/call-domain.ts +1 -0
  111. package/src/calls/voice-session-bridge.ts +1 -0
  112. package/src/cli/commands/auth.ts +92 -0
  113. package/src/cli/commands/avatar.ts +7 -6
  114. package/src/cli/commands/config.ts +2 -0
  115. package/src/cli/commands/oauth/providers.ts +29 -0
  116. package/src/cli/program.ts +12 -0
  117. package/src/cli.ts +15 -48
  118. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  119. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  120. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  121. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  122. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  123. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  124. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  125. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  126. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  127. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  128. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  129. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  130. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  131. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  132. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  133. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  134. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  135. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  136. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  137. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  138. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  139. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  140. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  141. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  142. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  143. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  144. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  145. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  146. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  147. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  148. package/src/config/bundled-tool-registry.ts +2 -14
  149. package/src/config/feature-flag-registry.json +8 -0
  150. package/src/config/loader.ts +64 -0
  151. package/src/config/raw-config-utils.ts +30 -0
  152. package/src/config/schema-utils.ts +28 -7
  153. package/src/config/schema.ts +8 -0
  154. package/src/config/schemas/elevenlabs.ts +18 -0
  155. package/src/config/schemas/memory-lifecycle.ts +4 -2
  156. package/src/config/schemas/memory-storage.ts +1 -1
  157. package/src/config/schemas/services.ts +8 -6
  158. package/src/contacts/contact-store.ts +13 -6
  159. package/src/contacts/contacts-write.ts +0 -1
  160. package/src/context/window-manager.ts +13 -2
  161. package/src/daemon/conversation-agent-loop-handlers.ts +48 -7
  162. package/src/daemon/conversation-agent-loop.ts +56 -19
  163. package/src/daemon/conversation-attachments.ts +18 -36
  164. package/src/daemon/conversation-error.ts +2 -1
  165. package/src/daemon/conversation-history.ts +18 -4
  166. package/src/daemon/conversation-lifecycle.ts +39 -15
  167. package/src/daemon/conversation-messaging.ts +70 -26
  168. package/src/daemon/conversation-process.ts +58 -34
  169. package/src/daemon/conversation-runtime-assembly.ts +21 -38
  170. package/src/daemon/conversation-slash.ts +121 -256
  171. package/src/daemon/conversation-surfaces.ts +143 -20
  172. package/src/daemon/conversation-tool-setup.ts +0 -6
  173. package/src/daemon/conversation-workspace.ts +21 -1
  174. package/src/daemon/conversation.ts +51 -29
  175. package/src/daemon/first-greeting.ts +35 -0
  176. package/src/daemon/handlers/config-embeddings.ts +148 -0
  177. package/src/daemon/handlers/config-model.ts +71 -26
  178. package/src/daemon/handlers/conversations.ts +0 -23
  179. package/src/daemon/handlers/recording.ts +26 -21
  180. package/src/daemon/host-cu-proxy.ts +2 -2
  181. package/src/daemon/lifecycle.ts +106 -64
  182. package/src/daemon/message-protocol.ts +3 -0
  183. package/src/daemon/message-types/conversations.ts +19 -0
  184. package/src/daemon/message-types/messages.ts +1 -0
  185. package/src/daemon/message-types/shared.ts +2 -0
  186. package/src/daemon/message-types/surfaces.ts +2 -0
  187. package/src/daemon/message-types/upgrades.ts +23 -0
  188. package/src/daemon/server.ts +83 -12
  189. package/src/daemon/shutdown-handlers.ts +8 -5
  190. package/src/daemon/startup-error.ts +9 -0
  191. package/src/daemon/tool-side-effects.ts +11 -28
  192. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  193. package/src/instrument.ts +0 -4
  194. package/src/media/app-icon-generator.ts +2 -2
  195. package/src/memory/app-git-service.ts +28 -16
  196. package/src/memory/app-store.ts +230 -41
  197. package/src/memory/attachments-store.ts +558 -130
  198. package/src/memory/conversation-attention-store.ts +70 -0
  199. package/src/memory/conversation-crud.ts +442 -3
  200. package/src/memory/conversation-directories.ts +125 -0
  201. package/src/memory/conversation-disk-view.ts +390 -0
  202. package/src/memory/conversation-key-store.ts +17 -5
  203. package/src/memory/conversation-queries.ts +5 -1
  204. package/src/memory/conversation-title-service.ts +21 -49
  205. package/src/memory/db-init.ts +28 -0
  206. package/src/memory/embedding-backend.ts +42 -53
  207. package/src/memory/embedding-gemini.test.ts +4 -4
  208. package/src/memory/embedding-local.ts +1 -3
  209. package/src/memory/embedding-ollama.ts +1 -3
  210. package/src/memory/embedding-openai.ts +1 -3
  211. package/src/memory/indexer.ts +9 -7
  212. package/src/memory/items-extractor.ts +42 -13
  213. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  214. package/src/memory/job-handlers/embedding.test.ts +1 -4
  215. package/src/memory/llm-request-log-store.ts +100 -1
  216. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  217. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  218. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  219. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  220. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  221. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  222. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  223. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  224. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  225. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  226. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  227. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  228. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  229. package/src/memory/migrations/index.ts +7 -0
  230. package/src/memory/migrations/registry.ts +13 -0
  231. package/src/memory/retriever.test.ts +601 -2
  232. package/src/memory/retriever.ts +85 -9
  233. package/src/memory/schema/conversations.ts +6 -0
  234. package/src/memory/schema/infrastructure.ts +13 -7
  235. package/src/memory/schema/oauth.ts +6 -0
  236. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  237. package/src/notifications/copy-composer.ts +26 -0
  238. package/src/notifications/decision-engine.ts +14 -1
  239. package/src/notifications/emit-signal.ts +1 -1
  240. package/src/notifications/signal.ts +36 -0
  241. package/src/oauth/byo-connection.test.ts +1 -45
  242. package/src/oauth/byo-connection.ts +2 -8
  243. package/src/oauth/connect-orchestrator.ts +15 -11
  244. package/src/oauth/connection-resolver.test.ts +191 -0
  245. package/src/oauth/connection-resolver.ts +66 -38
  246. package/src/oauth/connection.ts +0 -1
  247. package/src/oauth/oauth-store.ts +97 -47
  248. package/src/oauth/platform-connection.test.ts +0 -1
  249. package/src/oauth/platform-connection.ts +11 -3
  250. package/src/oauth/seed-providers.ts +78 -3
  251. package/src/oauth/token-persistence.ts +16 -10
  252. package/src/permissions/checker.ts +71 -8
  253. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  254. package/src/providers/anthropic/client.ts +8 -1
  255. package/src/providers/failover.ts +4 -1
  256. package/src/providers/gemini/client.ts +50 -0
  257. package/src/providers/model-catalog.ts +92 -0
  258. package/src/providers/model-intents.ts +29 -20
  259. package/src/providers/openai/client.ts +49 -0
  260. package/src/providers/types.ts +2 -0
  261. package/src/runtime/access-request-helper.ts +16 -7
  262. package/src/runtime/auth/credential-service.ts +3 -1
  263. package/src/runtime/auth/route-policy.ts +14 -1
  264. package/src/runtime/btw-sidechain.ts +101 -0
  265. package/src/runtime/channel-reply-delivery.ts +17 -1
  266. package/src/runtime/http-router.ts +3 -1
  267. package/src/runtime/http-server.ts +196 -141
  268. package/src/runtime/http-types.ts +1 -0
  269. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  270. package/src/runtime/routes/access-request-decision.ts +41 -0
  271. package/src/runtime/routes/app-management-routes.ts +6 -3
  272. package/src/runtime/routes/app-routes.ts +7 -3
  273. package/src/runtime/routes/approval-routes.ts +1 -0
  274. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  275. package/src/runtime/routes/attachment-routes.ts +45 -15
  276. package/src/runtime/routes/btw-routes.ts +21 -61
  277. package/src/runtime/routes/conversation-management-routes.ts +68 -0
  278. package/src/runtime/routes/conversation-query-routes.ts +180 -10
  279. package/src/runtime/routes/conversation-routes.ts +222 -28
  280. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  281. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  282. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  283. package/src/runtime/routes/llm-context-normalization.ts +1199 -0
  284. package/src/runtime/routes/log-export-routes.ts +3 -0
  285. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  286. package/src/runtime/routes/memory-item-routes.ts +4 -0
  287. package/src/runtime/routes/migration-routes.ts +4 -1
  288. package/src/runtime/routes/oauth-apps.ts +291 -0
  289. package/src/runtime/routes/secret-routes.ts +28 -1
  290. package/src/runtime/routes/settings-routes.ts +14 -0
  291. package/src/runtime/routes/trace-event-routes.ts +4 -1
  292. package/src/schedule/schedule-store.ts +9 -21
  293. package/src/security/secure-keys.ts +21 -0
  294. package/src/signals/bash.ts +1 -1
  295. package/src/swarm/backend-claude-code.ts +3 -6
  296. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  297. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  298. package/src/tools/AGENTS.md +6 -10
  299. package/src/tools/apps/executors.ts +17 -232
  300. package/src/tools/claude-code/claude-code.ts +2 -3
  301. package/src/tools/credentials/vault.ts +7 -12
  302. package/src/tools/host-filesystem/read.ts +13 -10
  303. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  304. package/src/tools/schedule/list.ts +2 -7
  305. package/src/tools/schema-transforms.ts +5 -0
  306. package/src/tools/shared/filesystem/format-diff.ts +2 -7
  307. package/src/tools/skills/execute.ts +1 -1
  308. package/src/tools/tool-manifest.ts +0 -6
  309. package/src/tools/ui-surface/definitions.ts +2 -2
  310. package/src/util/device-id.ts +28 -5
  311. package/src/util/platform.ts +6 -0
  312. package/src/util/pricing.ts +1 -0
  313. package/src/util/retry.ts +1 -3
  314. package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
  315. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  316. package/src/workspace/migrations/006-services-config.ts +5 -0
  317. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  318. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  319. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  320. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  321. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  322. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  323. package/src/workspace/migrations/registry.ts +10 -0
  324. package/src/workspace/top-level-renderer.ts +12 -0
  325. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  326. package/src/__tests__/asset-search-tool.test.ts +0 -536
  327. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  328. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  329. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  330. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  331. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  332. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  333. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  334. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  335. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  336. package/src/daemon/media-visibility-policy.ts +0 -59
  337. package/src/tools/assets/materialize.ts +0 -248
  338. package/src/tools/assets/search.ts +0 -400
@@ -2,6 +2,7 @@ import OpenAI from "openai";
2
2
 
3
3
  import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "../../prompts/cache-boundary.js";
4
4
  import { ProviderError } from "../../util/errors.js";
5
+ import { getLogger } from "../../util/logger.js";
5
6
  import { extractRetryAfterMs } from "../../util/retry.js";
6
7
  import { escapeXmlAttr } from "../../util/xml.js";
7
8
  import { createStreamTimeout } from "../stream-timeout.js";
@@ -14,6 +15,54 @@ import type {
14
15
  ToolDefinition,
15
16
  } from "../types.js";
16
17
 
18
+ const log = getLogger("openai-client");
19
+
20
+ /** Validation-specific timeout (10s) so a stalled network doesn't block key submission. */
21
+ const VALIDATION_TIMEOUT_MS = 10_000;
22
+
23
+ /**
24
+ * Validate an OpenAI API key by making a lightweight GET /v1/models call.
25
+ * Returns `{ valid: true }` on success or `{ valid: false, reason: string }` on failure.
26
+ */
27
+ export async function validateOpenAIApiKey(
28
+ apiKey: string,
29
+ ): Promise<{ valid: true } | { valid: false; reason: string }> {
30
+ try {
31
+ const client = new OpenAI({
32
+ apiKey,
33
+ timeout: VALIDATION_TIMEOUT_MS,
34
+ maxRetries: 0,
35
+ });
36
+ await client.models.list();
37
+ return { valid: true };
38
+ } catch (error) {
39
+ if (error instanceof OpenAI.APIError) {
40
+ if (error.status === 401) {
41
+ return { valid: false, reason: "API key is invalid or expired." };
42
+ }
43
+ if (error.status === 403) {
44
+ return {
45
+ valid: false,
46
+ reason: `OpenAI API error (${error.status}): ${error.message}`,
47
+ };
48
+ }
49
+ // Transient errors (429, 5xx, etc.) — validation is inconclusive,
50
+ // allow the key to be stored rather than blocking the user.
51
+ log.warn(
52
+ { status: error.status },
53
+ "OpenAI API returned a transient error during key validation — allowing key storage",
54
+ );
55
+ return { valid: true };
56
+ }
57
+ // Network errors — validation is inconclusive, allow key storage.
58
+ log.warn(
59
+ { error: error instanceof Error ? error.message : String(error) },
60
+ "Network error during OpenAI key validation — allowing key storage",
61
+ );
62
+ return { valid: true };
63
+ }
64
+ }
65
+
17
66
  export interface OpenAICompatibleProviderOptions {
18
67
  baseURL?: string;
19
68
  providerName?: string;
@@ -93,6 +93,8 @@ export type ModelIntent =
93
93
  export interface ProviderResponse {
94
94
  content: ContentBlock[];
95
95
  model: string;
96
+ /** Provider that actually produced this response, which may differ from a wrapper provider name. */
97
+ actualProvider?: string;
96
98
  usage: {
97
99
  /** Total input tokens (input_tokens + cache_creation + cache_read). */
98
100
  inputTokens: number;
@@ -21,7 +21,10 @@ import {
21
21
  updateCanonicalGuardianDelivery,
22
22
  } from "../memory/canonical-guardian-store.js";
23
23
  import { emitNotificationSignal } from "../notifications/emit-signal.js";
24
- import type { NotificationSourceChannel } from "../notifications/signal.js";
24
+ import type {
25
+ GuardianResolutionSource,
26
+ NotificationSourceChannel,
27
+ } from "../notifications/signal.js";
25
28
  import type { NotificationDeliveryResult } from "../notifications/types.js";
26
29
  import { getLogger } from "../util/logger.js";
27
30
  import { ensureVellumGuardianBinding } from "./guardian-vellum-migration.js";
@@ -100,10 +103,7 @@ export function notifyGuardianOfAccessRequest(
100
103
  let guardianExternalUserId: string | null = null;
101
104
  let guardianPrincipalId: string | null = null;
102
105
  let guardianBindingChannel: string | null = null;
103
- let guardianResolutionSource:
104
- | "source-channel-contact"
105
- | "vellum-anchor"
106
- | "none" = "none";
106
+ let guardianResolutionSource: GuardianResolutionSource = "none";
107
107
 
108
108
  const assistantGuardianPrincipalId =
109
109
  ensureVellumGuardianBinding(canonicalAssistantId);
@@ -207,11 +207,19 @@ export function notifyGuardianOfAccessRequest(
207
207
  // because the desktop path lacks the channel delivery context needed
208
208
  // to deliver the verification code. Phone is excluded because it is
209
209
  // not a deliverable notification channel.
210
+ // When the guardian was resolved via a verified same-channel contact,
211
+ // route only to that channel — delivering on desktop as well is noisy
212
+ // and the desktop path lacks the channel delivery context for approval.
213
+ // When the guardian was NOT verified on the source channel (e.g. resolved
214
+ // via vellum anchor), route to all channels so the guardian can see
215
+ // the request on desktop/other channels where they ARE verified.
210
216
  const TEXT_CHANNELS_WITH_DELIVERY: ReadonlySet<string> = new Set([
211
217
  "slack",
212
218
  "telegram",
213
219
  ]);
214
- const sameChannelOnly = TEXT_CHANNELS_WITH_DELIVERY.has(sourceChannel);
220
+ const sameChannelOnly =
221
+ TEXT_CHANNELS_WITH_DELIVERY.has(sourceChannel) &&
222
+ guardianResolutionSource === "source-channel-contact";
215
223
 
216
224
  void emitNotificationSignal({
217
225
  sourceEventName: "ingress.access_request",
@@ -226,7 +234,7 @@ export function notifyGuardianOfAccessRequest(
226
234
  },
227
235
  contextPayload: {
228
236
  requestId,
229
- requestCode: canonicalRequest.requestCode,
237
+ requestCode: canonicalRequest.requestCode ?? "",
230
238
  sourceChannel,
231
239
  conversationExternalId,
232
240
  actorExternalId,
@@ -234,6 +242,7 @@ export function notifyGuardianOfAccessRequest(
234
242
  actorUsername: actorUsername ?? null,
235
243
  senderIdentifier,
236
244
  guardianBindingChannel,
245
+ guardianResolutionSource,
237
246
  previousMemberStatus: previousMemberStatus ?? null,
238
247
  },
239
248
  dedupeKey: `access-request:${canonicalRequest.id}`,
@@ -31,6 +31,7 @@ import {
31
31
  createActorTokenRecord,
32
32
  revokeByDeviceBinding as revokeActorTokensByDevice,
33
33
  } from "../actor-token-store.js";
34
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
34
35
  import { getExternalAssistantId } from "./external-assistant-id.js";
35
36
  import { CURRENT_POLICY_EPOCH } from "./policy.js";
36
37
  import { hashToken, mintToken } from "./token-service.js";
@@ -107,7 +108,8 @@ function mintAccessToken(guardianPrincipalId: string): {
107
108
  expiresAt: number;
108
109
  issuedAt: number;
109
110
  } {
110
- const externalAssistantId = getExternalAssistantId() ?? "self";
111
+ const externalAssistantId =
112
+ getExternalAssistantId() ?? DAEMON_INTERNAL_ASSISTANT_ID;
111
113
  const sub = `actor:${externalAssistantId}:${guardianPrincipalId}`;
112
114
 
113
115
  const token = mintToken({
@@ -129,6 +129,7 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
129
129
  { endpoint: "btw", scopes: ["chat.write"] },
130
130
  { endpoint: "conversations", scopes: ["chat.read"] },
131
131
  { endpoint: "conversations:DELETE", scopes: ["chat.write"] },
132
+ { endpoint: "conversations/fork", scopes: ["chat.write"] },
132
133
  { endpoint: "conversations/switch", scopes: ["chat.write"] },
133
134
  { endpoint: "conversations/name", scopes: ["chat.write"] },
134
135
  { endpoint: "conversations/cancel", scopes: ["chat.write"] },
@@ -342,6 +343,10 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
342
343
  { endpoint: "model:PUT", scopes: ["settings.write"] },
343
344
  { endpoint: "model/image-gen", scopes: ["settings.write"] },
344
345
 
346
+ // Embedding config
347
+ { endpoint: "config/embeddings:GET", scopes: ["settings.read"] },
348
+ { endpoint: "config/embeddings:PUT", scopes: ["settings.write"] },
349
+
345
350
  // Conversation management
346
351
  { endpoint: "conversations/wipe", scopes: ["chat.write"] },
347
352
  { endpoint: "conversations/reorder", scopes: ["chat.write"] },
@@ -351,6 +356,7 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
351
356
 
352
357
  // Message content
353
358
  { endpoint: "messages/content", scopes: ["chat.read"] },
359
+ { endpoint: "messages/llm-context", scopes: ["chat.read"] },
354
360
 
355
361
  // Queued message deletion
356
362
  { endpoint: "messages/queued", scopes: ["chat.write"] },
@@ -435,7 +441,14 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
435
441
  { endpoint: "dictation", scopes: ["chat.write"] },
436
442
 
437
443
  // OAuth / integrations
438
- { endpoint: "integrations/oauth/start", scopes: ["settings.write"] },
444
+ { endpoint: "oauth/start", scopes: ["settings.write"] },
445
+ { endpoint: "integrations/oauth/start", scopes: ["settings.write"] }, // legacy alias
446
+ { endpoint: "oauth/apps", scopes: ["settings.read"] },
447
+ { endpoint: "oauth/apps.create", scopes: ["settings.write"] },
448
+ { endpoint: "oauth/apps.delete", scopes: ["settings.write"] },
449
+ { endpoint: "oauth/apps/connections", scopes: ["settings.read"] },
450
+ { endpoint: "oauth/apps/connect", scopes: ["settings.write"] },
451
+ { endpoint: "oauth/connections", scopes: ["settings.write"] },
439
452
 
440
453
  // Ingress config
441
454
  { endpoint: "integrations/ingress/config:GET", scopes: ["settings.read"] },
@@ -0,0 +1,101 @@
1
+ import { buildToolDefinitions } from "../daemon/conversation-tool-setup.js";
2
+ import { buildSystemPrompt } from "../prompts/system-prompt.js";
3
+ import {
4
+ createTimeout,
5
+ extractAllText,
6
+ userMessage,
7
+ } from "../providers/provider-send-message.js";
8
+ import type {
9
+ Message,
10
+ ModelIntent,
11
+ Provider,
12
+ ProviderEvent,
13
+ ProviderResponse,
14
+ ToolDefinition,
15
+ } from "../providers/types.js";
16
+
17
+ export interface BtwSidechainConversationLike {
18
+ provider: Provider;
19
+ systemPrompt: string;
20
+ hasSystemPromptOverride?: boolean;
21
+ getMessages(): Message[];
22
+ }
23
+
24
+ export interface RunBtwSidechainParams {
25
+ content: string;
26
+ conversation?: BtwSidechainConversationLike;
27
+ provider?: Provider;
28
+ messages?: Message[];
29
+ systemPrompt?: string;
30
+ tools?: ToolDefinition[];
31
+ maxTokens?: number;
32
+ modelIntent?: ModelIntent;
33
+ signal?: AbortSignal;
34
+ timeoutMs?: number;
35
+ onEvent?: (event: ProviderEvent) => void;
36
+ }
37
+
38
+ export interface RunBtwSidechainResult {
39
+ text: string;
40
+ hadTextDeltas: boolean;
41
+ response: ProviderResponse;
42
+ }
43
+
44
+ /**
45
+ * Run an ephemeral BTW-style side-chain LLM call.
46
+ *
47
+ * This mirrors the /v1/btw route behavior: no message persistence, tool_choice
48
+ * forced to none, latency-optimized by default, and the standard system prompt
49
+ * with BOOTSTRAP.md excluded unless an explicit system prompt override exists.
50
+ */
51
+ export async function runBtwSidechain(
52
+ params: RunBtwSidechainParams,
53
+ ): Promise<RunBtwSidechainResult> {
54
+ const trimmedContent = params.content.trim();
55
+ const provider = params.provider ?? params.conversation?.provider;
56
+ if (!provider) {
57
+ throw new Error("BTW side-chain requires a provider");
58
+ }
59
+
60
+ const tools = params.tools ?? buildToolDefinitions();
61
+ const history = params.messages ?? params.conversation?.getMessages() ?? [];
62
+ const messages = [...history, userMessage(trimmedContent)];
63
+ const systemPrompt =
64
+ params.systemPrompt ??
65
+ (params.conversation?.hasSystemPromptOverride
66
+ ? params.conversation.systemPrompt
67
+ : buildSystemPrompt({ excludeBootstrap: true }));
68
+
69
+ const { signal: timeoutSignal, cleanup } = createTimeout(
70
+ params.timeoutMs ?? 30_000,
71
+ );
72
+ const combinedSignal = params.signal
73
+ ? AbortSignal.any([params.signal, timeoutSignal])
74
+ : timeoutSignal;
75
+
76
+ let collectedText = "";
77
+ let hadTextDeltas = false;
78
+
79
+ try {
80
+ const response = await provider.sendMessage(messages, tools, systemPrompt, {
81
+ config: {
82
+ max_tokens: params.maxTokens ?? 1024,
83
+ tool_choice: { type: "none" },
84
+ modelIntent: params.modelIntent ?? "latency-optimized",
85
+ },
86
+ onEvent: (event) => {
87
+ if (event.type === "text_delta") {
88
+ hadTextDeltas = true;
89
+ collectedText += event.text;
90
+ }
91
+ params.onEvent?.(event);
92
+ },
93
+ signal: combinedSignal,
94
+ });
95
+
96
+ const text = collectedText.trim() || extractAllText(response).trim();
97
+ return { text, hadTextDeltas, response };
98
+ } finally {
99
+ cleanup();
100
+ }
101
+ }
@@ -45,14 +45,24 @@ function sleep(ms: number): Promise<void> {
45
45
  return new Promise((resolve) => setTimeout(resolve, ms));
46
46
  }
47
47
 
48
+ const NO_RESPONSE_RE = /^\s*<no_response\s*\/?>\s*$/;
49
+
50
+ /** Returns true when any segment is a `<no_response/>` sentinel. */
51
+ function hasNoResponseMarker(textSegments: string[]): boolean {
52
+ return textSegments.some((s) => NO_RESPONSE_RE.test(s));
53
+ }
54
+
48
55
  function toDeliverableTextSegments(
49
56
  textSegments: string[],
50
57
  fallbackText?: string,
51
58
  ): string[] {
52
59
  const nonEmptySegments = textSegments.filter(
53
- (segment) => segment.trim().length > 0,
60
+ (segment) => segment.trim().length > 0 && !NO_RESPONSE_RE.test(segment),
54
61
  );
55
62
  if (nonEmptySegments.length > 0) return nonEmptySegments;
63
+ // If the only text was <no_response/>, treat as intentional silence —
64
+ // do not fall back to fallbackText.
65
+ if (hasNoResponseMarker(textSegments)) return [];
56
66
  if (typeof fallbackText === "string" && fallbackText.trim().length > 0) {
57
67
  return [fallbackText];
58
68
  }
@@ -86,6 +96,12 @@ export async function deliverRenderedReplyViaCallback(
86
96
  const replyAttachments =
87
97
  attachments && attachments.length > 0 ? attachments : undefined;
88
98
 
99
+ // If the model output <no_response/> and no other deliverable text remains,
100
+ // suppress all delivery — including attachments — so nothing is posted.
101
+ if (deliverableSegments.length === 0 && hasNoResponseMarker(textSegments)) {
102
+ return;
103
+ }
104
+
89
105
  if (deliverableSegments.length === 0) {
90
106
  if (replyAttachments) {
91
107
  const result: ChannelDeliveryResult = await deliverChannelReply(
@@ -88,7 +88,9 @@ export class HttpRouter {
88
88
  ): Promise<Response | null> {
89
89
  // Normalize trailing slashes so "/integrations/twilio/config/" matches
90
90
  // a route defined as "integrations/twilio/config".
91
- const normalized = endpoint.endsWith("/") ? endpoint.slice(0, -1) : endpoint;
91
+ const normalized = endpoint.endsWith("/")
92
+ ? endpoint.slice(0, -1)
93
+ : endpoint;
92
94
 
93
95
  for (const compiled of this.compiledRoutes) {
94
96
  if (compiled.def.method !== req.method) continue;