@vellumai/assistant 0.12.1 → 0.12.2-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 (577) 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/node_modules/@vellumai/slack-text/src/index.ts +13 -8
  6. package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
  7. package/openapi.yaml +360 -51
  8. package/package.json +3 -1
  9. package/scripts/smoke-desktop-window-theme.ts +186 -0
  10. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  11. package/src/__tests__/anthropic-provider.test.ts +0 -63
  12. package/src/__tests__/app-compiler.test.ts +6 -0
  13. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  14. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  15. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  16. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  17. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  18. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  19. package/src/__tests__/computer-use-tools.test.ts +19 -7
  20. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  21. package/src/__tests__/config-schema.test.ts +28 -29
  22. package/src/__tests__/config-watcher.test.ts +23 -8
  23. package/src/__tests__/connection-model-compat.test.ts +2 -2
  24. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  25. package/src/__tests__/conversation-pairing.test.ts +26 -0
  26. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  27. package/src/__tests__/conversation-queue.test.ts +48 -207
  28. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  29. package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
  30. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  31. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  32. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
  33. package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
  34. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  35. package/src/__tests__/conversation-title-service.test.ts +14 -10
  36. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  37. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  38. package/src/__tests__/credential-record-retire.test.ts +171 -1
  39. package/src/__tests__/credential-routes.test.ts +250 -3
  40. package/src/__tests__/credential-security-invariants.test.ts +5 -3
  41. package/src/__tests__/cu-unified-flow.test.ts +14 -4
  42. package/src/__tests__/document-sync-tags.test.ts +25 -0
  43. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  44. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  45. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  46. package/src/__tests__/git-porcelain.test.ts +47 -0
  47. package/src/__tests__/heartbeat-service.test.ts +56 -40
  48. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  49. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  50. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  51. package/src/__tests__/host-cu-proxy.test.ts +596 -0
  52. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  53. package/src/__tests__/host-file-proxy.test.ts +11 -23
  54. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  55. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  56. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  57. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  58. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  59. package/src/__tests__/mcp-health-check.test.ts +8 -11
  60. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  61. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  62. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  63. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  64. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  65. package/src/__tests__/model-intents.test.ts +3 -0
  66. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  67. package/src/__tests__/oauth-commands-routes.test.ts +168 -0
  68. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  69. package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
  70. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  71. package/src/__tests__/openai-provider.test.ts +1 -0
  72. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  73. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  74. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  75. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  76. package/src/__tests__/qdrant-manager.test.ts +132 -0
  77. package/src/__tests__/resolve-app.test.ts +135 -0
  78. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  79. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  80. package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
  81. package/src/__tests__/service-account-token.test.ts +247 -0
  82. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  83. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  84. package/src/__tests__/skills.test.ts +9 -4
  85. package/src/__tests__/source-fingerprint.test.ts +184 -0
  86. package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
  87. package/src/__tests__/subagent-tools.test.ts +3 -2
  88. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  89. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  90. package/src/__tests__/ui-channel-variants.test.ts +1 -56
  91. package/src/__tests__/unicode.test.ts +13 -0
  92. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  93. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  94. package/src/__tests__/workspace-git-service.test.ts +26 -0
  95. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  96. package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
  97. package/src/acp/__tests__/acp-credentials.test.ts +13 -0
  98. package/src/acp/__tests__/agent-process.test.ts +258 -4
  99. package/src/acp/__tests__/auth-required.test.ts +96 -0
  100. package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
  101. package/src/acp/__tests__/client-handler.test.ts +90 -2
  102. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  103. package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
  104. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  105. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  106. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  107. package/src/acp/__tests__/session-manager.test.ts +895 -4
  108. package/src/acp/acp-claude-oauth.ts +328 -14
  109. package/src/acp/acp-credentials.ts +19 -0
  110. package/src/acp/agent-process.test.ts +4 -1
  111. package/src/acp/agent-process.ts +96 -10
  112. package/src/acp/auth-required.ts +50 -1
  113. package/src/acp/auto-install.test.ts +9 -7
  114. package/src/acp/claude-token-refresh.ts +150 -0
  115. package/src/acp/client-handler.ts +16 -3
  116. package/src/acp/model-config.test.ts +166 -0
  117. package/src/acp/model-config.ts +138 -0
  118. package/src/acp/prepare-agent-env.ts +21 -6
  119. package/src/acp/resolve-agent.test.ts +220 -6
  120. package/src/acp/resolve-agent.ts +88 -23
  121. package/src/acp/session-manager.test.ts +196 -3
  122. package/src/acp/session-manager.ts +441 -18
  123. package/src/acp/types.ts +50 -5
  124. package/src/agent/loop.ts +1 -0
  125. package/src/api/events/acp-session-model-update.test.ts +88 -0
  126. package/src/api/events/acp-session-model-update.ts +46 -0
  127. package/src/api/events/app-files-changed.ts +4 -3
  128. package/src/api/events/notification-intent.ts +2 -0
  129. package/src/api/events/user-message-echo.ts +7 -1
  130. package/src/api/index.ts +6 -0
  131. package/src/api/responses/conversation-message.ts +5 -0
  132. package/src/apps/app-store.ts +1 -1
  133. package/src/apps/resolve-app.ts +81 -0
  134. package/src/apps/source-fingerprint.ts +266 -0
  135. package/src/avatar/identity-avatar.ts +7 -2
  136. package/src/bundler/app-compiler.ts +15 -0
  137. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  138. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  139. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  140. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  141. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  142. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  143. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  144. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  145. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  146. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  147. package/src/calls/__tests__/voice-control-protocol.test.ts +120 -25
  148. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  149. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  150. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  151. package/src/calls/__tests__/voice-session-bridge.test.ts +56 -1
  152. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  153. package/src/calls/barge-in-guard.ts +118 -0
  154. package/src/calls/call-constants.ts +24 -4
  155. package/src/calls/call-controller.ts +499 -199
  156. package/src/calls/call-transport.ts +16 -0
  157. package/src/calls/media-stream-output.ts +32 -0
  158. package/src/calls/media-stream-server.ts +82 -30
  159. package/src/calls/media-stream-stt-session.ts +15 -0
  160. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  161. package/src/calls/resolve-call-tts-provider.ts +33 -2
  162. package/src/calls/tts-call-strategy.ts +9 -5
  163. package/src/calls/types.ts +2 -0
  164. package/src/calls/voice-control-protocol.ts +155 -21
  165. package/src/calls/voice-leg-coordinator.ts +189 -0
  166. package/src/calls/voice-progress-cadence.ts +423 -0
  167. package/src/calls/voice-session-bridge.ts +92 -72
  168. package/src/calls/voice-triage-escalate.ts +178 -59
  169. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  170. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  171. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  172. package/src/cli/commands/apps.help.ts +67 -13
  173. package/src/cli/commands/apps.ts +183 -0
  174. package/src/cli/commands/bash.help.ts +8 -13
  175. package/src/cli/commands/bash.ts +7 -25
  176. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  177. package/src/cli/commands/channels/index.help.ts +70 -2
  178. package/src/cli/commands/channels/index.ts +16 -3
  179. package/src/cli/commands/channels/send.ts +128 -0
  180. package/src/cli/commands/credentials.help.ts +6 -2
  181. package/src/cli/commands/credentials.ts +2 -2
  182. package/src/cli/commands/mcp.help.ts +8 -8
  183. package/src/cli/commands/mcp.ts +1 -1
  184. package/src/cli/commands/oauth/status.ts +5 -0
  185. package/src/cli/commands/roadmap.help.ts +5 -1
  186. package/src/cli/commands/roadmap.ts +1 -1
  187. package/src/cli/lib/bundled-marketplace.json +103 -90
  188. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  189. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  190. package/src/config/__tests__/platform-identity.test.ts +255 -0
  191. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  192. package/src/config/acp-agent-write.ts +43 -0
  193. package/src/config/acp-defaults.test.ts +13 -3
  194. package/src/config/acp-defaults.ts +23 -6
  195. package/src/config/acp-schema.test.ts +81 -0
  196. package/src/config/acp-schema.ts +30 -2
  197. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  198. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  199. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  200. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  201. package/src/config/bundled-skills/computer-use/SKILL.md +29 -2
  202. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  203. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  204. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  205. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  206. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  207. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  208. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  209. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  210. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  211. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  212. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  213. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  214. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  215. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  216. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  217. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  218. package/src/config/default-profile-catalog.ts +1 -1
  219. package/src/config/env.ts +2 -2
  220. package/src/config/feature-flag-registry.json +33 -13
  221. package/src/config/loader.ts +2 -0
  222. package/src/config/memory-v3-gate.test.ts +31 -0
  223. package/src/config/memory-v3-gate.ts +20 -16
  224. package/src/config/platform-identity.ts +201 -0
  225. package/src/config/platform-rehydration.ts +11 -27
  226. package/src/config/schema.ts +2 -2
  227. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  228. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  229. package/src/config/schemas/calls.ts +1 -28
  230. package/src/config/schemas/live-voice.ts +1 -173
  231. package/src/config/schemas/mcp.ts +1 -1
  232. package/src/config/schemas/memory-retrospective.ts +10 -0
  233. package/src/config/schemas/services.ts +18 -0
  234. package/src/config/schemas/skills.ts +0 -14
  235. package/src/config/schemas/voice.ts +150 -0
  236. package/src/credential-execution/process-manager.test.ts +18 -19
  237. package/src/credential-health/credential-health-service.ts +11 -17
  238. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  239. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  240. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  241. package/src/daemon/app-change-notify.ts +45 -0
  242. package/src/daemon/config-watcher.ts +13 -194
  243. package/src/daemon/conversation-error.ts +3 -2
  244. package/src/daemon/conversation-media-retry.ts +2 -1
  245. package/src/daemon/conversation-process.ts +35 -64
  246. package/src/daemon/conversation-runtime-assembly.ts +20 -7
  247. package/src/daemon/conversation-skill-tools.ts +13 -31
  248. package/src/daemon/conversation-store.ts +1 -1
  249. package/src/daemon/conversation-surfaces.ts +21 -10
  250. package/src/daemon/conversation-tool-setup.ts +45 -59
  251. package/src/daemon/conversation.ts +0 -3
  252. package/src/daemon/dictation-profile-store.ts +2 -1
  253. package/src/daemon/handlers/shared.ts +2 -1
  254. package/src/daemon/handlers/skills.ts +6 -3
  255. package/src/daemon/host-bash-proxy.ts +8 -12
  256. package/src/daemon/host-browser-proxy.ts +15 -1
  257. package/src/daemon/host-cu-proxy.ts +224 -19
  258. package/src/daemon/host-file-proxy.ts +8 -10
  259. package/src/daemon/host-proxy-preactivation.ts +2 -1
  260. package/src/daemon/host-transfer-proxy.ts +14 -18
  261. package/src/daemon/lifecycle.ts +0 -5
  262. package/src/daemon/mcp-reload-service.ts +5 -5
  263. package/src/daemon/message-types/sync.ts +1 -0
  264. package/src/daemon/orphan-reaper.ts +1 -1
  265. package/src/daemon/providers-setup.ts +1 -1
  266. package/src/daemon/reaction-history-render.ts +2 -1
  267. package/src/daemon/shutdown-handlers.ts +1 -9
  268. package/src/daemon/skill-memory-refresh.ts +64 -0
  269. package/src/daemon/tool-side-effects.ts +7 -18
  270. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  271. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  272. package/src/desktop/desktop-chrome-policy.ts +47 -0
  273. package/src/desktop/desktop-chrome-session.ts +37 -0
  274. package/src/desktop/desktop-dependencies.test.ts +102 -0
  275. package/src/desktop/desktop-dependencies.ts +308 -0
  276. package/src/desktop/desktop-feature.test.ts +18 -0
  277. package/src/desktop/desktop-feature.ts +19 -0
  278. package/src/desktop/desktop-panel-config.test.ts +135 -0
  279. package/src/desktop/desktop-panel-config.ts +149 -0
  280. package/src/desktop/desktop-session-manager.test.ts +730 -0
  281. package/src/desktop/desktop-session-manager.ts +850 -0
  282. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  283. package/src/desktop/desktop-stream-bridge.ts +214 -0
  284. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  285. package/src/desktop/desktop-wallpaper.ts +113 -0
  286. package/src/desktop/desktop-window-drag.ts +177 -0
  287. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  288. package/src/desktop/desktop-window-manager-config.ts +238 -0
  289. package/src/desktop/desktop-window-theme.ts +170 -0
  290. package/src/heartbeat/heartbeat-service.ts +21 -3
  291. package/src/home/__tests__/feed-writer.test.ts +22 -0
  292. package/src/home/feed-writer.ts +11 -2
  293. package/src/hooks/types.ts +33 -0
  294. package/src/i18n/AGENTS.md +22 -0
  295. package/src/i18n/__tests__/i18n.test.ts +108 -0
  296. package/src/i18n/index.ts +16 -0
  297. package/src/i18n/locales.ts +93 -0
  298. package/src/i18n/messages.ts +61 -0
  299. package/src/i18n/title.ts +25 -0
  300. package/src/inbound/platform-callback-registration.ts +4 -5
  301. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
  302. package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
  303. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  304. package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
  305. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  306. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  307. package/src/live-voice/__tests__/protocol.test.ts +39 -0
  308. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  309. package/src/live-voice/__tests__/session-controls.test.ts +79 -0
  310. package/src/live-voice/live-voice-photo.ts +25 -6
  311. package/src/live-voice/live-voice-session.ts +297 -699
  312. package/src/live-voice/protocol.ts +60 -2
  313. package/src/live-voice/session-controls.ts +111 -0
  314. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  315. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  316. package/src/mcp/effective-config.ts +3 -3
  317. package/src/mcp/mcp-header-store.ts +29 -45
  318. package/src/mcp/spec-schema.ts +111 -0
  319. package/src/mcp/startup.ts +8 -4
  320. package/src/mcp/workspace-mcp-config.ts +188 -0
  321. package/src/messaging/provider.ts +10 -3
  322. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  323. package/src/messaging/providers/callback-routing.ts +18 -0
  324. package/src/messaging/providers/channel-transport.ts +56 -0
  325. package/src/messaging/providers/discord/transport.ts +17 -0
  326. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  327. package/src/messaging/providers/slack/adapter.ts +7 -44
  328. package/src/messaging/providers/slack/transport.ts +16 -0
  329. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  330. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  331. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  332. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  333. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  334. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  335. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  336. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  337. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  338. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  339. package/src/notifications/adapters/macos.ts +3 -0
  340. package/src/notifications/assistant-reply-producer.ts +8 -3
  341. package/src/notifications/conversation-pairing.ts +36 -16
  342. package/src/notifications/delivered-post-record.ts +11 -1
  343. package/src/notifications/guardian-feed-projection.ts +125 -5
  344. package/src/notifications/home-feed-side-effect.ts +55 -7
  345. package/src/notifications/schedule-result-producer.ts +66 -22
  346. package/src/oauth/AGENTS.md +2 -0
  347. package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
  348. package/src/oauth/byo-connection.test.ts +105 -0
  349. package/src/oauth/byo-connection.ts +36 -6
  350. package/src/oauth/connection-resolver.test.ts +100 -0
  351. package/src/oauth/connection-resolver.ts +120 -3
  352. package/src/oauth/provider-serializer.ts +9 -0
  353. package/src/oauth/scope-utils.ts +24 -0
  354. package/src/oauth/seed-providers.ts +314 -27
  355. package/src/outbound-proxy/certs.ts +3 -5
  356. package/src/permissions/checker.test.ts +11 -6
  357. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  358. package/src/persistence/conversation-crud.ts +71 -63
  359. package/src/persistence/conversation-lineage.ts +12 -4
  360. package/src/persistence/conversation-queries.ts +2 -2
  361. package/src/persistence/conversation-title-placeholders.ts +23 -0
  362. package/src/persistence/conversation-title-service.ts +9 -26
  363. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  364. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  365. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  366. package/src/persistence/message-cursor.ts +78 -0
  367. package/src/persistence/message-reads.ts +30 -0
  368. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  369. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  370. package/src/persistence/schema/memory-core.ts +5 -0
  371. package/src/platform/client.test.ts +4 -2
  372. package/src/platform/client.ts +2 -10
  373. package/src/plugin-api/constants.ts +2 -0
  374. package/src/plugin-api/index.ts +1 -0
  375. package/src/plugin-api/types.ts +2 -0
  376. package/src/plugins/defaults/index.ts +2 -0
  377. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  378. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  379. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  380. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  381. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  382. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  383. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  384. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  385. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  386. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  387. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  388. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  389. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  390. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  391. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  392. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  393. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  394. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  395. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  396. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  397. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  398. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  399. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  400. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  401. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  402. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  403. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  404. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  405. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  406. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  407. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  408. package/src/plugins/defaults/memory/startup.ts +28 -2
  409. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  410. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  411. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  412. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  413. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  414. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  415. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  416. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  417. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  418. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  419. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  420. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  421. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  422. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  423. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  424. package/src/plugins/defaults/memory/worker.ts +6 -0
  425. package/src/plugins/mcp-servers.ts +15 -75
  426. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  427. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  428. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  429. package/src/providers/anthropic/client.ts +0 -47
  430. package/src/providers/gemini/client.ts +9 -23
  431. package/src/providers/gemini-thought-signature.test.ts +63 -0
  432. package/src/providers/gemini-thought-signature.ts +44 -0
  433. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  434. package/src/providers/inference/auth.ts +2 -3
  435. package/src/providers/inference/connection-availability.ts +27 -8
  436. package/src/providers/inference/endpoint-probe.ts +10 -1
  437. package/src/providers/inference/resolve-auth.ts +22 -4
  438. package/src/providers/inference/service-account-token.ts +248 -0
  439. package/src/providers/model-catalog.ts +34 -0
  440. package/src/providers/model-intents.ts +10 -5
  441. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  442. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  443. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  444. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  445. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  446. package/src/providers/openai/chat-completions-provider.ts +212 -79
  447. package/src/providers/openai/codex-models.ts +0 -4
  448. package/src/providers/openai/google-thought-signature.ts +320 -0
  449. package/src/providers/opencode/client.test.ts +73 -4
  450. package/src/providers/opencode/client.ts +60 -3
  451. package/src/providers/openrouter/client.ts +20 -0
  452. package/src/providers/outbound-request-sanitize.ts +96 -0
  453. package/src/providers/retry.ts +30 -19
  454. package/src/providers/unparseable-tool-args.ts +5 -1
  455. package/src/runtime/AGENTS.md +3 -1
  456. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  457. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  458. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  459. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  460. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  461. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  462. package/src/runtime/agent-wake.ts +2 -1
  463. package/src/runtime/assistant-event-hub.ts +21 -3
  464. package/src/runtime/auth/same-actor.ts +38 -3
  465. package/src/runtime/channel-send.ts +316 -0
  466. package/src/runtime/host-observe.ts +11 -1
  467. package/src/runtime/http-server.ts +160 -151
  468. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
  469. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  470. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -0
  471. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  472. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  473. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  474. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  475. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  476. package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
  477. package/src/runtime/routes/acp-routes.test.ts +15 -0
  478. package/src/runtime/routes/acp-routes.ts +72 -9
  479. package/src/runtime/routes/app-management-routes.ts +90 -2
  480. package/src/runtime/routes/app-routes.ts +1 -1
  481. package/src/runtime/routes/channel-send-routes.ts +85 -0
  482. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  483. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  484. package/src/runtime/routes/conversation-routes.ts +16 -3
  485. package/src/runtime/routes/credential-routes.ts +20 -14
  486. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  487. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  488. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  489. package/src/runtime/routes/events-routes.ts +4 -0
  490. package/src/runtime/routes/host-cu-routes.ts +6 -0
  491. package/src/runtime/routes/index.ts +4 -2
  492. package/src/runtime/routes/log-export-routes.ts +2 -0
  493. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  494. package/src/runtime/routes/migration-routes.ts +6 -20
  495. package/src/runtime/routes/oauth-commands-routes.ts +50 -0
  496. package/src/runtime/routes/oauth-providers.ts +7 -0
  497. package/src/runtime/routes/platform-routes.ts +18 -15
  498. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  499. package/src/runtime/routes/workspace-routes.ts +46 -63
  500. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  501. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  502. package/src/runtime/routes/workspace-utils.ts +17 -0
  503. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  504. package/src/runtime/services/conversation-serializer.ts +16 -3
  505. package/src/runtime/sync/resource-sync-events.ts +2 -1
  506. package/src/schedule/run-script.ts +2 -1
  507. package/src/schedule/schedule-store.ts +9 -2
  508. package/src/schedule/worker.ts +1 -1
  509. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  510. package/src/security/ces-http-record-backend.ts +120 -0
  511. package/src/security/ces-rpc-record-backend.ts +7 -0
  512. package/src/security/untrusted-content.ts +3 -1
  513. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  514. package/src/skills/workspace-skill-md-signature.ts +50 -0
  515. package/src/subagent/consult-context.ts +12 -25
  516. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  517. package/src/tools/acp/list-agents.test.ts +3 -1
  518. package/src/tools/acp/spawn.test.ts +146 -11
  519. package/src/tools/acp/spawn.ts +21 -3
  520. package/src/tools/acp/status.test.ts +94 -0
  521. package/src/tools/acp/status.ts +40 -3
  522. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  523. package/src/tools/browser/browser-execution.ts +1 -1
  524. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  525. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  526. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  527. package/src/tools/browser/network-recorder.ts +2 -1
  528. package/src/tools/capability-offer.test.ts +22 -2
  529. package/src/tools/capability-offer.ts +16 -1
  530. package/src/tools/computer-use/definitions.ts +121 -2
  531. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  532. package/src/tools/credentials/metadata-store.ts +100 -0
  533. package/src/tools/filesystem/search.ts +2 -1
  534. package/src/tools/host-filesystem/edit.test.ts +2 -1
  535. package/src/tools/host-filesystem/edit.ts +2 -2
  536. package/src/tools/host-filesystem/read.test.ts +2 -1
  537. package/src/tools/host-filesystem/read.ts +2 -2
  538. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  539. package/src/tools/host-filesystem/transfer.ts +2 -2
  540. package/src/tools/host-filesystem/write.test.ts +2 -1
  541. package/src/tools/host-filesystem/write.ts +2 -2
  542. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  543. package/src/tools/host-terminal/host-shell.ts +4 -20
  544. package/src/tools/network/web-search.ts +6 -1
  545. package/src/tools/policy-context.ts +6 -7
  546. package/src/tools/skills/scaffold-managed.ts +61 -3
  547. package/src/tools/subagent/spawn.ts +5 -8
  548. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  549. package/src/tools/terminal/sanitized-bash.ts +105 -0
  550. package/src/tools/ui-surface/channel-variants.ts +10 -59
  551. package/src/tools/watch/watch-retro-report.ts +0 -8
  552. package/src/types/host-capabilities.ts +5 -3
  553. package/src/util/__tests__/language-subtag.test.ts +23 -1
  554. package/src/util/ca-bundle.ts +13 -0
  555. package/src/util/language-subtag.ts +16 -0
  556. package/src/util/platform.ts +8 -0
  557. package/src/util/unicode.ts +15 -1
  558. package/src/watch/watch-retro.ts +6 -8
  559. package/src/watch/watch-timeline.ts +2 -2
  560. package/src/workflows/run-manager.ts +2 -1
  561. package/src/workspace/git-porcelain.ts +66 -0
  562. package/src/workspace/git-service.ts +6 -55
  563. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  564. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  565. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  566. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  567. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  568. package/src/workspace/migrations/registry.ts +6 -0
  569. package/src/__tests__/app-source-watcher.test.ts +0 -218
  570. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  571. package/src/__tests__/speaker-identification.test.ts +0 -56
  572. package/src/calls/speaker-identification.ts +0 -233
  573. package/src/daemon/app-source-watcher.ts +0 -218
  574. package/src/messaging/providers/whatsapp/client.ts +0 -29
  575. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  576. package/src/util/fs-watcher-error.ts +0 -36
  577. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -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
@@ -2,10 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import type { ToolDefinition } from "../providers/types.js";
4
4
  import { ACTIVATION_MOMENT_PARAMS } from "../telemetry/activation-funnel.js";
5
- import {
6
- injectActivationMomentParam,
7
- projectUiToolsForChannel,
8
- } from "../tools/ui-surface/channel-variants.js";
5
+ import { injectActivationMomentParam } from "../tools/ui-surface/channel-variants.js";
9
6
  import {
10
7
  uiDismissTool,
11
8
  uiShowTool,
@@ -23,58 +20,6 @@ const defs: ToolDefinition[] = [
23
20
  },
24
21
  ];
25
22
 
26
- function surfaceTypeEnum(def: ToolDefinition): string[] {
27
- return (
28
- def.input_schema as { properties: { surface_type: { enum: string[] } } }
29
- ).properties.surface_type.enum;
30
- }
31
-
32
- describe("projectUiToolsForChannel", () => {
33
- test("returns the same array for channels without a variant", () => {
34
- for (const channel of [undefined, "macos", "telegram", "phone"]) {
35
- expect(projectUiToolsForChannel(defs, channel)).toBe(defs);
36
- }
37
- });
38
-
39
- test("slack gets a task_progress-only ui_show", () => {
40
- const projected = projectUiToolsForChannel(defs, "slack");
41
- const uiShow = projected.find((d) => d.name === "ui_show")!;
42
-
43
- expect(surfaceTypeEnum(uiShow)).toEqual(["card"]);
44
- expect(uiShow.description).toContain("task_progress");
45
- expect(uiShow.description).toContain(
46
- "the only surface this channel renders",
47
- );
48
- expect(uiShow.description).not.toContain("oauth_connect");
49
- expect(uiShow.description).not.toContain("dynamic_page");
50
- expect(uiShow.description!.length).toBeLessThan(
51
- uiShowTool.description.length / 2,
52
- );
53
- });
54
-
55
- test("slack projection keeps execute and leaves other tools untouched", () => {
56
- const projected = projectUiToolsForChannel(defs, "slack");
57
- const uiShow = projected.find((d) => d.name === "ui_show") as {
58
- execute?: unknown;
59
- };
60
-
61
- expect(uiShow.execute).toBe(uiShowTool.execute);
62
- expect(projected.find((d) => d.name === "ui_update")).toBe(uiUpdateTool);
63
- expect(projected.find((d) => d.name === "ui_dismiss")).toBe(uiDismissTool);
64
- expect(projected.find((d) => d.name === "bash")).toBe(defs[3]);
65
- });
66
-
67
- test("projection never mutates the shared definitions", () => {
68
- const descriptionBefore = uiShowTool.description;
69
- const schemaBefore = JSON.stringify(uiShowTool.input_schema);
70
-
71
- projectUiToolsForChannel(defs, "slack");
72
-
73
- expect(uiShowTool.description).toBe(descriptionBefore);
74
- expect(JSON.stringify(uiShowTool.input_schema)).toBe(schemaBefore);
75
- });
76
- });
77
-
78
23
  describe("injectActivationMomentParam", () => {
79
24
  function properties(def: ToolDefinition): Record<string, unknown> {
80
25
  return (def.input_schema as { properties: Record<string, unknown> })
@@ -171,6 +171,19 @@ describe("stripOrphanedSurrogatesDeep", () => {
171
171
  expect(result.value.b[1]).toBe("also clean");
172
172
  });
173
173
 
174
+ test("skipKey leaves a property unscanned and uncopied", () => {
175
+ const payload = { type: "base64", data: `raw${HIGH}` };
176
+ const input = { text: `bad${HIGH}`, payload };
177
+ const result = stripOrphanedSurrogatesDeep(input, {
178
+ skipKey: (key, parent) => key === "data" && parent.type === "base64",
179
+ });
180
+ expect(result.changed).toBe(true);
181
+ expect(result.fixedStringCount).toBe(1);
182
+ expect(result.value.text).toBe(`bad${REPLACEMENT}`);
183
+ // The skipped subtree is carried over by reference, orphan and all.
184
+ expect(result.value.payload).toBe(payload);
185
+ });
186
+
174
187
  test("leaves non-plain objects untouched", () => {
175
188
  class Custom {
176
189
  value = `bad${HIGH}`;
@@ -136,6 +136,10 @@ describe("vellum-self-knowledge skill", () => {
136
136
  const result = await executeSkillLoad({ skill: "vellum-self-knowledge" });
137
137
  expect(result.content).toContain("## Critical Rule");
138
138
  expect(result.content).toContain("Never answer from memory");
139
+ expect(result.content).toContain("distinct, legacy Vellum product");
140
+ expect(result.content).toContain(
141
+ "Never use it to answer questions about this Vellum",
142
+ );
139
143
  });
140
144
 
141
145
  // ── Resolution order ─────────────────────────────────────────────────
@@ -1,13 +1,10 @@
1
1
  /**
2
2
  * The gate that decides whether a watch retrospective can report at all.
3
3
  *
4
- * A retrospective runs as a `clientless` wake, which pins the turn
5
- * non-interactive. `conversation-tool-setup` gates the whole `ui_surface`
6
- * family on a client being present, so in that turn `ui_show` is not denied,
7
- * it is absent: a retrospective told to call it can only tell the user it
8
- * cannot. Nothing about the card's schema, its renderer, or the prompt's
9
- * wording reveals that, which is why it is asserted here against the real
10
- * registry rather than assumed anywhere else.
4
+ * A retrospective runs as a `clientless` wake, but its report continues to
5
+ * use the post-turn renderer so the report lands after its tool call has been
6
+ * persisted. The core UI tools remain available on that turn for other
7
+ * background work, so this file pins both contracts against the real registry.
11
8
  */
12
9
 
13
10
  import { afterAll, describe, expect, test } from "bun:test";
@@ -54,20 +51,14 @@ describe("watch retrospective tool availability", () => {
54
51
  ).toBe(true);
55
52
  });
56
53
 
57
- test("ui_show is absent from a clientless turn", async () => {
54
+ test("core UI tools stay available on a clientless turn", async () => {
58
55
  await initializeTools();
59
56
  const ctx = clientlessContext();
60
57
 
61
- // The reason the report does not go through `ui_show`. If this ever flips
62
- // to true, the extra tool above can be reconsidered; while it is false,
63
- // routing the retrospective's card through `ui_show` produces a turn that
64
- // cannot report at all.
65
- expect(isToolActiveForContext("ui_show", ctx)).toBe(false);
66
- expect(isToolActiveForContext("ui_update", ctx)).toBe(false);
67
- expect(isToolActiveForContext("ui_dismiss", ctx)).toBe(false);
58
+ for (const name of ["ui_show", "ui_update", "ui_dismiss"]) {
59
+ expect(isToolActiveForContext(name, ctx)).toBe(true);
60
+ }
68
61
 
69
- // And the gate really is about the client, not about the tools being
70
- // unregistered: with one attached, the same names are active.
71
62
  const withClient = clientfulContext();
72
63
  expect(isToolActiveForContext("ui_show", withClient)).toBe(true);
73
64
  });
@@ -4,6 +4,7 @@ import {
4
4
  existsSync,
5
5
  mkdirSync,
6
6
  readFileSync,
7
+ renameSync,
7
8
  rmSync,
8
9
  statSync,
9
10
  symlinkSync,
@@ -523,6 +524,31 @@ describe("WorkspaceGitService", () => {
523
524
  expect(trackedFiles()).not.toContain("gone-b.txt");
524
525
  expect(trackedFiles()).not.toContain("gone-c.txt");
525
526
  });
527
+
528
+ test("batched rename stages origin and destination even when they split", async () => {
529
+ const service = new WorkspaceGitService(testDir);
530
+ await service.ensureInitialized();
531
+
532
+ writeFileSync(join(testDir, "old-name.txt"), "moved");
533
+ await service.commitChanges("Seed rename source");
534
+
535
+ renameSync(join(testDir, "old-name.txt"), join(testDir, "new-name.txt"));
536
+ for (const name of ["x0.txt", "x1.txt", "x2.txt", "x3.txt"]) {
537
+ writeFileSync(join(testDir, name), name);
538
+ }
539
+
540
+ await withStageBatchSize(2, async () => {
541
+ await service.commitChanges("Rename across batches");
542
+ });
543
+
544
+ expect(trackedFiles()).toContain("new-name.txt");
545
+ expect(trackedFiles()).not.toContain("old-name.txt");
546
+ const status = execFileSync("git", ["status", "--porcelain"], {
547
+ cwd: testDir,
548
+ encoding: "utf-8",
549
+ }).trim();
550
+ expect(status).toBe("");
551
+ });
526
552
  });
527
553
 
528
554
  describe("getStatus", () => {
@@ -0,0 +1,336 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from "node:fs";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { Database } from "bun:sqlite";
11
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
12
+
13
+ import { repairRetiredCodexGpt54ModelIdsMigration } from "../workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.js";
14
+ import { WORKSPACE_MIGRATIONS } from "../workspace/migrations/registry.js";
15
+ import { assertNotLiveDb } from "./assert-not-live-db.js";
16
+
17
+ const STALE = "gpt-5.4";
18
+ const STALE_MINI = "gpt-5.4-mini";
19
+ const REPLACEMENT = "gpt-5.5";
20
+ const REPLACEMENT_MINI = "gpt-5.6-luna";
21
+
22
+ const API_KEY_AUTH = '{"type":"api_key","credential":"credential/openai"}';
23
+ const SUBSCRIPTION_AUTH =
24
+ '{"type":"oauth_subscription","credential":"credential/chatgpt"}';
25
+
26
+ let workspaceDir: string;
27
+
28
+ function freshWorkspace(): void {
29
+ workspaceDir = join(
30
+ tmpdir(),
31
+ `vellum-migration-154-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
32
+ );
33
+ mkdirSync(workspaceDir, { recursive: true });
34
+ }
35
+
36
+ function writeConfig(data: Record<string, unknown>): void {
37
+ writeFileSync(
38
+ join(workspaceDir, "config.json"),
39
+ JSON.stringify(data, null, 2) + "\n",
40
+ );
41
+ }
42
+
43
+ function readConfig(): Record<string, unknown> {
44
+ return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
45
+ }
46
+
47
+ function readLlm(): Record<string, any> {
48
+ return readConfig().llm as Record<string, any>;
49
+ }
50
+
51
+ function seedRows(
52
+ rows: Array<{ name: string; provider: string; auth: string }>,
53
+ ): void {
54
+ mkdirSync(join(workspaceDir, "data", "db"), { recursive: true });
55
+ const db = new Database(join(workspaceDir, "data", "db", "assistant.db"));
56
+ db.run(`CREATE TABLE IF NOT EXISTS provider_connections (
57
+ name TEXT PRIMARY KEY,
58
+ provider TEXT NOT NULL,
59
+ auth TEXT NOT NULL,
60
+ created_at INTEGER NOT NULL,
61
+ updated_at INTEGER NOT NULL
62
+ )`);
63
+ for (const row of rows) {
64
+ db.query(
65
+ `INSERT INTO provider_connections (name, provider, auth, created_at, updated_at) VALUES (?, ?, ?, 1, 1)`,
66
+ ).run(row.name, row.provider, row.auth);
67
+ }
68
+ db.close();
69
+ }
70
+
71
+ const SUBSCRIPTION_ROW = {
72
+ name: "chatgpt-subscription",
73
+ provider: "chatgpt",
74
+ auth: SUBSCRIPTION_AUTH,
75
+ };
76
+ const API_KEY_ROW = {
77
+ name: "openai-key",
78
+ provider: "openai",
79
+ auth: API_KEY_AUTH,
80
+ };
81
+
82
+ beforeEach(() => {
83
+ freshWorkspace();
84
+ });
85
+
86
+ afterEach(() => {
87
+ if (existsSync(workspaceDir)) {
88
+ assertNotLiveDb(workspaceDir);
89
+ rmSync(workspaceDir, { recursive: true, force: true });
90
+ }
91
+ });
92
+
93
+ describe("154-repair-retired-codex-gpt-5-4-model-ids migration", () => {
94
+ test("has correct migration id and is registered", () => {
95
+ expect(repairRetiredCodexGpt54ModelIdsMigration.id).toBe(
96
+ "154-repair-retired-codex-gpt-5-4-model-ids",
97
+ );
98
+ expect(WORKSPACE_MIGRATIONS.map((m) => m.id)).toContain(
99
+ "154-repair-retired-codex-gpt-5-4-model-ids",
100
+ );
101
+ });
102
+
103
+ test("repairs chatgpt fragments in default, call sites, and profiles", () => {
104
+ writeConfig({
105
+ llm: {
106
+ default: { provider: "chatgpt", model: STALE },
107
+ callSites: {
108
+ recall: { provider: "chatgpt", model: STALE_MINI, maxTokens: 4096 },
109
+ heartbeat: { provider: "chatgpt", model: `${STALE}-tuned` },
110
+ malformed: STALE,
111
+ },
112
+ profiles: {
113
+ codex: { provider: "chatgpt", model: STALE, source: "user" },
114
+ budget: { provider: "chatgpt", model: STALE_MINI },
115
+ current: { provider: "chatgpt", model: "gpt-5.6-terra" },
116
+ // The identity fails schema validation regardless of a stray
117
+ // binding, so it is repaired without consulting the rows.
118
+ strayBinding: {
119
+ provider: "chatgpt",
120
+ provider_connection: "openai-key",
121
+ model: STALE,
122
+ },
123
+ },
124
+ },
125
+ });
126
+
127
+ repairRetiredCodexGpt54ModelIdsMigration.run(workspaceDir);
128
+
129
+ const llm = readLlm();
130
+ expect(llm.default.model).toBe(REPLACEMENT);
131
+ expect(llm.callSites.recall.model).toBe(REPLACEMENT_MINI);
132
+ expect(llm.callSites.recall.maxTokens).toBe(4096);
133
+ // Non-exact matches and malformed leaves are untouched.
134
+ expect(llm.callSites.heartbeat.model).toBe(`${STALE}-tuned`);
135
+ expect(llm.callSites.malformed).toBe(STALE);
136
+ expect(llm.profiles.codex.model).toBe(REPLACEMENT);
137
+ expect(llm.profiles.codex.source).toBe("user");
138
+ expect(llm.profiles.budget.model).toBe(REPLACEMENT_MINI);
139
+ expect(llm.profiles.current.model).toBe("gpt-5.6-terra");
140
+ expect(llm.profiles.strayBinding.model).toBe(REPLACEMENT);
141
+ });
142
+
143
+ test("repairs entry-bound fragments whose row is the subscription", () => {
144
+ seedRows([
145
+ // Post-DB-migration-366 row shape.
146
+ SUBSCRIPTION_ROW,
147
+ // Pre-366 row shape: provider still openai, auth proves the route.
148
+ { name: "legacy-sub", provider: "openai", auth: SUBSCRIPTION_AUTH },
149
+ ]);
150
+ writeConfig({
151
+ llm: {
152
+ default: {
153
+ provider: "openai",
154
+ provider_connection: "chatgpt-subscription",
155
+ model: STALE,
156
+ },
157
+ callSites: {
158
+ recall: { provider: "chatgpt-subscription", model: STALE_MINI },
159
+ },
160
+ profiles: {
161
+ bound: { provider: "chatgpt-subscription", model: STALE },
162
+ legacy: { provider: "legacy-sub", model: STALE_MINI },
163
+ // Legacy binding: dispatch honors it ahead of the declared
164
+ // provider and accepts the ChatGPT row for openai.
165
+ pinned: {
166
+ provider: "openai",
167
+ provider_connection: "chatgpt-subscription",
168
+ model: STALE,
169
+ },
170
+ },
171
+ },
172
+ });
173
+
174
+ repairRetiredCodexGpt54ModelIdsMigration.run(workspaceDir);
175
+
176
+ const llm = readLlm();
177
+ expect(llm.default.model).toBe(REPLACEMENT);
178
+ expect(llm.callSites.recall.model).toBe(REPLACEMENT_MINI);
179
+ expect(llm.profiles.bound.model).toBe(REPLACEMENT);
180
+ expect(llm.profiles.legacy.model).toBe(REPLACEMENT_MINI);
181
+ expect(llm.profiles.pinned.model).toBe(REPLACEMENT);
182
+ });
183
+
184
+ test("judges a binding as a row even when named like a vendor", () => {
185
+ seedRows([
186
+ { name: "openai", provider: "openai", auth: SUBSCRIPTION_AUTH },
187
+ { name: "chatgpt", provider: "openai", auth: API_KEY_AUTH },
188
+ ]);
189
+ writeConfig({
190
+ llm: {
191
+ profiles: {
192
+ subRow: {
193
+ provider: "openai",
194
+ provider_connection: "openai",
195
+ model: STALE,
196
+ },
197
+ keyRow: {
198
+ provider: "chatgpt-subscription",
199
+ provider_connection: "chatgpt",
200
+ model: STALE_MINI,
201
+ },
202
+ },
203
+ },
204
+ });
205
+
206
+ repairRetiredCodexGpt54ModelIdsMigration.run(workspaceDir);
207
+
208
+ const llm = readLlm();
209
+ expect(llm.profiles.subRow.model).toBe(REPLACEMENT);
210
+ expect(llm.profiles.keyRow.model).toBe(STALE_MINI);
211
+ });
212
+
213
+ test("leaves API-key, other-vendor, providerless, and dangling fragments untouched", () => {
214
+ seedRows([API_KEY_ROW, SUBSCRIPTION_ROW]);
215
+ const config = {
216
+ llm: {
217
+ // Explicit vendors keep the model: API-key OpenAI still serves it
218
+ // and an openai-compatible endpoint may too.
219
+ default: { provider: "openai", model: STALE },
220
+ defaultProvider: { provider: "chatgpt" },
221
+ callSites: {
222
+ vision: { provider: "openai-compatible", model: STALE_MINI },
223
+ recall: { provider: "openai", model: STALE },
224
+ // A providerless pin is out of scope: it serves the model on an
225
+ // API-key or managed winner, and on a subscription winner the
226
+ // request already fails upstream with no config loss.
227
+ heartbeatAgent: { model: STALE },
228
+ // A raw call-site binding is stripped by the schema.
229
+ filingAgent: {
230
+ provider: "openai-key",
231
+ provider_connection: "chatgpt-subscription",
232
+ model: STALE,
233
+ },
234
+ },
235
+ profiles: {
236
+ byok: { provider: "openai", model: STALE_MINI },
237
+ keyBound: { provider: "openai-key", model: STALE },
238
+ // Entry name with no row: nothing proves it is the subscription.
239
+ dangling: { provider: "ghost", model: STALE },
240
+ // Dispatch honors the explicit binding ahead of the provider.
241
+ keyOverride: {
242
+ provider: "chatgpt-subscription",
243
+ provider_connection: "openai-key",
244
+ model: STALE,
245
+ },
246
+ },
247
+ },
248
+ };
249
+ writeConfig(config);
250
+ const before = readFileSync(join(workspaceDir, "config.json"), "utf-8");
251
+
252
+ repairRetiredCodexGpt54ModelIdsMigration.run(workspaceDir);
253
+
254
+ expect(readFileSync(join(workspaceDir, "config.json"), "utf-8")).toBe(
255
+ before,
256
+ );
257
+ });
258
+
259
+ test("needs no DB for identity or vendor fragments", () => {
260
+ writeConfig({
261
+ llm: {
262
+ default: { provider: "chatgpt", model: STALE },
263
+ profiles: {
264
+ byok: { provider: "openai", model: STALE },
265
+ bound: { provider: "chatgpt-subscription", model: STALE },
266
+ },
267
+ },
268
+ });
269
+
270
+ repairRetiredCodexGpt54ModelIdsMigration.run(workspaceDir);
271
+
272
+ const llm = readLlm();
273
+ expect(llm.default.model).toBe(REPLACEMENT);
274
+ expect(llm.profiles.byok.model).toBe(STALE);
275
+ // An entry name with no DB file is dangling.
276
+ expect(llm.profiles.bound.model).toBe(STALE);
277
+ });
278
+
279
+ test("throws when an entry name needs rows and the DB is unreadable", () => {
280
+ // A DB file without the provider_connections table is unqueryable, so
281
+ // the run must fail (and retry later) instead of skipping the profile.
282
+ mkdirSync(join(workspaceDir, "data", "db"), { recursive: true });
283
+ writeFileSync(join(workspaceDir, "data", "db", "assistant.db"), "");
284
+ writeConfig({
285
+ llm: {
286
+ profiles: { bound: { provider: "chatgpt-subscription", model: STALE } },
287
+ },
288
+ });
289
+
290
+ expect(() =>
291
+ repairRetiredCodexGpt54ModelIdsMigration.run(workspaceDir),
292
+ ).toThrow();
293
+ expect(readLlm().profiles.bound.model).toBe(STALE);
294
+
295
+ // The identity form never needs the rows, so the same broken DB does
296
+ // not block its repair.
297
+ writeConfig({
298
+ llm: { default: { provider: "chatgpt", model: STALE } },
299
+ });
300
+ repairRetiredCodexGpt54ModelIdsMigration.run(workspaceDir);
301
+ expect(readLlm().default.model).toBe(REPLACEMENT);
302
+ });
303
+
304
+ test("is idempotent and a no-op without the stale IDs", () => {
305
+ writeConfig({
306
+ llm: {
307
+ default: { provider: "chatgpt", model: STALE },
308
+ profiles: { fine: { provider: "chatgpt", model: REPLACEMENT } },
309
+ },
310
+ });
311
+
312
+ repairRetiredCodexGpt54ModelIdsMigration.run(workspaceDir);
313
+ const first = readConfig();
314
+ repairRetiredCodexGpt54ModelIdsMigration.run(workspaceDir);
315
+ expect(readConfig()).toEqual(first);
316
+
317
+ const llm = first.llm as Record<string, any>;
318
+ expect(llm.default.model).toBe(REPLACEMENT);
319
+ expect(llm.profiles.fine.model).toBe(REPLACEMENT);
320
+ });
321
+
322
+ test("handles missing config, missing llm block, and invalid JSON", () => {
323
+ expect(() =>
324
+ repairRetiredCodexGpt54ModelIdsMigration.run(workspaceDir),
325
+ ).not.toThrow();
326
+
327
+ writeConfig({ theme: "dark" });
328
+ repairRetiredCodexGpt54ModelIdsMigration.run(workspaceDir);
329
+ expect(readConfig()).toEqual({ theme: "dark" });
330
+
331
+ writeFileSync(join(workspaceDir, "config.json"), "{not json");
332
+ expect(() =>
333
+ repairRetiredCodexGpt54ModelIdsMigration.run(workspaceDir),
334
+ ).not.toThrow();
335
+ });
336
+ });