@vellumai/assistant 0.12.1 → 0.12.2-staging.1

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 (548) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +20 -21
  3. package/docs/architecture/turn-actor.md +15 -8
  4. package/docs/guardian-request-flow.md +16 -0
  5. package/openapi.yaml +327 -52
  6. package/package.json +3 -1
  7. package/scripts/smoke-desktop-window-theme.ts +186 -0
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  9. package/src/__tests__/anthropic-provider.test.ts +0 -63
  10. package/src/__tests__/app-compiler.test.ts +6 -0
  11. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  13. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  14. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  15. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  16. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  17. package/src/__tests__/computer-use-tools.test.ts +19 -7
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +28 -29
  20. package/src/__tests__/config-watcher.test.ts +23 -8
  21. package/src/__tests__/connection-model-compat.test.ts +2 -2
  22. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  23. package/src/__tests__/conversation-pairing.test.ts +26 -0
  24. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  25. package/src/__tests__/conversation-queue.test.ts +48 -207
  26. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  27. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  28. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  29. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  30. package/src/__tests__/conversation-title-service.test.ts +14 -10
  31. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  32. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  33. package/src/__tests__/credential-record-retire.test.ts +171 -1
  34. package/src/__tests__/credential-routes.test.ts +212 -3
  35. package/src/__tests__/credential-security-invariants.test.ts +4 -2
  36. package/src/__tests__/cu-unified-flow.test.ts +8 -2
  37. package/src/__tests__/document-sync-tags.test.ts +25 -0
  38. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  39. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  40. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  41. package/src/__tests__/git-porcelain.test.ts +47 -0
  42. package/src/__tests__/heartbeat-service.test.ts +56 -40
  43. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  44. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  45. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  46. package/src/__tests__/host-cu-proxy.test.ts +539 -0
  47. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  48. package/src/__tests__/host-file-proxy.test.ts +11 -23
  49. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  50. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  51. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  52. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  53. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  54. package/src/__tests__/mcp-health-check.test.ts +8 -11
  55. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  56. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  57. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  58. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  59. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  60. package/src/__tests__/model-intents.test.ts +3 -0
  61. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  62. package/src/__tests__/oauth-commands-routes.test.ts +113 -0
  63. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  64. package/src/__tests__/openai-provider.test.ts +1 -0
  65. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  66. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  67. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  68. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  69. package/src/__tests__/qdrant-manager.test.ts +132 -0
  70. package/src/__tests__/resolve-app.test.ts +135 -0
  71. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  72. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  73. package/src/__tests__/service-account-token.test.ts +247 -0
  74. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  75. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  76. package/src/__tests__/skills.test.ts +9 -4
  77. package/src/__tests__/source-fingerprint.test.ts +184 -0
  78. package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
  79. package/src/__tests__/subagent-tools.test.ts +3 -2
  80. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  81. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  82. package/src/__tests__/unicode.test.ts +13 -0
  83. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  84. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  85. package/src/__tests__/workspace-git-service.test.ts +26 -0
  86. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  87. package/src/acp/__tests__/agent-process.test.ts +258 -4
  88. package/src/acp/__tests__/auth-required.test.ts +96 -0
  89. package/src/acp/__tests__/client-handler.test.ts +90 -2
  90. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  91. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  92. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  93. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  94. package/src/acp/__tests__/session-manager.test.ts +895 -4
  95. package/src/acp/agent-process.test.ts +4 -1
  96. package/src/acp/agent-process.ts +96 -10
  97. package/src/acp/auth-required.ts +50 -1
  98. package/src/acp/auto-install.test.ts +9 -7
  99. package/src/acp/client-handler.ts +16 -3
  100. package/src/acp/model-config.test.ts +166 -0
  101. package/src/acp/model-config.ts +138 -0
  102. package/src/acp/resolve-agent.test.ts +220 -6
  103. package/src/acp/resolve-agent.ts +88 -23
  104. package/src/acp/session-manager.test.ts +196 -3
  105. package/src/acp/session-manager.ts +441 -18
  106. package/src/acp/types.ts +50 -5
  107. package/src/agent/loop.ts +1 -0
  108. package/src/api/events/acp-session-model-update.test.ts +88 -0
  109. package/src/api/events/acp-session-model-update.ts +46 -0
  110. package/src/api/events/app-files-changed.ts +4 -3
  111. package/src/api/events/notification-intent.ts +2 -0
  112. package/src/api/events/user-message-echo.ts +7 -1
  113. package/src/api/index.ts +6 -0
  114. package/src/api/responses/conversation-message.ts +5 -0
  115. package/src/apps/app-store.ts +1 -1
  116. package/src/apps/resolve-app.ts +81 -0
  117. package/src/apps/source-fingerprint.ts +266 -0
  118. package/src/avatar/identity-avatar.ts +7 -2
  119. package/src/bundler/app-compiler.ts +15 -0
  120. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  121. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  122. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  123. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  124. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  125. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  126. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  127. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  128. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  129. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  130. package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
  131. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  132. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  133. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  134. package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
  135. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  136. package/src/calls/barge-in-guard.ts +118 -0
  137. package/src/calls/call-constants.ts +24 -4
  138. package/src/calls/call-controller.ts +499 -199
  139. package/src/calls/call-transport.ts +16 -0
  140. package/src/calls/media-stream-output.ts +32 -0
  141. package/src/calls/media-stream-server.ts +82 -30
  142. package/src/calls/media-stream-stt-session.ts +15 -0
  143. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  144. package/src/calls/resolve-call-tts-provider.ts +33 -2
  145. package/src/calls/tts-call-strategy.ts +9 -5
  146. package/src/calls/types.ts +2 -0
  147. package/src/calls/voice-control-protocol.ts +53 -21
  148. package/src/calls/voice-leg-coordinator.ts +189 -0
  149. package/src/calls/voice-progress-cadence.ts +423 -0
  150. package/src/calls/voice-session-bridge.ts +59 -46
  151. package/src/calls/voice-triage-escalate.ts +175 -56
  152. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  153. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  154. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  155. package/src/cli/commands/apps.help.ts +67 -13
  156. package/src/cli/commands/apps.ts +183 -0
  157. package/src/cli/commands/bash.help.ts +8 -13
  158. package/src/cli/commands/bash.ts +7 -25
  159. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  160. package/src/cli/commands/channels/index.help.ts +70 -2
  161. package/src/cli/commands/channels/index.ts +16 -3
  162. package/src/cli/commands/channels/send.ts +128 -0
  163. package/src/cli/commands/credentials.help.ts +6 -2
  164. package/src/cli/commands/credentials.ts +2 -2
  165. package/src/cli/commands/mcp.help.ts +8 -8
  166. package/src/cli/commands/mcp.ts +1 -1
  167. package/src/cli/commands/roadmap.help.ts +5 -1
  168. package/src/cli/commands/roadmap.ts +1 -1
  169. package/src/cli/lib/bundled-marketplace.json +103 -90
  170. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  171. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  172. package/src/config/__tests__/platform-identity.test.ts +255 -0
  173. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  174. package/src/config/acp-agent-write.ts +43 -0
  175. package/src/config/acp-defaults.test.ts +13 -3
  176. package/src/config/acp-defaults.ts +23 -6
  177. package/src/config/acp-schema.test.ts +81 -0
  178. package/src/config/acp-schema.ts +30 -2
  179. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  180. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  181. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  182. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  183. package/src/config/bundled-skills/computer-use/SKILL.md +20 -2
  184. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  185. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  186. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  187. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  188. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  189. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  190. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  191. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  192. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  193. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  194. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  195. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  196. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  197. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  198. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  199. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  200. package/src/config/default-profile-catalog.ts +1 -1
  201. package/src/config/env.ts +2 -2
  202. package/src/config/feature-flag-registry.json +24 -12
  203. package/src/config/loader.ts +1 -0
  204. package/src/config/memory-v3-gate.test.ts +31 -0
  205. package/src/config/memory-v3-gate.ts +20 -16
  206. package/src/config/platform-identity.ts +201 -0
  207. package/src/config/platform-rehydration.ts +11 -27
  208. package/src/config/schema.ts +2 -2
  209. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  210. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  211. package/src/config/schemas/calls.ts +1 -28
  212. package/src/config/schemas/live-voice.ts +1 -173
  213. package/src/config/schemas/mcp.ts +1 -1
  214. package/src/config/schemas/memory-retrospective.ts +10 -0
  215. package/src/config/schemas/services.ts +8 -0
  216. package/src/config/schemas/skills.ts +0 -14
  217. package/src/config/schemas/voice.ts +150 -0
  218. package/src/credential-execution/process-manager.test.ts +18 -19
  219. package/src/credential-health/credential-health-service.ts +11 -17
  220. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  221. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  222. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  223. package/src/daemon/app-change-notify.ts +45 -0
  224. package/src/daemon/config-watcher.ts +13 -194
  225. package/src/daemon/conversation-error.ts +3 -2
  226. package/src/daemon/conversation-media-retry.ts +2 -1
  227. package/src/daemon/conversation-process.ts +35 -64
  228. package/src/daemon/conversation-runtime-assembly.ts +2 -1
  229. package/src/daemon/conversation-skill-tools.ts +13 -31
  230. package/src/daemon/conversation-store.ts +1 -1
  231. package/src/daemon/conversation-surfaces.ts +15 -9
  232. package/src/daemon/conversation-tool-setup.ts +37 -43
  233. package/src/daemon/conversation.ts +0 -3
  234. package/src/daemon/dictation-profile-store.ts +2 -1
  235. package/src/daemon/handlers/shared.ts +2 -1
  236. package/src/daemon/handlers/skills.ts +6 -3
  237. package/src/daemon/host-bash-proxy.ts +8 -12
  238. package/src/daemon/host-browser-proxy.ts +15 -1
  239. package/src/daemon/host-cu-proxy.ts +186 -19
  240. package/src/daemon/host-file-proxy.ts +8 -10
  241. package/src/daemon/host-proxy-preactivation.ts +2 -1
  242. package/src/daemon/host-transfer-proxy.ts +14 -18
  243. package/src/daemon/lifecycle.ts +0 -5
  244. package/src/daemon/mcp-reload-service.ts +5 -5
  245. package/src/daemon/message-types/sync.ts +1 -0
  246. package/src/daemon/orphan-reaper.ts +1 -1
  247. package/src/daemon/providers-setup.ts +1 -1
  248. package/src/daemon/reaction-history-render.ts +2 -1
  249. package/src/daemon/shutdown-handlers.ts +1 -9
  250. package/src/daemon/skill-memory-refresh.ts +64 -0
  251. package/src/daemon/tool-side-effects.ts +7 -18
  252. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  253. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  254. package/src/desktop/desktop-chrome-policy.ts +47 -0
  255. package/src/desktop/desktop-chrome-session.ts +37 -0
  256. package/src/desktop/desktop-dependencies.test.ts +102 -0
  257. package/src/desktop/desktop-dependencies.ts +308 -0
  258. package/src/desktop/desktop-feature.test.ts +18 -0
  259. package/src/desktop/desktop-feature.ts +19 -0
  260. package/src/desktop/desktop-panel-config.test.ts +135 -0
  261. package/src/desktop/desktop-panel-config.ts +149 -0
  262. package/src/desktop/desktop-session-manager.test.ts +730 -0
  263. package/src/desktop/desktop-session-manager.ts +850 -0
  264. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  265. package/src/desktop/desktop-stream-bridge.ts +214 -0
  266. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  267. package/src/desktop/desktop-wallpaper.ts +113 -0
  268. package/src/desktop/desktop-window-drag.ts +177 -0
  269. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  270. package/src/desktop/desktop-window-manager-config.ts +238 -0
  271. package/src/desktop/desktop-window-theme.ts +170 -0
  272. package/src/heartbeat/heartbeat-service.ts +21 -3
  273. package/src/home/__tests__/feed-writer.test.ts +22 -0
  274. package/src/home/feed-writer.ts +11 -2
  275. package/src/hooks/types.ts +33 -0
  276. package/src/i18n/AGENTS.md +22 -0
  277. package/src/i18n/__tests__/i18n.test.ts +108 -0
  278. package/src/i18n/index.ts +16 -0
  279. package/src/i18n/locales.ts +93 -0
  280. package/src/i18n/messages.ts +61 -0
  281. package/src/i18n/title.ts +25 -0
  282. package/src/inbound/platform-callback-registration.ts +4 -5
  283. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  284. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  285. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  286. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  287. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  288. package/src/live-voice/live-voice-photo.ts +25 -6
  289. package/src/live-voice/live-voice-session.ts +213 -693
  290. package/src/live-voice/protocol.ts +0 -2
  291. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  292. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  293. package/src/mcp/effective-config.ts +3 -3
  294. package/src/mcp/mcp-header-store.ts +29 -45
  295. package/src/mcp/spec-schema.ts +111 -0
  296. package/src/mcp/startup.ts +8 -4
  297. package/src/mcp/workspace-mcp-config.ts +188 -0
  298. package/src/messaging/provider.ts +10 -3
  299. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  300. package/src/messaging/providers/callback-routing.ts +18 -0
  301. package/src/messaging/providers/channel-transport.ts +56 -0
  302. package/src/messaging/providers/discord/transport.ts +17 -0
  303. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  304. package/src/messaging/providers/slack/adapter.ts +7 -44
  305. package/src/messaging/providers/slack/transport.ts +16 -0
  306. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  307. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  308. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  309. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  310. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  311. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  312. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  313. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  314. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  315. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  316. package/src/notifications/adapters/macos.ts +3 -0
  317. package/src/notifications/assistant-reply-producer.ts +8 -3
  318. package/src/notifications/conversation-pairing.ts +36 -16
  319. package/src/notifications/delivered-post-record.ts +11 -1
  320. package/src/notifications/guardian-feed-projection.ts +125 -5
  321. package/src/notifications/home-feed-side-effect.ts +55 -7
  322. package/src/notifications/schedule-result-producer.ts +66 -22
  323. package/src/oauth/AGENTS.md +2 -0
  324. package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
  325. package/src/oauth/byo-connection.test.ts +105 -0
  326. package/src/oauth/byo-connection.ts +36 -6
  327. package/src/oauth/connection-resolver.test.ts +73 -0
  328. package/src/oauth/connection-resolver.ts +95 -2
  329. package/src/oauth/scope-utils.ts +24 -0
  330. package/src/oauth/seed-providers.ts +204 -25
  331. package/src/outbound-proxy/certs.ts +3 -5
  332. package/src/permissions/checker.test.ts +11 -6
  333. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  334. package/src/persistence/conversation-crud.ts +71 -63
  335. package/src/persistence/conversation-lineage.ts +12 -4
  336. package/src/persistence/conversation-queries.ts +2 -2
  337. package/src/persistence/conversation-title-placeholders.ts +23 -0
  338. package/src/persistence/conversation-title-service.ts +9 -26
  339. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  340. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  341. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  342. package/src/persistence/message-cursor.ts +78 -0
  343. package/src/persistence/message-reads.ts +30 -0
  344. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  345. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  346. package/src/persistence/schema/memory-core.ts +5 -0
  347. package/src/platform/client.test.ts +4 -2
  348. package/src/platform/client.ts +2 -10
  349. package/src/plugin-api/constants.ts +2 -0
  350. package/src/plugin-api/index.ts +1 -0
  351. package/src/plugin-api/types.ts +2 -0
  352. package/src/plugins/defaults/index.ts +2 -0
  353. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  354. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  355. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  356. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  357. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  358. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  359. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  360. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  361. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  362. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  363. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  364. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  365. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  366. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  367. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  368. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  369. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  370. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  371. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  372. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  373. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  374. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  375. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  376. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  377. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  378. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  379. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  380. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  381. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  382. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  383. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  384. package/src/plugins/defaults/memory/startup.ts +28 -2
  385. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  386. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  387. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  388. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  389. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  390. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  391. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  392. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  393. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  394. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  395. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  396. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  397. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  398. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  399. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  400. package/src/plugins/defaults/memory/worker.ts +6 -0
  401. package/src/plugins/mcp-servers.ts +15 -75
  402. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  403. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  404. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  405. package/src/providers/anthropic/client.ts +0 -47
  406. package/src/providers/gemini/client.ts +9 -23
  407. package/src/providers/gemini-thought-signature.test.ts +63 -0
  408. package/src/providers/gemini-thought-signature.ts +44 -0
  409. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  410. package/src/providers/inference/auth.ts +2 -3
  411. package/src/providers/inference/connection-availability.ts +27 -8
  412. package/src/providers/inference/endpoint-probe.ts +10 -1
  413. package/src/providers/inference/resolve-auth.ts +22 -4
  414. package/src/providers/inference/service-account-token.ts +248 -0
  415. package/src/providers/model-catalog.ts +34 -0
  416. package/src/providers/model-intents.ts +10 -5
  417. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  418. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  419. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  420. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  421. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  422. package/src/providers/openai/chat-completions-provider.ts +212 -79
  423. package/src/providers/openai/codex-models.ts +0 -4
  424. package/src/providers/openai/google-thought-signature.ts +320 -0
  425. package/src/providers/opencode/client.test.ts +73 -4
  426. package/src/providers/opencode/client.ts +60 -3
  427. package/src/providers/openrouter/client.ts +20 -0
  428. package/src/providers/outbound-request-sanitize.ts +96 -0
  429. package/src/providers/retry.ts +30 -19
  430. package/src/providers/unparseable-tool-args.ts +5 -1
  431. package/src/runtime/AGENTS.md +3 -1
  432. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  433. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  434. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  435. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  436. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  437. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  438. package/src/runtime/agent-wake.ts +2 -1
  439. package/src/runtime/assistant-event-hub.ts +21 -3
  440. package/src/runtime/auth/same-actor.ts +38 -3
  441. package/src/runtime/channel-send.ts +316 -0
  442. package/src/runtime/host-observe.ts +11 -1
  443. package/src/runtime/http-server.ts +160 -151
  444. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  445. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
  446. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  447. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  448. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  449. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  450. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  451. package/src/runtime/routes/acp-routes.test.ts +15 -0
  452. package/src/runtime/routes/acp-routes.ts +72 -9
  453. package/src/runtime/routes/app-management-routes.ts +73 -0
  454. package/src/runtime/routes/app-routes.ts +1 -1
  455. package/src/runtime/routes/channel-send-routes.ts +85 -0
  456. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  457. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  458. package/src/runtime/routes/conversation-routes.ts +16 -3
  459. package/src/runtime/routes/credential-routes.ts +19 -10
  460. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  461. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  462. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  463. package/src/runtime/routes/events-routes.ts +4 -0
  464. package/src/runtime/routes/host-cu-routes.ts +6 -0
  465. package/src/runtime/routes/index.ts +4 -2
  466. package/src/runtime/routes/log-export-routes.ts +2 -0
  467. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  468. package/src/runtime/routes/migration-routes.ts +6 -20
  469. package/src/runtime/routes/oauth-commands-routes.ts +36 -0
  470. package/src/runtime/routes/platform-routes.ts +18 -15
  471. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  472. package/src/runtime/routes/workspace-routes.ts +46 -63
  473. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  474. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  475. package/src/runtime/routes/workspace-utils.ts +17 -0
  476. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  477. package/src/runtime/services/conversation-serializer.ts +16 -3
  478. package/src/runtime/sync/resource-sync-events.ts +2 -1
  479. package/src/schedule/run-script.ts +2 -1
  480. package/src/schedule/schedule-store.ts +9 -2
  481. package/src/schedule/worker.ts +1 -1
  482. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  483. package/src/security/ces-http-record-backend.ts +120 -0
  484. package/src/security/ces-rpc-record-backend.ts +7 -0
  485. package/src/security/untrusted-content.ts +3 -1
  486. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  487. package/src/skills/workspace-skill-md-signature.ts +50 -0
  488. package/src/subagent/consult-context.ts +12 -25
  489. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  490. package/src/tools/acp/list-agents.test.ts +3 -1
  491. package/src/tools/acp/spawn.test.ts +146 -11
  492. package/src/tools/acp/spawn.ts +21 -3
  493. package/src/tools/acp/status.test.ts +94 -0
  494. package/src/tools/acp/status.ts +40 -3
  495. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  496. package/src/tools/browser/browser-execution.ts +1 -1
  497. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  498. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  499. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  500. package/src/tools/browser/network-recorder.ts +2 -1
  501. package/src/tools/capability-offer.test.ts +22 -2
  502. package/src/tools/capability-offer.ts +16 -1
  503. package/src/tools/computer-use/definitions.ts +121 -2
  504. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  505. package/src/tools/credentials/metadata-store.ts +100 -0
  506. package/src/tools/filesystem/search.ts +2 -1
  507. package/src/tools/host-filesystem/edit.test.ts +2 -1
  508. package/src/tools/host-filesystem/edit.ts +2 -2
  509. package/src/tools/host-filesystem/read.test.ts +2 -1
  510. package/src/tools/host-filesystem/read.ts +2 -2
  511. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  512. package/src/tools/host-filesystem/transfer.ts +2 -2
  513. package/src/tools/host-filesystem/write.test.ts +2 -1
  514. package/src/tools/host-filesystem/write.ts +2 -2
  515. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  516. package/src/tools/host-terminal/host-shell.ts +4 -20
  517. package/src/tools/network/web-search.ts +6 -1
  518. package/src/tools/policy-context.ts +6 -7
  519. package/src/tools/skills/scaffold-managed.ts +61 -3
  520. package/src/tools/subagent/spawn.ts +5 -8
  521. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  522. package/src/tools/terminal/sanitized-bash.ts +105 -0
  523. package/src/tools/watch/watch-retro-report.ts +0 -8
  524. package/src/types/host-capabilities.ts +5 -3
  525. package/src/util/__tests__/language-subtag.test.ts +23 -1
  526. package/src/util/ca-bundle.ts +13 -0
  527. package/src/util/language-subtag.ts +16 -0
  528. package/src/util/platform.ts +8 -0
  529. package/src/util/unicode.ts +15 -1
  530. package/src/watch/watch-timeline.ts +2 -2
  531. package/src/workflows/run-manager.ts +2 -1
  532. package/src/workspace/git-porcelain.ts +66 -0
  533. package/src/workspace/git-service.ts +6 -55
  534. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  535. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  536. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  537. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  538. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  539. package/src/workspace/migrations/registry.ts +6 -0
  540. package/src/__tests__/app-source-watcher.test.ts +0 -218
  541. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  542. package/src/__tests__/speaker-identification.test.ts +0 -56
  543. package/src/calls/speaker-identification.ts +0 -233
  544. package/src/daemon/app-source-watcher.ts +0 -218
  545. package/src/messaging/providers/whatsapp/client.ts +0 -29
  546. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  547. package/src/util/fs-watcher-error.ts +0 -36
  548. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -0,0 +1,247 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ // ── Mutable state for injected vault ────────────────────────────────────────
4
+
5
+ const vaultStore = new Map<string, string>();
6
+ const writtenKeys = new Map<string, string>();
7
+
8
+ // ── Import under test ────────────────────────────────────────────────────────
9
+ //
10
+ // No mock.module — prevents all bun worker contamination. Vault access is
11
+ // injected through _injectVaultAccessors / _resetVaultAccessors instead.
12
+
13
+ const {
14
+ getValidServiceAccountToken,
15
+ parseServiceAccountKey,
16
+ _resetServiceAccountMutex,
17
+ _injectVaultAccessors,
18
+ _resetVaultAccessors,
19
+ } = await import("../providers/inference/service-account-token.js");
20
+
21
+ // ── Hooks ────────────────────────────────────────────────────────────────────
22
+
23
+ beforeEach(() => {
24
+ vaultStore.clear();
25
+ writtenKeys.clear();
26
+ _resetServiceAccountMutex();
27
+ _injectVaultAccessors(
28
+ async (key: string) => vaultStore.get(key),
29
+ async (key: string, value: string) => {
30
+ vaultStore.set(key, value);
31
+ writtenKeys.set(key, value);
32
+ return true;
33
+ },
34
+ );
35
+ });
36
+
37
+ afterEach(() => {
38
+ _resetVaultAccessors();
39
+ });
40
+
41
+ // ── Real 2048-bit RSA PKCS#8 key generated offline for unit tests only ───────
42
+
43
+ const TEST_PRIVATE_KEY =
44
+ "-----BEGIN PRIVATE KEY-----\n" +
45
+ "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDFnEC0wI/cNKyO\n" +
46
+ "XzkqOGAvsTTx4Q+DPjDBPVoy3yHVk7cGYIf5sg3nsrQlE4ARMEQ9MXKUvw+VY59c\n" +
47
+ "zzpy9/JnCN9v5ymZUrowF/87Xn0muklHOhYPFz+73BunncH0Dk0mgCRWEfd/xoGB\n" +
48
+ "bMI40mI2hPMN5aGU4moTmVzTsgvatp0tAk90oaUQBZ/Y3PVjoR+SHbKhQtZW8eiO\n" +
49
+ "zUGyBhzmlmMZcquWxHui8QbO//kEKg7jv2+7npkdW/S0meoDeKZzqRNACKci1CQB\n" +
50
+ "N/skFqy6B8qYekhVYtAaIQOLtuD+VdLL7azMBqIHZSVQQXD6KisN8XSj95k/Fyeb\n" +
51
+ "UEmhvlwrAgMBAAECggEALKr7PWBhpGcLskr4AwEnTOCDlUFIXmRKZQPXRbSGU17G\n" +
52
+ "elyLn/eNiRgt/EuCIWCFrEdnHRRrE1unhDUbgy1kz8GDsCnSj2PRzlk+Sk87fuuQ\n" +
53
+ "8NbCcbrRn0dLwQG5+87XxaIllIcPKQTX9Ud+R5XWuXgn8LY9Zggf7jo2b2vJnjBO\n" +
54
+ "2SIS4M24a8x/1hHLjrTKtyO1yF3TI/9z72z4+yYiXAHxBugCcg6kv27pMEtOl5hv\n" +
55
+ "HW6ogBIDKVIVp3DmjaWxPawy1ytxpI1mR1lfGKoNI3iutLNfuHl3MwinYL7oCiL/\n" +
56
+ "rejuz37rXicFZ40YTKHDqKy+brLS6Ahg0R7XxSNtcQKBgQD7i4oh4stD+geFoPoJ\n" +
57
+ "o/grtOazn2GKuMOrcbBSaTfs4PZfcSRT8Egzhl3Vf/3Z30T3ChugFKkpN9naDEL3\n" +
58
+ "0SEH5sR5oMhGzzE3ncwTPR4fClpiX61JIY6iVL5AQukXIK+v+hCuhRu9YepMbGWq\n" +
59
+ "ndl/uH4LofuEctJYaovnghKedwKBgQDJHC7NiqNhGB6YzwgvYK2GVrFLdPpcfLbl\n" +
60
+ "y5DZKaVYCs3cLFCH7qHEdsmVEWkQ9UkRhxNPGpgQadvYNAd9Bbf2au2lfNuCt0Kn\n" +
61
+ "dYDgkxHAo4PByEznOBgQYoG5IjF8bdubQJbJHQh/bj5EkzeKDggD37ED0fTdWIiJ\n" +
62
+ "Nhh0/q2Y7QKBgGlLMsoWbjGJMqbcCG4KpfJdqghcWe9Agh982mrUwmU0kczHxoYg\n" +
63
+ "WYi/4P1iszcE/5BNOd0baOfpChb89PdZIfN1xJ6JD+ycBCUct3UCK16g7c6p8BZh\n" +
64
+ "ppwKzwyFNZ7JvuDx0xPKgh8XIoVVBoWRx55v8ssZxffePHBoQSHqT7LnAoGAB1vO\n" +
65
+ "opq2C7F0PScmJ4WkE8xMRHSNTKELmDDwpeGOOUB789hzQeYLaULncVrnu0UeXVPR\n" +
66
+ "8w0ID2GwN6L/uyVga9XRyEJfTdMizznPc2guOBxDj+2iUruMtdzE3l9P7Dug03dz\n" +
67
+ "jPxQ+UixYpT4bUbCKFIhCgC+svKwXETsdCt1cqkCgYBVSSabJNSvFfqYz/VJbRK7\n" +
68
+ "cvxEr3mcV/I/+5BoyTmTKQGkIRl6xCMhqZk019hXbdN66jMQ9zg6wni4UKA43cXI\n" +
69
+ "/3NHY86IvdpLORlYqKVHycZsqSVQE6Z3V1G4C2wVKUZtWsoT6Wr+Ind+V+VzbDD2\n" +
70
+ "wHbW4FhxHN9iwdHfmJyq+g==\n" +
71
+ "-----END PRIVATE KEY-----\n";
72
+
73
+ const TEST_SERVICE_ACCOUNT = JSON.stringify({
74
+ type: "service_account",
75
+ client_email: "test@example.com",
76
+ private_key: TEST_PRIVATE_KEY,
77
+ token_uri: "https://oauth2.googleapis.com/token",
78
+ });
79
+
80
+ // ── Tests ────────────────────────────────────────────────────────────────────
81
+
82
+ describe("service-account-token", () => {
83
+ // -- parseServiceAccountKey ------------------------------------------------
84
+
85
+ describe("parseServiceAccountKey", () => {
86
+ test("returns null for empty string", () => {
87
+ expect(parseServiceAccountKey("")).toBeNull();
88
+ });
89
+
90
+ test("returns null for invalid JSON", () => {
91
+ expect(parseServiceAccountKey("not-json")).toBeNull();
92
+ });
93
+
94
+ test("returns null when required fields are missing", () => {
95
+ expect(
96
+ parseServiceAccountKey(
97
+ JSON.stringify({ client_email: "test@example.com" }),
98
+ ),
99
+ ).toBeNull();
100
+ });
101
+
102
+ test("returns the key when all required fields are present", () => {
103
+ const key = parseServiceAccountKey(TEST_SERVICE_ACCOUNT);
104
+ expect(key).not.toBeNull();
105
+ expect(key?.client_email).toBe("test@example.com");
106
+ });
107
+ });
108
+
109
+ // -- getValidServiceAccountToken -------------------------------------------
110
+
111
+ test("returns not_found when no credential is stored", async () => {
112
+ const result = await getValidServiceAccountToken("credential/vertex-ai");
113
+ expect(result).toEqual({ ok: false, reason: "not_found" });
114
+ });
115
+
116
+ test("returns invalid_config when credential is not valid JSON", async () => {
117
+ vaultStore.set("credential/vertex-ai", "not-json");
118
+ const result = await getValidServiceAccountToken("credential/vertex-ai");
119
+ expect(result).toEqual({ ok: false, reason: "invalid_config" });
120
+ });
121
+
122
+ test("returns invalid_config when credential JSON is missing required fields", async () => {
123
+ vaultStore.set(
124
+ "credential/vertex-ai",
125
+ JSON.stringify({ client_email: "missing-other-fields@example.com" }),
126
+ );
127
+ const result = await getValidServiceAccountToken("credential/vertex-ai");
128
+ expect(result).toEqual({ ok: false, reason: "invalid_config" });
129
+ });
130
+
131
+ test("returns cached token when the cache blob is still fresh", async () => {
132
+ const futureExpiry = Math.floor(Date.now() / 1000) + 7200;
133
+ vaultStore.set("credential/vertex-ai", TEST_SERVICE_ACCOUNT);
134
+ vaultStore.set(
135
+ "credential/vertex-ai/token_cache",
136
+ JSON.stringify({
137
+ access_token: "cached-token",
138
+ expires_at: futureExpiry,
139
+ }),
140
+ );
141
+
142
+ const result = await getValidServiceAccountToken("credential/vertex-ai");
143
+ expect(result).toEqual({ ok: true, token: "cached-token" });
144
+ // Nothing new should have been written (no exchange performed).
145
+ expect(writtenKeys.size).toBe(0);
146
+ });
147
+
148
+ test("returns cached token when cache blob has no expiry", async () => {
149
+ vaultStore.set("credential/vertex-ai", TEST_SERVICE_ACCOUNT);
150
+ vaultStore.set(
151
+ "credential/vertex-ai/token_cache",
152
+ JSON.stringify({ access_token: "no-expiry-token" }),
153
+ );
154
+
155
+ const result = await getValidServiceAccountToken("credential/vertex-ai");
156
+ expect(result).toEqual({ ok: true, token: "no-expiry-token" });
157
+ });
158
+
159
+ test("coalesces concurrent callers for the same credential onto one exchange", async () => {
160
+ const pastExpiry = Math.floor(Date.now() / 1000) - 600;
161
+ vaultStore.set("credential/vertex-ai", TEST_SERVICE_ACCOUNT);
162
+ vaultStore.set(
163
+ "credential/vertex-ai/token_cache",
164
+ JSON.stringify({ access_token: "stale-token", expires_at: pastExpiry }),
165
+ );
166
+
167
+ let fetchCallCount = 0;
168
+ const originalFetch = globalThis.fetch;
169
+ globalThis.fetch = (async () => {
170
+ fetchCallCount += 1;
171
+ return {
172
+ ok: true,
173
+ json: async () => ({ access_token: "new-token", expires_in: 3600 }),
174
+ } as Response;
175
+ }) as unknown as typeof fetch;
176
+
177
+ try {
178
+ const [r1, r2] = await Promise.all([
179
+ getValidServiceAccountToken("credential/vertex-ai"),
180
+ getValidServiceAccountToken("credential/vertex-ai"),
181
+ ]);
182
+
183
+ expect(r1).toEqual({ ok: true, token: "new-token" });
184
+ expect(r2).toEqual({ ok: true, token: "new-token" });
185
+ expect(fetchCallCount).toBe(1);
186
+ } finally {
187
+ globalThis.fetch = originalFetch;
188
+ }
189
+ });
190
+
191
+ test("concurrent callers for different credentials exchange independently", async () => {
192
+ vaultStore.set("credential/vertex-a", TEST_SERVICE_ACCOUNT);
193
+ vaultStore.set("credential/vertex-b", TEST_SERVICE_ACCOUNT);
194
+
195
+ let fetchCallCount = 0;
196
+ const originalFetch = globalThis.fetch;
197
+ globalThis.fetch = (async () => {
198
+ fetchCallCount += 1;
199
+ return {
200
+ ok: true,
201
+ json: async () => ({
202
+ access_token: `token-${fetchCallCount}`,
203
+ expires_in: 3600,
204
+ }),
205
+ } as Response;
206
+ }) as unknown as typeof fetch;
207
+
208
+ try {
209
+ const [r1, r2] = await Promise.all([
210
+ getValidServiceAccountToken("credential/vertex-a"),
211
+ getValidServiceAccountToken("credential/vertex-b"),
212
+ ]);
213
+
214
+ expect(r1.ok).toBe(true);
215
+ expect(r2.ok).toBe(true);
216
+ // Each credential triggers its own exchange.
217
+ expect(fetchCallCount).toBe(2);
218
+ } finally {
219
+ globalThis.fetch = originalFetch;
220
+ }
221
+ });
222
+
223
+ test("caches token+expiry as a single JSON blob", async () => {
224
+ vaultStore.set("credential/vertex-ai", TEST_SERVICE_ACCOUNT);
225
+
226
+ const originalFetch = globalThis.fetch;
227
+ globalThis.fetch = (async () => ({
228
+ ok: true,
229
+ json: async () => ({ access_token: "fresh-token", expires_in: 3600 }),
230
+ })) as unknown as typeof fetch;
231
+
232
+ try {
233
+ await getValidServiceAccountToken("credential/vertex-ai");
234
+
235
+ const blob = vaultStore.get("credential/vertex-ai/token_cache");
236
+ expect(blob).toBeDefined();
237
+ const parsed = JSON.parse(blob!) as {
238
+ access_token: string;
239
+ expires_at: number;
240
+ };
241
+ expect(parsed.access_token).toBe("fresh-token");
242
+ expect(typeof parsed.expires_at).toBe("number");
243
+ } finally {
244
+ globalThis.fetch = originalFetch;
245
+ }
246
+ });
247
+ });
@@ -60,7 +60,7 @@ function makeConfig(overrides: Partial<AssistantConfig> = {}): AssistantConfig {
60
60
  return {
61
61
  skills: {
62
62
  entries: {},
63
- load: { extraDirs: [], watch: true, watchDebounceMs: 250 },
63
+ load: { extraDirs: [] },
64
64
  install: { nodeManager: "npm" },
65
65
  allowBundled: null,
66
66
  remoteProviders: {
@@ -28,7 +28,7 @@ function makeConfig(overrides: Partial<AssistantConfig> = {}): AssistantConfig {
28
28
  return {
29
29
  skills: {
30
30
  entries: {},
31
- load: { extraDirs: [], watch: true, watchDebounceMs: 250 },
31
+ load: { extraDirs: [] },
32
32
  install: { nodeManager: "npm" },
33
33
  allowBundled: null,
34
34
  remoteProviders: {
@@ -233,7 +233,7 @@ describe("resolveSkillStates with feature flags", () => {
233
233
  const config = makeConfig({
234
234
  skills: {
235
235
  entries: { [DECLARED_SKILL_ID]: { enabled: true } },
236
- load: { extraDirs: [], watch: true, watchDebounceMs: 250 },
236
+ load: { extraDirs: [] },
237
237
  install: { nodeManager: "npm" },
238
238
  allowBundled: null,
239
239
  remoteProviders: {
@@ -875,7 +875,9 @@ describe("always-candidate frontmatter parsing", () => {
875
875
  // drops trailing hints and the whole avoid-when list.
876
876
  const { DEFAULT_CARD_CHARS, buildSkillContent } =
877
877
  await import("../plugins/defaults/memory/substrate/skill-content.js");
878
- const schedule = loadSkillCatalog().find((skill) => skill.id === "schedule");
878
+ const schedule = loadSkillCatalog().find(
879
+ (skill) => skill.id === "schedule",
880
+ );
879
881
  expect(schedule).toBeDefined();
880
882
 
881
883
  const card = buildSkillContent(schedule!);
@@ -886,7 +888,9 @@ describe("always-candidate frontmatter parsing", () => {
886
888
  test("the bundled schedule card carries its monitoring vocabulary", async () => {
887
889
  const { buildSkillContent } =
888
890
  await import("../plugins/defaults/memory/substrate/skill-content.js");
889
- const schedule = loadSkillCatalog().find((skill) => skill.id === "schedule");
891
+ const schedule = loadSkillCatalog().find(
892
+ (skill) => skill.id === "schedule",
893
+ );
890
894
  const card = buildSkillContent(schedule!).toLowerCase();
891
895
 
892
896
  for (const term of ["monitor", "page", "dashboard", "status", "alert"]) {
@@ -1087,14 +1091,14 @@ describe("bundled computer-use skill", () => {
1087
1091
  expect(cuSkill!.bundled).toBe(true);
1088
1092
  });
1089
1093
 
1090
- test("computer-use skill has a valid tool manifest with 11 tools", () => {
1094
+ test("computer-use skill has a valid tool manifest with 12 tools", () => {
1091
1095
  const catalog = loadSkillCatalog();
1092
1096
  const cuSkill = catalog.find((s) => s.id === "computer-use");
1093
1097
  expect(cuSkill).toBeDefined();
1094
1098
  expect(cuSkill!.toolManifest).toBeDefined();
1095
1099
  expect(cuSkill!.toolManifest!.present).toBe(true);
1096
1100
  expect(cuSkill!.toolManifest!.valid).toBe(true);
1097
- expect(cuSkill!.toolManifest!.toolCount).toBe(11);
1101
+ expect(cuSkill!.toolManifest!.toolCount).toBe(12);
1098
1102
  expect(cuSkill!.toolManifest!.toolNames).toEqual([
1099
1103
  "computer_use_observe",
1100
1104
  "computer_use_click",
@@ -1105,6 +1109,7 @@ describe("bundled computer-use skill", () => {
1105
1109
  "computer_use_wait",
1106
1110
  "computer_use_open_app",
1107
1111
  "computer_use_run_applescript",
1112
+ "computer_use_sequence",
1108
1113
  "computer_use_done",
1109
1114
  "computer_use_respond",
1110
1115
  ]);
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Tests for app source fingerprinting: walk exclusions, comparison, inspect
3
+ * statuses, and the compile-time sidecar format.
4
+ */
5
+
6
+ import {
7
+ mkdirSync,
8
+ mkdtempSync,
9
+ readFileSync,
10
+ rmSync,
11
+ symlinkSync,
12
+ writeFileSync,
13
+ } from "node:fs";
14
+ import { tmpdir } from "node:os";
15
+ import { join } from "node:path";
16
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
17
+
18
+ import {
19
+ compareAppSourceFingerprint,
20
+ computeAppSourceFingerprint,
21
+ inspectAppSource,
22
+ parseAppSourceFingerprint,
23
+ SOURCE_FINGERPRINT_FILENAME,
24
+ writeAppSourceFingerprint,
25
+ } from "../apps/source-fingerprint.js";
26
+
27
+ let root: string;
28
+
29
+ beforeEach(() => {
30
+ root = mkdtempSync(join(tmpdir(), "app-source-fingerprint-"));
31
+ });
32
+
33
+ afterEach(() => {
34
+ rmSync(root, { recursive: true, force: true });
35
+ });
36
+
37
+ function write(rel: string, contents: string): void {
38
+ const abs = join(root, rel);
39
+ mkdirSync(join(abs, ".."), { recursive: true });
40
+ writeFileSync(abs, contents);
41
+ }
42
+
43
+ describe("computeAppSourceFingerprint", () => {
44
+ test("digests source files with POSIX-relative keys", () => {
45
+ write("src/main.tsx", "export const x = 1;");
46
+ write("src/index.html", "<html></html>");
47
+
48
+ const fp = computeAppSourceFingerprint(root);
49
+
50
+ expect(Object.keys(fp.files).sort()).toEqual([
51
+ "src/index.html",
52
+ "src/main.tsx",
53
+ ]);
54
+ expect(fp.algorithm).toBe("sha256");
55
+ expect(fp.files["src/main.tsx"]).toMatch(/^[0-9a-f]{64}$/);
56
+ });
57
+
58
+ test("skips dist, records, node_modules, and .git", () => {
59
+ write("src/main.tsx", "src");
60
+ write("dist/main.js", "compiled");
61
+ write("records/rec-1.json", "{}");
62
+ write("node_modules/pkg/index.js", "dep");
63
+ write("src/node_modules/nested/index.js", "nested");
64
+ write(".git/HEAD", "ref");
65
+ write("src/.git/config", "nope");
66
+
67
+ const fp = computeAppSourceFingerprint(root);
68
+
69
+ expect(Object.keys(fp.files)).toEqual(["src/main.tsx"]);
70
+ });
71
+
72
+ test("skips symlinks", () => {
73
+ write("src/real.tsx", "hello");
74
+ mkdirSync(join(root, "src"), { recursive: true });
75
+ symlinkSync(join(root, "src/real.tsx"), join(root, "src/link.tsx"));
76
+
77
+ const fp = computeAppSourceFingerprint(root);
78
+
79
+ expect(Object.keys(fp.files)).toEqual(["src/real.tsx"]);
80
+ });
81
+ });
82
+
83
+ describe("compareAppSourceFingerprint", () => {
84
+ test("reports modified, added, and removed paths", () => {
85
+ write("src/main.tsx", "one");
86
+ write("src/keep.tsx", "keep");
87
+ const baseline = computeAppSourceFingerprint(root);
88
+
89
+ write("src/main.tsx", "two");
90
+ write("src/new.tsx", "new");
91
+ rmSync(join(root, "src/keep.tsx"));
92
+
93
+ const diff = compareAppSourceFingerprint(root, baseline);
94
+ expect(diff.clean).toBe(false);
95
+ expect(diff.modified).toEqual(["src/main.tsx"]);
96
+ expect(diff.added).toEqual(["src/new.tsx"]);
97
+ expect(diff.removed).toEqual(["src/keep.tsx"]);
98
+ });
99
+
100
+ test("is clean when source matches the baseline", () => {
101
+ write("src/main.tsx", "same");
102
+ const baseline = computeAppSourceFingerprint(root);
103
+ const diff = compareAppSourceFingerprint(root, baseline);
104
+ expect(diff.clean).toBe(true);
105
+ expect(diff.modified).toEqual([]);
106
+ expect(diff.added).toEqual([]);
107
+ expect(diff.removed).toEqual([]);
108
+ });
109
+ });
110
+
111
+ describe("inspectAppSource", () => {
112
+ test("reports never_compiled when dist index is missing even if a fingerprint exists", () => {
113
+ write("src/main.tsx", "src");
114
+ mkdirSync(join(root, "dist"), { recursive: true });
115
+ writeAppSourceFingerprint(root, join(root, "dist"));
116
+
117
+ const result = inspectAppSource(root);
118
+ expect(result.status).toBe("never_compiled");
119
+ });
120
+
121
+ test("reports unknown_baseline when dist exists without a fingerprint", () => {
122
+ write("src/main.tsx", "src");
123
+ write("dist/index.html", "<html></html>");
124
+ const result = inspectAppSource(root);
125
+ expect(result.status).toBe("unknown_baseline");
126
+ });
127
+
128
+ test("reports clean after writing a fingerprint that matches source", () => {
129
+ write("src/main.tsx", "src");
130
+ mkdirSync(join(root, "dist"), { recursive: true });
131
+ writeFileSync(join(root, "dist", "index.html"), "<html></html>");
132
+ writeAppSourceFingerprint(root, join(root, "dist"));
133
+
134
+ const result = inspectAppSource(root);
135
+ expect(result.status).toBe("clean");
136
+ expect(result.compiledAt).toMatch(/^\d{4}-\d{2}-\d{2}T/);
137
+ });
138
+
139
+ test("reports stale after a source edit", () => {
140
+ write("src/main.tsx", "before");
141
+ mkdirSync(join(root, "dist"), { recursive: true });
142
+ writeFileSync(join(root, "dist", "index.html"), "<html></html>");
143
+ writeAppSourceFingerprint(root, join(root, "dist"));
144
+ write("src/main.tsx", "after");
145
+
146
+ const result = inspectAppSource(root);
147
+ expect(result.status).toBe("stale");
148
+ expect(result.modified).toEqual(["src/main.tsx"]);
149
+ });
150
+ });
151
+
152
+ describe("parseAppSourceFingerprint", () => {
153
+ test("accepts a compile sidecar with compiledAt", () => {
154
+ const parsed = parseAppSourceFingerprint({
155
+ algorithm: "sha256",
156
+ compiledAt: "2026-09-09T00:00:00.000Z",
157
+ files: { "src/main.tsx": "abc" },
158
+ });
159
+ expect(parsed).toEqual({
160
+ algorithm: "sha256",
161
+ compiledAt: "2026-09-09T00:00:00.000Z",
162
+ files: { "src/main.tsx": "abc" },
163
+ });
164
+ });
165
+
166
+ test("returns null for an invalid sidecar", () => {
167
+ expect(parseAppSourceFingerprint({ algorithm: "md5", files: {} })).toBe(
168
+ null,
169
+ );
170
+ expect(parseAppSourceFingerprint(null)).toBeNull();
171
+ });
172
+
173
+ test("writeAppSourceFingerprint records a sidecar the parser accepts", () => {
174
+ write("src/main.tsx", "src");
175
+ mkdirSync(join(root, "dist"), { recursive: true });
176
+ writeAppSourceFingerprint(root, join(root, "dist"));
177
+ const raw = JSON.parse(
178
+ readFileSync(join(root, "dist", SOURCE_FINGERPRINT_FILENAME), "utf-8"),
179
+ );
180
+ expect(parseAppSourceFingerprint(raw)?.files["src/main.tsx"]).toMatch(
181
+ /^[0-9a-f]{64}$/,
182
+ );
183
+ });
184
+ });
@@ -249,14 +249,23 @@ describe("createResolveToolsCallback — toolContextPin", () => {
249
249
  });
250
250
  }
251
251
 
252
- test("control: without a pin, a clientless fork drops every client-gated tool from the wire", () => {
252
+ test("control: without a pin, a clientless fork drops client-gated tools but keeps host and UI tools", () => {
253
253
  projectedSkillToolNames = [];
254
254
  const resolve = createResolveToolsCallback(
255
255
  CLIENT_GATED_DEFS,
256
256
  clientlessExecutionCtx(),
257
257
  )!;
258
258
 
259
- expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toEqual(["remember"]);
259
+ // Host tool definitions stay on the wire for background turns, and
260
+ // ui_show stays because background UI surfaces persist and return. The
261
+ // executor rejects host tools on explicitly non-interactive turns before
262
+ // dispatch; ask_question and request_system_permission remain
263
+ // client-gated at resolution.
264
+ expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toEqual([
265
+ "remember",
266
+ "host_bash",
267
+ "ui_show",
268
+ ]);
260
269
  });
261
270
 
262
271
  test("a desktop-source pin restores the host/UI/client tool defs on the wire", () => {
@@ -291,7 +300,14 @@ describe("createResolveToolsCallback — toolContextPin", () => {
291
300
  }),
292
301
  )!;
293
302
 
294
- expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toEqual(["remember"]);
303
+ // Host tool definitions stay on the wire for background turns, and
304
+ // ui_show survives the clientless pin: it persists and returns. The
305
+ // executor rejects host tools on explicitly non-interactive turns.
306
+ expect(resolve(EMPTY_HISTORY).map((t) => t.name)).toEqual([
307
+ "remember",
308
+ "host_bash",
309
+ "ui_show",
310
+ ]);
295
311
  });
296
312
 
297
313
  test("invariant: a pinned-in tool is on the wire but can never execute", async () => {
@@ -3447,8 +3447,9 @@ describe("Subagent advisor-role consult", () => {
3447
3447
  });
3448
3448
 
3449
3449
  test("advisor consults still run when the parent conversation is gone", async () => {
3450
- // The parent is looked up only for its warm skill catalog, so an evicted
3451
- // conversation costs a section of the context pack, not the consult.
3450
+ // Advisor consults do not look up the parent conversation for a skill
3451
+ // catalog snapshot. An evicted parent still gets a consult; the context
3452
+ // pack loads the catalog from disk independently.
3452
3453
  mockFindConversation = () => undefined;
3453
3454
  const { captured, restore } = stubSpawn();
3454
3455
  try {
@@ -455,10 +455,11 @@ describe("resolveCallTtsProvider with requiresPcmAudio", () => {
455
455
  expect(result.provider?.id).toBe("elevenlabs");
456
456
  });
457
457
 
458
- test("without requiresPcmAudio the configured provider is not swapped", async () => {
458
+ test("without requiresPcmAudio the configured provider is not swapped for playability", async () => {
459
459
  testConfig.services.tts.provider = "deepgram";
460
- // No credentials at all — the CR transport path does not consult the
461
- // media-stream playability capability.
460
+ storedKeys["deepgram"] = "dg-key";
461
+ // The CR transport path does not consult the media-stream playability
462
+ // capability.
462
463
  registerStubProvider("deepgram", async () => {
463
464
  return { audio: Buffer.from("x"), contentType: "audio/mpeg" };
464
465
  });
@@ -468,6 +469,37 @@ describe("resolveCallTtsProvider with requiresPcmAudio", () => {
468
469
  expect(result.useSynthesizedPath).toBe(true);
469
470
  expect(result.audioFormat).toBe("mp3");
470
471
  });
472
+
473
+ test("managed speech stands in for a configured provider whose credential is missing", async () => {
474
+ testConfig.services.tts.provider = "deepgram";
475
+ // No deepgram key, managed speech available: the phone speaks through
476
+ // managed speech, the same substitution live voice makes.
477
+ registerStubProvider("deepgram", async () => {
478
+ throw new Error("should not be used");
479
+ });
480
+ registerStubProvider("vellum", async () => {
481
+ return { audio: Buffer.from("x"), contentType: "audio/mpeg" };
482
+ });
483
+
484
+ const result = await resolveCallTtsProvider();
485
+ expect(result.provider?.id).toBe("vellum");
486
+ expect(result.useSynthesizedPath).toBe(true);
487
+ });
488
+
489
+ test("without managed speech a credential-less configured provider is kept", async () => {
490
+ mockManagedSpeechAvailable = false;
491
+ try {
492
+ testConfig.services.tts.provider = "deepgram";
493
+ registerStubProvider("deepgram", async () => {
494
+ return { audio: Buffer.from("x"), contentType: "audio/mpeg" };
495
+ });
496
+
497
+ const result = await resolveCallTtsProvider();
498
+ expect(result.provider?.id).toBe("deepgram");
499
+ } finally {
500
+ mockManagedSpeechAvailable = true;
501
+ }
502
+ });
471
503
  });
472
504
 
473
505
  // ---------------------------------------------------------------------------
@@ -584,6 +616,7 @@ describe("speakSystemPrompt mid-stream failure after audio started", () => {
584
616
  test("non-PCM transport: skips the native text fallback — no full-prompt re-speak", async () => {
585
617
  testConfig.services.tts.provider = "fish-audio";
586
618
  testConfig.services.tts.providers["fish-audio"].referenceId = "ref-123";
619
+ storedKeys["fish-audio"] = "fa-key";
587
620
 
588
621
  const fishStream = jest.fn(failAfterFirstChunk());
589
622
  registerStreamingStubProvider("fish-audio", fishStream);
@@ -657,6 +690,7 @@ describe("speakSystemPrompt aborted synthesis", () => {
657
690
  test("non-PCM transport with native-fallback provider: abort skips the text fallback and end-of-turn", async () => {
658
691
  testConfig.services.tts.provider = "fish-audio";
659
692
  testConfig.services.tts.providers["fish-audio"].referenceId = "ref-123";
693
+ storedKeys["fish-audio"] = "fa-key";
660
694
 
661
695
  const controller = new AbortController();
662
696
  controller.abort();
@@ -742,6 +776,7 @@ describe("speakSystemPrompt aborted synthesis", () => {
742
776
  test("provider AbortError without any signal is a failure — non-PCM native fallback still runs", async () => {
743
777
  testConfig.services.tts.provider = "fish-audio";
744
778
  testConfig.services.tts.providers["fish-audio"].referenceId = "ref-123";
779
+ storedKeys["fish-audio"] = "fa-key";
745
780
 
746
781
  const fishSynthesize = jest.fn(async () => {
747
782
  throw new DOMException("socket torn down", "AbortError");
@@ -2,7 +2,7 @@
2
2
  * Reusable constants and helpers for the computer-use skill migration test suite.
3
3
  */
4
4
 
5
- /** The 11 computer_use_* action tool names provided by the bundled computer-use skill. */
5
+ /** The 12 computer_use_* action tool names provided by the bundled computer-use skill. */
6
6
  export const COMPUTER_USE_TOOL_NAMES = [
7
7
  "computer_use_observe",
8
8
  "computer_use_click",
@@ -13,9 +13,10 @@ export const COMPUTER_USE_TOOL_NAMES = [
13
13
  "computer_use_wait",
14
14
  "computer_use_open_app",
15
15
  "computer_use_run_applescript",
16
+ "computer_use_sequence",
16
17
  "computer_use_done",
17
18
  "computer_use_respond",
18
19
  ] as const;
19
20
 
20
21
  /** Number of computer_use_* tools. */
21
- export const COMPUTER_USE_TOOL_COUNT = COMPUTER_USE_TOOL_NAMES.length; // 11
22
+ export const COMPUTER_USE_TOOL_COUNT = COMPUTER_USE_TOOL_NAMES.length; // 12