@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
package/src/acp/types.ts CHANGED
@@ -4,14 +4,20 @@
4
4
 
5
5
  import type { StopReason } from "@agentclientprotocol/sdk";
6
6
 
7
+ import type { AcpAgentConfig as ConfiguredAcpAgent } from "../config/acp-schema.js";
8
+ import type { AcpModelOption } from "./model-config.js";
9
+
7
10
  /**
8
- * Configuration for a single ACP agent process.
11
+ * A configured ACP agent plus what the daemon learns about it at spawn time.
12
+ * The configured half is the schema's own inferred type, so a new config leaf
13
+ * is declared once, in `config/acp-schema.ts`.
9
14
  */
10
- export interface AcpAgentConfig {
15
+ export interface AcpAgentConfig extends ConfiguredAcpAgent {
16
+ /**
17
+ * Always set on a resolved agent: the resolver fills in a bundled profile's
18
+ * command when the config entry omits it.
19
+ */
11
20
  command: string;
12
- args: string[];
13
- description?: string;
14
- env?: Record<string, string>;
15
21
  /**
16
22
  * Identity of the Claude token `prepareAgentEnv` resolved into `env`,
17
23
  * whichever source it came from. Recorded on the history row when Claude
@@ -51,6 +57,32 @@ export interface AcpSessionState {
51
57
  authErrorCredential?: string;
52
58
  /** Latest context-window usage gauge, from the most recent `usage_update`. */
53
59
  latestUsage?: AcpUsageSnapshot;
60
+ /**
61
+ * Model the session is running on, as the adapter reports it. Absent while
62
+ * the adapter advertises no model selector, which is how the feature stays
63
+ * invisible for agents that cannot switch models.
64
+ */
65
+ model?: string;
66
+ /** Models the adapter offers this session, flattened from its selector. */
67
+ availableModels?: AcpModelOption[];
68
+ /** Time-ordered identifier of the assistant process issuing revisions. */
69
+ modelRevisionEpoch?: string;
70
+ /** Monotonic revision within `modelRevisionEpoch`. */
71
+ modelRevision?: number;
72
+ }
73
+
74
+ /**
75
+ * Statuses a session is still live in. One list for every place that asks the
76
+ * question: the boot sweep over persisted rows, the in-memory liveness guard,
77
+ * `close()`, and the delete route's conflict guard.
78
+ */
79
+ export const ACP_LIVE_STATUSES = ["running", "initializing"] as const;
80
+
81
+ /** Whether a status is one a session can still move from. */
82
+ export function isLiveAcpStatus(
83
+ status: AcpSessionState["status"],
84
+ ): status is (typeof ACP_LIVE_STATUSES)[number] {
85
+ return ACP_LIVE_STATUSES.some((live) => live === status);
54
86
  }
55
87
 
56
88
  /** Context-window usage snapshot tracked from ACP `usage_update`. */
@@ -64,3 +96,16 @@ export interface AcpUsageSnapshot {
64
96
  /** Cumulative output tokens across all turns, from `PromptResponse.usage`. */
65
97
  outputTokens?: number;
66
98
  }
99
+
100
+ /**
101
+ * The adapter answered `session/set_config_option` with an error: the value
102
+ * was refused. Raised only for the adapter's own answer to that request, so a
103
+ * closed connection, an exited process, or a failed authentication is never
104
+ * mistaken for a refusal.
105
+ */
106
+ export class AcpConfigOptionRefusedError extends Error {
107
+ constructor(message: string) {
108
+ super(message);
109
+ this.name = "AcpConfigOptionRefusedError";
110
+ }
111
+ }
package/src/agent/loop.ts CHANGED
@@ -2485,6 +2485,7 @@ export class AgentLoop {
2485
2485
  contentBlocks: response.content.length,
2486
2486
  toolUseCount: modelToolUseBlocks.length,
2487
2487
  durationMs: providerDurationMs,
2488
+ cacheReadInputTokens: response.usage.cacheReadInputTokens,
2488
2489
  },
2489
2490
  "LLM call complete",
2490
2491
  );
@@ -0,0 +1,88 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { AcpSessionModelUpdateEventSchema } from "./acp-session-model-update.js";
4
+
5
+ const MODEL_REVISION_EPOCH = "01900000-0000-7000-8000-000000000001";
6
+
7
+ describe("AcpSessionModelUpdateEventSchema", () => {
8
+ test("parses an event carrying the selection and grouped options", () => {
9
+ const event = {
10
+ type: "acp_session_model_update" as const,
11
+ acpSessionId: "acp-session-abc",
12
+ modelRevisionEpoch: MODEL_REVISION_EPOCH,
13
+ modelRevision: 7,
14
+ model: "opus",
15
+ availableModels: [
16
+ { value: "opus", label: "Opus", description: "Most capable" },
17
+ { value: "sonnet", label: "Sonnet", group: "Recommended" },
18
+ ],
19
+ };
20
+
21
+ const result = AcpSessionModelUpdateEventSchema.safeParse(event);
22
+
23
+ expect(result.success).toBe(true);
24
+ expect(result.success && result.data).toEqual(event);
25
+ });
26
+
27
+ test("parses an adapter with no selection and no options", () => {
28
+ const event = {
29
+ type: "acp_session_model_update" as const,
30
+ acpSessionId: "acp-session-abc",
31
+ modelRevisionEpoch: MODEL_REVISION_EPOCH,
32
+ modelRevision: 8,
33
+ availableModels: [],
34
+ };
35
+
36
+ const result = AcpSessionModelUpdateEventSchema.safeParse(event);
37
+
38
+ expect(result.success).toBe(true);
39
+ expect(result.success && result.data).toEqual(event);
40
+ });
41
+
42
+ test("rejects a missing availableModels array", () => {
43
+ const result = AcpSessionModelUpdateEventSchema.safeParse({
44
+ type: "acp_session_model_update",
45
+ acpSessionId: "acp-session-abc",
46
+ modelRevisionEpoch: MODEL_REVISION_EPOCH,
47
+ modelRevision: 9,
48
+ model: "opus",
49
+ });
50
+
51
+ expect(result.success).toBe(false);
52
+ });
53
+
54
+ test("rejects a missing model revision", () => {
55
+ const result = AcpSessionModelUpdateEventSchema.safeParse({
56
+ type: "acp_session_model_update",
57
+ acpSessionId: "acp-session-abc",
58
+ modelRevisionEpoch: MODEL_REVISION_EPOCH,
59
+ availableModels: [],
60
+ });
61
+
62
+ expect(result.success).toBe(false);
63
+ });
64
+
65
+ test("rejects a missing model revision epoch", () => {
66
+ const result = AcpSessionModelUpdateEventSchema.safeParse({
67
+ type: "acp_session_model_update",
68
+ acpSessionId: "acp-session-abc",
69
+ modelRevision: 10,
70
+ availableModels: [],
71
+ });
72
+
73
+ expect(result.success).toBe(false);
74
+ });
75
+
76
+ test("rejects an unrecognized field under .strict()", () => {
77
+ const result = AcpSessionModelUpdateEventSchema.safeParse({
78
+ type: "acp_session_model_update",
79
+ acpSessionId: "acp-session-abc",
80
+ modelRevisionEpoch: MODEL_REVISION_EPOCH,
81
+ modelRevision: 11,
82
+ availableModels: [],
83
+ unexpected: true,
84
+ });
85
+
86
+ expect(result.success).toBe(false);
87
+ });
88
+ });
@@ -0,0 +1,46 @@
1
+ /**
2
+ * `acp_session_model_update` SSE event.
3
+ *
4
+ * Server to client snapshot of an ACP session's model selection, derived from
5
+ * the adapter's `configOptions`. `model` is the currently selected value (absent
6
+ * when the adapter reports none) and `availableModels` is the flattened set of
7
+ * values the adapter offers, empty when it advertises no model selector.
8
+ * `modelRevisionEpoch` scopes `modelRevision` to one assistant process, and
9
+ * the pair orders this snapshot against the same session state returned by
10
+ * `GET /v1/acp/sessions`. A side gauge, not part of the ordered update
11
+ * timeline: carries no `seq`.
12
+ *
13
+ * `.strict()` like the other ACP events, for the reason spelled out in
14
+ * `acp-auth-required.ts`: an older packaged client rejects an event carrying a
15
+ * field it does not know, so additive signals get their own event type.
16
+ *
17
+ * Canonical wire-contract source. Re-exported to external consumers via
18
+ * `@vellumai/assistant-api` (the `api/index.ts` barrel).
19
+ */
20
+
21
+ import { z } from "zod";
22
+
23
+ export const AcpSessionModelUpdateEventSchema = z
24
+ .object({
25
+ type: z.literal("acp_session_model_update"),
26
+ acpSessionId: z.string(),
27
+ modelRevisionEpoch: z.string().uuid(),
28
+ modelRevision: z.number().int().nonnegative(),
29
+ model: z.string().optional(),
30
+ availableModels: z.array(
31
+ z.object({
32
+ /** Adapter-reported value to pass back when selecting this model. */
33
+ value: z.string(),
34
+ /** Human-readable label from the adapter. */
35
+ label: z.string(),
36
+ description: z.string().optional(),
37
+ /** Name of the adapter's option group, when the options were grouped. */
38
+ group: z.string().optional(),
39
+ }),
40
+ ),
41
+ })
42
+ .strict();
43
+
44
+ export type AcpSessionModelUpdateEvent = z.infer<
45
+ typeof AcpSessionModelUpdateEventSchema
46
+ >;
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * `app_files_changed` SSE event.
3
3
  *
4
- * Broadcast when an app's source files change on disk emitted when a new
5
- * app is created and by the app source watcher on edits — so connected
6
- * clients re-read the app (refresh preview, reload the running surface).
4
+ * Broadcast after an app is created, compiled, or otherwise updated so
5
+ * connected clients re-read the app (refresh preview, reload the running
6
+ * surface). Workspace compiles go through `assistant apps refresh` or
7
+ * `app_refresh`; file edits do not emit this on their own.
7
8
  *
8
9
  * Canonical wire-contract source. Daemon code imports the type
9
10
  * directly from this file; external consumers import via
@@ -20,6 +20,8 @@ import { z } from "zod";
20
20
  export const NotificationIntentEventSchema = z.object({
21
21
  type: z.literal("notification_intent"),
22
22
  sourceEventName: z.string(),
23
+ /** Verified assistant name supplied by the assistant when available. */
24
+ assistantName: z.string().optional(),
23
25
  title: z.string(),
24
26
  body: z.string(),
25
27
  deliveryId: z.string().optional(),
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `user_message_echo` SSE event.
3
3
  *
4
- * Emitted when a user message is persisted to a conversation direct
4
+ * Emitted when a user message is persisted to a conversation: direct
5
5
  * sends, slash/canned/compaction turns, and synthetic surface-action
6
6
  * prompts. Carries the user's outbound `text` so clients that did not
7
7
  * originate the send (passive viewers, other devices) can render the
@@ -15,6 +15,11 @@
15
15
  * 202 response. `requestId` correlates with `message_queued` /
16
16
  * `message_dequeued` for the same turn.
17
17
  *
18
+ * `cameraFrame` is present only on standalone ambient camera-frame echoes,
19
+ * before `sync_changed` refetches hydrate their images. Photos and spoken
20
+ * turns omit it. Older strict parsers drop marked echoes and recover the
21
+ * persisted rows through the following `sync_changed` invalidation.
22
+ *
18
23
  * Canonical wire-contract source. Daemon code imports the type directly
19
24
  * from this file; external consumers import via `@vellumai/assistant-api`.
20
25
  */
@@ -29,6 +34,7 @@ export const UserMessageEchoEventSchema = z
29
34
  messageId: z.string().optional(),
30
35
  requestId: z.string().optional(),
31
36
  clientMessageId: z.string().optional(),
37
+ cameraFrame: z.literal(true).optional(),
32
38
  })
33
39
  .strict();
34
40
 
package/src/api/index.ts CHANGED
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { AcpAuthRequiredEventSchema } from "./events/acp-auth-required.js";
4
4
  import { AcpSessionCompletedEventSchema } from "./events/acp-session-completed.js";
5
5
  import { AcpSessionErrorEventSchema } from "./events/acp-session-error.js";
6
+ import { AcpSessionModelUpdateEventSchema } from "./events/acp-session-model-update.js";
6
7
  import { AcpSessionSpawnedEventSchema } from "./events/acp-session-spawned.js";
7
8
  import { AcpSessionUpdateEventSchema } from "./events/acp-session-update.js";
8
9
  import { AcpSessionUsageEventSchema } from "./events/acp-session-usage.js";
@@ -186,6 +187,10 @@ export {
186
187
  type AcpSessionErrorEvent,
187
188
  AcpSessionErrorEventSchema,
188
189
  } from "./events/acp-session-error.js";
190
+ export {
191
+ type AcpSessionModelUpdateEvent,
192
+ AcpSessionModelUpdateEventSchema,
193
+ } from "./events/acp-session-model-update.js";
189
194
  export {
190
195
  type AcpSessionSpawnedEvent,
191
196
  AcpSessionSpawnedEventSchema,
@@ -964,6 +969,7 @@ export const AssistantEventSchema = z.discriminatedUnion("type", [
964
969
  AcpAuthRequiredEventSchema,
965
970
  AcpSessionCompletedEventSchema,
966
971
  AcpSessionErrorEventSchema,
972
+ AcpSessionModelUpdateEventSchema,
967
973
  AcpSessionSpawnedEventSchema,
968
974
  AcpSessionUpdateEventSchema,
969
975
  AcpSessionUsageEventSchema,
@@ -615,6 +615,11 @@ export const ConversationMessageSchema = z.object({
615
615
  * sentinel text, and treat the row as the turn's reply so nothing keeps
616
616
  * waiting for one. */
617
617
  noResponse: z.boolean().optional(),
618
+ /** Set only on standalone ambient camera-frame rows, derived from
619
+ * `messageMetadataIsAmbientSightKeep`. Clients may fold consecutive frames
620
+ * into the following user message. Absent on shutter photos, spoken turns
621
+ * carrying parked frames, and every other row. */
622
+ cameraFrame: z.literal(true).optional(),
618
623
  /** How this assistant row's plain text reached the user, set only on a turn
619
624
  * that routed its reply through the `send_user_message` tool. `"private"`
620
625
  * means the row's text blocks are the model's working notes and the text
@@ -114,7 +114,7 @@ export function resolveEffectiveAppHtmlFromDir(sourceDir: string): string {
114
114
  if (isLegacySingleFileDir(sourceDir)) {
115
115
  return UNSUPPORTED_LEGACY_APP_HTML;
116
116
  }
117
- return `<p>App compilation failed. Edit a source file to trigger a rebuild.</p>`;
117
+ return `<p>App compilation failed. Run <code>assistant apps refresh</code> to rebuild.</p>`;
118
118
  }
119
119
 
120
120
  /**
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Resolve a user-facing app query (display name, slug, or id) to an
3
+ * enumerated app. Used by `assistant apps inspect` / `refresh`.
4
+ */
5
+
6
+ import { basename } from "node:path";
7
+
8
+ import { type EnumeratedApp, listAllApps } from "./app-store.js";
9
+
10
+ export type ResolveAppQueryResult =
11
+ | { readonly ok: true; readonly app: EnumeratedApp }
12
+ | { readonly ok: false; readonly reason: "not_found"; readonly query: string }
13
+ | {
14
+ readonly ok: false;
15
+ readonly reason: "ambiguous";
16
+ readonly query: string;
17
+ readonly matches: readonly EnumeratedApp[];
18
+ };
19
+
20
+ function matchesQuery(app: EnumeratedApp, query: string): boolean {
21
+ if (app.id === query) {
22
+ return true;
23
+ }
24
+ const lowered = query.toLowerCase();
25
+ if (app.name.toLowerCase() === lowered) {
26
+ return true;
27
+ }
28
+ const dirName = basename(app.sourcePath);
29
+ return dirName.toLowerCase() === lowered;
30
+ }
31
+
32
+ /**
33
+ * Resolve `query` against every surfaced app. Matching is exact on id, and
34
+ * case-insensitive on display name and directory slug. Multiple distinct apps
35
+ * that match yield `ambiguous` rather than picking a winner.
36
+ */
37
+ export function resolveAppQuery(query: string): ResolveAppQueryResult {
38
+ const trimmed = query.trim();
39
+ if (trimmed.length === 0) {
40
+ return { ok: false, reason: "not_found", query };
41
+ }
42
+
43
+ const matches: EnumeratedApp[] = [];
44
+ const seen = new Set<string>();
45
+ for (const app of listAllApps()) {
46
+ if (!matchesQuery(app, trimmed) || seen.has(app.id)) {
47
+ continue;
48
+ }
49
+ seen.add(app.id);
50
+ matches.push(app);
51
+ }
52
+
53
+ if (matches.length === 1) {
54
+ return { ok: true, app: matches[0] };
55
+ }
56
+ if (matches.length > 1) {
57
+ return { ok: false, reason: "ambiguous", query: trimmed, matches };
58
+ }
59
+ return { ok: false, reason: "not_found", query: trimmed };
60
+ }
61
+
62
+ export function formatAppMatch(app: EnumeratedApp): string {
63
+ const origin =
64
+ app.origin.kind === "plugin"
65
+ ? `plugin ${app.origin.pluginName}`
66
+ : "workspace";
67
+ return `${app.name} (${origin}, ${app.id})`;
68
+ }
69
+
70
+ export function appNotFoundMessage(query: string): string {
71
+ return `App "${query}" not found. Run 'assistant apps list' to see available apps.`;
72
+ }
73
+
74
+ export function appAmbiguousMessage(
75
+ query: string,
76
+ matches: readonly EnumeratedApp[],
77
+ ): string {
78
+ const listed = matches.map((app) => ` ${formatAppMatch(app)}`).join("\n");
79
+ return `Ambiguous app name "${query}". Matches:\n${listed}\nRun 'assistant apps list' and pass a unique name, slug, or id.`;
80
+ }
81
+
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Content fingerprint of an app's source tree, recorded at compile time so
3
+ * later inspects can tell whether source has drifted from the last successful
4
+ * build.
5
+ *
6
+ * The fingerprint is a one-way per-file digest map written into
7
+ * `dist/.source-fingerprint.json` after a successful compile. It answers "did
8
+ * this change?" and "which files?", not a reconstructable diff.
9
+ */
10
+
11
+ import { createHash } from "node:crypto";
12
+ import {
13
+ existsSync,
14
+ readdirSync,
15
+ readFileSync,
16
+ writeFileSync,
17
+ } from "node:fs";
18
+ import { join } from "node:path";
19
+
20
+ /** Digest algorithm recorded alongside the file map, for forward compatibility. */
21
+ export type FingerprintAlgorithm = "sha256";
22
+
23
+ /** Filename written into `dist/` next to compiled output. */
24
+ export const SOURCE_FINGERPRINT_FILENAME = ".source-fingerprint.json";
25
+
26
+ /** Directory names skipped at the app root only (compile output and records). */
27
+ const ROOT_SKIP_DIRS: ReadonlySet<string> = new Set(["dist", "records"]);
28
+
29
+ /** Directory names skipped at any depth. */
30
+ const SKIP_DIRS_ANY_DEPTH: ReadonlySet<string> = new Set([
31
+ "node_modules",
32
+ ".git",
33
+ ]);
34
+
35
+ /**
36
+ * Per-file content digest of an app source tree. Keys are POSIX-style
37
+ * (forward-slash) paths relative to the app directory; values are lowercase
38
+ * hex SHA-256 digests of each file's bytes.
39
+ */
40
+ export interface AppSourceFingerprint {
41
+ readonly algorithm: FingerprintAlgorithm;
42
+ readonly files: Readonly<Record<string, string>>;
43
+ /** ISO-8601 timestamp of the compile that produced this baseline, when known. */
44
+ readonly compiledAt?: string;
45
+ }
46
+
47
+ /**
48
+ * Difference between a recorded fingerprint and the current on-disk source.
49
+ * Paths are POSIX-relative. A rename surfaces as one `removed` plus one `added`.
50
+ */
51
+ export interface FingerprintComparison {
52
+ readonly modified: readonly string[];
53
+ readonly added: readonly string[];
54
+ readonly removed: readonly string[];
55
+ readonly clean: boolean;
56
+ }
57
+
58
+ export type AppSourceInspectStatus =
59
+ | "clean"
60
+ | "stale"
61
+ | "never_compiled"
62
+ | "unknown_baseline";
63
+
64
+ export interface AppSourceInspectResult {
65
+ readonly status: AppSourceInspectStatus;
66
+ readonly compiledAt: string | null;
67
+ readonly modified: readonly string[];
68
+ readonly added: readonly string[];
69
+ readonly removed: readonly string[];
70
+ }
71
+
72
+ function hashFile(absPath: string): string | null {
73
+ try {
74
+ return createHash("sha256").update(readFileSync(absPath)).digest("hex");
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Walk `appDir` and digest every regular source file. Skips `dist/` and
82
+ * `records/` at the app root, and `node_modules` / `.git` at any depth.
83
+ * Symlinks are neither visited nor followed.
84
+ */
85
+ export function computeAppSourceFingerprint(
86
+ appDir: string,
87
+ ): AppSourceFingerprint {
88
+ const files: Record<string, string> = {};
89
+
90
+ const walk = (relDir: string): void => {
91
+ const absDir = relDir ? join(appDir, relDir) : appDir;
92
+ let entries;
93
+ try {
94
+ entries = readdirSync(absDir, { withFileTypes: true });
95
+ } catch {
96
+ return;
97
+ }
98
+ for (const entry of entries) {
99
+ if (relDir === "" && ROOT_SKIP_DIRS.has(entry.name)) {
100
+ continue;
101
+ }
102
+ if (entry.isSymbolicLink()) {
103
+ continue;
104
+ }
105
+ if (entry.isDirectory() && SKIP_DIRS_ANY_DEPTH.has(entry.name)) {
106
+ continue;
107
+ }
108
+ const rel = relDir ? `${relDir}/${entry.name}` : entry.name;
109
+ if (entry.isDirectory()) {
110
+ walk(rel);
111
+ } else if (entry.isFile()) {
112
+ const digest = hashFile(join(absDir, entry.name));
113
+ if (digest !== null) {
114
+ files[rel] = digest;
115
+ }
116
+ }
117
+ }
118
+ };
119
+
120
+ walk("");
121
+ return { algorithm: "sha256", files };
122
+ }
123
+
124
+ export function compareAppSourceFingerprint(
125
+ appDir: string,
126
+ baseline: AppSourceFingerprint,
127
+ ): FingerprintComparison {
128
+ const current = computeAppSourceFingerprint(appDir).files;
129
+ const modified: string[] = [];
130
+ const added: string[] = [];
131
+ const removed: string[] = [];
132
+
133
+ for (const [path, digest] of Object.entries(current)) {
134
+ const recorded = baseline.files[path];
135
+ if (recorded === undefined) {
136
+ added.push(path);
137
+ } else if (recorded !== digest) {
138
+ modified.push(path);
139
+ }
140
+ }
141
+ for (const path of Object.keys(baseline.files)) {
142
+ if (current[path] === undefined) {
143
+ removed.push(path);
144
+ }
145
+ }
146
+
147
+ modified.sort();
148
+ added.sort();
149
+ removed.sort();
150
+ return {
151
+ modified,
152
+ added,
153
+ removed,
154
+ clean: modified.length === 0 && added.length === 0 && removed.length === 0,
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Parse a fingerprint from already-decoded JSON. Lenient: any shape problem
160
+ * yields `null` so a missing or hand-edited sidecar reports "no baseline"
161
+ * rather than throwing.
162
+ */
163
+ export function parseAppSourceFingerprint(
164
+ value: unknown,
165
+ ): AppSourceFingerprint | null {
166
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
167
+ return null;
168
+ }
169
+ const obj = value as Record<string, unknown>;
170
+ if (obj.algorithm !== "sha256") {
171
+ return null;
172
+ }
173
+ const rawFiles = obj.files;
174
+ if (
175
+ typeof rawFiles !== "object" ||
176
+ rawFiles === null ||
177
+ Array.isArray(rawFiles)
178
+ ) {
179
+ return null;
180
+ }
181
+ const files: Record<string, string> = {};
182
+ for (const [path, digest] of Object.entries(rawFiles)) {
183
+ if (typeof digest !== "string") {
184
+ return null;
185
+ }
186
+ files[path] = digest;
187
+ }
188
+ const compiledAt =
189
+ typeof obj.compiledAt === "string" ? obj.compiledAt : undefined;
190
+ return compiledAt === undefined
191
+ ? { algorithm: "sha256", files }
192
+ : { algorithm: "sha256", files, compiledAt };
193
+ }
194
+
195
+ export function readAppSourceFingerprint(
196
+ appDir: string,
197
+ ): AppSourceFingerprint | null {
198
+ const path = join(appDir, "dist", SOURCE_FINGERPRINT_FILENAME);
199
+ if (!existsSync(path)) {
200
+ return null;
201
+ }
202
+ try {
203
+ return parseAppSourceFingerprint(JSON.parse(readFileSync(path, "utf-8")));
204
+ } catch {
205
+ return null;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Write a source fingerprint into `distDir`. Pass the snapshot taken
211
+ * before compile so inspect compares against the tree that produced this
212
+ * dist, even if source changes during the build.
213
+ */
214
+ export function writeAppSourceFingerprint(
215
+ appDir: string,
216
+ distDir: string,
217
+ fingerprint: AppSourceFingerprint = computeAppSourceFingerprint(appDir),
218
+ ): void {
219
+ const payload: AppSourceFingerprint = {
220
+ algorithm: fingerprint.algorithm,
221
+ compiledAt: fingerprint.compiledAt ?? new Date().toISOString(),
222
+ files: fingerprint.files,
223
+ };
224
+ writeFileSync(
225
+ join(distDir, SOURCE_FINGERPRINT_FILENAME),
226
+ `${JSON.stringify(payload)}\n`,
227
+ "utf-8",
228
+ );
229
+ }
230
+
231
+ /**
232
+ * Compare current app source against the fingerprint recorded at last
233
+ * successful compile.
234
+ */
235
+ export function inspectAppSource(appDir: string): AppSourceInspectResult {
236
+ const compiled = existsSync(join(appDir, "dist", "index.html"));
237
+ if (!compiled) {
238
+ return {
239
+ status: "never_compiled",
240
+ compiledAt: null,
241
+ modified: [],
242
+ added: [],
243
+ removed: [],
244
+ };
245
+ }
246
+
247
+ const baseline = readAppSourceFingerprint(appDir);
248
+ if (baseline === null) {
249
+ return {
250
+ status: "unknown_baseline",
251
+ compiledAt: null,
252
+ modified: [],
253
+ added: [],
254
+ removed: [],
255
+ };
256
+ }
257
+
258
+ const comparison = compareAppSourceFingerprint(appDir, baseline);
259
+ return {
260
+ status: comparison.clean ? "clean" : "stale",
261
+ compiledAt: baseline.compiledAt ?? null,
262
+ modified: comparison.modified,
263
+ added: comparison.added,
264
+ removed: comparison.removed,
265
+ };
266
+ }