@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
@@ -1,30 +1,7 @@
1
1
  /**
2
- * Tests for `isToolActiveForContext` host-tool capability gating.
3
- *
4
- * Scenarios verified:
5
- * - chrome-extension is its own executor and is exempt from the hasNoClient
6
- * gate (the extension's own popup UI gates commands; there is no SSE
7
- * interactive approval channel, and chrome-extension turns intentionally
8
- * run with `hasNoClient: true` because chrome-extension is not in
9
- * `INTERACTIVE_INTERFACES`).
10
- * - macos requires a connected SSE client for host tools that flow through
11
- * the proxy (e.g. host_bash, host_file_*, host_browser), so
12
- * `hasNoClient: true` denies those on macos.
13
- * - host_browser IS in the macos capability set — the proxy routes
14
- * host_browser_request frames to the desktop client via SSE (or via the
15
- * Chrome extension registry when an extension connection is present).
16
- *
17
- * The per-capability check (`supportsHostProxy(transport, capability)`) runs
18
- * first and is authoritative for structural support, so host_bash and
19
- * host_file_* are filtered out for chrome-extension regardless of the
20
- * hasNoClient flag.
21
- *
22
- * Cross-client exception: tools whose capabilities are in
23
- * CROSS_CLIENT_EXPOSED_CAPABILITIES (host_bash, host_file, host_browser)
24
- * are allowed for non-host-proxy interactive interfaces ("web", "ios")
25
- * when at least one capable client is connected via the event hub.
26
- * chrome-extension is excluded as a security boundary, regardless of
27
- * whether the capability is technically supported elsewhere.
2
+ * Tests for host-tool schema visibility. Host tool definitions stay stable
3
+ * across interactive and background turns. Chrome extension remains limited
4
+ * to host_browser because it is the only host capability it implements.
28
5
  */
29
6
 
30
7
  import { beforeEach, describe, expect, mock, test } from "bun:test";
@@ -260,7 +237,7 @@ describe("isToolActiveForContext - Slack task_progress UI exception", () => {
260
237
  expect(isToolActiveForContext("ui_update", ctx)).toBe(true);
261
238
  });
262
239
 
263
- test("ui_dismiss remains hidden for Slack without dynamic UI support", () => {
240
+ test("ui_dismiss stays active for Slack without dynamic UI support", () => {
264
241
  expect(
265
242
  isToolActiveForContext(
266
243
  "ui_dismiss",
@@ -272,42 +249,45 @@ describe("isToolActiveForContext - Slack task_progress UI exception", () => {
272
249
  },
273
250
  }),
274
251
  ),
275
- ).toBe(false);
252
+ ).toBe(true);
276
253
  });
277
254
 
278
- test("Slack task_progress UI tools still require a connected client path", () => {
279
- expect(
280
- isToolActiveForContext(
281
- "ui_show",
282
- makeCtx({
283
- hasNoClient: true,
284
- channelCapabilities: {
285
- channel: "slack",
286
- supportsDynamicUi: false,
287
- },
288
- }),
289
- ),
290
- ).toBe(false);
255
+ test("Slack UI tools stay active for a clientless turn", () => {
256
+ for (const name of ["ui_show", "ui_update", "ui_dismiss"]) {
257
+ expect(
258
+ isToolActiveForContext(
259
+ name,
260
+ makeCtx({
261
+ hasNoClient: true,
262
+ channelCapabilities: {
263
+ channel: "slack",
264
+ supportsDynamicUi: false,
265
+ },
266
+ }),
267
+ ),
268
+ ).toBe(true);
269
+ }
291
270
  });
292
271
 
293
- test("other non-dynamic channels still hide UI surface tools", () => {
294
- expect(
295
- isToolActiveForContext(
296
- "ui_show",
297
- makeCtx({
298
- hasNoClient: false,
299
- channelCapabilities: {
300
- channel: "telegram",
301
- supportsDynamicUi: false,
302
- },
303
- }),
304
- ),
305
- ).toBe(false);
272
+ test("other non-dynamic channels keep UI surface tools on the wire", () => {
273
+ for (const name of ["ui_show", "ui_update", "ui_dismiss"]) {
274
+ expect(
275
+ isToolActiveForContext(
276
+ name,
277
+ makeCtx({
278
+ hasNoClient: false,
279
+ channelCapabilities: {
280
+ channel: "telegram",
281
+ supportsDynamicUi: false,
282
+ },
283
+ }),
284
+ ),
285
+ ).toBe(true);
286
+ }
306
287
  });
307
288
  });
308
289
 
309
290
  describe("isToolActiveForContext — host tool capability gating", () => {
310
- // macOS transport: SSE-based interactive approval required.
311
291
  test("host_bash is active for macOS with a connected client", () => {
312
292
  expect(
313
293
  isToolActiveForContext(
@@ -317,27 +297,22 @@ describe("isToolActiveForContext — host tool capability gating", () => {
317
297
  ).toBe(true);
318
298
  });
319
299
 
320
- test("host_bash is NOT active for macOS when hasNoClient is true (security invariant)", () => {
321
- // macOS uses an SSE-based interactive approval channel. Without a
322
- // connected client the guardian auto-approve path could execute host
323
- // commands unattended, so host tools must be denied.
300
+ test("host_bash remains active for a background macOS turn", () => {
324
301
  expect(
325
302
  isToolActiveForContext(
326
303
  "host_bash",
327
304
  makeCtx({ hasNoClient: true, transportInterface: "macos" }),
328
305
  ),
329
- ).toBe(false);
306
+ ).toBe(true);
330
307
  });
331
-
332
- test("host_file_read is NOT active for macOS when hasNoClient is true", () => {
308
+ test("host_file_read remains active for a background macOS turn", () => {
333
309
  expect(
334
310
  isToolActiveForContext(
335
311
  "host_file_read",
336
312
  makeCtx({ hasNoClient: true, transportInterface: "macos" }),
337
313
  ),
338
- ).toBe(false);
314
+ ).toBe(true);
339
315
  });
340
-
341
316
  test("host_browser is active for macOS with a connected client", () => {
342
317
  // macOS supports host_browser — the proxy routes host_browser_request
343
318
  // frames to the desktop client via SSE (or via the Chrome extension
@@ -350,19 +325,14 @@ describe("isToolActiveForContext — host tool capability gating", () => {
350
325
  ).toBe(true);
351
326
  });
352
327
 
353
- test("host_browser is NOT active for macOS when hasNoClient is true", () => {
354
- // macOS supports host_browser structurally, but without a connected
355
- // client the host_browser_request frames have no consumer, so the tool
356
- // is denied.
328
+ test("host_browser remains active for a background macOS turn", () => {
357
329
  expect(
358
330
  isToolActiveForContext(
359
331
  "host_browser",
360
332
  makeCtx({ hasNoClient: true, transportInterface: "macos" }),
361
333
  ),
362
- ).toBe(false);
334
+ ).toBe(true);
363
335
  });
364
-
365
- // chrome-extension transport: the extension is its own executor.
366
336
  test("host_browser is active for chrome-extension even when hasNoClient is true", () => {
367
337
  // chrome-extension turns run with `hasNoClient: true` by design because
368
338
  // chrome-extension is not in `INTERACTIVE_INTERFACES` — it is not an
@@ -418,10 +388,7 @@ describe("isToolActiveForContext — host tool capability gating", () => {
418
388
  ).toBe(false);
419
389
  });
420
390
 
421
- // Backwards-compat fallback: no transport plumbed through.
422
- test("host_bash falls back to hasNoClient gate when transport is undefined (client connected)", () => {
423
- // Without a transport interface we cannot run the per-capability check,
424
- // so we fall back to the coarse-grained `hasNoClient` behavior.
391
+ test("host_bash remains active without transport metadata for a connected turn", () => {
425
392
  expect(
426
393
  isToolActiveForContext(
427
394
  "host_bash",
@@ -430,20 +397,18 @@ describe("isToolActiveForContext — host tool capability gating", () => {
430
397
  ).toBe(true);
431
398
  });
432
399
 
433
- test("host_bash falls back to hasNoClient gate when transport is undefined (no client)", () => {
400
+ test("host_bash remains active without transport metadata for a background turn", () => {
434
401
  expect(
435
402
  isToolActiveForContext(
436
403
  "host_bash",
437
404
  makeCtx({ hasNoClient: true, transportInterface: undefined }),
438
405
  ),
439
- ).toBe(false);
406
+ ).toBe(true);
440
407
  });
441
408
  });
442
409
 
443
410
  describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)", () => {
444
411
  test("host_bash is active for web transport when a host_bash-capable client is connected", () => {
445
- // Cross-client path: a web turn should see host_bash when a macOS client
446
- // with host_bash capability is connected via the event hub.
447
412
  mockClientCountByCapability.set("host_bash", 1);
448
413
  expect(
449
414
  isToolActiveForContext(
@@ -453,30 +418,24 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
453
418
  ).toBe(true);
454
419
  });
455
420
 
456
- test("host_bash is NOT active for web transport when no capable client is connected", () => {
457
- // No cross-client fallback: hub has no host_bash-capable subscribers.
421
+ test("host_bash remains active for web when no capable client is connected", () => {
458
422
  mockClientCountByCapability.set("host_bash", 0);
459
423
  expect(
460
424
  isToolActiveForContext(
461
425
  "host_bash",
462
426
  makeCtx({ hasNoClient: false, transportInterface: "web" }),
463
427
  ),
464
- ).toBe(false);
428
+ ).toBe(true);
465
429
  });
466
-
467
- test("host_file_read is NOT active for web transport when only a host_bash client is connected", () => {
468
- // The cross-client exception is per-capability: a host_bash-capable
469
- // client in the hub does not satisfy host_file's exposure check, since
470
- // listClientsByCapability is queried with the tool's actual capability.
430
+ test("host_file_read remains active for web without host_file clients", () => {
471
431
  mockClientCountByCapability.set("host_bash", 1);
472
432
  expect(
473
433
  isToolActiveForContext(
474
434
  "host_file_read",
475
435
  makeCtx({ hasNoClient: false, transportInterface: "web" }),
476
436
  ),
477
- ).toBe(false);
437
+ ).toBe(true);
478
438
  });
479
-
480
439
  test("host_bash for macos transport is unaffected by the cross-client exception", () => {
481
440
  // macos natively supports host_bash via host proxy — the supportsHostProxy
482
441
  // check passes, so the cross-client branch is never reached.
@@ -489,19 +448,15 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
489
448
  ).toBe(true);
490
449
  });
491
450
 
492
- test("host_bash for macos with no client is still denied (security invariant unaffected)", () => {
493
- // Even with a capable client in the hub, the macos SSE path takes
494
- // precedence — it passes the supportsHostProxy check, bypasses the
495
- // cross-client branch, and reaches the hasNoClient gate.
451
+ test("host_bash remains active for a background macOS turn", () => {
496
452
  mockClientCountByCapability.set("host_bash", 1);
497
453
  expect(
498
454
  isToolActiveForContext(
499
455
  "host_bash",
500
456
  makeCtx({ hasNoClient: true, transportInterface: "macos" }),
501
457
  ),
502
- ).toBe(false);
458
+ ).toBe(true);
503
459
  });
504
-
505
460
  test("host_bash is NOT active for chrome-extension even when a capable client is connected", () => {
506
461
  // Security boundary: chrome-extension only gets host_browser. The
507
462
  // cross-client exception explicitly excludes chrome-extension transport
@@ -515,16 +470,14 @@ describe("isToolActiveForContext — cross-client exception (Phase 1: host_bash)
515
470
  ).toBe(false);
516
471
  });
517
472
 
518
- test("host_bash is NOT active for web transport when hasNoClient is true (no approval UI)", () => {
519
- // hasNoClient gate: no interactive approval UI available for this turn.
520
- // Cross-client exception must not bypass this gate.
473
+ test("host_bash remains active for a background web turn", () => {
521
474
  mockClientCountByCapability.set("host_bash", 1);
522
475
  expect(
523
476
  isToolActiveForContext(
524
477
  "host_bash",
525
478
  makeCtx({ hasNoClient: true, transportInterface: "web" }),
526
479
  ),
527
- ).toBe(false);
480
+ ).toBe(true);
528
481
  });
529
482
  });
530
483
 
@@ -547,14 +500,14 @@ describe("isToolActiveForContext — cross-client exposure for host_file_*", ()
547
500
  ).toBe(true);
548
501
  });
549
502
 
550
- test(`${tool} is NOT exposed for web when no host_file client is connected`, () => {
503
+ test(`${tool} remains active for web when no host_file client is connected`, () => {
551
504
  mockClientCountByCapability.set("host_file", 0);
552
505
  expect(
553
506
  isToolActiveForContext(
554
507
  tool,
555
508
  makeCtx({ hasNoClient: false, transportInterface: "web" }),
556
509
  ),
557
- ).toBe(false);
510
+ ).toBe(true);
558
511
  });
559
512
 
560
513
  test(`${tool} is NOT exposed for chrome-extension (security boundary)`, () => {
@@ -570,20 +523,19 @@ describe("isToolActiveForContext — cross-client exposure for host_file_*", ()
570
523
  ).toBe(false);
571
524
  });
572
525
 
573
- test(`${tool} is NOT exposed when hasNoClient is true (no approval UI)`, () => {
526
+ test(`${tool} remains active for a background web turn`, () => {
574
527
  mockClientCountByCapability.set("host_file", 1);
575
528
  expect(
576
529
  isToolActiveForContext(
577
530
  tool,
578
531
  makeCtx({ hasNoClient: true, transportInterface: "web" }),
579
532
  ),
580
- ).toBe(false);
533
+ ).toBe(true);
581
534
  });
582
535
  }
583
536
 
584
- test("listClientsByCapability is queried with the actual capability, not host_bash (regression guard for D5 latent bug)", () => {
585
- mockClientCountByCapability.set("host_bash", 0);
586
- mockClientCountByCapability.set("host_file", 1);
537
+ test("host_file_transfer does not depend on the live host_file roster", () => {
538
+ mockClientCountByCapability.set("host_file", 0);
587
539
  expect(
588
540
  isToolActiveForContext(
589
541
  "host_file_transfer",
@@ -622,37 +574,33 @@ describe("isToolActiveForContext — cross-client exposure for host_browser", ()
622
574
  ).toBe(true);
623
575
  });
624
576
 
625
- test("host_browser is NOT exposed for web when no host_browser client is connected", () => {
577
+ test("host_browser remains active for web when no host_browser client is connected", () => {
626
578
  mockClientCountByCapability.set("host_browser", 0);
627
579
  expect(
628
580
  isToolActiveForContext(
629
581
  "host_browser",
630
582
  makeCtx({ hasNoClient: false, transportInterface: "web" }),
631
583
  ),
632
- ).toBe(false);
584
+ ).toBe(true);
633
585
  });
634
-
635
- test("host_browser is NOT exposed for ios when no host_browser client is connected", () => {
586
+ test("host_browser remains active for ios when no host_browser client is connected", () => {
636
587
  mockClientCountByCapability.set("host_browser", 0);
637
588
  expect(
638
589
  isToolActiveForContext(
639
590
  "host_browser",
640
591
  makeCtx({ hasNoClient: false, transportInterface: "ios" }),
641
592
  ),
642
- ).toBe(false);
593
+ ).toBe(true);
643
594
  });
644
-
645
- test("host_browser is NOT exposed when hasNoClient is true (no approval UI)", () => {
646
- // hasNoClient gate: cross-client exception must not bypass this.
595
+ test("host_browser remains active for a background web turn", () => {
647
596
  mockClientCountByCapability.set("host_browser", 1);
648
597
  expect(
649
598
  isToolActiveForContext(
650
599
  "host_browser",
651
600
  makeCtx({ hasNoClient: true, transportInterface: "web" }),
652
601
  ),
653
- ).toBe(false);
602
+ ).toBe(true);
654
603
  });
655
-
656
604
  test("host_browser for macos transport is unaffected by the cross-client exception", () => {
657
605
  // macos natively supports host_browser via host proxy — the
658
606
  // supportsHostProxy check passes, so the cross-client branch is never
@@ -681,10 +629,7 @@ describe("isToolActiveForContext — cross-client exposure for host_browser", ()
681
629
  ).toBe(true);
682
630
  });
683
631
 
684
- test("listClientsByCapability is queried with host_browser, not host_bash or host_file (per-capability invariant)", () => {
685
- // Defense against any future regression that hardcodes a different
686
- // capability in the cross-client check. Only host_browser-capable
687
- // clients should satisfy host_browser exposure.
632
+ test("host_browser does not depend on the live host_browser roster", () => {
688
633
  mockClientCountByCapability.set("host_bash", 1);
689
634
  mockClientCountByCapability.set("host_file", 1);
690
635
  mockClientCountByCapability.set("host_browser", 0);
@@ -693,7 +638,7 @@ describe("isToolActiveForContext — cross-client exposure for host_browser", ()
693
638
  "host_browser",
694
639
  makeCtx({ hasNoClient: false, transportInterface: "web" }),
695
640
  ),
696
- ).toBe(false);
641
+ ).toBe(true);
697
642
  });
698
643
  });
699
644
 
@@ -41,10 +41,13 @@ mock.module("../../mcp/mcp-header-store.js", () => ({
41
41
  }));
42
42
 
43
43
  mock.module("../../config/loader.js", () => ({
44
- getConfig: () => ({ mcp: { servers: {} } }),
45
44
  invalidateConfigCache: () => {},
46
45
  }));
47
46
 
47
+ mock.module("../../mcp/workspace-mcp-config.js", () => ({
48
+ loadWorkspaceMcpConfig: () => ({ servers: {} }),
49
+ }));
50
+
48
51
  mock.module("../../tools/registry.js", () => ({
49
52
  registerMcpTools: () => [],
50
53
  unregisterAllMcpTools: () => {},
@@ -0,0 +1,133 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, utimesSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import {
5
+ afterEach,
6
+ beforeEach,
7
+ describe,
8
+ expect,
9
+ mock,
10
+ test,
11
+ } from "bun:test";
12
+
13
+ const WORKSPACE_DIR = mkdtempSync(join(tmpdir(), "skill-md-mtime-refresh-"));
14
+ process.env.VELLUM_WORKSPACE_DIR = WORKSPACE_DIR;
15
+
16
+ const seedGraphCalls: number[] = [];
17
+ const seedV2Calls: number[] = [];
18
+
19
+ mock.module("../../config/loader.js", () => ({
20
+ getConfig: () => ({}),
21
+ }));
22
+
23
+ mock.module("../../plugins/defaults/memory/graph/capability-seed.js", () => ({
24
+ seedSkillGraphNodes: () => {
25
+ seedGraphCalls.push(Date.now());
26
+ },
27
+ seedUninstalledCatalogSkillMemories: async () => {},
28
+ }));
29
+
30
+ mock.module(
31
+ "../../plugins/defaults/memory/substrate/boot-maintenance.js",
32
+ () => ({
33
+ maybeSeedCapabilitySkills: () => {
34
+ seedV2Calls.push(Date.now());
35
+ },
36
+ maybeSeedCliCommandCards: () => {},
37
+ }),
38
+ );
39
+
40
+ const {
41
+ _resetSkillMdMtimeStateForTests,
42
+ maybeRefreshSkillCapabilityMemoriesFromMtime,
43
+ refreshSkillCapabilityMemories,
44
+ startWorkspaceSkillMdMtimePoll,
45
+ stopWorkspaceSkillMdMtimePoll,
46
+ } = await import("../skill-memory-refresh.js");
47
+
48
+ const SKILLS_DIR = join(WORKSPACE_DIR, "skills");
49
+
50
+ function writeSkill(id: string): string {
51
+ const dir = join(SKILLS_DIR, id);
52
+ mkdirSync(dir, { recursive: true });
53
+ const skillMd = join(dir, "SKILL.md");
54
+ writeFileSync(
55
+ skillMd,
56
+ `---\nname: ${id}\ndescription: ${id}\n---\nbody\n`,
57
+ );
58
+ return skillMd;
59
+ }
60
+
61
+ describe("maybeRefreshSkillCapabilityMemoriesFromMtime", () => {
62
+ beforeEach(() => {
63
+ rmSync(SKILLS_DIR, { recursive: true, force: true });
64
+ mkdirSync(SKILLS_DIR, { recursive: true });
65
+ seedGraphCalls.length = 0;
66
+ seedV2Calls.length = 0;
67
+ _resetSkillMdMtimeStateForTests();
68
+ });
69
+
70
+ afterEach(() => {
71
+ _resetSkillMdMtimeStateForTests();
72
+ });
73
+
74
+ test("first call reseeds, a second call with unchanged SKILL.md does not", () => {
75
+ writeSkill("alpha");
76
+ expect(maybeRefreshSkillCapabilityMemoriesFromMtime()).toBe(true);
77
+ expect(seedGraphCalls).toHaveLength(1);
78
+ expect(seedV2Calls).toHaveLength(1);
79
+
80
+ expect(maybeRefreshSkillCapabilityMemoriesFromMtime()).toBe(false);
81
+ expect(seedGraphCalls).toHaveLength(1);
82
+ });
83
+
84
+ test("explicit refresh remembers the signature so the next poll is a no-op", () => {
85
+ writeSkill("alpha");
86
+ refreshSkillCapabilityMemories();
87
+ expect(seedGraphCalls).toHaveLength(1);
88
+ expect(maybeRefreshSkillCapabilityMemoriesFromMtime()).toBe(false);
89
+ });
90
+
91
+ test("SKILL.md mtime change reseeds", () => {
92
+ const skillMd = writeSkill("alpha");
93
+ expect(maybeRefreshSkillCapabilityMemoriesFromMtime()).toBe(true);
94
+
95
+ const later = new Date(Date.now() + 5_000);
96
+ utimesSync(skillMd, later, later);
97
+ expect(maybeRefreshSkillCapabilityMemoriesFromMtime()).toBe(true);
98
+ expect(seedGraphCalls).toHaveLength(2);
99
+ });
100
+
101
+ test("TOOLS.json change without a SKILL.md mtime change does not reseed", () => {
102
+ writeSkill("alpha");
103
+ expect(maybeRefreshSkillCapabilityMemoriesFromMtime()).toBe(true);
104
+
105
+ writeFileSync(
106
+ join(SKILLS_DIR, "alpha", "TOOLS.json"),
107
+ JSON.stringify({ version: 1, tools: [] }),
108
+ );
109
+ expect(maybeRefreshSkillCapabilityMemoriesFromMtime()).toBe(false);
110
+ expect(seedGraphCalls).toHaveLength(1);
111
+ });
112
+
113
+ test("adding or removing a skill directory reseeds", () => {
114
+ writeSkill("alpha");
115
+ expect(maybeRefreshSkillCapabilityMemoriesFromMtime()).toBe(true);
116
+
117
+ writeSkill("bravo");
118
+ expect(maybeRefreshSkillCapabilityMemoriesFromMtime()).toBe(true);
119
+
120
+ rmSync(join(SKILLS_DIR, "bravo"), { recursive: true, force: true });
121
+ expect(maybeRefreshSkillCapabilityMemoriesFromMtime()).toBe(true);
122
+ expect(seedGraphCalls).toHaveLength(3);
123
+ });
124
+
125
+ test("startWorkspaceSkillMdMtimePoll runs once immediately and is idempotent", () => {
126
+ writeSkill("alpha");
127
+ startWorkspaceSkillMdMtimePoll();
128
+ expect(seedGraphCalls).toHaveLength(1);
129
+ startWorkspaceSkillMdMtimePoll();
130
+ expect(seedGraphCalls).toHaveLength(1);
131
+ stopWorkspaceSkillMdMtimePoll();
132
+ });
133
+ });
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Fan out an app change to open conversation surfaces, connected clients,
3
+ * and the publish pipeline.
4
+ *
5
+ * Compilation is the caller's responsibility. This module only notifies:
6
+ * it must not call `compileApp()` because that begins with `rm -rf dist/`
7
+ * and would race a compile already in flight.
8
+ */
9
+
10
+ import { broadcastMessage } from "../runtime/assistant-event-hub.js";
11
+ import { publishAppsChanged } from "../runtime/sync/resource-sync-events.js";
12
+ import { updatePublishedAppDeployment } from "../services/published-app-updater.js";
13
+ import type { Conversation } from "./conversation.js";
14
+ import { allConversations } from "./conversation-registry.js";
15
+ import { refreshSurfacesForApp } from "./conversation-surfaces.js";
16
+
17
+ export function broadcastAppFilesChanged(appId: string): void {
18
+ broadcastMessage({ type: "app_files_changed", appId });
19
+ publishAppsChanged();
20
+ }
21
+
22
+ /**
23
+ * Refresh every in-memory conversation that has this app open, then broadcast
24
+ * and re-deploy. Pass `alsoRefresh` when the calling conversation might not
25
+ * yet be in the registry (tool post-execution hooks).
26
+ */
27
+ export function notifyAppSurfacesChanged(
28
+ appId: string,
29
+ opts?: { fileChange?: boolean; status?: string },
30
+ alsoRefresh?: Conversation,
31
+ ): void {
32
+ const seen = new Set<string>();
33
+ if (alsoRefresh) {
34
+ refreshSurfacesForApp(alsoRefresh, appId, opts);
35
+ seen.add(alsoRefresh.conversationId);
36
+ }
37
+ for (const conversation of allConversations()) {
38
+ if (seen.has(conversation.conversationId)) {
39
+ continue;
40
+ }
41
+ refreshSurfacesForApp(conversation, appId, opts);
42
+ }
43
+ broadcastAppFilesChanged(appId);
44
+ void updatePublishedAppDeployment(appId);
45
+ }