@vellumai/assistant 0.5.13 → 0.5.15

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 (425) hide show
  1. package/.env.example +1 -6
  2. package/AGENTS.md +4 -0
  3. package/ARCHITECTURE.md +0 -1
  4. package/bunfig.toml +1 -0
  5. package/docs/architecture/memory.md +3 -3
  6. package/openapi.yaml +127 -22
  7. package/package.json +1 -1
  8. package/src/__tests__/access-request-decision.test.ts +2 -32
  9. package/src/__tests__/actor-token-service.test.ts +1 -31
  10. package/src/__tests__/anthropic-provider.test.ts +53 -40
  11. package/src/__tests__/app-git-history.test.ts +9 -17
  12. package/src/__tests__/app-git-service.test.ts +14 -20
  13. package/src/__tests__/app-store-dir-names.test.ts +10 -20
  14. package/src/__tests__/approval-cascade.test.ts +2 -19
  15. package/src/__tests__/approval-primitive.test.ts +2 -27
  16. package/src/__tests__/approval-routes-http.test.ts +2 -30
  17. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -28
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -45
  19. package/src/__tests__/attachments-store.test.ts +5 -32
  20. package/src/__tests__/audit-log-rotation.test.ts +5 -36
  21. package/src/__tests__/avatar-e2e.test.ts +1 -9
  22. package/src/__tests__/avatar-generator.test.ts +1 -7
  23. package/src/__tests__/browser-fill-credential.test.ts +0 -4
  24. package/src/__tests__/browser-manager.test.ts +0 -6
  25. package/src/__tests__/call-controller.test.ts +1 -22
  26. package/src/__tests__/call-conversation-messages.test.ts +0 -21
  27. package/src/__tests__/call-domain.test.ts +0 -25
  28. package/src/__tests__/call-pointer-messages.test.ts +0 -21
  29. package/src/__tests__/call-recovery.test.ts +0 -22
  30. package/src/__tests__/call-routes-http.test.ts +0 -24
  31. package/src/__tests__/call-store.test.ts +0 -21
  32. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -24
  33. package/src/__tests__/canonical-guardian-store.test.ts +48 -21
  34. package/src/__tests__/channel-approval-routes.test.ts +6 -26
  35. package/src/__tests__/channel-approvals.test.ts +1 -38
  36. package/src/__tests__/channel-delivery-store.test.ts +0 -21
  37. package/src/__tests__/channel-guardian.test.ts +0 -26
  38. package/src/__tests__/channel-reply-delivery.test.ts +5 -0
  39. package/src/__tests__/channel-retry-sweep.test.ts +0 -21
  40. package/src/__tests__/checker.test.ts +26 -61
  41. package/src/__tests__/clawhub.test.ts +9 -25
  42. package/src/__tests__/cli-command-risk-guard.test.ts +0 -18
  43. package/src/__tests__/config-loader-backfill.test.ts +9 -28
  44. package/src/__tests__/config-schema-cmd.test.ts +5 -25
  45. package/src/__tests__/config-schema.test.ts +21 -40
  46. package/src/__tests__/config-watcher.test.ts +4 -91
  47. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -21
  48. package/src/__tests__/contacts-tools.test.ts +0 -21
  49. package/src/__tests__/context-memory-e2e.test.ts +0 -21
  50. package/src/__tests__/context-window-manager.test.ts +130 -3
  51. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -4
  52. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -4
  53. package/src/__tests__/conversation-agent-loop.test.ts +0 -4
  54. package/src/__tests__/conversation-attachments.test.ts +1 -24
  55. package/src/__tests__/conversation-attention-store.test.ts +0 -21
  56. package/src/__tests__/conversation-attention-telegram.test.ts +0 -22
  57. package/src/__tests__/conversation-clear-safety.test.ts +0 -22
  58. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -21
  59. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -24
  60. package/src/__tests__/conversation-disk-view-integration.test.ts +1 -23
  61. package/src/__tests__/conversation-disk-view.test.ts +5 -27
  62. package/src/__tests__/conversation-error.test.ts +1 -1
  63. package/src/__tests__/conversation-fork-crud.test.ts +1 -33
  64. package/src/__tests__/conversation-fork-route.test.ts +0 -27
  65. package/src/__tests__/conversation-history-web-search.test.ts +23 -16
  66. package/src/__tests__/conversation-init.benchmark.test.ts +22 -43
  67. package/src/__tests__/conversation-key-store-disk-view.test.ts +8 -34
  68. package/src/__tests__/conversation-load-history-repair.test.ts +0 -4
  69. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -4
  70. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -4
  71. package/src/__tests__/conversation-queue.test.ts +8 -8
  72. package/src/__tests__/conversation-routes-disk-view.test.ts +13 -51
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +64 -38
  74. package/src/__tests__/conversation-slash-commands.test.ts +5 -0
  75. package/src/__tests__/conversation-slash-queue.test.ts +0 -4
  76. package/src/__tests__/conversation-slash-unknown.test.ts +0 -4
  77. package/src/__tests__/conversation-speed-override.test.ts +326 -0
  78. package/src/__tests__/conversation-starter-routes.test.ts +0 -23
  79. package/src/__tests__/conversation-store.test.ts +0 -21
  80. package/src/__tests__/conversation-unread-route.test.ts +0 -24
  81. package/src/__tests__/conversation-usage.test.ts +56 -21
  82. package/src/__tests__/conversation-wipe.test.ts +0 -21
  83. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -4
  84. package/src/__tests__/conversation-workspace-injection.test.ts +0 -4
  85. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -4
  86. package/src/__tests__/credential-execution-shell-lockdown.test.ts +8 -5
  87. package/src/__tests__/credential-vault-unit.test.ts +9 -428
  88. package/src/__tests__/credentials-cli.test.ts +10 -10
  89. package/src/__tests__/daemon-assistant-events.test.ts +0 -19
  90. package/src/__tests__/date-context.test.ts +77 -97
  91. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +7 -24
  92. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +29 -42
  93. package/src/__tests__/delete-managed-skill-tool.test.ts +2 -10
  94. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -26
  95. package/src/__tests__/docker-signing-key-bootstrap.test.ts +61 -15
  96. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -36
  97. package/src/__tests__/email-cli.test.ts +6 -6
  98. package/src/__tests__/ephemeral-permissions.test.ts +5 -17
  99. package/src/__tests__/first-greeting.test.ts +4 -32
  100. package/src/__tests__/followup-tools.test.ts +0 -21
  101. package/src/__tests__/gateway-only-enforcement.test.ts +0 -20
  102. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -23
  103. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -23
  104. package/src/__tests__/guardian-action-followup-store.test.ts +0 -21
  105. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -21
  106. package/src/__tests__/guardian-action-late-reply.test.ts +0 -21
  107. package/src/__tests__/guardian-action-store.test.ts +0 -21
  108. package/src/__tests__/guardian-action-sweep.test.ts +0 -21
  109. package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -23
  110. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -22
  111. package/src/__tests__/guardian-dispatch.test.ts +0 -21
  112. package/src/__tests__/guardian-grant-minting.test.ts +0 -22
  113. package/src/__tests__/guardian-outbound-http.test.ts +0 -22
  114. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -23
  115. package/src/__tests__/guardian-routing-invariants.test.ts +0 -22
  116. package/src/__tests__/guardian-routing-state.test.ts +0 -22
  117. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -24
  118. package/src/__tests__/headless-browser-interactions.test.ts +0 -4
  119. package/src/__tests__/headless-browser-navigate.test.ts +0 -4
  120. package/src/__tests__/headless-browser-read-tools.test.ts +0 -4
  121. package/src/__tests__/headless-browser-snapshot.test.ts +0 -4
  122. package/src/__tests__/heartbeat-service.test.ts +99 -26
  123. package/src/__tests__/hooks-blocking.test.ts +3 -3
  124. package/src/__tests__/hooks-config.test.ts +7 -7
  125. package/src/__tests__/hooks-discovery.test.ts +3 -3
  126. package/src/__tests__/hooks-integration.test.ts +5 -5
  127. package/src/__tests__/hooks-manager.test.ts +3 -3
  128. package/src/__tests__/hooks-runner.test.ts +5 -23
  129. package/src/__tests__/hooks-settings.test.ts +3 -3
  130. package/src/__tests__/hooks-templates.test.ts +3 -3
  131. package/src/__tests__/http-conversation-lineage.test.ts +0 -27
  132. package/src/__tests__/identity-intro-cache.test.ts +0 -4
  133. package/src/__tests__/inbound-invite-redemption.test.ts +0 -22
  134. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -16
  135. package/src/__tests__/intent-routing.test.ts +2 -55
  136. package/src/__tests__/invite-redemption-service.test.ts +0 -21
  137. package/src/__tests__/invite-routes-http.test.ts +0 -21
  138. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +0 -17
  139. package/src/__tests__/journal-context.test.ts +8 -75
  140. package/src/__tests__/list-messages-attachments.test.ts +0 -22
  141. package/src/__tests__/llm-context-route-provider.test.ts +0 -21
  142. package/src/__tests__/llm-request-log-turn-query.test.ts +46 -28
  143. package/src/__tests__/llm-usage-store.test.ts +0 -21
  144. package/src/__tests__/log-export-workspace.test.ts +1 -1
  145. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
  146. package/src/__tests__/managed-store.test.ts +1 -1
  147. package/src/__tests__/mcp-cli.test.ts +7 -10
  148. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -21
  149. package/src/__tests__/memory-jobs-worker-backoff.test.ts +0 -11
  150. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -21
  151. package/src/__tests__/memory-recall-log-store.test.ts +0 -27
  152. package/src/__tests__/memory-recall-quality.test.ts +0 -21
  153. package/src/__tests__/memory-regressions.experimental.test.ts +31 -30
  154. package/src/__tests__/memory-regressions.test.ts +282 -70
  155. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -21
  156. package/src/__tests__/memory-upsert-concurrency.test.ts +0 -21
  157. package/src/__tests__/messaging-send-tool.test.ts +201 -0
  158. package/src/__tests__/migration-cross-version-compatibility.test.ts +18 -13
  159. package/src/__tests__/migration-export-http.test.ts +7 -1
  160. package/src/__tests__/migration-import-commit-http.test.ts +16 -14
  161. package/src/__tests__/migration-import-preflight-http.test.ts +27 -44
  162. package/src/__tests__/migration-validate-http.test.ts +1 -28
  163. package/src/__tests__/native-web-search.test.ts +25 -22
  164. package/src/__tests__/non-member-access-request.test.ts +0 -22
  165. package/src/__tests__/notification-guardian-path.test.ts +0 -21
  166. package/src/__tests__/notification-schedule-dedup.test.ts +1 -25
  167. package/src/__tests__/oauth-apps-routes.test.ts +103 -2
  168. package/src/__tests__/oauth-cli.test.ts +52 -0
  169. package/src/__tests__/oauth-provider-profiles.test.ts +0 -16
  170. package/src/__tests__/oauth-provider-serializer.test.ts +232 -0
  171. package/src/__tests__/oauth-providers-routes.test.ts +257 -0
  172. package/src/__tests__/oauth-store.test.ts +0 -21
  173. package/src/__tests__/onboarding-template-contract.test.ts +2 -2
  174. package/src/__tests__/openai-provider.test.ts +261 -0
  175. package/src/__tests__/pairing-concurrent.test.ts +6 -6
  176. package/src/__tests__/pairing-routes.test.ts +7 -1
  177. package/src/__tests__/path-policy.test.ts +1 -1
  178. package/src/__tests__/platform.test.ts +64 -88
  179. package/src/__tests__/playbook-execution.test.ts +0 -21
  180. package/src/__tests__/playbook-tools.test.ts +0 -21
  181. package/src/__tests__/pricing.test.ts +100 -0
  182. package/src/__tests__/relay-server.test.ts +1 -25
  183. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -24
  184. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -24
  185. package/src/__tests__/runtime-events-sse.test.ts +0 -24
  186. package/src/__tests__/sandbox-diagnostics.test.ts +2 -1
  187. package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
  188. package/src/__tests__/schedule-store.test.ts +0 -21
  189. package/src/__tests__/schedule-tools.test.ts +0 -21
  190. package/src/__tests__/scheduler-recurrence.test.ts +0 -21
  191. package/src/__tests__/scoped-approval-grants.test.ts +0 -21
  192. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -21
  193. package/src/__tests__/secret-allowlist.test.ts +1 -1
  194. package/src/__tests__/secret-ingress-channel.test.ts +0 -5
  195. package/src/__tests__/secret-ingress-cli.test.ts +0 -6
  196. package/src/__tests__/secret-ingress-http.test.ts +0 -5
  197. package/src/__tests__/secret-ingress.test.ts +0 -5
  198. package/src/__tests__/send-endpoint-busy.test.ts +0 -24
  199. package/src/__tests__/sequence-store.test.ts +0 -21
  200. package/src/__tests__/server-history-render.test.ts +0 -24
  201. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -4
  202. package/src/__tests__/skill-load-inline-command.test.ts +9 -0
  203. package/src/__tests__/skill-load-inline-includes.test.ts +9 -0
  204. package/src/__tests__/skill-load-tool.test.ts +11 -0
  205. package/src/__tests__/skills-uninstall.test.ts +10 -8
  206. package/src/__tests__/skills.test.ts +1 -1
  207. package/src/__tests__/slack-channel-config.test.ts +1 -1
  208. package/src/__tests__/slack-inbound-verification.test.ts +0 -22
  209. package/src/__tests__/starter-bundle.test.ts +4 -1
  210. package/src/__tests__/suggestion-routes.test.ts +2 -0
  211. package/src/__tests__/system-prompt.test.ts +1 -1
  212. package/src/__tests__/terminal-tools.test.ts +1 -1
  213. package/src/__tests__/test-preload.ts +31 -0
  214. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
  215. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  216. package/src/__tests__/tool-executor.test.ts +0 -20
  217. package/src/__tests__/tool-input-summary.test.ts +124 -0
  218. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -1
  219. package/src/__tests__/trust-store.test.ts +7 -1
  220. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -1
  221. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -1
  222. package/src/__tests__/trusted-contact-multichannel.test.ts +1 -1
  223. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  224. package/src/__tests__/turn-boundary-resolution.test.ts +1 -1
  225. package/src/__tests__/twilio-routes.test.ts +1 -1
  226. package/src/__tests__/update-bulletin.test.ts +1 -1
  227. package/src/__tests__/vbundle-pax-and-symlink.test.ts +1 -1
  228. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -0
  229. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
  230. package/src/__tests__/voice-session-bridge.test.ts +1 -1
  231. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -4
  232. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
  233. package/src/__tests__/workspace-migration-down-functions.test.ts +15 -3
  234. package/src/__tests__/workspace-migration-seed-device-id.test.ts +40 -4
  235. package/src/agent/loop.ts +6 -9
  236. package/src/approvals/guardian-decision-primitive.ts +46 -18
  237. package/src/approvals/guardian-request-resolvers.ts +19 -2
  238. package/src/calls/active-call-lease.ts +2 -2
  239. package/src/cli/AGENTS.md +1 -1
  240. package/src/cli/commands/doctor.ts +9 -9
  241. package/src/cli/commands/memory.ts +142 -0
  242. package/src/cli/commands/oauth/__tests__/connect.test.ts +13 -11
  243. package/src/cli/commands/oauth/__tests__/ping.test.ts +1 -1
  244. package/src/cli/commands/oauth/connect.ts +13 -12
  245. package/src/cli/commands/oauth/index.ts +1 -1
  246. package/src/cli/commands/oauth/providers.ts +47 -62
  247. package/src/cli/commands/platform/__tests__/connect.test.ts +72 -46
  248. package/src/cli/commands/platform/__tests__/disconnect.test.ts +54 -1
  249. package/src/cli/commands/platform/__tests__/status.test.ts +36 -0
  250. package/src/cli/commands/platform/connect.ts +17 -7
  251. package/src/cli/commands/platform/disconnect.ts +28 -3
  252. package/src/cli/commands/platform/index.ts +3 -3
  253. package/src/cli.ts +1 -299
  254. package/src/config/assistant-feature-flags.ts +23 -15
  255. package/src/config/bundled-skills/app-builder/TOOLS.json +16 -0
  256. package/src/config/bundled-skills/app-builder/tools/app-create.ts +4 -0
  257. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +5 -1
  258. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +9 -1
  259. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +5 -1
  260. package/src/config/bundled-skills/contacts/TOOLS.json +8 -0
  261. package/src/config/bundled-skills/contacts/tools/contact-search.ts +10 -1
  262. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +16 -2
  263. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +1 -0
  264. package/src/config/bundled-skills/messaging/SKILL.md +7 -7
  265. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +37 -0
  266. package/src/config/bundled-skills/slack/SKILL.md +18 -0
  267. package/src/config/env-registry.ts +15 -11
  268. package/src/config/env.ts +1 -11
  269. package/src/config/feature-flag-registry.json +16 -0
  270. package/src/config/schema.ts +4 -0
  271. package/src/config/schemas/heartbeat.ts +6 -1
  272. package/src/config/schemas/inference.ts +14 -3
  273. package/src/config/schemas/memory-processing.ts +16 -8
  274. package/src/config/schemas/memory-retrieval.ts +3 -3
  275. package/src/config/skills.ts +1 -1
  276. package/src/context/window-manager.ts +174 -51
  277. package/src/credential-execution/executable-discovery.ts +2 -2
  278. package/src/daemon/approved-devices-store.ts +2 -2
  279. package/src/daemon/assistant-attachments.ts +2 -0
  280. package/src/daemon/config-watcher.ts +4 -50
  281. package/src/daemon/conversation-agent-loop-handlers.ts +9 -1
  282. package/src/daemon/conversation-agent-loop.ts +12 -0
  283. package/src/daemon/conversation-error.ts +3 -5
  284. package/src/daemon/conversation-history.ts +7 -3
  285. package/src/daemon/conversation-lifecycle.ts +16 -0
  286. package/src/daemon/conversation-messaging.ts +1 -0
  287. package/src/daemon/conversation-notifiers.ts +67 -30
  288. package/src/daemon/conversation-process.ts +161 -2
  289. package/src/daemon/conversation-queue-manager.ts +2 -0
  290. package/src/daemon/conversation-runtime-assembly.ts +33 -11
  291. package/src/daemon/conversation-slash.ts +14 -3
  292. package/src/daemon/conversation-tool-setup.ts +2 -0
  293. package/src/daemon/conversation-usage.ts +32 -4
  294. package/src/daemon/conversation.ts +33 -1
  295. package/src/daemon/daemon-control.ts +32 -16
  296. package/src/daemon/date-context.ts +47 -45
  297. package/src/daemon/dictation-profile-store.ts +2 -2
  298. package/src/daemon/handlers/conversations.ts +19 -0
  299. package/src/daemon/handlers/shared.ts +14 -21
  300. package/src/daemon/lifecycle.ts +5 -7
  301. package/src/daemon/message-types/conversations.ts +2 -0
  302. package/src/daemon/message-types/guardian-actions.ts +3 -17
  303. package/src/daemon/message-types/integrations.ts +11 -1
  304. package/src/daemon/message-types/messages.ts +1 -0
  305. package/src/daemon/pairing-store.ts +2 -79
  306. package/src/daemon/server.ts +154 -8
  307. package/src/daemon/watch-handler.ts +65 -21
  308. package/src/email/guardrails.ts +3 -3
  309. package/src/heartbeat/heartbeat-service.ts +14 -7
  310. package/src/hooks/cli.ts +2 -2
  311. package/src/hooks/config.ts +2 -2
  312. package/src/hooks/discovery.ts +2 -2
  313. package/src/hooks/manager.ts +2 -2
  314. package/src/hooks/runner.ts +5 -2
  315. package/src/hooks/templates.ts +2 -2
  316. package/src/memory/admin.ts +181 -2
  317. package/src/memory/app-git-service.ts +61 -4
  318. package/src/memory/attachments-store.ts +2 -0
  319. package/src/memory/canonical-guardian-store.ts +16 -0
  320. package/src/memory/db-init.ts +8 -0
  321. package/src/memory/embedding-local.ts +5 -2
  322. package/src/memory/indexer.ts +44 -26
  323. package/src/memory/items-extractor.ts +34 -82
  324. package/src/memory/job-handlers/batch-extraction.ts +741 -0
  325. package/src/memory/job-handlers/journal-carry-forward.test.ts +383 -0
  326. package/src/memory/job-handlers/journal-carry-forward.ts +255 -0
  327. package/src/memory/jobs-store.ts +28 -0
  328. package/src/memory/jobs-worker.ts +56 -9
  329. package/src/memory/lifecycle-events-store.ts +4 -2
  330. package/src/memory/llm-request-log-store.ts +40 -2
  331. package/src/memory/llm-usage-store.ts +4 -3
  332. package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +71 -0
  333. package/src/memory/migrations/200-usage-llm-call-count.ts +20 -0
  334. package/src/memory/migrations/index.ts +2 -0
  335. package/src/memory/query-expansion.ts +83 -0
  336. package/src/memory/retriever.test.ts +119 -0
  337. package/src/memory/retriever.ts +513 -105
  338. package/src/memory/schema/guardian.ts +4 -0
  339. package/src/memory/schema/infrastructure.ts +1 -0
  340. package/src/memory/search/formatting.test.ts +140 -0
  341. package/src/memory/search/formatting.ts +143 -198
  342. package/src/memory/search/mmr.ts +136 -0
  343. package/src/memory/search/staleness.ts +0 -15
  344. package/src/memory/search/tier-classifier.ts +10 -21
  345. package/src/memory/search/types.ts +17 -0
  346. package/src/messaging/providers/slack/adapter.ts +51 -5
  347. package/src/notifications/broadcaster.ts +13 -0
  348. package/src/notifications/copy-composer.ts +8 -0
  349. package/src/oauth/connect-orchestrator.ts +1 -1
  350. package/src/oauth/connection-resolver.ts +2 -2
  351. package/src/oauth/provider-serializer.ts +116 -0
  352. package/src/permissions/trust-store.ts +24 -7
  353. package/src/prompts/__tests__/build-cli-reference-section.test.ts +5 -0
  354. package/src/prompts/journal-context.ts +50 -35
  355. package/src/prompts/persona-resolver.ts +1 -1
  356. package/src/prompts/system-prompt.ts +27 -28
  357. package/src/prompts/templates/BOOTSTRAP.md +14 -1
  358. package/src/prompts/templates/HEARTBEAT.md +10 -0
  359. package/src/prompts/templates/NOW.md +19 -25
  360. package/src/prompts/templates/SOUL.md +13 -1
  361. package/src/prompts/templates/UPDATES.md +12 -0
  362. package/src/prompts/update-bulletin.ts +1 -1
  363. package/src/providers/anthropic/client.ts +89 -18
  364. package/src/providers/model-catalog.ts +22 -2
  365. package/src/providers/model-intents.ts +2 -2
  366. package/src/providers/openai/client.ts +40 -1
  367. package/src/providers/retry.ts +23 -4
  368. package/src/providers/types.ts +2 -0
  369. package/src/runtime/assistant-scope.ts +1 -1
  370. package/src/runtime/auth/__tests__/credential-service.test.ts +1 -0
  371. package/src/runtime/auth/route-policy.ts +1 -0
  372. package/src/runtime/auth/token-service.ts +51 -29
  373. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -1
  374. package/src/runtime/guardian-decision-types.ts +16 -10
  375. package/src/runtime/http-server.ts +3 -14
  376. package/src/runtime/http-types.ts +1 -0
  377. package/src/runtime/migrations/vbundle-builder.ts +7 -4
  378. package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -4
  379. package/src/runtime/migrations/vbundle-importer.ts +1 -1
  380. package/src/runtime/routes/conversation-query-routes.ts +40 -8
  381. package/src/runtime/routes/conversation-routes.ts +125 -3
  382. package/src/runtime/routes/guardian-action-routes.ts +9 -3
  383. package/src/runtime/routes/identity-routes.ts +25 -4
  384. package/src/runtime/routes/llm-context-normalization.ts +1 -0
  385. package/src/runtime/routes/log-export-routes.ts +34 -12
  386. package/src/runtime/routes/migration-routes.ts +6 -10
  387. package/src/runtime/routes/oauth-apps.ts +2 -9
  388. package/src/runtime/routes/oauth-providers.ts +60 -0
  389. package/src/runtime/routes/pairing-routes.ts +0 -8
  390. package/src/runtime/routes/settings-routes.ts +0 -1
  391. package/src/runtime/routes/telemetry-routes.ts +16 -4
  392. package/src/security/encrypted-store.ts +2 -2
  393. package/src/security/secret-allowlist.ts +3 -3
  394. package/src/signals/emit-event.ts +42 -0
  395. package/src/signals/user-message.ts +37 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +83 -19
  397. package/src/telemetry/usage-telemetry-reporter.ts +23 -17
  398. package/src/tools/browser/runtime-check.ts +2 -2
  399. package/src/tools/credentials/vault.ts +2 -249
  400. package/src/tools/memory/definitions.ts +1 -1
  401. package/src/tools/memory/handlers.test.ts +50 -8
  402. package/src/tools/memory/handlers.ts +3 -1
  403. package/src/tools/side-effects.ts +1 -6
  404. package/src/tools/terminal/safe-env.ts +3 -2
  405. package/src/tools/terminal/shell.ts +11 -14
  406. package/src/tools/tool-approval-handler.ts +20 -1
  407. package/src/tools/tool-input-summary.ts +66 -0
  408. package/src/tools/types.ts +4 -0
  409. package/src/usage/types.ts +4 -0
  410. package/src/util/device-id.ts +10 -10
  411. package/src/util/platform.ts +71 -33
  412. package/src/util/pricing.ts +19 -6
  413. package/src/util/strip-comment-lines.ts +28 -0
  414. package/src/workspace/git-service.ts +8 -18
  415. package/src/workspace/migrations/003-seed-device-id.ts +6 -4
  416. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +7 -1
  417. package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -4
  418. package/src/workspace/migrations/021-move-signals-to-workspace.ts +84 -0
  419. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +94 -0
  420. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +86 -0
  421. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +126 -0
  422. package/src/workspace/migrations/migrate-to-workspace-volume.ts +3 -6
  423. package/src/workspace/migrations/registry.ts +8 -0
  424. package/src/signals/confirm.ts +0 -82
  425. package/src/signals/trust-rule.ts +0 -174
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Handle generic event signals from the CLI.
3
+ *
4
+ * When the CLI writes a JSON-encoded {@link ServerMessage} to
5
+ * `signals/emit-event`, the daemon's ConfigWatcher detects the file
6
+ * change and invokes {@link handleEmitEventSignal}, which reads the
7
+ * payload and publishes it to connected clients via the in-process
8
+ * {@link assistantEventHub}.
9
+ *
10
+ * This provides a general-purpose CLI→daemon event bridge so that any
11
+ * CLI command can place arbitrary events onto the hub without needing
12
+ * a dedicated signal handler per event type.
13
+ */
14
+
15
+ import { readFileSync } from "node:fs";
16
+ import { join } from "node:path";
17
+
18
+ import type { ServerMessage } from "../daemon/message-protocol.js";
19
+ import { buildAssistantEvent } from "../runtime/assistant-event.js";
20
+ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
21
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
22
+ import { getLogger } from "../util/logger.js";
23
+ import { getSignalsDir } from "../util/platform.js";
24
+
25
+ const log = getLogger("signal:emit-event");
26
+
27
+ export function handleEmitEventSignal(): void {
28
+ try {
29
+ const content = readFileSync(join(getSignalsDir(), "emit-event"), "utf-8");
30
+ const message = JSON.parse(content) as ServerMessage;
31
+
32
+ assistantEventHub
33
+ .publish(buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, message))
34
+ .catch((err: unknown) => {
35
+ log.error({ err }, "Failed to publish event from signal");
36
+ });
37
+
38
+ log.info({ type: message.type }, "Emit-event signal handled");
39
+ } catch (err) {
40
+ log.error({ err }, "Failed to handle emit-event signal");
41
+ }
42
+ }
@@ -23,6 +23,18 @@ import { getSignalsDir } from "../util/platform.js";
23
23
 
24
24
  const log = getLogger("signal:user-message");
25
25
 
26
+ // ── Attachment descriptor ───────────────────────────────────────────
27
+
28
+ /** A file-backed attachment included in a signal payload. */
29
+ export interface SignalAttachment {
30
+ /** Absolute path to the file on disk. */
31
+ path: string;
32
+ /** Display filename (e.g. "f_0001.jpg"). */
33
+ filename: string;
34
+ /** MIME type (e.g. "image/jpeg"). */
35
+ mimeType: string;
36
+ }
37
+
26
38
  // ── Daemon callback registry ─────────────────────────────────────────
27
39
 
28
40
  type UserMessageCallback = (params: {
@@ -31,6 +43,7 @@ type UserMessageCallback = (params: {
31
43
  sourceChannel: string;
32
44
  sourceInterface: string;
33
45
  bypassSecretCheck?: boolean;
46
+ attachments?: SignalAttachment[];
34
47
  }) => Promise<{ accepted: boolean; error?: string; message?: string }>;
35
48
 
36
49
  let _sendUserMessage: UserMessageCallback | null = null;
@@ -99,6 +112,11 @@ export async function handleUserMessageSignal(filename: string): Promise<void> {
99
112
  interface?: string;
100
113
  requestId?: string;
101
114
  bypassSecretCheck?: boolean;
115
+ attachments?: Array<{
116
+ path?: string;
117
+ filename?: string;
118
+ mimeType?: string;
119
+ }>;
102
120
  };
103
121
  const { requestId } = parsed;
104
122
  parsedRequestId = requestId;
@@ -131,12 +149,31 @@ export async function handleUserMessageSignal(filename: string): Promise<void> {
131
149
  return;
132
150
  }
133
151
 
152
+ // Validate and normalize attachments
153
+ const attachments: SignalAttachment[] = [];
154
+ if (Array.isArray(parsed.attachments)) {
155
+ for (const a of parsed.attachments) {
156
+ if (
157
+ typeof a.path === "string" &&
158
+ typeof a.filename === "string" &&
159
+ typeof a.mimeType === "string"
160
+ ) {
161
+ attachments.push({
162
+ path: a.path,
163
+ filename: a.filename,
164
+ mimeType: a.mimeType,
165
+ });
166
+ }
167
+ }
168
+ }
169
+
134
170
  const result = await _sendUserMessage({
135
171
  conversationKey: parsed.conversationKey,
136
172
  content: parsed.content,
137
173
  sourceChannel: parsed.sourceChannel ?? "vellum",
138
174
  sourceInterface: parsed.interface ?? "cli",
139
175
  bypassSecretCheck: parsed.bypassSecretCheck === true,
176
+ ...(attachments.length > 0 ? { attachments } : {}),
140
177
  });
141
178
 
142
179
  log.info(
@@ -49,17 +49,15 @@ mock.module("../platform/client.js", () => ({
49
49
  },
50
50
  }));
51
51
 
52
- const mockGetTelemetryPlatformUrl = mock(() => "https://platform.vellum.ai");
53
- const mockGetTelemetryAppToken = mock(() => "");
52
+ const mockGetPlatformBaseUrl = mock(() => "https://platform.vellum.ai");
54
53
 
55
54
  const mockGetPlatformOrganizationId = mock(() => "");
56
55
  const mockGetPlatformUserId = mock(() => "");
57
56
 
58
57
  mock.module("../config/env.js", () => ({
58
+ getPlatformBaseUrl: mockGetPlatformBaseUrl,
59
59
  getPlatformOrganizationId: mockGetPlatformOrganizationId,
60
60
  getPlatformUserId: mockGetPlatformUserId,
61
- getTelemetryPlatformUrl: mockGetTelemetryPlatformUrl,
62
- getTelemetryAppToken: mockGetTelemetryAppToken,
63
61
  // Re-export anything else the module might import transitively
64
62
  str: () => undefined,
65
63
  num: () => undefined,
@@ -91,6 +89,20 @@ mock.module("../version.js", () => ({
91
89
  APP_VERSION: "1.2.3-test",
92
90
  }));
93
91
 
92
+ let mockCollectUsageData = true;
93
+
94
+ mock.module("../config/loader.js", () => ({
95
+ getConfig: () => ({ collectUsageData: mockCollectUsageData }),
96
+ }));
97
+
98
+ const mockQueryUnreportedLifecycleEvents = mock(
99
+ () => [] as { id: string; eventName: string; createdAt: number }[],
100
+ );
101
+
102
+ mock.module("../memory/lifecycle-events-store.js", () => ({
103
+ queryUnreportedLifecycleEvents: mockQueryUnreportedLifecycleEvents,
104
+ }));
105
+
94
106
  // ---------------------------------------------------------------------------
95
107
  // Production import (after mocks)
96
108
  // ---------------------------------------------------------------------------
@@ -135,14 +147,16 @@ let mockFetch: ReturnType<typeof mock>;
135
147
 
136
148
  beforeEach(() => {
137
149
  eventIdCounter = 0;
150
+ mockCollectUsageData = true;
138
151
  mockGetMemoryCheckpoint.mockReset();
139
152
  mockSetMemoryCheckpoint.mockReset();
140
153
  mockQueryUnreportedUsageEvents.mockReset();
141
154
  mockQueryUnreportedTurnEvents.mockReset();
142
155
  mockQueryUnreportedTurnEvents.mockReturnValue([]);
156
+ mockQueryUnreportedLifecycleEvents.mockReset();
157
+ mockQueryUnreportedLifecycleEvents.mockReturnValue([]);
143
158
  mockPlatformClient = null;
144
- mockGetTelemetryPlatformUrl.mockReset();
145
- mockGetTelemetryAppToken.mockReset();
159
+ mockGetPlatformBaseUrl.mockReset();
146
160
  mockGetDeviceId.mockReset();
147
161
  mockGetDeviceId.mockReturnValue("test-device-id");
148
162
  mockGetExternalAssistantId.mockReset();
@@ -154,8 +168,7 @@ beforeEach(() => {
154
168
 
155
169
  // Defaults
156
170
  mockGetMemoryCheckpoint.mockReturnValue(null);
157
- mockGetTelemetryPlatformUrl.mockReturnValue("https://platform.vellum.ai");
158
- mockGetTelemetryAppToken.mockReturnValue("default-test-token");
171
+ mockGetPlatformBaseUrl.mockReturnValue("https://platform.vellum.ai");
159
172
 
160
173
  mockFetch = mock(() =>
161
174
  Promise.resolve(new Response('{"accepted":0}', { status: 200 })),
@@ -195,10 +208,9 @@ describe("UsageTelemetryReporter", () => {
195
208
  expect(mockFetch).not.toHaveBeenCalled();
196
209
  });
197
210
 
198
- test("anonymous flush uses X-Telemetry-Token and default URL", async () => {
211
+ test("anonymous flush sends request without auth headers", async () => {
199
212
  mockPlatformClient = null;
200
- mockGetTelemetryPlatformUrl.mockReturnValue("https://platform.test.ai");
201
- mockGetTelemetryAppToken.mockReturnValue("anon-token");
213
+ mockGetPlatformBaseUrl.mockReturnValue("https://platform.test.ai");
202
214
 
203
215
  const events = [makeUsageEvent()];
204
216
  mockQueryUnreportedUsageEvents.mockReturnValue(events);
@@ -213,9 +225,9 @@ describe("UsageTelemetryReporter", () => {
213
225
  const [url, opts] = mockFetch.mock.calls[0] as [string, RequestInit];
214
226
  expect(url).toStartWith("https://platform.test.ai");
215
227
  expect(url).toEndWith("/telemetry/ingest/");
216
- expect((opts.headers as Record<string, string>)["X-Telemetry-Token"]).toBe(
217
- "anon-token",
218
- );
228
+ const headers = opts.headers as Record<string, string>;
229
+ expect(headers["Content-Type"]).toBe("application/json");
230
+ expect(headers["X-Telemetry-Token"]).toBeUndefined();
219
231
  });
220
232
 
221
233
  test("watermark advances on successful upload", async () => {
@@ -279,7 +291,7 @@ describe("UsageTelemetryReporter", () => {
279
291
  const body1 = JSON.parse(
280
292
  (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
281
293
  );
282
- expect(body1.installation_id).toBe("test-device-id");
294
+ expect(body1.device_id).toBe("test-device-id");
283
295
 
284
296
  // Second flush — should use the same value
285
297
  mockQueryUnreportedUsageEvents.mockReturnValue([makeUsageEvent()]);
@@ -288,7 +300,7 @@ describe("UsageTelemetryReporter", () => {
288
300
  const body2 = JSON.parse(
289
301
  (mockFetch.mock.calls[1] as [string, RequestInit])[1].body as string,
290
302
  );
291
- expect(body2.installation_id).toBe("test-device-id");
303
+ expect(body2.device_id).toBe("test-device-id");
292
304
  });
293
305
 
294
306
  test("empty batch makes no HTTP call", async () => {
@@ -362,8 +374,8 @@ describe("UsageTelemetryReporter", () => {
362
374
  (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
363
375
  );
364
376
 
365
- // Top-level: installation_id, assistant_version, and events array (no turn_events key)
366
- expect(body.installation_id).toBe("test-device-id");
377
+ // Top-level: device_id, assistant_version, and events array (no turn_events key)
378
+ expect(body.device_id).toBe("test-device-id");
367
379
  expect(body.assistant_version).toBe("1.2.3-test");
368
380
  expect(Array.isArray(body.events)).toBe(true);
369
381
  expect(body.events.length).toBe(1);
@@ -437,7 +449,7 @@ describe("UsageTelemetryReporter", () => {
437
449
  const body = JSON.parse(
438
450
  (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
439
451
  );
440
- expect(body.installation_id).toBe("test-device-id");
452
+ expect(body.device_id).toBe("test-device-id");
441
453
  expect(body.assistant_id).toBe(DAEMON_INTERNAL_ASSISTANT_ID);
442
454
  });
443
455
 
@@ -477,4 +489,56 @@ describe("UsageTelemetryReporter", () => {
477
489
  expect(turnEvent.daemon_event_id).toBe("evt-mixed-turn");
478
490
  expect(turnEvent.recorded_at).toBe(1700000050000);
479
491
  });
492
+
493
+ test("flush is skipped and watermarks advanced when collectUsageData is false", async () => {
494
+ mockCollectUsageData = false;
495
+ const events = [makeUsageEvent()];
496
+ mockQueryUnreportedUsageEvents.mockReturnValue(events);
497
+ mockFetch.mockImplementation(() =>
498
+ Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
499
+ );
500
+
501
+ const reporter = new UsageTelemetryReporter();
502
+ await reporter.flush();
503
+
504
+ // No HTTP call should have been made
505
+ expect(mockFetch).not.toHaveBeenCalled();
506
+
507
+ // All 6 watermarks should have been advanced (3 timestamps + 3 IDs)
508
+ expect(mockSetMemoryCheckpoint).toHaveBeenCalledTimes(6);
509
+
510
+ const calls = mockSetMemoryCheckpoint.mock.calls;
511
+ const keys = calls.map((c) => c[0]);
512
+ expect(keys).toContain("telemetry:usage:last_reported_at");
513
+ expect(keys).toContain("telemetry:usage:last_reported_id");
514
+ expect(keys).toContain("telemetry:turns:last_reported_at");
515
+ expect(keys).toContain("telemetry:turns:last_reported_id");
516
+ expect(keys).toContain("telemetry:lifecycle:last_reported_at");
517
+ expect(keys).toContain("telemetry:lifecycle:last_reported_id");
518
+ });
519
+
520
+ test("events sent normally after re-enabling collectUsageData", async () => {
521
+ // First flush with opt-out — watermarks advance, nothing sent
522
+ mockCollectUsageData = false;
523
+ const reporter = new UsageTelemetryReporter();
524
+ await reporter.flush();
525
+ expect(mockFetch).not.toHaveBeenCalled();
526
+ mockSetMemoryCheckpoint.mockReset();
527
+
528
+ // Re-enable and flush with new events
529
+ mockCollectUsageData = true;
530
+ const events = [makeUsageEvent({ id: "evt-after-reenable" })];
531
+ mockQueryUnreportedUsageEvents.mockReturnValue(events);
532
+ mockFetch.mockImplementation(() =>
533
+ Promise.resolve(new Response('{"accepted":1}', { status: 200 })),
534
+ );
535
+
536
+ await reporter.flush();
537
+
538
+ expect(mockFetch).toHaveBeenCalledTimes(1);
539
+ const body = JSON.parse(
540
+ (mockFetch.mock.calls[0] as [string, RequestInit])[1].body as string,
541
+ );
542
+ expect(body.events[0].daemon_event_id).toBe("evt-after-reenable");
543
+ });
480
544
  });
@@ -6,15 +6,15 @@
6
6
  *
7
7
  * Two auth modes:
8
8
  * - Authenticated: Api-Key header via managed proxy context
9
- * - Anonymous: X-Telemetry-Token static token from env
9
+ * - Anonymous: unauthenticated POST (telemetry endpoints are public)
10
10
  */
11
11
 
12
12
  import {
13
+ getPlatformBaseUrl,
13
14
  getPlatformOrganizationId,
14
15
  getPlatformUserId,
15
- getTelemetryAppToken,
16
- getTelemetryPlatformUrl,
17
16
  } from "../config/env.js";
17
+ import { getConfig } from "../config/loader.js";
18
18
  import {
19
19
  getMemoryCheckpoint,
20
20
  setMemoryCheckpoint,
@@ -93,6 +93,22 @@ export class UsageTelemetryReporter {
93
93
  try {
94
94
  if (batchCount >= MAX_CONSECUTIVE_BATCHES) return;
95
95
 
96
+ // Respect runtime opt-out: if the user has disabled usage data collection,
97
+ // skip the flush and advance watermarks so events recorded during the
98
+ // opt-out window are never sent retroactively.
99
+ if (!getConfig().collectUsageData) {
100
+ // Advance only the timestamp watermarks. Leave the ID watermarks
101
+ // untouched so the compound-cursor branch stays active — setting them
102
+ // to "" would make the truthy check fail, falling back to a
103
+ // timestamp-only `gt(createdAt, watermark)` query that silently drops
104
+ // events created in the same millisecond as the opt-out watermark.
105
+ const now = String(Date.now());
106
+ setMemoryCheckpoint(CHECKPOINT_KEY_WATERMARK, now);
107
+ setMemoryCheckpoint(CHECKPOINT_KEY_TURN_WATERMARK, now);
108
+ setMemoryCheckpoint(CHECKPOINT_KEY_LIFECYCLE_WATERMARK, now);
109
+ return;
110
+ }
111
+
96
112
  // Read usage watermark (compound cursor: createdAt + id)
97
113
  const watermark = Number(
98
114
  getMemoryCheckpoint(CHECKPOINT_KEY_WATERMARK) ?? "0",
@@ -138,11 +154,9 @@ export class UsageTelemetryReporter {
138
154
  )
139
155
  return;
140
156
 
141
- // Resolve auth context — skip flush when neither auth mode is viable
157
+ // Resolve auth context — authenticated path uses client, anonymous path
158
+ // sends unauthenticated (telemetry endpoints are public).
142
159
  const client = await VellumPlatformClient.create();
143
- if (!client && (!getTelemetryAppToken() || !getTelemetryPlatformUrl())) {
144
- return;
145
- }
146
160
 
147
161
  // Build payload
148
162
  const typedEvents: TelemetryEvent[] = [
@@ -203,16 +217,8 @@ export class UsageTelemetryReporter {
203
217
  if (client) {
204
218
  resp = await client.fetch(TELEMETRY_PATH, fetchInit);
205
219
  } else {
206
- const platformUrl = getTelemetryPlatformUrl();
207
- if (!platformUrl) return;
208
- const url = `${platformUrl}${TELEMETRY_PATH}`;
209
- resp = await fetch(url, {
210
- ...fetchInit,
211
- headers: {
212
- "Content-Type": "application/json",
213
- "X-Telemetry-Token": getTelemetryAppToken(),
214
- },
215
- });
220
+ const url = `${getPlatformBaseUrl()}${TELEMETRY_PATH}`;
221
+ resp = await fetch(url, fetchInit);
216
222
  }
217
223
 
218
224
  if (!resp.ok) {
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
 
4
- import { getRootDir } from "../../util/platform.js";
4
+ import { getExternalDir } from "../../util/platform.js";
5
5
 
6
6
  export interface BrowserRuntimeStatus {
7
7
  playwrightAvailable: boolean;
@@ -75,7 +75,7 @@ export async function importPlaywright(): Promise<typeof import("playwright")> {
75
75
  // filesystem instead of the compiled module cache.
76
76
  // Use the internal assistant root (outside tool sandbox working dir)
77
77
  // so untrusted workspace writes cannot plant a forged playwright package.
78
- const externalDir = join(getRootDir(), "external");
78
+ const externalDir = getExternalDir();
79
79
  const pwPkg = join(externalDir, "node_modules", "playwright");
80
80
 
81
81
  if (!existsSync(join(pwPkg, "package.json"))) {
@@ -3,25 +3,16 @@ import {
3
3
  setSlackChannelConfig,
4
4
  type SlackChannelConfigResult,
5
5
  } from "../../daemon/handlers/config-slack-channel.js";
6
- import { orchestrateOAuthConnect } from "../../oauth/connect-orchestrator.js";
7
6
  import { syncManualTokenConnection } from "../../oauth/manual-token-connection.js";
8
7
  import {
9
8
  disconnectOAuthProvider,
10
9
  getActiveConnection,
11
- getAppByProviderAndClientId,
12
- getMostRecentAppByProvider,
13
- getProvider,
14
- listProviders,
15
10
  } from "../../oauth/oauth-store.js";
16
11
  import { RiskLevel } from "../../permissions/types.js";
17
12
  import type { ToolDefinition } from "../../providers/types.js";
18
- import { buildAssistantEvent } from "../../runtime/assistant-event.js";
19
- import { assistantEventHub } from "../../runtime/assistant-event-hub.js";
20
- import { DAEMON_INTERNAL_ASSISTANT_ID } from "../../runtime/assistant-scope.js";
21
13
  import { credentialKey } from "../../security/credential-key.js";
22
14
  import {
23
15
  deleteSecureKeyAsync,
24
- getSecureKeyAsync,
25
16
  listSecureKeysAsync,
26
17
  setSecureKeyAsync,
27
18
  } from "../../security/secure-keys.js";
@@ -90,16 +81,9 @@ class CredentialStoreTool implements Tool {
90
81
  properties: {
91
82
  action: {
92
83
  type: "string",
93
- enum: [
94
- "store",
95
- "list",
96
- "delete",
97
- "prompt",
98
- "oauth2_connect",
99
- "describe",
100
- ],
84
+ enum: ["store", "list", "delete", "prompt"],
101
85
  description:
102
- 'The operation to perform. Use "prompt" to ask the user for a secret via secure UI - the value never enters the conversation. Use "oauth2_connect" to connect an OAuth2 service via browser authorization. Use "describe" to get setup metadata for a well-known OAuth service (dashboard URL, scopes, redirect URI, etc.). For well-known services (google, slack), only the service name is required - endpoints, scopes, and stored client credentials are resolved automatically.',
86
+ 'The operation to perform. Use "prompt" to ask the user for a secret via secure UI - the value never enters the conversation.',
103
87
  },
104
88
  service: {
105
89
  type: "string",
@@ -150,22 +134,6 @@ class CredentialStoreTool implements Tool {
150
134
  description:
151
135
  'Human-readable description of intended usage (for store/prompt actions), e.g. "GitHub login for pushing changes"',
152
136
  },
153
- scopes: {
154
- type: "array",
155
- items: { type: "string" },
156
- description:
157
- "OAuth2 scopes to request (only for oauth2_connect action). Auto-filled for well-known services (google, slack).",
158
- },
159
- client_id: {
160
- type: "string",
161
- description:
162
- "OAuth2 client ID (only for oauth2_connect action). If omitted, looked up from previously stored credentials.",
163
- },
164
- client_secret: {
165
- type: "string",
166
- description:
167
- "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)",
168
- },
169
137
  alias: {
170
138
  type: "string",
171
139
  description:
@@ -817,221 +785,6 @@ class CredentialStoreTool implements Tool {
817
785
  };
818
786
  }
819
787
 
820
- case "oauth2_connect": {
821
- const service = input.service as string | undefined;
822
- if (!service)
823
- return {
824
- content: "Error: service is required for oauth2_connect action",
825
- isError: true,
826
- };
827
-
828
- // Protocol-level config from the DB (authUrl, tokenUrl, scopes, etc.)
829
- const providerRow = getProvider(service);
830
-
831
- // Resolve client_id and client_secret.
832
- // Priority:
833
- // 1. Explicit input from the caller
834
- // 2. oauth-store DB - when clientId is already known, look up the
835
- // matching app so the secret comes from the same app. Only fall
836
- // back to the most-recent-app heuristic when clientId is unknown.
837
- let clientId = input.client_id as string | undefined;
838
- let clientSecret = input.client_secret as string | undefined;
839
-
840
- if (!clientId || !clientSecret) {
841
- const dbApp = clientId
842
- ? getAppByProviderAndClientId(service, clientId)
843
- : getMostRecentAppByProvider(service);
844
- if (dbApp) {
845
- if (!clientId) clientId = dbApp.clientId;
846
- if (!clientSecret) {
847
- clientSecret = await getSecureKeyAsync(
848
- dbApp.clientSecretCredentialPath,
849
- );
850
- }
851
- }
852
- }
853
-
854
- // Early guardrails that stay in vault.ts (credential resolution is vault-specific)
855
- const inputScopes = input.scopes as string[] | undefined;
856
-
857
- if (!providerRow) {
858
- return {
859
- content: `Error: no OAuth provider registered for "${service}". Ensure the provider is seeded in the database.`,
860
- isError: true,
861
- };
862
- }
863
-
864
- if (!clientId)
865
- return {
866
- content:
867
- "Error: client_id is required for oauth2_connect action. Provide it directly or store it first with credential_store.",
868
- isError: true,
869
- };
870
-
871
- // Fail early when client_secret is required but missing - guide the
872
- // agent to collect it from the user rather than letting it improvise
873
- // browser-automation workarounds that inevitably fail.
874
- const requiresSecret = !!providerRow.requiresClientSecret;
875
- if (requiresSecret && !clientSecret) {
876
- return {
877
- content: `Error: client_secret is required for ${service} but not found in the vault.\n\nUse credential_store with action "prompt" to securely collect the client_secret from the user before calling oauth2_connect again.`,
878
- isError: true,
879
- };
880
- }
881
-
882
- // Delegate to the shared orchestrator - it resolves authUrl, tokenUrl,
883
- // extraParams, userinfoUrl, and tokenEndpointAuthMethod from the DB.
884
- const result = await orchestrateOAuthConnect({
885
- service,
886
- clientId,
887
- clientSecret,
888
- isInteractive: !!context.isInteractive,
889
- sendToClient: context.sendToClient,
890
- ...(inputScopes ? { requestedScopes: inputScopes } : {}),
891
- onDeferredComplete: (deferredResult) => {
892
- // Emit oauth_connect_result to all connected SSE clients so the
893
- // UI can update immediately when the deferred browser flow completes.
894
- assistantEventHub
895
- .publish(
896
- buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, {
897
- type: "oauth_connect_result",
898
- success: deferredResult.success,
899
- service: deferredResult.service,
900
- accountInfo: deferredResult.accountInfo,
901
- error: deferredResult.error,
902
- }),
903
- )
904
- .catch((err) => {
905
- log.warn(
906
- { err, service: deferredResult.service },
907
- "Failed to publish oauth_connect_result event",
908
- );
909
- });
910
-
911
- if (deferredResult.success) {
912
- log.info(
913
- {
914
- service: deferredResult.service,
915
- accountInfo: deferredResult.accountInfo,
916
- },
917
- "Deferred OAuth connect completed successfully",
918
- );
919
- } else {
920
- log.warn(
921
- {
922
- service: deferredResult.service,
923
- err: deferredResult.error,
924
- },
925
- "Deferred OAuth connect failed",
926
- );
927
- }
928
- },
929
- });
930
-
931
- if (!result.success) {
932
- return { content: `Error: ${result.error}`, isError: true };
933
- }
934
-
935
- if (result.deferred) {
936
- return {
937
- content: `To connect ${service}, open this link and authorize access:\n\n${result.authUrl}\n\nOnce you authorize, the connection will be set up automatically. You can verify by asking me to check your inbox.`,
938
- isError: false,
939
- };
940
- }
941
-
942
- return {
943
- content: `Successfully connected "${service}"${
944
- result.accountInfo ? ` as ${result.accountInfo}` : ""
945
- }. The service is now ready to use.`,
946
- isError: false,
947
- };
948
- }
949
-
950
- case "describe": {
951
- const descService = (input.service as string | undefined) ?? "";
952
- if (!descService) {
953
- return {
954
- content: "Error: service is required for describe action",
955
- isError: true,
956
- };
957
- }
958
- const descProviderRow = getProvider(descService);
959
- if (!descProviderRow) {
960
- const availableServices = listProviders().map((p) => p.providerKey);
961
- return {
962
- content: `No well-known OAuth config found for "${descService}". Available services: ${availableServices.join(", ")}`,
963
- isError: false,
964
- };
965
- }
966
-
967
- // Compute the redirect URI based on callback transport
968
- let redirectUri: string;
969
- const transport =
970
- (descProviderRow.callbackTransport as
971
- | "loopback"
972
- | "gateway"
973
- | null) ?? "loopback";
974
- const loopbackPort = descProviderRow.loopbackPort;
975
- if (transport === "loopback" && loopbackPort) {
976
- redirectUri = `http://localhost:${loopbackPort}/oauth/callback`;
977
- } else if (transport === "loopback") {
978
- redirectUri =
979
- "(automatic - no redirect URI needed, uses random localhost port)";
980
- } else {
981
- // Try to compute the actual URL from config/env
982
- try {
983
- const { loadConfig } = await import("../../config/loader.js");
984
- const { getPublicBaseUrl } =
985
- await import("../../inbound/public-ingress-urls.js");
986
- const baseUrl = getPublicBaseUrl(loadConfig());
987
- redirectUri = `${baseUrl}/webhooks/oauth/callback`;
988
- } catch {
989
- redirectUri =
990
- "(requires ingress.publicBaseUrl - not currently configured)";
991
- }
992
- }
993
-
994
- const requiresClientSecret = !!descProviderRow.requiresClientSecret;
995
-
996
- const descDefaultScopes: string[] = descProviderRow.defaultScopes
997
- ? JSON.parse(descProviderRow.defaultScopes)
998
- : [];
999
-
1000
- const info: Record<string, unknown> = {
1001
- service: descService,
1002
- authUrl: descProviderRow.authUrl,
1003
- tokenUrl: descProviderRow.tokenUrl,
1004
- scopes: descDefaultScopes,
1005
- callbackTransport: transport,
1006
- redirectUri,
1007
- requiresClientSecret,
1008
- };
1009
- if (
1010
- descProviderRow.displayName &&
1011
- descProviderRow.dashboardUrl &&
1012
- descProviderRow.appType
1013
- ) {
1014
- info.setup = {
1015
- displayName: descProviderRow.displayName,
1016
- dashboardUrl: descProviderRow.dashboardUrl,
1017
- appType: descProviderRow.appType,
1018
- requiresClientSecret: !!descProviderRow.requiresClientSecret,
1019
- ...(descProviderRow.setupNotes
1020
- ? { notes: JSON.parse(descProviderRow.setupNotes) }
1021
- : {}),
1022
- };
1023
- }
1024
- if (descProviderRow.extraParams) {
1025
- try {
1026
- info.extraParams = JSON.parse(descProviderRow.extraParams);
1027
- } catch {
1028
- // Non-fatal
1029
- }
1030
- }
1031
-
1032
- return { content: JSON.stringify(info, null, 2), isError: false };
1033
- }
1034
-
1035
788
  default:
1036
789
  return { content: `Error: unknown action "${action}"`, isError: true };
1037
790
  }