@vellumai/assistant 0.4.48 → 0.4.49

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 (252) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/README.md +2 -23
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/runbook-trusted-contacts.md +3 -8
  6. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  7. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  8. package/package.json +1 -1
  9. package/src/__tests__/actor-token-service.test.ts +0 -1
  10. package/src/__tests__/anthropic-provider.test.ts +156 -0
  11. package/src/__tests__/approval-cascade.test.ts +810 -0
  12. package/src/__tests__/approval-primitive.test.ts +0 -1
  13. package/src/__tests__/approval-routes-http.test.ts +2 -0
  14. package/src/__tests__/assistant-attachments.test.ts +12 -34
  15. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  16. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  17. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  18. package/src/__tests__/channel-guardian.test.ts +0 -2
  19. package/src/__tests__/channel-readiness-routes.test.ts +15 -6
  20. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  21. package/src/__tests__/checker.test.ts +9 -29
  22. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  23. package/src/__tests__/computer-use-tools.test.ts +2 -19
  24. package/src/__tests__/config-watcher.test.ts +0 -1
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  26. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  27. package/src/__tests__/context-token-estimator.test.ts +196 -13
  28. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  29. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  30. package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
  31. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  32. package/src/__tests__/credential-metadata-store.test.ts +64 -73
  33. package/src/__tests__/credential-security-invariants.test.ts +13 -7
  34. package/src/__tests__/credential-vault-unit.test.ts +280 -49
  35. package/src/__tests__/credential-vault.test.ts +138 -16
  36. package/src/__tests__/credentials-cli.test.ts +71 -0
  37. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  38. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  39. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  40. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  41. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  42. package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
  43. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  44. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  45. package/src/__tests__/heartbeat-service.test.ts +0 -1
  46. package/src/__tests__/host-cu-proxy.test.ts +629 -0
  47. package/src/__tests__/host-shell-tool.test.ts +27 -15
  48. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  49. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  50. package/src/__tests__/integration-status.test.ts +32 -51
  51. package/src/__tests__/intent-routing.test.ts +0 -1
  52. package/src/__tests__/invite-routes-http.test.ts +10 -9
  53. package/src/__tests__/keychain-broker-client.test.ts +11 -43
  54. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  55. package/src/__tests__/oauth-cli.test.ts +373 -14
  56. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  57. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  58. package/src/__tests__/oauth-store.test.ts +756 -0
  59. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  60. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  61. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  62. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  63. package/src/__tests__/recording-handler.test.ts +3 -4
  64. package/src/__tests__/registry.test.ts +2 -2
  65. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  66. package/src/__tests__/schedule-store.test.ts +0 -1
  67. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  68. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  69. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  70. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  71. package/src/__tests__/send-endpoint-busy.test.ts +21 -6
  72. package/src/__tests__/sequence-store.test.ts +0 -1
  73. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  74. package/src/__tests__/skill-include-graph.test.ts +66 -0
  75. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  76. package/src/__tests__/skill-load-tool.test.ts +149 -1
  77. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  78. package/src/__tests__/skills-uninstall.test.ts +1 -1
  79. package/src/__tests__/skills.test.ts +3 -3
  80. package/src/__tests__/slack-channel-config.test.ts +67 -3
  81. package/src/__tests__/slack-share-routes.test.ts +17 -19
  82. package/src/__tests__/system-prompt.test.ts +0 -1
  83. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  84. package/src/__tests__/terminal-tools.test.ts +4 -3
  85. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  86. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  87. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  88. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  90. package/src/__tests__/tool-executor.test.ts +0 -1
  91. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  92. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  93. package/src/__tests__/trust-store.test.ts +1 -22
  94. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  95. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  96. package/src/__tests__/twilio-routes.test.ts +0 -16
  97. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  98. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  99. package/src/agent/ax-tree-compaction.test.ts +235 -0
  100. package/src/agent/loop.ts +76 -130
  101. package/src/calls/call-domain.ts +1 -6
  102. package/src/calls/relay-server.ts +9 -13
  103. package/src/calls/twilio-config.ts +2 -7
  104. package/src/calls/twilio-routes.ts +1 -2
  105. package/src/calls/voice-ingress-preflight.ts +1 -1
  106. package/src/cli/commands/browser-relay.ts +18 -12
  107. package/src/cli/commands/completions.ts +0 -3
  108. package/src/cli/commands/credentials.ts +101 -15
  109. package/src/cli/commands/oauth/apps.ts +255 -0
  110. package/src/cli/commands/oauth/connections.ts +299 -0
  111. package/src/cli/commands/oauth/index.ts +52 -0
  112. package/src/cli/commands/oauth/providers.ts +242 -0
  113. package/src/cli/commands/skills.ts +4 -338
  114. package/src/cli/program.ts +1 -5
  115. package/src/cli/reference.ts +1 -3
  116. package/src/config/assistant-feature-flags.ts +0 -3
  117. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  118. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  119. package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
  120. package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
  121. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
  122. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  123. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  124. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  125. package/src/config/env-registry.ts +14 -83
  126. package/src/config/env.ts +11 -50
  127. package/src/config/feature-flag-registry.json +16 -16
  128. package/src/config/loader.ts +0 -6
  129. package/src/config/schema.ts +3 -1
  130. package/src/config/skills.ts +21 -2
  131. package/src/context/image-dimensions.ts +229 -0
  132. package/src/context/token-estimator.ts +75 -12
  133. package/src/context/window-manager.ts +49 -10
  134. package/src/daemon/assistant-attachments.ts +1 -13
  135. package/src/daemon/handlers/config-ingress.ts +8 -33
  136. package/src/daemon/handlers/config-slack-channel.ts +49 -46
  137. package/src/daemon/handlers/config-telegram.ts +32 -16
  138. package/src/daemon/handlers/sessions.ts +10 -24
  139. package/src/daemon/handlers/shared.ts +0 -130
  140. package/src/daemon/host-cu-proxy.ts +401 -0
  141. package/src/daemon/lifecycle.ts +36 -68
  142. package/src/daemon/message-protocol.ts +3 -0
  143. package/src/daemon/message-types/computer-use.ts +2 -119
  144. package/src/daemon/message-types/host-cu.ts +19 -0
  145. package/src/daemon/message-types/messages.ts +3 -0
  146. package/src/daemon/server.ts +14 -21
  147. package/src/daemon/session-agent-loop-handlers.ts +2 -0
  148. package/src/daemon/session-attachments.ts +1 -2
  149. package/src/daemon/session-slash.ts +1 -1
  150. package/src/daemon/session-surfaces.ts +40 -28
  151. package/src/daemon/session-tool-setup.ts +2 -9
  152. package/src/daemon/session.ts +138 -15
  153. package/src/daemon/tool-side-effects.ts +2 -8
  154. package/src/daemon/watch-handler.ts +2 -2
  155. package/src/events/tool-metrics-listener.ts +2 -2
  156. package/src/hooks/manager.ts +1 -4
  157. package/src/inbound/public-ingress-urls.ts +7 -7
  158. package/src/logfire.ts +16 -5
  159. package/src/memory/conversation-key-store.ts +21 -0
  160. package/src/memory/db-init.ts +4 -0
  161. package/src/memory/migrations/149-oauth-tables.ts +60 -0
  162. package/src/memory/migrations/index.ts +1 -0
  163. package/src/memory/schema/index.ts +1 -0
  164. package/src/memory/schema/oauth.ts +65 -0
  165. package/src/messaging/provider.ts +4 -4
  166. package/src/messaging/providers/gmail/client.ts +82 -2
  167. package/src/messaging/providers/gmail/people-client.ts +10 -10
  168. package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
  169. package/src/messaging/providers/whatsapp/adapter.ts +11 -8
  170. package/src/messaging/registry.ts +2 -32
  171. package/src/notifications/copy-composer.ts +0 -5
  172. package/src/notifications/signal.ts +4 -5
  173. package/src/oauth/byo-connection.test.ts +126 -25
  174. package/src/oauth/byo-connection.ts +22 -6
  175. package/src/oauth/connect-orchestrator.ts +113 -57
  176. package/src/oauth/connect-types.ts +17 -23
  177. package/src/oauth/connection-resolver.ts +35 -11
  178. package/src/oauth/connection.ts +1 -1
  179. package/src/oauth/manual-token-connection.ts +104 -0
  180. package/src/oauth/oauth-store.ts +496 -0
  181. package/src/oauth/platform-connection.test.ts +29 -0
  182. package/src/oauth/platform-connection.ts +6 -5
  183. package/src/oauth/provider-behaviors.ts +124 -0
  184. package/src/oauth/scope-policy.ts +9 -2
  185. package/src/oauth/seed-providers.ts +161 -0
  186. package/src/oauth/token-persistence.ts +74 -78
  187. package/src/permissions/checker.ts +3 -3
  188. package/src/permissions/defaults.ts +0 -1
  189. package/src/permissions/prompter.ts +10 -1
  190. package/src/permissions/trust-store.ts +13 -0
  191. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  192. package/src/prompts/system-prompt.ts +28 -40
  193. package/src/providers/anthropic/client.ts +133 -24
  194. package/src/providers/retry.ts +1 -27
  195. package/src/runtime/auth/route-policy.ts +0 -3
  196. package/src/runtime/channel-reply-delivery.ts +0 -40
  197. package/src/runtime/gateway-client.ts +0 -7
  198. package/src/runtime/http-server.ts +8 -6
  199. package/src/runtime/http-types.ts +2 -2
  200. package/src/runtime/middleware/twilio-validation.ts +1 -11
  201. package/src/runtime/pending-interactions.ts +14 -12
  202. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  203. package/src/runtime/routes/conversation-routes.ts +73 -19
  204. package/src/runtime/routes/events-routes.ts +21 -11
  205. package/src/runtime/routes/host-cu-routes.ts +97 -0
  206. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  207. package/src/runtime/routes/integrations/slack/share.ts +6 -7
  208. package/src/runtime/routes/log-export-routes.ts +126 -8
  209. package/src/runtime/routes/settings-routes.ts +55 -48
  210. package/src/runtime/routes/surface-action-routes.ts +1 -1
  211. package/src/runtime/routes/watch-routes.ts +128 -0
  212. package/src/schedule/integration-status.ts +10 -9
  213. package/src/security/credential-key.ts +0 -156
  214. package/src/security/keychain-broker-client.ts +5 -6
  215. package/src/security/oauth2.ts +1 -1
  216. package/src/security/token-manager.ts +119 -46
  217. package/src/skills/catalog-install.ts +358 -0
  218. package/src/skills/include-graph.ts +32 -0
  219. package/src/telegram/bot-username.ts +2 -3
  220. package/src/tools/browser/network-recorder.ts +1 -1
  221. package/src/tools/browser/network-recording-types.ts +1 -1
  222. package/src/tools/computer-use/definitions.ts +46 -11
  223. package/src/tools/computer-use/registry.ts +4 -5
  224. package/src/tools/credentials/broker.ts +1 -2
  225. package/src/tools/credentials/metadata-store.ts +17 -121
  226. package/src/tools/credentials/vault.ts +94 -167
  227. package/src/tools/registry.ts +2 -7
  228. package/src/tools/skills/load.ts +62 -3
  229. package/src/tools/watch/watch-state.ts +0 -12
  230. package/src/util/logger.ts +7 -41
  231. package/src/util/platform.ts +9 -28
  232. package/src/watcher/providers/google-calendar.ts +2 -1
  233. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  234. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  235. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  236. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  237. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  238. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  239. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  240. package/src/cli/commands/dev.ts +0 -129
  241. package/src/cli/commands/map.ts +0 -391
  242. package/src/cli/commands/oauth.ts +0 -77
  243. package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
  244. package/src/daemon/computer-use-session.ts +0 -1026
  245. package/src/daemon/ride-shotgun-handler.ts +0 -569
  246. package/src/oauth/provider-base-urls.ts +0 -21
  247. package/src/oauth/provider-profiles.ts +0 -192
  248. package/src/prompts/computer-use-prompt.ts +0 -98
  249. package/src/runtime/routes/computer-use-routes.ts +0 -641
  250. package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
  251. package/src/runtime/telegram-streaming-delivery.ts +0 -393
  252. package/src/tools/computer-use/request-computer-control.ts +0 -56
@@ -34,7 +34,7 @@ const reasonProperty = {
34
34
  export const computerUseClickTool: Tool = {
35
35
  name: "computer_use_click",
36
36
  description:
37
- "Click on a UI element by its [ID] from the accessibility tree, or at raw screen coordinates as fallback. Supports single click, double-click, and right-click via the click_type parameter.",
37
+ "Click an element on screen. Prefer element_id (from the accessibility tree) over x/y coordinates.",
38
38
  category: "computer-use",
39
39
  defaultRiskLevel: RiskLevel.Low,
40
40
  executionMode: "proxy",
@@ -86,7 +86,7 @@ export const computerUseClickTool: Tool = {
86
86
  export const computerUseTypeTextTool: Tool = {
87
87
  name: "computer_use_type_text",
88
88
  description:
89
- "Type text at the current cursor position. The target field must already be focused (click it first).",
89
+ "Type text at the current cursor position. First click a text field (by element_id) to focus it, then call this tool. If a field shows 'FOCUSED', skip the click.",
90
90
  category: "computer-use",
91
91
  defaultRiskLevel: RiskLevel.Low,
92
92
  executionMode: "proxy",
@@ -352,13 +352,7 @@ export const computerUseOpenAppTool: Tool = {
352
352
  export const computerUseRunAppleScriptTool: Tool = {
353
353
  name: "computer_use_run_applescript",
354
354
  description:
355
- "Execute an AppleScript to control applications via Apple's scripting bridge. " +
356
- "Use this for operations that are more reliable through scripting than UI interaction: " +
357
- "setting a browser URL directly, navigating Finder to a path, querying app state " +
358
- "(tab count, window titles, document status), or clicking deeply nested menu items. " +
359
- "The script's return value (if any) will be reported back. " +
360
- 'NEVER use "do shell script" — it is blocked for security. ' +
361
- "Keep scripts short and targeted to a single operation.",
355
+ "Run an AppleScript command. Prefer this over click/type when possible — it doesn't move the cursor or interrupt the user. Never use 'do shell script' inside AppleScript (blocked for security).",
362
356
  category: "computer-use",
363
357
  defaultRiskLevel: RiskLevel.Low,
364
358
  executionMode: "proxy",
@@ -395,7 +389,8 @@ export const computerUseRunAppleScriptTool: Tool = {
395
389
 
396
390
  export const computerUseDoneTool: Tool = {
397
391
  name: "computer_use_done",
398
- description: "Task is complete",
392
+ description:
393
+ "Signal that the computer use task is complete. Provide a summary of what was accomplished. This ends the computer use session.",
399
394
  category: "computer-use",
400
395
  defaultRiskLevel: RiskLevel.Low,
401
396
  executionMode: "proxy",
@@ -428,7 +423,7 @@ export const computerUseDoneTool: Tool = {
428
423
  export const computerUseRespondTool: Tool = {
429
424
  name: "computer_use_respond",
430
425
  description:
431
- "Respond directly to the user with a text answer. Use this when the user is asking a question (about their schedule, meetings, calendar, etc.) rather than asking you to control the computer.",
426
+ "Respond to the user with a text answer instead of performing computer actions. Use this when you can answer directly without interacting with the screen.",
432
427
  category: "computer-use",
433
428
  defaultRiskLevel: RiskLevel.Low,
434
429
  executionMode: "proxy",
@@ -458,11 +453,41 @@ export const computerUseRespondTool: Tool = {
458
453
  execute: proxyExecute,
459
454
  };
460
455
 
456
+ // ---------------------------------------------------------------------------
457
+ // observe
458
+ // ---------------------------------------------------------------------------
459
+
460
+ export const computerUseObserveTool: Tool = {
461
+ name: "computer_use_observe",
462
+ description:
463
+ "Capture the current screen state. Returns the accessibility tree with [ID] element references and optionally a screenshot.\n\nThe accessibility tree shows interactive elements like [3] AXButton 'Save' or [17] AXTextField 'Search'. Use element_id to target these elements in subsequent actions — this is much more reliable than pixel coordinates.\n\nCall this before your first computer use action, or to check screen state without acting.",
464
+ category: "computer-use",
465
+ defaultRiskLevel: RiskLevel.Low,
466
+ executionMode: "proxy",
467
+
468
+ getDefinition(): ToolDefinition {
469
+ return {
470
+ name: this.name,
471
+ description: this.description,
472
+ input_schema: {
473
+ type: "object",
474
+ properties: {
475
+ reason: reasonProperty,
476
+ },
477
+ required: ["reason"],
478
+ },
479
+ };
480
+ },
481
+
482
+ execute: proxyExecute,
483
+ };
484
+
461
485
  // ---------------------------------------------------------------------------
462
486
  // All tools exported as array for convenience
463
487
  // ---------------------------------------------------------------------------
464
488
 
465
489
  export const allComputerUseTools: Tool[] = [
490
+ computerUseObserveTool,
466
491
  computerUseClickTool,
467
492
  computerUseTypeTextTool,
468
493
  computerUseKeyTool,
@@ -474,3 +499,13 @@ export const allComputerUseTools: Tool[] = [
474
499
  computerUseDoneTool,
475
500
  computerUseRespondTool,
476
501
  ];
502
+
503
+ /**
504
+ * Tools safe for the legacy fallback path (no skill projection).
505
+ *
506
+ * Excludes `computer_use_observe` because the macOS client doesn't handle it
507
+ * in the legacy code path — it falls back to `.done` which skips sending an
508
+ * observation, causing the daemon to block on `pendingObservation` until timeout.
509
+ */
510
+ export const legacyFallbackComputerUseTools: Tool[] =
511
+ allComputerUseTools.filter((t) => t.name !== "computer_use_observe");
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * Registers computer-use tools with the daemon's tool registry.
3
3
  *
4
- * The 12 computer_use_* action tools and the computer_use_request_control
5
- * escalation tool are now provided by the bundled computer-use skill.
6
- * This module retains registerComputerUseActionTools() for backward
7
- * compatibility (used by tests), but it is no longer called during
8
- * normal startup.
4
+ * The computer_use_* action tools are now provided by the bundled
5
+ * computer-use skill. This module retains registerComputerUseActionTools()
6
+ * for backward compatibility (used by tests), but it is no longer called
7
+ * during normal startup.
9
8
  */
10
9
 
11
10
  import { registerTool } from "../registry.js";
@@ -1,6 +1,6 @@
1
1
  import { v4 as uuid } from "uuid";
2
2
 
3
- import { credentialKey, migrateKeys } from "../../security/credential-key.js";
3
+ import { credentialKey } from "../../security/credential-key.js";
4
4
  import { getSecureKey } from "../../security/secure-keys.js";
5
5
  import { getLogger } from "../../util/logger.js";
6
6
  import type {
@@ -60,7 +60,6 @@ export class CredentialBroker {
60
60
  * Returns a single-use token on success, or a denial reason on failure.
61
61
  */
62
62
  authorize(request: AuthorizeRequest): AuthorizeResult {
63
- migrateKeys();
64
63
  const metadata = getCredentialMetadata(request.service, request.field);
65
64
  if (!metadata) {
66
65
  return {
@@ -4,6 +4,11 @@
4
4
  * Persists non-secret metadata about credentials (policy, timestamps, IDs)
5
5
  * in a versioned JSON file under protected storage. Secret values remain
6
6
  * in the secure key backend only.
7
+ *
8
+ * OAuth-specific fields (expiresAt, grantedScopes, oauth2TokenUrl,
9
+ * oauth2ClientId, oauth2TokenEndpointAuthMethod, hasRefreshToken) are now
10
+ * exclusively managed by the SQLite oauth-store and have been removed
11
+ * from this interface as of v5.
7
12
  */
8
13
 
9
14
  import { randomUUID } from "node:crypto";
@@ -21,26 +26,16 @@ export interface CredentialMetadata {
21
26
  allowedTools: string[];
22
27
  allowedDomains: string[];
23
28
  usageDescription?: string;
24
- expiresAt?: number;
25
- grantedScopes?: string[];
26
- /** OAuth2 token endpoint — enables autonomous token refresh without an IntegrationDefinition. */
27
- oauth2TokenUrl?: string;
28
- /** OAuth2 client ID — paired with oauth2TokenUrl for refresh. */
29
- oauth2ClientId?: string;
30
- /** How the client authenticates at the token endpoint (client_secret_basic or client_secret_post). */
31
- oauth2TokenEndpointAuthMethod?: string;
32
29
  /** Human-friendly name for this credential (e.g. "fal-primary"). */
33
30
  alias?: string;
34
31
  /** Templates describing how to inject this credential into proxied requests. */
35
32
  injectionTemplates?: CredentialInjectionTemplate[];
36
- /** Whether a refresh token exists in the secure store for this service. */
37
- hasRefreshToken?: boolean;
38
33
  createdAt: number;
39
34
  updatedAt: number;
40
35
  }
41
36
 
42
37
  /** Current on-disk schema version. */
43
- const CURRENT_VERSION = 4;
38
+ const CURRENT_VERSION = 5;
44
39
 
45
40
  interface MetadataFile {
46
41
  version: typeof CURRENT_VERSION;
@@ -88,9 +83,10 @@ function isValidCredentialRecord(
88
83
  }
89
84
 
90
85
  /**
91
- * Migrate a v1 record to v2 by backfilling new optional fields with defaults.
86
+ * Migrate any record to v5 by stripping OAuth-specific fields that are
87
+ * now exclusively managed by the SQLite oauth-store.
92
88
  */
93
- function migrateRecordV1toV2(
89
+ function migrateRecordToV5(
94
90
  record: Record<string, unknown>,
95
91
  ): CredentialMetadata {
96
92
  return {
@@ -107,73 +103,15 @@ function migrateRecordV1toV2(
107
103
  typeof record.usageDescription === "string"
108
104
  ? record.usageDescription
109
105
  : undefined,
110
- expiresAt:
111
- typeof record.expiresAt === "number" ? record.expiresAt : undefined,
112
- grantedScopes: Array.isArray(record.grantedScopes)
113
- ? (record.grantedScopes as string[])
114
- : undefined,
115
- oauth2TokenUrl:
116
- typeof record.oauth2TokenUrl === "string"
117
- ? record.oauth2TokenUrl
118
- : undefined,
119
- oauth2ClientId:
120
- typeof record.oauth2ClientId === "string"
121
- ? record.oauth2ClientId
122
- : undefined,
123
- oauth2TokenEndpointAuthMethod:
124
- typeof record.oauth2TokenEndpointAuthMethod === "string"
125
- ? record.oauth2TokenEndpointAuthMethod
126
- : undefined,
127
106
  alias: typeof record.alias === "string" ? record.alias : undefined,
128
107
  injectionTemplates: Array.isArray(record.injectionTemplates)
129
108
  ? (record.injectionTemplates as CredentialInjectionTemplate[])
130
109
  : undefined,
131
- hasRefreshToken:
132
- typeof record.hasRefreshToken === "boolean"
133
- ? record.hasRefreshToken
134
- : undefined,
135
110
  createdAt: record.createdAt as number,
136
111
  updatedAt: record.updatedAt as number,
137
112
  };
138
113
  }
139
114
 
140
- /**
141
- * Migrate a v2 record to v3 by stripping the oauth2ClientSecret field.
142
- * Client secrets are now read exclusively from the secure key store.
143
- */
144
- function migrateRecordV2toV3(record: CredentialMetadata): CredentialMetadata {
145
- const { oauth2ClientSecret: _removed, ...rest } =
146
- record as CredentialMetadata & { oauth2ClientSecret?: string };
147
- return rest;
148
- }
149
-
150
- /**
151
- * Migrate v3 credentials to v4:
152
- * - Delete ghost `refresh_token` metadata records
153
- * - Set `hasRefreshToken: true` on corresponding `access_token` records
154
- */
155
- function migrateV3toV4(
156
- credentials: CredentialMetadata[],
157
- ): CredentialMetadata[] {
158
- // Collect services that had refresh_token ghost records
159
- const servicesWithRefresh = new Set<string>();
160
- for (const c of credentials) {
161
- if (c.field === "refresh_token") {
162
- servicesWithRefresh.add(c.service);
163
- }
164
- }
165
-
166
- // Remove all refresh_token records and set hasRefreshToken on access_token records
167
- const filtered = credentials.filter((c) => c.field !== "refresh_token");
168
- for (const c of filtered) {
169
- if (c.field === "access_token" && servicesWithRefresh.has(c.service)) {
170
- c.hasRefreshToken = true;
171
- }
172
- }
173
-
174
- return filtered;
175
- }
176
-
177
115
  function loadFile(): LoadResult {
178
116
  const raw = readTextFileSync(getMetadataPath());
179
117
  if (raw == null) {
@@ -189,7 +127,8 @@ function loadFile(): LoadResult {
189
127
  fileVersion !== 1 &&
190
128
  fileVersion !== 2 &&
191
129
  fileVersion !== 3 &&
192
- fileVersion !== 4
130
+ fileVersion !== 4 &&
131
+ fileVersion !== 5
193
132
  ) {
194
133
  // Unrecognized version (future, fractional, negative, zero) — refuse to touch it
195
134
  return { unknownVersion: true };
@@ -201,24 +140,12 @@ function loadFile(): LoadResult {
201
140
  const validRecords = rawCredentials.filter(isValidCredentialRecord);
202
141
 
203
142
  if (fileVersion < CURRENT_VERSION) {
204
- // Apply migrations in sequence: v1→v2→v3→v4
205
- let credentials: CredentialMetadata[];
206
- if (fileVersion === 1) {
207
- credentials = migrateV3toV4(
208
- validRecords.map(migrateRecordV1toV2).map(migrateRecordV2toV3),
209
- );
210
- } else if (fileVersion === 2) {
211
- credentials = migrateV3toV4(
212
- (validRecords as unknown as CredentialMetadata[]).map(
213
- migrateRecordV2toV3,
214
- ),
215
- );
216
- } else {
217
- // fileVersion === 3
218
- credentials = migrateV3toV4(
219
- validRecords as unknown as CredentialMetadata[],
220
- );
221
- }
143
+ // Migrate all older versions to v5 by stripping OAuth-specific fields
144
+ // and removing ghost refresh_token records
145
+ const filtered = validRecords.filter(
146
+ (r) => (r as Record<string, unknown>).field !== "refresh_token",
147
+ );
148
+ const credentials = filtered.map(migrateRecordToV5);
222
149
  const migrated: MetadataFile = { version: CURRENT_VERSION, credentials };
223
150
  try {
224
151
  saveFile(migrated);
@@ -272,17 +199,10 @@ export function upsertCredentialMetadata(
272
199
  allowedTools?: string[];
273
200
  allowedDomains?: string[];
274
201
  usageDescription?: string;
275
- /** Pass `null` to explicitly clear a previously-set expiry. */
276
- expiresAt?: number | null;
277
- grantedScopes?: string[];
278
- oauth2TokenUrl?: string;
279
- oauth2ClientId?: string;
280
- oauth2TokenEndpointAuthMethod?: string;
281
202
  /** Pass `null` to explicitly clear a previously-set alias. */
282
203
  alias?: string | null;
283
204
  /** Pass `null` to explicitly clear injection templates. */
284
205
  injectionTemplates?: CredentialInjectionTemplate[] | null;
285
- hasRefreshToken?: boolean;
286
206
  },
287
207
  ): CredentialMetadata {
288
208
  const result = loadFile();
@@ -305,22 +225,6 @@ export function upsertCredentialMetadata(
305
225
  existing.allowedDomains = policy.allowedDomains;
306
226
  if (policy?.usageDescription !== undefined)
307
227
  existing.usageDescription = policy.usageDescription;
308
- if (policy?.expiresAt !== undefined) {
309
- if (policy.expiresAt == null) {
310
- delete existing.expiresAt;
311
- } else {
312
- existing.expiresAt = policy.expiresAt;
313
- }
314
- }
315
- if (policy?.grantedScopes !== undefined)
316
- existing.grantedScopes = policy.grantedScopes;
317
- if (policy?.oauth2TokenUrl !== undefined)
318
- existing.oauth2TokenUrl = policy.oauth2TokenUrl;
319
- if (policy?.oauth2ClientId !== undefined)
320
- existing.oauth2ClientId = policy.oauth2ClientId;
321
- if (policy?.oauth2TokenEndpointAuthMethod !== undefined)
322
- existing.oauth2TokenEndpointAuthMethod =
323
- policy.oauth2TokenEndpointAuthMethod;
324
228
  if (policy?.alias !== undefined) {
325
229
  if (policy.alias == null) {
326
230
  delete existing.alias;
@@ -335,8 +239,6 @@ export function upsertCredentialMetadata(
335
239
  existing.injectionTemplates = policy.injectionTemplates;
336
240
  }
337
241
  }
338
- if (policy?.hasRefreshToken !== undefined)
339
- existing.hasRefreshToken = policy.hasRefreshToken;
340
242
  existing.updatedAt = now;
341
243
  saveFile(data);
342
244
  return existing;
@@ -349,14 +251,8 @@ export function upsertCredentialMetadata(
349
251
  allowedTools: policy?.allowedTools ?? [],
350
252
  allowedDomains: policy?.allowedDomains ?? [],
351
253
  usageDescription: policy?.usageDescription,
352
- expiresAt: policy?.expiresAt ?? undefined,
353
- grantedScopes: policy?.grantedScopes,
354
- oauth2TokenUrl: policy?.oauth2TokenUrl,
355
- oauth2ClientId: policy?.oauth2ClientId,
356
- oauth2TokenEndpointAuthMethod: policy?.oauth2TokenEndpointAuthMethod,
357
254
  alias: policy?.alias ?? undefined,
358
255
  injectionTemplates: policy?.injectionTemplates ?? undefined,
359
- hasRefreshToken: policy?.hasRefreshToken,
360
256
  createdAt: now,
361
257
  updatedAt: now,
362
258
  };