@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
@@ -952,11 +952,7 @@ describe("AssistantConfigSchema", () => {
952
952
  enabled: true,
953
953
  text: 'At the very beginning of the call, introduce yourself as an assistant calling on behalf of the person you represent. Do not say "AI assistant".',
954
954
  },
955
- safety: {
956
- denyCategories: [],
957
- },
958
955
  voice: {
959
- interruptSensitivity: "low",
960
956
  telephonyStreaming: true,
961
957
  utteranceEndMs: 1000,
962
958
  },
@@ -978,7 +974,6 @@ describe("AssistantConfigSchema", () => {
978
974
  maxDurationSeconds: 1800,
979
975
  userConsultTimeoutSeconds: 60,
980
976
  disclosure: { enabled: false, text: "Custom disclosure" },
981
- safety: { denyCategories: ["spam"] },
982
977
  },
983
978
  });
984
979
  expect(result.calls.enabled).toBe(false);
@@ -986,7 +981,6 @@ describe("AssistantConfigSchema", () => {
986
981
  expect(result.calls.userConsultTimeoutSeconds).toBe(60);
987
982
  expect(result.calls.disclosure.enabled).toBe(false);
988
983
  expect(result.calls.disclosure.text).toBe("Custom disclosure");
989
- expect(result.calls.safety.denyCategories).toEqual(["spam"]);
990
984
  });
991
985
 
992
986
  // ── Live voice config ───────────────────────────────────────────────
@@ -994,7 +988,6 @@ describe("AssistantConfigSchema", () => {
994
988
  test("applies liveVoice defaults", () => {
995
989
  const result = AssistantConfigSchema.parse({});
996
990
  expect(result.liveVoice).toEqual({
997
- mode: "open-mic",
998
991
  vad: {
999
992
  speechEnergyThreshold: 800,
1000
993
  noiseFloorMargin: 3,
@@ -1005,6 +998,18 @@ describe("AssistantConfigSchema", () => {
1005
998
  echoEmaHalfLifeMs: 400,
1006
999
  echoDrainSlackMs: 300,
1007
1000
  },
1001
+ flux: {
1002
+ turnEnd: { enabled: true },
1003
+ eotThreshold: 0.7,
1004
+ eotTimeoutMs: 5000,
1005
+ },
1006
+ archiveAudio: false,
1007
+ });
1008
+ });
1009
+
1010
+ test("applies voice defaults", () => {
1011
+ const result = AssistantConfigSchema.parse({});
1012
+ expect(result.voice).toEqual({
1008
1013
  frontModel: {
1009
1014
  endpointDecisionTimeoutMs: 1200,
1010
1015
  endpointExtensionMs: 1500,
@@ -1019,35 +1024,24 @@ describe("AssistantConfigSchema", () => {
1019
1024
  generationTimeoutMs: 1500,
1020
1025
  },
1021
1026
  },
1022
- flux: {
1023
- turnEnd: { enabled: true },
1024
- eotThreshold: 0.7,
1025
- eotTimeoutMs: 5000,
1026
- },
1027
- maxSessionDurationSeconds: 1800,
1028
- archiveAudio: false,
1029
1027
  });
1030
1028
  });
1031
1029
 
1032
1030
  test("accepts valid liveVoice config overrides", () => {
1033
1031
  const result = AssistantConfigSchema.parse({
1034
1032
  liveVoice: {
1035
- mode: "ptt",
1036
1033
  vad: {
1037
1034
  speechEnergyThreshold: 1500,
1038
1035
  silenceThresholdMs: 1000,
1039
1036
  bargeInMinSpeechMs: 120,
1040
1037
  },
1041
- maxSessionDurationSeconds: 900,
1042
1038
  },
1043
1039
  });
1044
- expect(result.liveVoice.mode).toBe("ptt");
1045
1040
  expect(result.liveVoice.vad.speechEnergyThreshold).toBe(1500);
1046
1041
  expect(result.liveVoice.vad.silenceThresholdMs).toBe(1000);
1047
1042
  expect(result.liveVoice.vad.bargeInMinSpeechMs).toBe(120);
1048
1043
  // Unspecified vad fields still get defaults
1049
1044
  expect(result.liveVoice.vad.maxTurnDurationMs).toBe(30000);
1050
- expect(result.liveVoice.maxSessionDurationSeconds).toBe(900);
1051
1045
  // A partial liveVoice override leaves Flux turn-end disabled by default.
1052
1046
  expect(result.liveVoice.flux.turnEnd.enabled).toBe(true);
1053
1047
  });
@@ -1136,18 +1130,10 @@ describe("AssistantConfigSchema", () => {
1136
1130
  expect(result.success).toBe(false);
1137
1131
  });
1138
1132
 
1139
- test("rejects non-array calls.safety.denyCategories", () => {
1140
- const result = AssistantConfigSchema.safeParse({
1141
- calls: { safety: { denyCategories: "spam" } },
1142
- });
1143
- expect(result.success).toBe(false);
1144
- });
1145
-
1146
1133
  // ── Calls voice config ──────────────────────────────────────────────
1147
1134
 
1148
1135
  test("config without calls.voice parses correctly and produces defaults", () => {
1149
1136
  const result = AssistantConfigSchema.parse({});
1150
- expect(result.calls.voice.interruptSensitivity).toBe("low");
1151
1137
  expect(result.calls.voice.telephonyStreaming).toBe(true);
1152
1138
  expect(result.calls.voice.utteranceEndMs).toBe(1000);
1153
1139
  });
@@ -1165,9 +1151,23 @@ describe("AssistantConfigSchema", () => {
1165
1151
  expect(result.calls.voice.utteranceEndMs).toBe(2500);
1166
1152
  });
1167
1153
 
1154
+ test("persisted calls configs carrying retired keys keep parsing", () => {
1155
+ // Zod strips unrecognized keys, so a persisted config that still carries
1156
+ // `calls.safety.denyCategories` or `calls.voice.interruptSensitivity`
1157
+ // parses.
1158
+ const result = AssistantConfigSchema.parse({
1159
+ calls: {
1160
+ safety: { denyCategories: ["spam"] },
1161
+ voice: { interruptSensitivity: "high" },
1162
+ },
1163
+ });
1164
+ expect(result.calls).not.toHaveProperty("safety");
1165
+ expect(result.calls.voice).not.toHaveProperty("interruptSensitivity");
1166
+ });
1167
+
1168
1168
  test("language is no longer part of the voice config schema", () => {
1169
- // The retired knob was read by nothing; Zod strips the unrecognized key
1170
- // so persisted configs that still carry it keep parsing.
1169
+ // Zod strips the unrecognized key, so persisted configs that still carry
1170
+ // it parse.
1171
1171
  const result = AssistantConfigSchema.parse({
1172
1172
  calls: { voice: { language: "es-ES" } },
1173
1173
  });
@@ -2633,7 +2633,6 @@ describe("loadConfig with schema validation", () => {
2633
2633
  expect(config.calls.maxDurationSeconds).toBe(3600);
2634
2634
  expect(config.calls.userConsultTimeoutSeconds).toBe(120);
2635
2635
  expect(config.calls.disclosure.enabled).toBe(true);
2636
- expect(config.calls.safety.denyCategories).toEqual([]);
2637
2636
  expect(
2638
2637
  (config.calls.voice as Record<string, unknown>).language,
2639
2638
  ).toBeUndefined();
@@ -113,8 +113,11 @@ mock.module("../providers/registry.js", () => ({
113
113
  resolveProviderFromConnection: async () => null,
114
114
  }));
115
115
 
116
+ let mcpReloadCallCount = 0;
116
117
  mock.module("../daemon/mcp-reload-service.js", () => ({
117
- reloadMcpServers: async () => {},
118
+ reloadMcpServers: async () => {
119
+ mcpReloadCallCount += 1;
120
+ },
118
121
  }));
119
122
 
120
123
  mock.module("../signals/conversation-undo.js", () => ({
@@ -154,10 +157,6 @@ mock.module("../runtime/sync/resource-sync-events.js", () => ({
154
157
  },
155
158
  }));
156
159
 
157
- mock.module("../daemon/skill-memory-refresh.js", () => ({
158
- refreshSkillCapabilityMemories: () => {},
159
- }));
160
-
161
160
  mock.module("../platform/sync-identity.js", () => ({
162
161
  syncIdentityNameToPlatform: () => {},
163
162
  }));
@@ -183,6 +182,7 @@ function findFileWatch(filePath: string): CapturedFileWatch | undefined {
183
182
 
184
183
  const WORKSPACE_FILES = new Set([
185
184
  "config.json",
185
+ "mcp.json",
186
186
  "SOUL.md",
187
187
  "IDENTITY.md",
188
188
  "ui/theme.json",
@@ -239,6 +239,7 @@ beforeEach(() => {
239
239
  evictCallCount = 0;
240
240
  identityCallCount = 0;
241
241
  themeCallCount = 0;
242
+ mcpReloadCallCount = 0;
242
243
  watcher = new ConfigWatcher(undefined, TEST_DEBOUNCE_MS);
243
244
  });
244
245
 
@@ -270,9 +271,9 @@ describe("ConfigWatcher workspace file handlers", () => {
270
271
 
271
272
  test("unregistered workspace files are not subscribed (only the registered handler set is)", () => {
272
273
  watcher.start();
273
- // Per-file watching only registers config.json, SOUL.md, IDENTITY.md,
274
- // and ui/theme.json. The whole workspace dir must not be watched
275
- // either that was the ENXIO-on-Unix-sockets bug.
274
+ // Per-file watching only registers config.json, mcp.json, SOUL.md,
275
+ // IDENTITY.md, and ui/theme.json. The whole workspace dir must not
276
+ // be watched either: that was the ENXIO-on-Unix-sockets bug.
276
277
  expect(findFileWatch(join(WORKSPACE_DIR, "OTHER.md"))).toBeUndefined();
277
278
  expect(findWatcher(WORKSPACE_DIR)).toBeUndefined();
278
279
  });
@@ -285,6 +286,14 @@ describe("ConfigWatcher workspace file handlers", () => {
285
286
  expect(evictCallCount).toBe(0);
286
287
  });
287
288
 
289
+ test("mcp.json change reloads MCP servers without evicting conversations", async () => {
290
+ watcher.start();
291
+ simulateFileChange(WORKSPACE_DIR, "mcp.json");
292
+ await new Promise((r) => setTimeout(r, WAIT_MS));
293
+ expect(mcpReloadCallCount).toBe(1);
294
+ expect(evictCallCount).toBe(0);
295
+ });
296
+
288
297
  test("config.json change calls refreshConfigFromSources", async () => {
289
298
  let refreshCalled = false;
290
299
  watcher.refreshConfigFromSources = async () => {
@@ -332,6 +341,12 @@ describe("ConfigWatcher watcher lifecycle", () => {
332
341
  expect(findFileWatch(join(WORKSPACE_DIR, "IDENTITY.md"))).toBeDefined();
333
342
  });
334
343
 
344
+ test("start does not watch the skills directory", () => {
345
+ mkdirSync(join(WORKSPACE_DIR, "skills"), { recursive: true });
346
+ watcher.start();
347
+ expect(findWatcher(join(WORKSPACE_DIR, "skills"))).toBeUndefined();
348
+ });
349
+
335
350
  test("stop cancels pending debounce work, no eviction fires after", async () => {
336
351
  watcher.start();
337
352
  simulateFileChange(WORKSPACE_DIR, "SOUL.md");
@@ -19,7 +19,7 @@ describe("isConnectionCompatibleWithModel", () => {
19
19
  expect(
20
20
  isConnectionCompatibleWithModel(
21
21
  { auth: { type: "oauth_subscription" } as never },
22
- "gpt-5.4",
22
+ "gpt-5.5",
23
23
  ),
24
24
  ).toBe(true);
25
25
  });
@@ -79,7 +79,7 @@ describe("describeSubscriptionModelIncompatibility", () => {
79
79
 
80
80
  test("returns null when model is Codex-compatible", () => {
81
81
  expect(
82
- describeSubscriptionModelIncompatibility([subscriptionConn], "gpt-5.4"),
82
+ describeSubscriptionModelIncompatibility([subscriptionConn], "gpt-5.5"),
83
83
  ).toBeNull();
84
84
  });
85
85
  });
@@ -59,7 +59,7 @@ describe("conversation-key-store disk view", () => {
59
59
 
60
60
  const meta = JSON.parse(readFileSync(metaPath, "utf-8"));
61
61
  expect(meta.id).toBe(first.conversationId);
62
- expect(meta.title).toBe("Generating title...");
62
+ expect(meta.title).toBe("conversation.title.generating");
63
63
  expect(meta.type).toBe("standard");
64
64
  expect(meta.channel).toBeNull();
65
65
  expect(readdirSync(conversationsDir)).toEqual([expectedDirName]);
@@ -901,6 +901,32 @@ describe("pairDeliveryWithConversation", () => {
901
901
  // body has to land there for that button to open something that contains
902
902
  // the notification the user tapped.
903
903
 
904
+ test("assistant reply keeps the source conversation without appending its preview", async () => {
905
+ mockExistingConversations["conv-user-chat"] = {
906
+ id: "conv-user-chat",
907
+ source: "user",
908
+ title: "An ordinary chat",
909
+ };
910
+ const signal = makeSignal({
911
+ requiresConversation: undefined,
912
+ sourceContextId: "conv-user-chat",
913
+ sourceEventName: "chat.assistant_reply",
914
+ });
915
+
916
+ const result = await pairDeliveryWithConversation(
917
+ signal,
918
+ "vellum" as NotificationChannel,
919
+ makeCopy({ body: "A short push preview…" }),
920
+ );
921
+
922
+ expect(result.conversationId).toBe("conv-user-chat");
923
+ expect(result.messageId).toBeNull();
924
+ expect(result.createdNewConversation).toBe(false);
925
+ expect(createConversationMock).not.toHaveBeenCalled();
926
+ expect(addMessageMock).not.toHaveBeenCalled();
927
+ expect(messagesInvalidated).toEqual([]);
928
+ });
929
+
904
930
  test("passive vellum signal appends the body to the producing conversation", async () => {
905
931
  mockExistingConversations["conv-producer"] = {
906
932
  id: "conv-producer",
@@ -161,12 +161,6 @@ function makeFakeContext(opts: {
161
161
  trustClass: "guardian" as const,
162
162
  guardianPrincipalId: "user-1",
163
163
  },
164
- setTrustContext(
165
- this: { trustContext?: TrustContext },
166
- trustContext: TrustContext | null,
167
- ) {
168
- this.trustContext = trustContext ?? undefined;
169
- },
170
164
  setTransportHints() {},
171
165
  applyHostEnvFromTransport() {},
172
166
  ensureHostProxiesForTurn() {},
@@ -415,10 +409,10 @@ describe("drainQueue preactivation re-add for host-proxy interfaces", () => {
415
409
  "U-contact",
416
410
  );
417
411
  expect(ctx.currentTurnTrustContext?.sourceChannel).toBe("slack");
418
- // The drain is where a queued message commits to a run, so the resting
419
- // slot names the sender too: history is scoped from it, and a slot still
420
- // naming the guardian would hand the contact's turn the guardian's rows.
421
- expect(ctx.trustContext).toBe(contactTrust);
412
+ // The slot itself is left alone; only the turn's view is corrected. A
413
+ // drain stamping it would attribute the conversation to a sender whose
414
+ // turn can still lose the processing lock at persist time.
415
+ expect(ctx.trustContext?.trustClass).toBe("guardian");
422
416
  });
423
417
 
424
418
  test("buildPassthroughBatch refuses to coalesce two channel senders", async () => {
@@ -450,17 +450,6 @@ async function waitForCondition(
450
450
  }
451
451
  }
452
452
 
453
- /** Count the history reloads a conversation performs from this point on. */
454
- function countHistoryReloads(conversation: Conversation): () => number {
455
- const originalLoad = conversation.loadFromDb.bind(conversation);
456
- let reloads = 0;
457
- conversation.loadFromDb = async () => {
458
- reloads++;
459
- return originalLoad();
460
- };
461
- return () => reloads;
462
- }
463
-
464
453
  /**
465
454
  * Resolve the Nth pending AgentLoop.run() call. Fires the minimal events
466
455
  * that `runAgentLoop` expects (usage + message_complete) so the conversation
@@ -726,22 +715,21 @@ describe("Conversation message queue", () => {
726
715
  await new Promise((r) => setTimeout(r, 10));
727
716
  });
728
717
 
729
- test("a drained turn commits its sender as the resting actor and re-scopes history", async () => {
730
- // A contact's channel turn leaves the resting slot, and the resident
731
- // history scoped from it, naming the contact. The guardian's message
732
- // queued behind that turn must run as the guardian everywhere the slot is
733
- // read, not only in the per-turn snapshot: the `<turn_context>` actor
734
- // section is derived at turn start and history is scoped from the slot,
735
- // so a drain that stamped only the snapshot rendered the guardian's own
736
- // web turn as a trusted contact's and hid the guardian's rows from it.
718
+ test("the turn-context actor section describes the turn's actor, not the conversation's resting actor", async () => {
719
+ // The drain carries its sender on the per-turn field and leaves the
720
+ // resting slot naming the previous actor. The actor section is frozen at
721
+ // turn start, so it has to read the turn's actor: reading the slot is what
722
+ // told the guardian's own drained turn it was talking to a trusted
723
+ // contact.
737
724
  const conversation = makeConversation();
738
725
  await conversation.loadFromDb();
739
726
 
740
- conversation.setTrustContext({
741
- trustClass: "trusted_contact",
742
- sourceChannel: "slack",
727
+ const contact = {
728
+ trustClass: "trusted_contact" as const,
729
+ sourceChannel: "slack" as const,
743
730
  requesterExternalUserId: "U-contact",
744
- });
731
+ };
732
+ conversation.setTrustContext(contact);
745
733
 
746
734
  const p1 = conversation.processMessage({
747
735
  content: "msg-1",
@@ -756,7 +744,6 @@ describe("Conversation message queue", () => {
756
744
  sourceChannel: "vellum" as const,
757
745
  requesterExternalUserId: "guardian-principal",
758
746
  };
759
- const reloads = countHistoryReloads(conversation);
760
747
  conversation.enqueueMessage({
761
748
  content: "msg-2",
762
749
  requestId: "req-2",
@@ -767,83 +754,30 @@ describe("Conversation message queue", () => {
767
754
  await p1;
768
755
  await waitForPendingRun(2);
769
756
 
770
- // Read while run 2 is in flight: the slot, the snapshot, and the frozen
771
- // actor section all describe the guardian, and the history was reloaded
772
- // for that scope exactly once.
773
- expect(conversation.getTrustContext()).toBe(guardian);
757
+ // Guard the test itself: the slot must disagree with the turn, or the
758
+ // assertion below passes for the wrong reason.
759
+ expect(conversation.getTrustContext()).toBe(contact);
774
760
  expect(conversation.currentTurnTrustContext).toBe(guardian);
761
+ // A guardian turn renders no actor section, whatever the slot says.
775
762
  expect(conversation.currentTurnInboundActorContext).toBeNull();
776
- expect(reloads()).toBe(1);
777
763
 
778
764
  await resolveRun(1);
779
765
  await new Promise((r) => setTimeout(r, 10));
780
766
  });
781
767
 
782
- test("a batched drain commits the head's sender as the resting actor", async () => {
783
- // Same commitment on the batched path, which coalesces only messages that
784
- // share a trust identity, so the head's sender is the batch's actor.
768
+ test("the actor section names the drained contact while the slot holds the guardian", async () => {
769
+ // The inverse direction, so the assertion above cannot pass merely because
770
+ // the section is always absent: a contact's message drained behind the
771
+ // guardian's turn must be described as that contact.
785
772
  const conversation = makeConversation();
786
773
  await conversation.loadFromDb();
787
774
 
788
- conversation.setTrustContext({
789
- trustClass: "trusted_contact",
790
- sourceChannel: "slack",
791
- requesterExternalUserId: "U-contact",
792
- });
793
-
794
- const p1 = conversation.processMessage({
795
- content: "msg-1",
796
- attachments: [],
797
- onEvent: () => {},
798
- requestId: "req-1",
799
- });
800
- await waitForPendingRun(1);
801
-
802
775
  const guardian = {
803
776
  trustClass: "guardian" as const,
804
777
  sourceChannel: "vellum" as const,
805
778
  requesterExternalUserId: "guardian-principal",
806
779
  };
807
- const reloads = countHistoryReloads(conversation);
808
- conversation.enqueueMessage({
809
- content: "msg-2",
810
- requestId: "req-2",
811
- trustContext: guardian,
812
- });
813
- conversation.enqueueMessage({
814
- content: "msg-3",
815
- requestId: "req-3",
816
- trustContext: guardian,
817
- });
818
-
819
- await resolveRun(0);
820
- await p1;
821
- await waitForPendingRun(2);
822
-
823
- // One batched run for both siblings, committed as the guardian.
824
- expect(pendingRuns.length).toBe(2);
825
- expect(conversation.getTrustContext()).toBe(guardian);
826
- expect(conversation.currentTurnTrustContext).toBe(guardian);
827
- expect(conversation.currentTurnInboundActorContext).toBeNull();
828
- expect(reloads()).toBe(1);
829
-
830
- await resolveRun(1);
831
- await new Promise((r) => setTimeout(r, 10));
832
- });
833
-
834
- test("a drained turn keeps its sender when the slot moves during the history reload", async () => {
835
- // The commit captures the sender before the reload awaits. A writer that
836
- // moves the slot inside that await (a wake's stamp, a pointer elevation)
837
- // must not become the turn's actor: the history was reloaded for the
838
- // sender, and running someone else's trust over it is the escalation.
839
- const conversation = makeConversation();
840
- await conversation.loadFromDb();
841
-
842
- conversation.setTrustContext({
843
- trustClass: "trusted_contact",
844
- sourceChannel: "slack",
845
- requesterExternalUserId: "U-contact",
846
- });
780
+ conversation.setTrustContext(guardian);
847
781
 
848
782
  const p1 = conversation.processMessage({
849
783
  content: "msg-1",
@@ -853,49 +787,39 @@ describe("Conversation message queue", () => {
853
787
  });
854
788
  await waitForPendingRun(1);
855
789
 
856
- const guardian = {
857
- trustClass: "guardian" as const,
858
- sourceChannel: "vellum" as const,
859
- requesterExternalUserId: "guardian-principal",
860
- };
861
- const intruder = {
862
- trustClass: "unknown" as const,
863
- sourceChannel: "telegram" as const,
864
- requesterExternalUserId: "T-stranger",
790
+ const contact = {
791
+ trustClass: "trusted_contact" as const,
792
+ sourceChannel: "slack" as const,
793
+ requesterExternalUserId: "U-contact",
865
794
  };
866
795
  conversation.enqueueMessage({
867
796
  content: "msg-2",
868
797
  requestId: "req-2",
869
- trustContext: guardian,
798
+ trustContext: contact,
870
799
  });
871
800
 
872
- // The drain's reload is the await the writer lands inside.
873
- const originalLoad = conversation.loadFromDb.bind(conversation);
874
- let movedDuringReload = false;
875
- conversation.loadFromDb = async () => {
876
- const result = await originalLoad();
877
- conversation.setTrustContext(intruder);
878
- movedDuringReload = true;
879
- return result;
880
- };
881
-
882
801
  await resolveRun(0);
883
802
  await p1;
884
803
  await waitForPendingRun(2);
885
804
 
886
- expect(movedDuringReload).toBe(true);
887
- expect(conversation.getTrustContext()).toBe(intruder);
888
- expect(conversation.currentTurnTrustContext).toBe(guardian);
889
- expect(conversation.currentTurnInboundActorContext).toBeNull();
805
+ expect(conversation.getTrustContext()).toBe(guardian);
806
+ expect(conversation.currentTurnTrustContext).toBe(contact);
807
+ expect(conversation.currentTurnInboundActorContext).toMatchObject({
808
+ trustClass: "trusted_contact",
809
+ sourceChannel: "slack",
810
+ canonicalActorIdentity: "U-contact",
811
+ });
890
812
 
891
813
  await resolveRun(1);
892
814
  await new Promise((r) => setTimeout(r, 10));
893
815
  });
894
816
 
895
- test("a drain whose history reload fails puts the resting actor back", async () => {
896
- // A reload that fails starts no turn: the message is requeued for the
897
- // next drain, so the slot must not keep naming a sender whose turn never
898
- // began, or conversation-level readers report an owner that is not there.
817
+ test("a processMessage turn whose history reload fails puts the resting actor back", async () => {
818
+ // `processMessage` is the commitment point, so it stamps the slot before
819
+ // scoping history. A reload that throws starts no turn, and leaving the
820
+ // stamp would attribute the conversation to a sender that never ran: an
821
+ // actorless dispatch afterwards (a deferred wake) resolves the resting
822
+ // slot and would inherit it.
899
823
  const conversation = makeConversation();
900
824
  await conversation.loadFromDb();
901
825
 
@@ -906,110 +830,27 @@ describe("Conversation message queue", () => {
906
830
  };
907
831
  conversation.setTrustContext(contact);
908
832
 
909
- const p1 = conversation.processMessage({
910
- content: "msg-1",
911
- attachments: [],
912
- onEvent: () => {},
913
- requestId: "req-1",
914
- });
915
- await waitForPendingRun(1);
916
-
917
- const guardian = {
918
- trustClass: "guardian" as const,
919
- sourceChannel: "vellum" as const,
920
- requesterExternalUserId: "guardian-principal",
921
- };
922
- conversation.enqueueMessage({
923
- content: "msg-2",
924
- requestId: "req-2",
925
- trustContext: guardian,
926
- });
927
-
928
- let reloadAttempts = 0;
929
833
  conversation.loadFromDb = async () => {
930
- reloadAttempts++;
931
834
  throw new Error("history store exploded");
932
835
  };
933
836
 
934
- // Finish the first turn; the drain (and its one retry) fail on the reload.
935
- await resolveRun(0);
936
- await p1;
937
- await waitForCondition(() => reloadAttempts >= 2);
938
- await new Promise((r) => setTimeout(r, 20));
939
-
940
- expect(pendingRuns.length).toBe(1);
941
- expect(conversation.getQueueDepth()).toBe(1);
942
- expect(conversation.getTrustContext()).toBe(contact);
943
- });
944
-
945
- test("the turn-context actor section follows the turn's actor when the slot moves before the loop opens", async () => {
946
- // The actor section is frozen at turn start from the turn's own actor,
947
- // not from the resting slot. The drain stamps the slot and then awaits
948
- // (persist) before the loop opens; an out-of-band writer landing in that
949
- // window (pointer elevation restore, a wake's restore) moves the slot
950
- // without owning the turn, and a read of the slot there would describe
951
- // that writer's actor to the model.
952
- const conversation = makeConversation();
953
- await conversation.loadFromDb();
954
-
955
- const contact = {
956
- trustClass: "trusted_contact" as const,
957
- sourceChannel: "slack" as const,
958
- requesterExternalUserId: "U-contact",
959
- };
960
- conversation.setTrustContext(contact);
961
-
962
- const p1 = conversation.processMessage({
963
- content: "msg-1",
964
- attachments: [],
965
- onEvent: () => {},
966
- requestId: "req-1",
967
- });
968
- await waitForPendingRun(1);
969
-
970
837
  const guardian = {
971
838
  trustClass: "guardian" as const,
972
839
  sourceChannel: "vellum" as const,
973
840
  requesterExternalUserId: "guardian-principal",
974
841
  };
975
- conversation.enqueueMessage({
976
- content: "msg-2",
977
- requestId: "req-2",
978
- trustContext: guardian,
979
- });
980
-
981
- // Land the out-of-band slot write inside the drain's window, after its
982
- // stamp and before the loop opens: the drain awaits persistUserMessage
983
- // there. The resting slot alone is moved; the per-turn snapshot is the
984
- // drain's to carry into the run.
985
- const originalPersist = conversation.persistUserMessage.bind(conversation);
986
- let slotMoved = false;
987
- (
988
- conversation as unknown as {
989
- persistUserMessage: typeof conversation.persistUserMessage;
990
- }
991
- ).persistUserMessage = async (opts) => {
992
- const result = await originalPersist(opts);
993
- conversation.setTrustContext(contact);
994
- slotMoved = true;
995
- return result;
996
- };
997
-
998
- await resolveRun(0);
999
- await p1;
1000
- await waitForPendingRun(2);
842
+ await expect(
843
+ conversation.processMessage({
844
+ content: "msg-1",
845
+ attachments: [],
846
+ onEvent: () => {},
847
+ requestId: "req-1",
848
+ trustContext: guardian,
849
+ }),
850
+ ).rejects.toThrow("history store exploded");
1001
851
 
1002
- // Guard the test itself: the injection must have run, and the slot must
1003
- // disagree with the turn, or the assertion below passes for the wrong
1004
- // reason.
1005
- expect(slotMoved).toBe(true);
1006
852
  expect(conversation.getTrustContext()).toBe(contact);
1007
- expect(conversation.currentTurnTrustContext).toBe(guardian);
1008
- // The guardian's turn renders no actor section, whatever the slot says.
1009
- expect(conversation.currentTurnInboundActorContext).toBeNull();
1010
-
1011
- await resolveRun(1);
1012
- await new Promise((r) => setTimeout(r, 10));
853
+ expect(pendingRuns.length).toBe(0);
1013
854
  });
1014
855
 
1015
856
  test("a processMessage turn keeps its turn-start trust when the slot moves before the loop opens", async () => {