@vellumai/assistant 0.12.1-dev.202609141913.5e16607 → 0.12.1-staging.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 (439) hide show
  1. package/AGENTS.md +0 -4
  2. package/ARCHITECTURE.md +12 -11
  3. package/docs/architecture/turn-actor.md +8 -15
  4. package/docs/guardian-request-flow.md +0 -16
  5. package/openapi.yaml +49 -291
  6. package/package.json +1 -1
  7. package/src/__tests__/always-loaded-tools-guard.test.ts +10 -18
  8. package/src/__tests__/app-compiler.test.ts +0 -6
  9. package/src/__tests__/app-control-tool-schemas.test.ts +2 -5
  10. package/src/__tests__/app-dir-path-guard.test.ts +3 -1
  11. package/src/__tests__/app-source-watcher.test.ts +218 -0
  12. package/src/{calls/__tests__ → __tests__}/call-controller.test.ts +82 -503
  13. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +2 -5
  14. package/src/__tests__/computer-use-tools.test.ts +5 -17
  15. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  16. package/src/__tests__/config-schema.test.ts +29 -28
  17. package/src/__tests__/config-watcher-skill-reseed.test.ts +359 -0
  18. package/src/__tests__/config-watcher.test.ts +8 -23
  19. package/src/__tests__/connection-model-compat.test.ts +2 -2
  20. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  21. package/src/__tests__/conversation-pairing.test.ts +0 -26
  22. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +10 -4
  23. package/src/__tests__/conversation-queue.test.ts +207 -48
  24. package/src/__tests__/conversation-retry-discard.test.ts +1 -54
  25. package/src/__tests__/conversation-skill-tools.test.ts +0 -27
  26. package/src/__tests__/conversation-surfaces-app-control.test.ts +3 -8
  27. package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -67
  28. package/src/__tests__/conversation-title-service.test.ts +10 -14
  29. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +5 -3
  30. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -51
  31. package/src/__tests__/credential-record-retire.test.ts +1 -171
  32. package/src/__tests__/credential-routes.test.ts +3 -212
  33. package/src/__tests__/credential-security-invariants.test.ts +2 -4
  34. package/src/__tests__/cu-unified-flow.test.ts +2 -8
  35. package/src/__tests__/document-sync-tags.test.ts +0 -25
  36. package/src/__tests__/drain-kick-guard.test.ts +2 -0
  37. package/src/__tests__/drain-requeue-on-contention.test.ts +6 -0
  38. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  39. package/src/__tests__/host-bash-proxy.test.ts +45 -26
  40. package/src/__tests__/host-browser-proxy.test.ts +17 -6
  41. package/src/__tests__/host-file-proxy-targeted.test.ts +0 -23
  42. package/src/__tests__/host-file-proxy.test.ts +23 -11
  43. package/src/__tests__/host-transfer-proxy-targeted.test.ts +9 -30
  44. package/src/__tests__/llm-catalog-parity.test.ts +0 -25
  45. package/src/__tests__/mcp-auth-routes.test.ts +9 -6
  46. package/src/__tests__/mcp-config-secret-boundary.test.ts +136 -78
  47. package/src/__tests__/mcp-health-check.test.ts +11 -8
  48. package/src/__tests__/mcp-list-plugin-servers.test.ts +10 -8
  49. package/src/{calls/__tests__ → __tests__}/media-stream-audio-transcode.test.ts +1 -1
  50. package/src/{calls/__tests__ → __tests__}/media-stream-output.test.ts +8 -37
  51. package/src/{calls/__tests__ → __tests__}/media-stream-parser.test.ts +1 -1
  52. package/src/{calls/__tests__ → __tests__}/media-stream-server-integration.test.ts +57 -150
  53. package/src/{calls/__tests__ → __tests__}/media-stream-stt-session.test.ts +5 -5
  54. package/src/{calls/__tests__ → __tests__}/media-turn-detector.test.ts +1 -1
  55. package/src/__tests__/messages-after-tiebreaker.test.ts +0 -91
  56. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  57. package/src/__tests__/messaging-send-tool.test.ts +548 -355
  58. package/src/__tests__/model-intents.test.ts +0 -3
  59. package/src/__tests__/notification-vellum-adapter.test.ts +2 -85
  60. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  61. package/src/__tests__/platform-callback-registration.test.ts +16 -8
  62. package/src/__tests__/platform-client-verify-credential.test.ts +0 -2
  63. package/src/__tests__/plugin-import-boundary-guard.test.ts +0 -13
  64. package/src/__tests__/script-proxy-certs.test.ts +0 -35
  65. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  66. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  67. package/src/__tests__/speaker-identification.test.ts +56 -0
  68. package/src/__tests__/subagent-tool-gate-mode.test.ts +3 -19
  69. package/src/__tests__/subagent-tools.test.ts +2 -3
  70. package/src/__tests__/telephony-tts-guard.test.ts +3 -38
  71. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -4
  72. package/src/{calls/__tests__/voice-session-bridge-turn-flow.test.ts → __tests__/voice-session-bridge.test.ts} +12 -20
  73. package/src/__tests__/watch-retro-tool-availability.test.ts +17 -8
  74. package/src/__tests__/workspace-git-service.test.ts +0 -26
  75. package/src/acp/__tests__/agent-process.test.ts +4 -258
  76. package/src/acp/__tests__/auth-required.test.ts +0 -96
  77. package/src/acp/__tests__/client-handler.test.ts +2 -90
  78. package/src/acp/__tests__/session-manager-persistence.test.ts +1 -5
  79. package/src/acp/__tests__/session-manager-resume.test.ts +6 -270
  80. package/src/acp/__tests__/session-manager-usage.test.ts +2 -4
  81. package/src/acp/__tests__/session-manager.test.ts +4 -895
  82. package/src/acp/agent-process.test.ts +1 -4
  83. package/src/acp/agent-process.ts +9 -94
  84. package/src/acp/auth-required.ts +1 -50
  85. package/src/acp/auto-install.test.ts +7 -9
  86. package/src/acp/client-handler.ts +3 -16
  87. package/src/acp/resolve-agent.test.ts +6 -220
  88. package/src/acp/resolve-agent.ts +23 -88
  89. package/src/acp/session-manager.test.ts +3 -196
  90. package/src/acp/session-manager.ts +18 -441
  91. package/src/acp/types.ts +5 -50
  92. package/src/api/events/app-files-changed.ts +3 -4
  93. package/src/api/events/notification-intent.ts +0 -2
  94. package/src/api/index.ts +0 -6
  95. package/src/apps/app-store.ts +1 -1
  96. package/src/bundler/app-compiler.ts +0 -15
  97. package/src/calls/__tests__/voice-control-protocol.test.ts +25 -58
  98. package/src/calls/__tests__/voice-session-bridge.test.ts +1 -37
  99. package/src/calls/__tests__/voice-triage-escalate.test.ts +0 -105
  100. package/src/calls/call-constants.ts +4 -24
  101. package/src/calls/call-controller.ts +199 -499
  102. package/src/calls/call-transport.ts +0 -16
  103. package/src/calls/media-stream-output.ts +0 -32
  104. package/src/calls/media-stream-server.ts +30 -82
  105. package/src/calls/media-stream-stt-session.ts +0 -15
  106. package/src/calls/resolve-call-tts-provider.ts +2 -33
  107. package/src/calls/speaker-identification.ts +233 -0
  108. package/src/calls/tts-call-strategy.ts +5 -9
  109. package/src/calls/types.ts +0 -2
  110. package/src/calls/voice-control-protocol.ts +21 -53
  111. package/src/calls/voice-session-bridge.ts +46 -59
  112. package/src/calls/voice-triage-escalate.ts +56 -175
  113. package/src/cli/commands/apps.help.ts +13 -67
  114. package/src/cli/commands/apps.ts +0 -183
  115. package/src/cli/commands/bash.help.ts +13 -8
  116. package/src/cli/commands/bash.ts +25 -7
  117. package/src/cli/commands/channels/index.help.ts +2 -70
  118. package/src/cli/commands/channels/index.ts +3 -16
  119. package/src/cli/commands/credentials.help.ts +2 -6
  120. package/src/cli/commands/credentials.ts +2 -2
  121. package/src/cli/commands/mcp.help.ts +8 -8
  122. package/src/cli/commands/mcp.ts +1 -1
  123. package/src/cli/commands/roadmap.help.ts +1 -5
  124. package/src/cli/commands/roadmap.ts +1 -1
  125. package/src/cli/lib/bundled-marketplace.json +90 -103
  126. package/src/config/__tests__/default-profile-catalog.test.ts +0 -22
  127. package/src/config/__tests__/memory-retrospective-schema.test.ts +1 -15
  128. package/src/config/__tests__/platform-rehydration.test.ts +9 -99
  129. package/src/config/acp-defaults.test.ts +3 -13
  130. package/src/config/acp-defaults.ts +6 -23
  131. package/src/config/acp-schema.ts +2 -30
  132. package/src/config/bundled-skills/acp/SKILL.md +14 -22
  133. package/src/config/bundled-skills/acp/TOOLS.json +1 -5
  134. package/src/config/bundled-skills/app-builder/SKILL.md +9 -12
  135. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  136. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  137. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  138. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +139 -67
  139. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  140. package/src/config/bundled-skills/schedule/SKILL.md +4 -3
  141. package/src/config/bundled-skills/screen-annotation/SKILL.md +1 -8
  142. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +1 -4
  143. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  144. package/src/config/default-profile-catalog.ts +1 -1
  145. package/src/config/env.ts +2 -2
  146. package/src/config/feature-flag-registry.json +12 -24
  147. package/src/config/loader.ts +0 -1
  148. package/src/config/memory-v3-gate.test.ts +0 -31
  149. package/src/config/memory-v3-gate.ts +16 -20
  150. package/src/config/platform-rehydration.ts +27 -11
  151. package/src/config/schema.ts +2 -2
  152. package/src/config/schemas/__tests__/live-voice.test.ts +158 -8
  153. package/src/config/schemas/calls.ts +28 -1
  154. package/src/config/schemas/live-voice.ts +173 -1
  155. package/src/config/schemas/mcp.ts +1 -1
  156. package/src/config/schemas/memory-retrospective.ts +0 -10
  157. package/src/config/schemas/services.ts +0 -8
  158. package/src/config/schemas/skills.ts +14 -0
  159. package/src/credential-execution/process-manager.test.ts +19 -18
  160. package/src/daemon/__tests__/conversation-tool-setup.test.ts +121 -66
  161. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +1 -4
  162. package/src/daemon/app-source-watcher.ts +218 -0
  163. package/src/daemon/config-watcher.ts +194 -13
  164. package/src/daemon/conversation-process.ts +64 -35
  165. package/src/daemon/conversation-skill-tools.ts +31 -13
  166. package/src/daemon/conversation-store.ts +1 -1
  167. package/src/daemon/conversation-surfaces.ts +8 -10
  168. package/src/daemon/conversation-tool-setup.ts +43 -37
  169. package/src/daemon/conversation.ts +3 -0
  170. package/src/daemon/host-bash-proxy.ts +12 -8
  171. package/src/daemon/host-browser-proxy.ts +1 -15
  172. package/src/daemon/host-file-proxy.ts +10 -8
  173. package/src/daemon/host-transfer-proxy.ts +18 -14
  174. package/src/daemon/lifecycle.ts +5 -0
  175. package/src/daemon/mcp-reload-service.ts +5 -5
  176. package/src/daemon/message-types/sync.ts +0 -1
  177. package/src/daemon/orphan-reaper.ts +1 -1
  178. package/src/daemon/providers-setup.ts +1 -1
  179. package/src/daemon/shutdown-handlers.ts +9 -1
  180. package/src/daemon/skill-memory-refresh.ts +0 -64
  181. package/src/daemon/tool-side-effects.ts +18 -7
  182. package/src/home/__tests__/feed-writer.test.ts +0 -22
  183. package/src/home/feed-writer.ts +2 -11
  184. package/src/hooks/types.ts +0 -33
  185. package/src/inbound/platform-callback-registration.ts +5 -4
  186. package/src/live-voice/__tests__/live-voice-metrics.test.ts +3 -0
  187. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  188. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  189. package/src/{calls → live-voice}/__tests__/progress-narration.test.ts +4 -4
  190. package/src/{calls → live-voice}/__tests__/progress-phrases.test.ts +1 -1
  191. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +69 -13
  192. package/src/live-voice/live-voice-session.ts +693 -213
  193. package/src/{calls → live-voice}/progress-narration.ts +2 -2
  194. package/src/live-voice/protocol.ts +2 -0
  195. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  196. package/src/mcp/effective-config.ts +3 -3
  197. package/src/mcp/mcp-header-store.ts +45 -29
  198. package/src/mcp/startup.ts +4 -8
  199. package/src/messaging/provider.ts +3 -10
  200. package/src/messaging/providers/callback-routing.ts +0 -18
  201. package/src/messaging/providers/channel-transport.ts +0 -56
  202. package/src/messaging/providers/discord/transport.ts +0 -17
  203. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +17 -6
  204. package/src/messaging/providers/slack/adapter.ts +44 -7
  205. package/src/messaging/providers/slack/transport.ts +0 -16
  206. package/src/messaging/providers/telegram-bot/adapter.ts +48 -1
  207. package/src/messaging/providers/telegram-bot/client.ts +26 -0
  208. package/src/messaging/providers/telegram-bot/transport.ts +0 -20
  209. package/src/messaging/providers/whatsapp/adapter.ts +48 -1
  210. package/src/messaging/providers/whatsapp/client.ts +29 -0
  211. package/src/messaging/providers/whatsapp/transport.ts +0 -18
  212. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +0 -8
  213. package/src/notifications/__tests__/assistant-reply-producer.test.ts +0 -55
  214. package/src/notifications/__tests__/guardian-feed-projection.test.ts +2 -158
  215. package/src/notifications/__tests__/home-feed-side-effect.test.ts +3 -153
  216. package/src/notifications/__tests__/schedule-result-producer.test.ts +3 -99
  217. package/src/notifications/adapters/macos.ts +0 -3
  218. package/src/notifications/assistant-reply-producer.ts +3 -8
  219. package/src/notifications/conversation-pairing.ts +16 -36
  220. package/src/notifications/delivered-post-record.ts +1 -11
  221. package/src/notifications/guardian-feed-projection.ts +5 -125
  222. package/src/notifications/home-feed-side-effect.ts +7 -55
  223. package/src/notifications/schedule-result-producer.ts +22 -66
  224. package/src/oauth/__tests__/seed-providers-managed.test.ts +0 -54
  225. package/src/oauth/byo-connection.test.ts +0 -45
  226. package/src/oauth/byo-connection.ts +6 -36
  227. package/src/oauth/connection-resolver.test.ts +0 -73
  228. package/src/oauth/connection-resolver.ts +2 -95
  229. package/src/oauth/seed-providers.ts +0 -165
  230. package/src/outbound-proxy/certs.ts +5 -3
  231. package/src/permissions/checker.test.ts +6 -11
  232. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  233. package/src/persistence/conversation-crud.ts +63 -71
  234. package/src/persistence/conversation-lineage.ts +4 -12
  235. package/src/persistence/conversation-title-service.ts +26 -9
  236. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  237. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  238. package/src/persistence/message-reads.ts +0 -30
  239. package/src/persistence/migrations/157-invite-contact-id.ts +6 -18
  240. package/src/persistence/schema/memory-core.ts +0 -5
  241. package/src/platform/client.test.ts +2 -4
  242. package/src/platform/client.ts +10 -2
  243. package/src/plugin-api/constants.ts +0 -2
  244. package/src/plugin-api/index.ts +0 -1
  245. package/src/plugin-api/types.ts +0 -2
  246. package/src/plugins/defaults/index.ts +0 -2
  247. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +0 -46
  248. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +5 -9
  249. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +10 -20
  250. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +0 -219
  251. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +0 -31
  252. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -63
  253. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +0 -4
  254. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -4
  255. package/src/plugins/defaults/memory/hooks/init.ts +0 -10
  256. package/src/plugins/defaults/memory/hooks/shutdown.ts +0 -2
  257. package/src/plugins/defaults/memory/jobs-worker.ts +0 -13
  258. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +60 -34
  259. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +5 -3
  260. package/src/plugins/defaults/memory/memory-retrospective-job.ts +3 -12
  261. package/src/plugins/defaults/memory/memory-retrospective-state.ts +71 -312
  262. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +1 -2
  263. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +3 -5
  264. package/src/plugins/defaults/memory/startup.ts +1 -6
  265. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +9 -7
  266. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  267. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +0 -89
  268. package/src/plugins/defaults/memory/worker.ts +0 -6
  269. package/src/plugins/mcp-servers.ts +75 -15
  270. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  271. package/src/providers/gemini/client.ts +23 -9
  272. package/src/providers/inference/auth.ts +3 -2
  273. package/src/providers/inference/connection-availability.ts +8 -27
  274. package/src/providers/inference/resolve-auth.ts +4 -22
  275. package/src/providers/model-catalog.ts +0 -34
  276. package/src/providers/model-intents.ts +5 -10
  277. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +18 -146
  278. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +1 -83
  279. package/src/providers/openai/chat-completions-provider.ts +78 -192
  280. package/src/providers/openai/codex-models.ts +4 -0
  281. package/src/runtime/AGENTS.md +0 -2
  282. package/src/runtime/auth/same-actor.ts +3 -38
  283. package/src/runtime/http-server.ts +151 -160
  284. package/src/runtime/routes/__tests__/acp-routes.test.ts +4 -159
  285. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +1 -108
  286. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  287. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +12 -16
  288. package/src/runtime/routes/acp-routes.test.ts +0 -15
  289. package/src/runtime/routes/acp-routes.ts +9 -72
  290. package/src/runtime/routes/app-management-routes.ts +0 -73
  291. package/src/runtime/routes/app-routes.ts +1 -1
  292. package/src/runtime/routes/conversation-list-routes.ts +3 -14
  293. package/src/runtime/routes/conversation-query-routes.ts +0 -40
  294. package/src/runtime/routes/credential-routes.ts +10 -19
  295. package/src/runtime/routes/debug-bash-routes.ts +173 -0
  296. package/src/runtime/routes/diagnostics-routes.ts +3 -55
  297. package/src/runtime/routes/index.ts +2 -4
  298. package/src/runtime/routes/log-export-routes.ts +0 -2
  299. package/src/runtime/routes/mcp-auth-routes.ts +47 -39
  300. package/src/runtime/routes/migration-routes.ts +20 -6
  301. package/src/runtime/routes/platform-routes.ts +15 -18
  302. package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -28
  303. package/src/runtime/services/conversation-serializer.ts +3 -16
  304. package/src/runtime/sync/resource-sync-events.ts +1 -2
  305. package/src/schedule/worker.ts +1 -1
  306. package/src/security/ces-rpc-record-backend.ts +0 -7
  307. package/src/subagent/consult-context.ts +25 -12
  308. package/src/tools/acp/list-agents.test.ts +1 -3
  309. package/src/tools/acp/spawn.test.ts +11 -146
  310. package/src/tools/acp/spawn.ts +3 -21
  311. package/src/tools/acp/status.ts +3 -40
  312. package/src/tools/app-control/skill-proxy-bridge.ts +1 -2
  313. package/src/tools/capability-offer.test.ts +2 -22
  314. package/src/tools/capability-offer.ts +1 -16
  315. package/src/tools/computer-use/definitions.ts +2 -4
  316. package/src/tools/computer-use/skill-proxy-bridge.ts +1 -2
  317. package/src/tools/credentials/metadata-store.ts +0 -100
  318. package/src/tools/host-filesystem/edit.test.ts +1 -2
  319. package/src/tools/host-filesystem/edit.ts +2 -2
  320. package/src/tools/host-filesystem/read.test.ts +1 -2
  321. package/src/tools/host-filesystem/read.ts +2 -2
  322. package/src/tools/host-filesystem/transfer.test.ts +1 -2
  323. package/src/tools/host-filesystem/transfer.ts +2 -2
  324. package/src/tools/host-filesystem/write.test.ts +1 -2
  325. package/src/tools/host-filesystem/write.ts +2 -2
  326. package/src/tools/host-terminal/host-shell.test.ts +0 -16
  327. package/src/tools/host-terminal/host-shell.ts +20 -4
  328. package/src/tools/policy-context.ts +7 -6
  329. package/src/tools/subagent/spawn.ts +8 -5
  330. package/src/tools/watch/watch-retro-report.ts +8 -0
  331. package/src/util/__tests__/language-subtag.test.ts +1 -23
  332. package/src/util/fs-watcher-error.ts +36 -0
  333. package/src/util/language-subtag.ts +0 -16
  334. package/src/util/platform.ts +0 -8
  335. package/src/workspace/git-service.ts +54 -5
  336. package/src/workspace/migrations/registry.ts +0 -6
  337. package/scripts/smoke-desktop-window-theme.ts +0 -186
  338. package/src/__tests__/git-porcelain.test.ts +0 -47
  339. package/src/__tests__/helpers/set-workspace-mcp.ts +0 -11
  340. package/src/__tests__/message-deleted-hook-dispatch.test.ts +0 -119
  341. package/src/__tests__/resolve-app.test.ts +0 -135
  342. package/src/__tests__/service-account-token.test.ts +0 -247
  343. package/src/__tests__/source-fingerprint.test.ts +0 -184
  344. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +0 -336
  345. package/src/acp/__tests__/helpers/acp-model-option.ts +0 -51
  346. package/src/acp/model-config.test.ts +0 -166
  347. package/src/acp/model-config.ts +0 -138
  348. package/src/api/events/acp-session-model-update.test.ts +0 -88
  349. package/src/api/events/acp-session-model-update.ts +0 -46
  350. package/src/apps/resolve-app.ts +0 -81
  351. package/src/apps/source-fingerprint.ts +0 -266
  352. package/src/calls/__tests__/barge-in-guard.test.ts +0 -81
  353. package/src/calls/__tests__/voice-leg-coordinator.test.ts +0 -229
  354. package/src/calls/__tests__/voice-progress-cadence.test.ts +0 -274
  355. package/src/calls/barge-in-guard.ts +0 -118
  356. package/src/calls/voice-leg-coordinator.ts +0 -189
  357. package/src/calls/voice-progress-cadence.ts +0 -423
  358. package/src/cli/commands/__tests__/apps.test.ts +0 -245
  359. package/src/cli/commands/__tests__/bash.test.ts +0 -53
  360. package/src/cli/commands/__tests__/roadmap.help.test.ts +0 -19
  361. package/src/cli/commands/channels/__tests__/send.test.ts +0 -218
  362. package/src/cli/commands/channels/send.ts +0 -128
  363. package/src/config/__tests__/platform-identity.test.ts +0 -255
  364. package/src/config/acp-agent-write.ts +0 -43
  365. package/src/config/acp-schema.test.ts +0 -81
  366. package/src/config/platform-identity.ts +0 -201
  367. package/src/config/schemas/__tests__/voice.test.ts +0 -148
  368. package/src/config/schemas/voice.ts +0 -150
  369. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +0 -133
  370. package/src/daemon/app-change-notify.ts +0 -45
  371. package/src/desktop/__tests__/fake-desktop.ts +0 -119
  372. package/src/desktop/desktop-chrome-policy.test.ts +0 -71
  373. package/src/desktop/desktop-chrome-policy.ts +0 -42
  374. package/src/desktop/desktop-dependencies.test.ts +0 -102
  375. package/src/desktop/desktop-dependencies.ts +0 -306
  376. package/src/desktop/desktop-feature.test.ts +0 -18
  377. package/src/desktop/desktop-feature.ts +0 -19
  378. package/src/desktop/desktop-panel-config.test.ts +0 -135
  379. package/src/desktop/desktop-panel-config.ts +0 -149
  380. package/src/desktop/desktop-session-manager.test.ts +0 -627
  381. package/src/desktop/desktop-session-manager.ts +0 -814
  382. package/src/desktop/desktop-stream-bridge.test.ts +0 -395
  383. package/src/desktop/desktop-stream-bridge.ts +0 -214
  384. package/src/desktop/desktop-wallpaper.test.ts +0 -191
  385. package/src/desktop/desktop-wallpaper.ts +0 -113
  386. package/src/desktop/desktop-window-theme.ts +0 -170
  387. package/src/i18n/AGENTS.md +0 -22
  388. package/src/i18n/__tests__/i18n.test.ts +0 -108
  389. package/src/i18n/index.ts +0 -16
  390. package/src/i18n/locales.ts +0 -93
  391. package/src/i18n/messages.ts +0 -61
  392. package/src/i18n/title.ts +0 -25
  393. package/src/mcp/__tests__/workspace-mcp-config.test.ts +0 -135
  394. package/src/mcp/spec-schema.ts +0 -111
  395. package/src/mcp/workspace-mcp-config.ts +0 -188
  396. package/src/messaging/providers/__tests__/proactive-address.test.ts +0 -95
  397. package/src/persistence/conversation-title-placeholders.ts +0 -23
  398. package/src/persistence/message-cursor.ts +0 -78
  399. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +0 -149
  400. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +0 -118
  401. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +0 -83
  402. package/src/plugins/defaults/memory/hooks/message-deleted.ts +0 -21
  403. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +0 -126
  404. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +0 -79
  405. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +0 -28
  406. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +0 -9
  407. package/src/providers/gemini-thought-signature.test.ts +0 -63
  408. package/src/providers/gemini-thought-signature.ts +0 -44
  409. package/src/providers/inference/service-account-token.ts +0 -248
  410. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +0 -613
  411. package/src/providers/openai/__tests__/google-thought-signature.test.ts +0 -235
  412. package/src/providers/openai/google-thought-signature.ts +0 -320
  413. package/src/runtime/__tests__/channel-send.test.ts +0 -443
  414. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +0 -89
  415. package/src/runtime/__tests__/live-voice-upgrade.test.ts +0 -58
  416. package/src/runtime/__tests__/runtime-ws-test-utils.ts +0 -72
  417. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +0 -59
  418. package/src/runtime/channel-send.ts +0 -316
  419. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +0 -205
  420. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +0 -127
  421. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +0 -107
  422. package/src/runtime/routes/channel-send-routes.ts +0 -85
  423. package/src/runtime/routes/desktop-setup-routes.test.ts +0 -74
  424. package/src/runtime/routes/desktop-setup-routes.ts +0 -35
  425. package/src/security/__tests__/ces-http-record-backend.test.ts +0 -137
  426. package/src/security/ces-http-record-backend.ts +0 -120
  427. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +0 -60
  428. package/src/skills/workspace-skill-md-signature.ts +0 -50
  429. package/src/tools/acp/status.test.ts +0 -94
  430. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +0 -57
  431. package/src/tools/terminal/sanitized-bash.ts +0 -105
  432. package/src/util/ca-bundle.ts +0 -13
  433. package/src/workspace/git-porcelain.ts +0 -66
  434. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +0 -258
  435. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +0 -78
  436. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +0 -214
  437. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +0 -102
  438. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +0 -199
  439. /package/src/{calls → live-voice}/progress-phrases.ts +0 -0
@@ -1,126 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Memory retrospective: cursor timestamp backfill.
3
- // ---------------------------------------------------------------------------
4
- //
5
- // State rows written before `last_processed_created_at` existed carry an
6
- // id-only cursor, which stops bounding reads the moment its row is deleted
7
- // (see `memory-retrospective-state.ts`). This pass copies `createdAt` from
8
- // each such row's message while the message still exists. Rows whose message
9
- // is already gone stay NULL: nothing remains to recover, and they are counted
10
- // so the stall is visible in the log. Runs detached at daemon init (ahead of
11
- // any client regenerating a reply) and again at memory-worker startup;
12
- // idempotent, keyset-paginated by conversation id, and yielding between pages.
13
-
14
- import { and, asc, eq, gt, isNull, ne } from "drizzle-orm";
15
-
16
- import { messageCreatedAtByIds } from "../../../persistence/message-reads.js";
17
- import { memoryRetrospectiveState } from "../../../persistence/schema/index.js";
18
- import { withSqliteRetry } from "./host-utils.js";
19
- import { getLogger } from "./logging.js";
20
- import { memoryDbOrNull } from "./memory-db.js";
21
- import { ensureRetrospectiveCursorColumn } from "./memory-retrospective-state.js";
22
-
23
- const log = getLogger("memory-retrospective-cursor-backfill");
24
-
25
- const DEFAULT_PAGE_SIZE = 500;
26
-
27
- export interface CursorBackfillResult {
28
- /** Rows with a cursor id but no timestamp. */
29
- scanned: number;
30
- /** Rows whose message still exists and received its `createdAt`. */
31
- backfilled: number;
32
- /** Rows whose cursor message no longer exists; left NULL. */
33
- unrecoverable: number;
34
- }
35
-
36
- /**
37
- * Fill `last_processed_created_at` on every state row that has a cursor id
38
- * but no timestamp. Best-effort: a missing memory connection or column is a
39
- * no-op, and a row the job rewrites mid-pass is skipped by the update's
40
- * `IS NULL` guard rather than stomped. Pages by conversation id so a large
41
- * backlog never materializes at once or holds the event loop.
42
- */
43
- export async function backfillRetrospectiveCursorTimestamps(opts?: {
44
- pageSize?: number;
45
- }): Promise<CursorBackfillResult> {
46
- const pageSize = opts?.pageSize ?? DEFAULT_PAGE_SIZE;
47
- const result: CursorBackfillResult = {
48
- scanned: 0,
49
- backfilled: 0,
50
- unrecoverable: 0,
51
- };
52
- const context = "backfillRetrospectiveCursorTimestamps";
53
- const mdb = memoryDbOrNull(context);
54
- if (!mdb || !ensureRetrospectiveCursorColumn(context)) {
55
- return result;
56
- }
57
-
58
- let afterConversationId = "";
59
- for (;;) {
60
- const page = mdb
61
- .select({
62
- conversationId: memoryRetrospectiveState.conversationId,
63
- messageId: memoryRetrospectiveState.lastProcessedMessageId,
64
- })
65
- .from(memoryRetrospectiveState)
66
- .where(
67
- and(
68
- isNull(memoryRetrospectiveState.lastProcessedCreatedAt),
69
- ne(memoryRetrospectiveState.lastProcessedMessageId, ""),
70
- gt(memoryRetrospectiveState.conversationId, afterConversationId),
71
- ),
72
- )
73
- .orderBy(asc(memoryRetrospectiveState.conversationId))
74
- .limit(pageSize)
75
- .all();
76
- if (page.length === 0) {
77
- break;
78
- }
79
- afterConversationId = page[page.length - 1]!.conversationId;
80
- result.scanned += page.length;
81
-
82
- const createdAtById = messageCreatedAtByIds(
83
- page.map((row) => row.messageId),
84
- );
85
- const recoverable = page.flatMap((row) => {
86
- const createdAt = createdAtById.get(row.messageId);
87
- return createdAt === undefined ? [] : [{ ...row, createdAt }];
88
- });
89
- result.unrecoverable += page.length - recoverable.length;
90
-
91
- if (recoverable.length > 0) {
92
- await withSqliteRetry(
93
- () => {
94
- mdb.transaction((tx) => {
95
- for (const row of recoverable) {
96
- tx.update(memoryRetrospectiveState)
97
- .set({ lastProcessedCreatedAt: row.createdAt })
98
- .where(
99
- and(
100
- eq(
101
- memoryRetrospectiveState.conversationId,
102
- row.conversationId,
103
- ),
104
- eq(
105
- memoryRetrospectiveState.lastProcessedMessageId,
106
- row.messageId,
107
- ),
108
- isNull(memoryRetrospectiveState.lastProcessedCreatedAt),
109
- ),
110
- )
111
- .run();
112
- }
113
- });
114
- },
115
- { op: context, context: { afterConversationId } },
116
- );
117
- result.backfilled += recoverable.length;
118
- }
119
- await Bun.sleep(0);
120
- }
121
-
122
- if (result.backfilled > 0 || result.unrecoverable > 0) {
123
- log.info(result, "Retrospective cursor timestamps backfilled");
124
- }
125
- return result;
126
- }
@@ -1,79 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Memory retrospective: keep the cursor's timestamp when its row is deleted.
3
- // ---------------------------------------------------------------------------
4
- //
5
- // The cursor normally sits on the latest assistant reply, and the retry route
6
- // deletes exactly that row when the user regenerates it. A cursor written
7
- // before the timestamp column existed carries only the id, so once the row is
8
- // gone nothing can place it. The `message-deleted` hook delivers each deleted
9
- // row's `(createdAt, id)` here: when the conversation's cursor is among the
10
- // rows and has no timestamp yet, the row's `createdAt` is recorded. The
11
- // timestamps travel in the call, so the write does not need the rows to
12
- // still exist.
13
-
14
- import { and, eq, isNull } from "drizzle-orm";
15
-
16
- import { memoryRetrospectiveState } from "../../../persistence/schema/index.js";
17
- import { withSqliteRetry } from "./host-utils.js";
18
- import { getLogger } from "./logging.js";
19
- import { memoryDbOrNull } from "./memory-db.js";
20
- import { ensureRetrospectiveCursorColumn } from "./memory-retrospective-state.js";
21
-
22
- const log = getLogger("memory-retrospective-cursor-preserve");
23
-
24
- /**
25
- * Record `createdAt` on the conversation's retrospective cursor when it points
26
- * at one of `rows` and has no timestamp yet. Best-effort: an unavailable
27
- * memory connection or column is a no-op, and a cursor the job has moved
28
- * since the caller read it is skipped by the `IS NULL` guard.
29
- */
30
- export async function preserveRetrospectiveCursorTimestamps(
31
- conversationId: string,
32
- rows: ReadonlyArray<{ id: string; createdAt: number }>,
33
- ): Promise<void> {
34
- if (rows.length === 0) {
35
- return;
36
- }
37
- const context = "preserveRetrospectiveCursorTimestamps";
38
- try {
39
- const mdb = memoryDbOrNull(context);
40
- if (!mdb || !ensureRetrospectiveCursorColumn(context)) {
41
- return;
42
- }
43
- const state = mdb
44
- .select({
45
- messageId: memoryRetrospectiveState.lastProcessedMessageId,
46
- createdAt: memoryRetrospectiveState.lastProcessedCreatedAt,
47
- })
48
- .from(memoryRetrospectiveState)
49
- .where(eq(memoryRetrospectiveState.conversationId, conversationId))
50
- .get();
51
- if (!state || state.createdAt !== null) {
52
- return;
53
- }
54
- const row = rows.find((candidate) => candidate.id === state.messageId);
55
- if (!row) {
56
- return;
57
- }
58
- await withSqliteRetry(
59
- () =>
60
- mdb
61
- .update(memoryRetrospectiveState)
62
- .set({ lastProcessedCreatedAt: row.createdAt })
63
- .where(
64
- and(
65
- eq(memoryRetrospectiveState.conversationId, conversationId),
66
- eq(memoryRetrospectiveState.lastProcessedMessageId, row.id),
67
- isNull(memoryRetrospectiveState.lastProcessedCreatedAt),
68
- ),
69
- )
70
- .run(),
71
- { op: context, context: { conversationId } },
72
- );
73
- } catch (err) {
74
- log.warn(
75
- { err, conversationId },
76
- "could not preserve the retrospective cursor timestamp before its row was deleted",
77
- );
78
- }
79
- }
@@ -1,28 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Memory retrospective: the cursor a state row hands to the messages-after
3
- // reads.
4
- // ---------------------------------------------------------------------------
5
-
6
- import type { MessageCursor } from "../../../persistence/message-cursor.js";
7
-
8
- /**
9
- * The `(createdAt, id)` cursor a retrospective state row carries, or null
10
- * when nothing has been processed yet (no row, or the `""` sentinel left by
11
- * failure-only attempts). The timestamp lets the reads keep their bound after
12
- * the cursor's row is deleted, which is what a regenerated reply does to the
13
- * latest assistant message the cursor usually sits on.
14
- */
15
- export function retrospectiveCursor(
16
- state: {
17
- lastProcessedMessageId: string;
18
- lastProcessedCreatedAt?: number | null;
19
- } | null,
20
- ): MessageCursor | null {
21
- if (!state || state.lastProcessedMessageId === "") {
22
- return null;
23
- }
24
- return {
25
- id: state.lastProcessedMessageId,
26
- createdAt: state.lastProcessedCreatedAt ?? null,
27
- };
28
- }
@@ -1,9 +0,0 @@
1
- /**
2
- * Plugin-local re-export of the workspace SKILL.md mtime poll so hook files
3
- * under `hooks/` do not grow a new escaping specifier. The daemon module is
4
- * already a memory-plugin baseline import from `startup.ts` / `worker.ts`.
5
- */
6
- export {
7
- startWorkspaceSkillMdMtimePoll,
8
- stopWorkspaceSkillMdMtimePoll,
9
- } from "../../../daemon/skill-memory-refresh.js";
@@ -1,63 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import {
4
- GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE,
5
- isGemini3Model,
6
- unsignedThoughtSignatureFallback,
7
- } from "./gemini-thought-signature.js";
8
-
9
- describe("isGemini3Model", () => {
10
- test("matches native Gemini 3 ids", () => {
11
- expect(isGemini3Model("gemini-3.7-flash")).toBe(true);
12
- expect(isGemini3Model("models/gemini-3.1-pro-preview")).toBe(true);
13
- });
14
-
15
- test("matches OpenAI-compatible catalog prefixes", () => {
16
- expect(isGemini3Model("google/gemini-3.7-flash")).toBe(true);
17
- });
18
-
19
- test("does not match Gemini 2.5 or unrelated ids", () => {
20
- expect(isGemini3Model("gemini-2.5-flash")).toBe(false);
21
- expect(isGemini3Model("gpt-5.2")).toBe(false);
22
- });
23
- });
24
-
25
- describe("unsignedThoughtSignatureFallback", () => {
26
- test("stamps the dummy on the first unsigned Gemini 3 call", () => {
27
- expect(
28
- unsignedThoughtSignatureFallback([undefined, undefined], {
29
- model: "gemini-3.7-flash",
30
- }),
31
- ).toEqual({
32
- index: 0,
33
- signature: GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE,
34
- });
35
- });
36
-
37
- test("does not stamp when any call already has a signature", () => {
38
- expect(
39
- unsignedThoughtSignatureFallback(["signed-thought-1", undefined], {
40
- model: "gemini-3.7-flash",
41
- }),
42
- ).toBeUndefined();
43
- });
44
-
45
- test("does not stamp for non-Gemini-3 models when a model id is provided", () => {
46
- expect(
47
- unsignedThoughtSignatureFallback([undefined], { model: "gpt-5.2" }),
48
- ).toBeUndefined();
49
- });
50
-
51
- test("stamps unsigned calls when no model gate is supplied", () => {
52
- expect(unsignedThoughtSignatureFallback([undefined])).toEqual({
53
- index: 0,
54
- signature: GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE,
55
- });
56
- });
57
-
58
- test("does not stamp an empty function-call list", () => {
59
- expect(
60
- unsignedThoughtSignatureFallback([], { model: "gemini-3.7-flash" }),
61
- ).toBeUndefined();
62
- });
63
- });
@@ -1,44 +0,0 @@
1
- /**
2
- * Gemini 3.x requires a thought signature on the first function-call part of
3
- * each step. When history has no captured signature (unsigned tool_use from an
4
- * earlier provider, or a client-injected call), Google documents these dummy
5
- * values to skip validation.
6
- */
7
- export const GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE =
8
- "context_engineering_is_the_way_to_go";
9
-
10
- /**
11
- * True for Gemini 3.x model ids, including the native `models/` prefix and
12
- * OpenAI-compatible catalog prefixes such as `google/gemini-3.7-flash`.
13
- */
14
- export function isGemini3Model(model: string): boolean {
15
- if (model.startsWith("gemini-3") || model.startsWith("models/gemini-3")) {
16
- return true;
17
- }
18
- return /\/gemini-3/.test(model);
19
- }
20
-
21
- /**
22
- * Decide whether unsigned function-call history should receive Google's
23
- * documented dummy thought signature. Returns the first call's index when
24
- * every call is unsigned. Pass `model` to restrict this to Gemini 3.x;
25
- * omit it for retry backfills that already observed a thought-signature 4xx.
26
- */
27
- export function unsignedThoughtSignatureFallback(
28
- signatures: ReadonlyArray<string | undefined | null>,
29
- options?: { model?: string },
30
- ): { index: 0; signature: string } | undefined {
31
- if (options?.model !== undefined && !isGemini3Model(options.model)) {
32
- return undefined;
33
- }
34
- if (signatures.length === 0) {
35
- return undefined;
36
- }
37
- if (signatures.some((signature) => Boolean(signature))) {
38
- return undefined;
39
- }
40
- return {
41
- index: 0,
42
- signature: GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE,
43
- };
44
- }
@@ -1,248 +0,0 @@
1
- /**
2
- * Token acquisition for Google service-account credentials (Vertex AI).
3
- *
4
- * Reads the service-account JSON from the vault, signs a short-lived JWT
5
- * with RS256, and exchanges it for a bearer access token at the token_uri
6
- * embedded in the key file. Caches the resulting token and its expiry as a
7
- * single JSON blob in the vault so the write is treated as one unit.
8
- *
9
- * A per-credential mutex prevents concurrent callers for the same credential
10
- * from racing to refresh. Callers for different credentials are independent.
11
- */
12
-
13
- import { createSign } from "node:crypto";
14
-
15
- import {
16
- getSecureKeyAsync as _realGetKey,
17
- setSecureKeyAsync as _realSetKey,
18
- } from "../../security/secure-keys.js";
19
- import { getLogger } from "../../util/logger.js";
20
-
21
- // Mutable references — swapped in tests via _injectVaultAccessors so tests
22
- // never need mock.module (which contaminates the entire bun worker process).
23
- let _getKey: typeof _realGetKey = _realGetKey;
24
- let _setKey: typeof _realSetKey = _realSetKey;
25
-
26
- const log = getLogger("service-account-token");
27
-
28
- /** Refresh 5 minutes before expiry to avoid using a nearly-expired token. */
29
- const REFRESH_MARGIN_SECONDS = 300;
30
-
31
- /** Google Cloud Platform scope required for Vertex AI inference. */
32
- const GCP_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
33
-
34
- export interface ServiceAccountKey {
35
- client_email: string;
36
- private_key: string;
37
- token_uri: string;
38
- }
39
-
40
- export type ServiceAccountTokenResult =
41
- | { ok: true; token: string }
42
- | { ok: false; reason: "not_found" | "invalid_config" | "exchange_failed" };
43
-
44
- /**
45
- * Parse and validate a service-account JSON string.
46
- * Returns the key object when valid, or null when the JSON is missing, invalid,
47
- * or lacks required fields (client_email, private_key, token_uri).
48
- */
49
- export function parseServiceAccountKey(json: string): ServiceAccountKey | null {
50
- let parsed: unknown;
51
- try {
52
- parsed = JSON.parse(json);
53
- } catch {
54
- return null;
55
- }
56
- if (!parsed || typeof parsed !== "object") {
57
- return null;
58
- }
59
- const key = parsed as Record<string, unknown>;
60
- if (
61
- typeof key["client_email"] !== "string" ||
62
- !key["client_email"] ||
63
- typeof key["private_key"] !== "string" ||
64
- !key["private_key"] ||
65
- typeof key["token_uri"] !== "string" ||
66
- !key["token_uri"]
67
- ) {
68
- return null;
69
- }
70
- // All required fields validated as non-empty strings above.
71
- return parsed as ServiceAccountKey;
72
- }
73
-
74
- /** Per-credential in-flight mutex. Prevents concurrent exchange races per account. */
75
- const exchangesInFlight = new Map<string, Promise<ServiceAccountTokenResult>>();
76
-
77
- /**
78
- * Return a valid bearer access token for a Google service account, fetching
79
- * and caching one if the stored token is absent or about to expire.
80
- *
81
- * @param credential - Vault key under which the service-account JSON is stored
82
- * (e.g. `"credential/my-vertex-ai"`). The derived token is cached as a JSON
83
- * blob at `<credential>/token_cache`.
84
- */
85
- export async function getValidServiceAccountToken(
86
- credential: string,
87
- ): Promise<ServiceAccountTokenResult> {
88
- const cacheKey = `${credential}/token_cache`;
89
- const cached = await _getKey(cacheKey);
90
- if (cached) {
91
- try {
92
- const blob = JSON.parse(cached) as {
93
- access_token?: string;
94
- expires_at?: number;
95
- };
96
- if (blob.access_token) {
97
- if (!blob.expires_at) {
98
- return { ok: true, token: blob.access_token };
99
- }
100
- const now = Date.now() / 1000;
101
- if (now < blob.expires_at - REFRESH_MARGIN_SECONDS) {
102
- return { ok: true, token: blob.access_token };
103
- }
104
- }
105
- } catch {
106
- // Corrupted cache entry — fall through to re-exchange.
107
- }
108
- }
109
-
110
- const inFlight = exchangesInFlight.get(credential);
111
- if (inFlight) {
112
- return await inFlight;
113
- }
114
-
115
- const promise = doExchange(credential, cacheKey);
116
- exchangesInFlight.set(credential, promise);
117
- try {
118
- return await promise;
119
- } finally {
120
- exchangesInFlight.delete(credential);
121
- }
122
- }
123
-
124
- async function doExchange(
125
- credential: string,
126
- cacheKey: string,
127
- ): Promise<ServiceAccountTokenResult> {
128
- const keyJson = await _getKey(credential);
129
- if (!keyJson) {
130
- return { ok: false, reason: "not_found" };
131
- }
132
-
133
- const key = parseServiceAccountKey(keyJson);
134
- if (!key) {
135
- log.error(
136
- { credential },
137
- "Service account credential is not valid JSON or missing required fields",
138
- );
139
- return { ok: false, reason: "invalid_config" };
140
- }
141
-
142
- const jwt = buildJwt(key.client_email, key.private_key, key.token_uri);
143
-
144
- let accessToken: string;
145
- let expiresIn: number;
146
- try {
147
- const resp = await fetch(key.token_uri, {
148
- method: "POST",
149
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
150
- body: new URLSearchParams({
151
- grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
152
- assertion: jwt,
153
- }).toString(),
154
- });
155
-
156
- if (!resp.ok) {
157
- const body = await resp.text();
158
- log.error(
159
- { status: resp.status, body, credential },
160
- "Service account token exchange failed",
161
- );
162
- return { ok: false, reason: "exchange_failed" };
163
- }
164
-
165
- const data = (await resp.json()) as {
166
- access_token?: string;
167
- expires_in?: number;
168
- };
169
-
170
- if (!data.access_token) {
171
- log.error({ credential }, "Token exchange response missing access_token");
172
- return { ok: false, reason: "exchange_failed" };
173
- }
174
-
175
- accessToken = data.access_token;
176
- expiresIn = data.expires_in ?? 3600;
177
- } catch (err) {
178
- log.error({ err, credential }, "Service account token exchange threw");
179
- return { ok: false, reason: "exchange_failed" };
180
- }
181
-
182
- const newExpiresAt = Math.floor(Date.now() / 1000 + expiresIn);
183
- const ok = await _setKey(
184
- cacheKey,
185
- JSON.stringify({ access_token: accessToken, expires_at: newExpiresAt }),
186
- );
187
- if (!ok) {
188
- log.warn(
189
- { credential },
190
- "Failed to cache service account token — token still valid for this request",
191
- );
192
- } else {
193
- log.info({ credential }, "Service account token exchanged and cached");
194
- }
195
- return { ok: true, token: accessToken };
196
- }
197
-
198
- function buildJwt(
199
- clientEmail: string,
200
- privateKey: string,
201
- tokenUri: string,
202
- ): string {
203
- const now = Math.floor(Date.now() / 1000);
204
- const header = Buffer.from(
205
- JSON.stringify({ alg: "RS256", typ: "JWT" }),
206
- ).toString("base64url");
207
- const payload = Buffer.from(
208
- JSON.stringify({
209
- iss: clientEmail,
210
- scope: GCP_SCOPE,
211
- aud: tokenUri,
212
- exp: now + 3600,
213
- iat: now,
214
- }),
215
- ).toString("base64url");
216
-
217
- const signingInput = `${header}.${payload}`;
218
- const sign = createSign("SHA256");
219
- sign.update(signingInput);
220
- sign.end();
221
- const signature = sign.sign(privateKey, "base64url");
222
-
223
- return `${signingInput}.${signature}`;
224
- }
225
-
226
- // ---------------------------------------------------------------------------
227
- // Test helpers
228
- // ---------------------------------------------------------------------------
229
-
230
- /** @internal Test-only: reset all in-flight exchange mutexes. */
231
- export function _resetServiceAccountMutex(): void {
232
- exchangesInFlight.clear();
233
- }
234
-
235
- /** @internal Test-only: swap vault accessors so tests never need mock.module. */
236
- export function _injectVaultAccessors(
237
- get: typeof _realGetKey,
238
- set: typeof _realSetKey,
239
- ): void {
240
- _getKey = get;
241
- _setKey = set;
242
- }
243
-
244
- /** @internal Test-only: restore real vault accessors. */
245
- export function _resetVaultAccessors(): void {
246
- _getKey = _realGetKey;
247
- _setKey = _realSetKey;
248
- }