@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,19 +1,19 @@
1
1
  /**
2
2
  * File watchers and config reload logic extracted from DaemonServer.
3
- * Watches workspace files (config, prompts) and skills directories
4
- * for changes.
3
+ * Watches workspace files (config, prompts, signals, avatar, sounds).
4
+ * Skill capability cards reseed from SKILL.md mtimes in the memory worker
5
+ * and the assistant process, not from this watcher.
5
6
  */
6
7
  import {
7
8
  existsSync,
8
9
  type FSWatcher,
9
10
  mkdirSync,
10
- readdirSync,
11
11
  readFileSync,
12
12
  unwatchFile,
13
13
  watch,
14
14
  watchFile,
15
15
  } from "node:fs";
16
- import { basename, join } from "node:path";
16
+ import { join } from "node:path";
17
17
 
18
18
  import { AVATAR_IMAGE_FILENAME } from "@vellumai/avatar-manifest";
19
19
 
@@ -44,50 +44,14 @@ import {
44
44
  getSoundsDir,
45
45
  getWorkspaceDir,
46
46
  getWorkspacePromptPath,
47
- getWorkspaceSkillsDir,
47
+ WORKSPACE_MCP_FILENAME,
48
48
  } from "../util/platform.js";
49
49
  import { evictConversationsForReload } from "./conversation-store.js";
50
50
  import { parseIdentityFields } from "./handlers/identity.js";
51
51
  import { reloadMcpServers } from "./mcp-reload-service.js";
52
- import { refreshSkillCapabilityMemories } from "./skill-memory-refresh.js";
53
52
 
54
53
  const log = getLogger("config-watcher");
55
54
 
56
- const SKILL_WATCH_SKIPPED_DIRS = new Set([
57
- "node_modules",
58
- ".git",
59
- "__pycache__",
60
- ".install-staging",
61
- ".cache",
62
- ".next",
63
- ".turbo",
64
- ".venv",
65
- "coverage",
66
- ]);
67
-
68
- function isSkippedSkillWatchPath(relativePath: string): boolean {
69
- if (relativePath === "(unknown)") {
70
- return false;
71
- }
72
-
73
- const segments = relativePath.split(/[\\/]+/).filter(Boolean);
74
- return segments.some((segment) => SKILL_WATCH_SKIPPED_DIRS.has(segment));
75
- }
76
-
77
- /**
78
- * Files whose contents feed the skill catalog: `SKILL.md` carries a skill's
79
- * metadata and body, `TOOLS.json` its tool manifest. Both live at the top
80
- * level of a skill directory. Every other file (scripts, reference docs, build
81
- * output) is read fresh on demand or irrelevant to the catalog, so changes to
82
- * them do not require a catalog reload.
83
- */
84
- const SKILL_CATALOG_FILENAMES = new Set(["SKILL.md", "TOOLS.json"]);
85
-
86
- /** True when a changed path's basename is a skill-catalog file. */
87
- function isSkillCatalogFile(filename: string): boolean {
88
- return SKILL_CATALOG_FILENAMES.has(basename(filename));
89
- }
90
-
91
55
  /**
92
56
  * Attach a resilient error handler to an FSWatcher so that async errors
93
57
  * (e.g. ENXIO when a Unix socket file like `gateway.sock` appears in a
@@ -227,19 +191,11 @@ export class ConfigWatcher {
227
191
  return;
228
192
  }
229
193
  try {
230
- const prevMcpFingerprint = JSON.stringify(this.lastConfig?.mcp ?? {});
231
194
  const changed = await this.refreshConfigFromSources();
232
195
  if (changed) {
233
196
  evictConversationsForReload();
234
197
  refreshAuthenticatedApiRateLimit();
235
198
  publishConfigChanged();
236
- const newConfig = this.lastConfig ?? getConfig();
237
- const newMcpFingerprint = JSON.stringify(newConfig.mcp ?? {});
238
- if (newMcpFingerprint !== prevMcpFingerprint) {
239
- reloadMcpServers().catch((err: unknown) => {
240
- log.error({ err }, "MCP reload after config change failed");
241
- });
242
- }
243
199
  }
244
200
  } catch (err) {
245
201
  log.error(
@@ -248,6 +204,14 @@ export class ConfigWatcher {
248
204
  );
249
205
  }
250
206
  },
207
+ [WORKSPACE_MCP_FILENAME]: () => {
208
+ if (this.suppressReload) {
209
+ return;
210
+ }
211
+ reloadMcpServers().catch((err: unknown) => {
212
+ log.error({ err }, "MCP reload after mcp.json change failed");
213
+ });
214
+ },
251
215
  "SOUL.md": () => {
252
216
  evictConversationsForReload();
253
217
  },
@@ -270,7 +234,6 @@ export class ConfigWatcher {
270
234
  this.startAvatarWatcher();
271
235
  this.startSignalsWatcher();
272
236
  this.startUsersWatcher();
273
- this.startSkillsWatchers();
274
237
  }
275
238
 
276
239
  stop(): void {
@@ -485,150 +448,6 @@ export class ConfigWatcher {
485
448
  );
486
449
  }
487
450
  }
488
-
489
- private startSkillsWatchers(): void {
490
- const skillsDir = getWorkspaceSkillsDir();
491
- if (!existsSync(skillsDir)) {
492
- return;
493
- }
494
-
495
- const scheduleSkillsReload = (file: string): void => {
496
- if (isSkippedSkillWatchPath(file)) {
497
- return;
498
- }
499
-
500
- this.debounceTimers.schedule("skills:catalog", () => {
501
- log.info({ file }, "Skill catalog changed, reloading");
502
- evictConversationsForReload();
503
- refreshSkillCapabilityMemories(getConfig());
504
- });
505
- };
506
-
507
- // Only SKILL.md and TOOLS.json feed the skill catalog, and both live at the
508
- // top level of a skill directory. Changes to any other file (scripts,
509
- // reference docs, build output) never alter the catalog, so the watchers
510
- // react only to those two files — plus skill directories appearing or
511
- // disappearing, which is an install or removal.
512
- try {
513
- const recursiveWatcher = watch(
514
- skillsDir,
515
- { recursive: true },
516
- (_eventType, filename) => {
517
- if (!filename || isSkillCatalogFile(String(filename))) {
518
- scheduleSkillsReload(filename ? String(filename) : "(unknown)");
519
- }
520
- },
521
- );
522
- attachWatcherErrorHandler(recursiveWatcher, skillsDir);
523
- this.watchers.push(recursiveWatcher);
524
- log.info({ dir: skillsDir }, "Watching skills catalog recursively");
525
- return;
526
- } catch (err) {
527
- log.info(
528
- { err, dir: skillsDir },
529
- "Recursive skills watch unavailable; using per-skill-directory watchers",
530
- );
531
- }
532
-
533
- // Fallback when recursive watches are unavailable: watch the skills root
534
- // (to detect a skill being installed or removed) plus each immediate skill
535
- // directory (to detect SKILL.md / TOOLS.json edits). Nested subdirectories
536
- // are deliberately not watched, bounding the descriptor count to one per
537
- // skill rather than one per subdirectory.
538
- const skillDirWatchers = new Map<string, FSWatcher>();
539
-
540
- const watchDir = (
541
- dirPath: string,
542
- onChange: (filename: string | null) => void,
543
- ): FSWatcher | null => {
544
- try {
545
- const watcher = watch(dirPath, (_eventType, filename) => {
546
- onChange(filename ? String(filename) : null);
547
- });
548
- attachWatcherErrorHandler(watcher, dirPath);
549
- this.watchers.push(watcher);
550
- return watcher;
551
- } catch (err) {
552
- log.warn({ err, dirPath }, "Failed to watch skill directory");
553
- return null;
554
- }
555
- };
556
-
557
- const removeWatcher = (watcher: FSWatcher): void => {
558
- const idx = this.watchers.indexOf(watcher);
559
- if (idx !== -1) {
560
- this.watchers.splice(idx, 1);
561
- }
562
- };
563
-
564
- const listSkillDirs = (): string[] => {
565
- try {
566
- return readdirSync(skillsDir, { withFileTypes: true })
567
- .filter(
568
- (entry) =>
569
- entry.isDirectory() && !SKILL_WATCH_SKIPPED_DIRS.has(entry.name),
570
- )
571
- .map((entry) => join(skillsDir, entry.name));
572
- } catch (err) {
573
- log.warn({ err, skillsDir }, "Failed to enumerate skill directories");
574
- return [];
575
- }
576
- };
577
-
578
- // Reconcile the per-skill-directory watchers against what is on disk.
579
- // Returns true when the set of watched directories changed — i.e. a skill
580
- // was installed or removed — so the caller can reload the catalog.
581
- const refreshSkillDirWatchers = (): boolean => {
582
- const nextDirs = new Set(listSkillDirs());
583
- let changed = false;
584
-
585
- for (const [dirPath, watcher] of skillDirWatchers.entries()) {
586
- if (nextDirs.has(dirPath)) {
587
- continue;
588
- }
589
- watcher.close();
590
- skillDirWatchers.delete(dirPath);
591
- removeWatcher(watcher);
592
- changed = true;
593
- }
594
-
595
- for (const dirPath of nextDirs) {
596
- if (skillDirWatchers.has(dirPath)) {
597
- continue;
598
- }
599
- const watcher = watchDir(dirPath, (filename) => {
600
- if (!filename || isSkillCatalogFile(filename)) {
601
- scheduleSkillsReload(filename ?? "(unknown)");
602
- }
603
- });
604
- if (watcher) {
605
- skillDirWatchers.set(dirPath, watcher);
606
- changed = true;
607
- }
608
- }
609
-
610
- return changed;
611
- };
612
-
613
- const rootWatcher = watchDir(skillsDir, () => {
614
- // A change directly under the skills root means a skill directory was
615
- // added, removed, or renamed. Re-sync the per-skill watchers and reload
616
- // the catalog when the set actually changed.
617
- if (refreshSkillDirWatchers()) {
618
- scheduleSkillsReload("(skill installed or removed)");
619
- }
620
- });
621
-
622
- if (!rootWatcher) {
623
- return;
624
- }
625
-
626
- refreshSkillDirWatchers();
627
- log.info(
628
- { dir: skillsDir },
629
- "Watching skills catalog with per-skill-directory fallback",
630
- );
631
- }
632
451
  }
633
452
 
634
453
  /**
@@ -30,6 +30,7 @@ import {
30
30
  isModelNotFoundError,
31
31
  isVisionNotSupportedError,
32
32
  } from "../util/provider-error-patterns.js";
33
+ import { safeStringSlice } from "../util/unicode.js";
33
34
 
34
35
  /**
35
36
  * Classified conversation error ready for client emission.
@@ -580,7 +581,7 @@ function classifyCore(
580
581
  const detailMatch = message.match(/API error \(\d+\):\s*(.+)/i);
581
582
  const detail = detailMatch?.[1];
582
583
  const suffix = detail
583
- ? `: ${detail.length > 200 ? detail.slice(0, 200) + "…" : detail}`
584
+ ? `: ${detail.length > 200 ? safeStringSlice(detail, 0, 200) + "…" : detail}`
584
585
  : "";
585
586
  return {
586
587
  code: "PROVIDER_API",
@@ -612,7 +613,7 @@ function extractProviderDetail(message: string): string | undefined {
612
613
  if (!detail) {
613
614
  return undefined;
614
615
  }
615
- return detail.length > 200 ? `${detail.slice(0, 200)}…` : detail;
616
+ return detail.length > 200 ? `${safeStringSlice(detail, 0, 200)}…` : detail;
616
617
  }
617
618
 
618
619
  /**
@@ -16,6 +16,7 @@ import { estimateContentBlockTokens } from "../context/token-estimator.js";
16
16
  import { getSummaryFromContextMessage } from "../plugins/defaults/compaction/window-manager.js";
17
17
  import { mediaSourceDescriptor } from "../providers/media-resolve.js";
18
18
  import type { ContentBlock, Message } from "../providers/types.js";
19
+ import { safeStringSlice } from "../util/unicode.js";
19
20
 
20
21
  export interface StripMediaOptions {
21
22
  /** Token budget available for media in the latest user message. Keeps as
@@ -238,7 +239,7 @@ function fileBlockToStub(
238
239
  const extracted = (block.extracted_text ?? "").trim();
239
240
  const preview =
240
241
  extracted.length > MAX_MEDIA_STUB_TEXT
241
- ? `${extracted.slice(0, MAX_MEDIA_STUB_TEXT)}...`
242
+ ? `${safeStringSlice(extracted, 0, MAX_MEDIA_STUB_TEXT)}...`
242
243
  : extracted;
243
244
  return {
244
245
  type: "text",
@@ -646,52 +646,6 @@ export async function kickQueueDrain(
646
646
  }
647
647
  }
648
648
 
649
- /**
650
- * Commit `actor` as the actor the turn about to start runs for: the resting
651
- * slot names them, so the history is scoped to them and every reader of the
652
- * slot (the `<turn_context>` actor section, memory retrieval, the Slack
653
- * transcript filters) describes them rather than whoever sent last, and the
654
- * per-turn snapshot then reads back that same actor, which is also returned
655
- * for callers that must hold it in a local. A caller with no actor of its own
656
- * (internal dispatch, or a queued message whose enqueue found the slot empty)
657
- * passes `undefined`, and the resting actor stands.
658
- *
659
- * `reloadHistory` is off only for a steered drain, which keeps its resident
660
- * history: a steer comes from the actor whose turn it cut off, and that
661
- * history may hold the in-memory repair of the abandoned `tool_use`, which a
662
- * reload would discard.
663
- *
664
- * The committed actor is captured and snapshotted before the reload awaits:
665
- * the slot is writable out-of-band across that await (a wake's stamp, a
666
- * pointer elevation), and a read after it would hand the turn that writer's
667
- * actor. A reload that fails starts no turn, so the slot is put back to what
668
- * it held before, guarded so a writer that legitimately moved it in between
669
- * is left alone.
670
- */
671
- async function commitTurnActor(
672
- conversation: Conversation,
673
- actor: TrustContext | undefined,
674
- options: { reloadHistory: boolean },
675
- ): Promise<TrustContext | undefined> {
676
- const prior = restingTrust(conversation);
677
- if (actor) {
678
- conversation.setTrustContext(actor);
679
- }
680
- const turnTrustContext = restingTrust(conversation);
681
- conversation.currentTurnTrustContext = turnTrustContext;
682
- if (options.reloadHistory) {
683
- try {
684
- await conversation.ensureActorScopedHistory();
685
- } catch (err) {
686
- if (actor && restingTrust(conversation) === actor) {
687
- conversation.setTrustContext(prior ?? null);
688
- }
689
- throw err;
690
- }
691
- }
692
- return turnTrustContext;
693
- }
694
-
695
649
  async function drainSingleMessage(
696
650
  conversation: Conversation,
697
651
  next: QueuedMessage,
@@ -794,12 +748,11 @@ async function drainSingleMessage(
794
748
  // a different actor's context if a concurrent request mutates the live fields.
795
749
  // Trust comes from the queued message, not the live slot: the slot holds
796
750
  // whichever actor sent most recently, which is this sender only when nobody
797
- // else sent while this message waited.
798
- const turnTrustContext = await commitTurnActor(
799
- conversation,
800
- next.trustContext,
801
- { reloadHistory: !steered },
802
- );
751
+ // else sent while this message waited. Held in a local as well, because the
752
+ // field is writable out-of-band across the awaits between here and the loop
753
+ // call below.
754
+ const turnTrustContext = next.trustContext ?? restingTrust(conversation);
755
+ conversation.currentTurnTrustContext = turnTrustContext;
803
756
  conversation.currentTurnChannelCapabilities =
804
757
  conversation.channelCapabilities;
805
758
 
@@ -1407,12 +1360,9 @@ async function drainBatch(
1407
1360
  // The head's trust governs the batch, which is sound only because
1408
1361
  // `buildPassthroughBatch` refuses to coalesce messages from different
1409
1362
  // actors; without that boundary this would run a tail under the head's
1410
- // trust.
1411
- const turnTrustContext = await commitTurnActor(
1412
- conversation,
1413
- head.trustContext,
1414
- { reloadHistory: true },
1415
- );
1363
+ // trust. Held in a local for the same reason as the single-message drain.
1364
+ const turnTrustContext = head.trustContext ?? restingTrust(conversation);
1365
+ conversation.currentTurnTrustContext = turnTrustContext;
1416
1366
  conversation.currentTurnChannelCapabilities =
1417
1367
  conversation.channelCapabilities;
1418
1368
 
@@ -1890,16 +1840,37 @@ export async function processMessage(
1890
1840
  metadata: callerMetadata,
1891
1841
  trustContext: committingTrustContext,
1892
1842
  } = options;
1843
+ const priorRestingTrust = restingTrust(conversation);
1844
+ if (committingTrustContext) {
1845
+ conversation.setTrustContext(committingTrustContext);
1846
+ }
1893
1847
  // Held in a local as well as on the conversation: the field is writable
1894
1848
  // out-of-band while this turn is in flight (`agent-wake` stamps it and
1895
1849
  // restores the prior value in a `finally`), so reading it back at the agent
1896
1850
  // loop call below would reintroduce the late read this capture exists to
1897
- // avoid. The local is what the loop runs under.
1898
- const turnTrustContext = await commitTurnActor(
1899
- conversation,
1900
- committingTrustContext,
1901
- { reloadHistory: true },
1902
- );
1851
+ // avoid. The local is what the loop runs under. Captured before the history
1852
+ // reload for the same reason: that await is one of the windows a writer can
1853
+ // land in.
1854
+ const turnTrustContext = restingTrust(conversation);
1855
+ conversation.currentTurnTrustContext = turnTrustContext;
1856
+ try {
1857
+ await conversation.ensureActorScopedHistory();
1858
+ } catch (err) {
1859
+ // This is the commitment point for the turn, so the stamp above is
1860
+ // correct, but a reload that fails starts no turn: the conversation must
1861
+ // not be left attributed to a sender that never ran. Guarded on identity
1862
+ // so a writer that legitimately moved the slot across the await keeps it.
1863
+ // Only the resting slot needs putting back; `runAgentLoopImpl` re-seeds
1864
+ // the per-turn field at the head of every turn, so no later dispatch can
1865
+ // inherit it.
1866
+ if (
1867
+ committingTrustContext &&
1868
+ restingTrust(conversation) === committingTrustContext
1869
+ ) {
1870
+ conversation.setTrustContext(priorRestingTrust ?? null);
1871
+ }
1872
+ throw err;
1873
+ }
1903
1874
  conversation.currentTurnAuthContext = conversation.authContext;
1904
1875
  conversation.currentTurnSourceActorPrincipalId =
1905
1876
  sourceActorPrincipalId ?? conversation.authContext?.actorPrincipalId;
@@ -87,6 +87,7 @@ import { TERMINAL_STATUSES } from "../subagent/types.js";
87
87
  import { canonicalizeInboundIdentity } from "../util/canonicalize-identity.js";
88
88
  import { safeParseRecord } from "../util/json.js";
89
89
  import { getLogger } from "../util/logger.js";
90
+ import { safeStringSlice } from "../util/unicode.js";
90
91
  import { channelSupportsInlineOptions } from "./channel-ui-capability.js";
91
92
  import { findConversationOrSubagent } from "./conversation-registry.js";
92
93
  import {
@@ -575,7 +576,7 @@ function injectActiveSurfaceContext(
575
576
  if (schema && schema !== '"{}"' && schema !== "{}") {
576
577
  const truncatedSchema =
577
578
  schema.length > MAX_SCHEMA_LENGTH
578
- ? schema.slice(0, MAX_SCHEMA_LENGTH) + "… (truncated)"
579
+ ? safeStringSlice(schema, 0, MAX_SCHEMA_LENGTH) + "… (truncated)"
579
580
  : schema;
580
581
  lines.push("", `Data schema: ${truncatedSchema}`);
581
582
  }
@@ -818,10 +819,15 @@ export function applySightFrameRetention(
818
819
  * `null` on the happy path (desktop, full capabilities, no special context)
819
820
  * where no block is injected. Split from {@link injectChannelCapabilityContext}
820
821
  * so callers can capture the exact injected text for metadata persistence.
822
+ *
823
+ * Live vs clientless guidance belongs here, not in tool schemas. Tool
824
+ * definitions stay identical so the tools cache prefix can reuse; this block
825
+ * sits later in the system prompt and can describe the current turn.
821
826
  */
822
827
  export function buildChannelCapabilityBlock(
823
828
  caps: ChannelCapabilities,
824
829
  clientOs: string | undefined = caps.clientOS,
830
+ isNonInteractive = false,
825
831
  ): string | null {
826
832
  // Happy path: desktop with full capabilities and no special context — skip injection.
827
833
  if (
@@ -873,18 +879,24 @@ export function buildChannelCapabilityBlock(
873
879
  "- Do NOT reference the dashboard UI, settings panels, or visual preference pickers.",
874
880
  );
875
881
  if (!caps.supportsDynamicUi) {
876
- if (caps.channel === "slack") {
882
+ if (isNonInteractive) {
877
883
  lines.push(
878
- '- Do NOT use app_create. Only use ui_show/ui_update for card surfaces with template: "task_progress"; present all other information as text.',
884
+ "- ui_show, ui_update, and ui_dismiss persist conversation content for the next capable client that opens this conversation. Do not claim a surface is visible now or wait for an action.",
879
885
  );
880
886
  } else {
887
+ if (caps.channel === "slack") {
888
+ lines.push(
889
+ '- Do NOT use app_create. Only use ui_show/ui_update for card surfaces with template: "task_progress"; present all other information as text.',
890
+ );
891
+ } else {
892
+ lines.push(
893
+ "- Do NOT use ui_show, ui_update, or app_create. This channel cannot render them.",
894
+ );
895
+ }
881
896
  lines.push(
882
- "- Do NOT use ui_show, ui_update, or app_create this channel cannot render them.",
897
+ "- Present information as well-formatted text instead of dynamic UI.",
883
898
  );
884
899
  }
885
- lines.push(
886
- "- Present information as well-formatted text instead of dynamic UI.",
887
- );
888
900
  }
889
901
  if (caps.channel === "whatsapp") {
890
902
  lines.push(
@@ -2852,6 +2864,7 @@ export async function applyRuntimeInjections(
2852
2864
  const channelCapabilityBlock = buildChannelCapabilityBlock(
2853
2865
  channelCapabilities,
2854
2866
  clientOs,
2867
+ options.isNonInteractive === true,
2855
2868
  );
2856
2869
  if (channelCapabilityBlock !== null) {
2857
2870
  channelCapabilitiesCaptured = channelCapabilityBlock;
@@ -75,11 +75,13 @@ export interface SkillToolProjection {
75
75
  }
76
76
 
77
77
  /**
78
- * Conversation-scoped cache for skill projection. Avoids re-scanning the entire
79
- * conversation history and re-reading the filesystem on every agent turn.
78
+ * Conversation-scoped cache for skill projection. Avoids re-scanning the
79
+ * conversation history on every agent turn. Catalog membership is not stored
80
+ * here: each projection reads a fresh `loadSkillCatalog()` so newly
81
+ * installed or removed skills are visible on the next turn.
80
82
  *
81
- * Each conversation should own its own cache instance to prevent cross-conversation
82
- * state bleed.
83
+ * Each conversation should own its own cache instance to prevent
84
+ * cross-conversation state bleed.
83
85
  */
84
86
  export interface SkillProjectionCache {
85
87
  /** Cached deriveActiveSkills result. */
@@ -95,9 +97,6 @@ export interface SkillProjectionCache {
95
97
  /** The accumulated active skill entries. */
96
98
  entries: ActiveSkillEntry[];
97
99
  };
98
- /** Cached skill catalog. Invalidated when the conversation is marked stale
99
- * (e.g. skill directories changed on disk while a run is in progress). */
100
- catalog?: SkillSummary[];
101
100
  }
102
101
 
103
102
  export interface ProjectSkillToolsOptions {
@@ -113,9 +112,9 @@ export interface ProjectSkillToolsOptions {
113
112
  */
114
113
  previouslyActiveSkillIds?: Map<string, string>;
115
114
  /**
116
- * Conversation-scoped projection cache. When provided, projectSkillTools will
117
- * avoid redundant deriveActiveSkills scans and loadSkillCatalog filesystem
118
- * reads across agent turns.
115
+ * Conversation-scoped projection cache. When provided, projectSkillTools
116
+ * avoids redundant deriveActiveSkills scans across agent turns. Catalog
117
+ * membership is always a fresh `loadSkillCatalog()` read.
119
118
  */
120
119
  cache?: SkillProjectionCache;
121
120
  /**
@@ -310,23 +309,6 @@ function getCachedActiveSkills(
310
309
  return entries;
311
310
  }
312
311
 
313
- /**
314
- * Return the skill catalog, caching it across agent turns.
315
- *
316
- * The cache is invalidated when the conversation is marked stale (e.g. skill
317
- * directories changed on disk while the conversation is still processing).
318
- */
319
- function getCachedCatalog(cache?: SkillProjectionCache): SkillSummary[] {
320
- if (!cache) {
321
- return loadSkillCatalog();
322
- }
323
-
324
- if (!cache.catalog) {
325
- cache.catalog = loadSkillCatalog();
326
- }
327
- return cache.catalog;
328
- }
329
-
330
312
  // ---------------------------------------------------------------------------
331
313
  // Main export
332
314
  // ---------------------------------------------------------------------------
@@ -363,11 +345,11 @@ export function projectSkillTools(
363
345
  const contextIds = contextEntries.map((e) => e.id);
364
346
  const allCandidateIds = new Set<string>([...contextIds, ...preactivated]);
365
347
 
366
- // Load the catalog (cached for conversation lifetime), then scope it to the
367
- // conversation's per-chat plugin selection so plugin-contributed skills from
368
- // unselected plugins are not resolvable for this run (null = no restriction).
348
+ // Load the catalog, then scope it to the conversation's per-chat plugin
349
+ // selection so plugin-contributed skills from unselected plugins are not
350
+ // resolvable for this run (null = no restriction).
369
351
  const catalog = filterSkillsByEnabledPlugins(
370
- getCachedCatalog(options?.cache),
352
+ loadSkillCatalog(),
371
353
  options?.effectiveEnabledPluginSet ?? null,
372
354
  );
373
355
  const catalogById = new Map<string, SkillSummary>();
@@ -548,7 +548,7 @@ export function clearAllActiveConversations(): number {
548
548
  }
549
549
 
550
550
  /**
551
- * Evict in-memory conversations after a config/prompt/skills reload so the next
551
+ * Evict in-memory conversations after a config/prompt reload so the next
552
552
  * turn rebuilds them against the new config. Conversations with in-flight work
553
553
  * (a live turn, a queued successor, or an active subagent) are marked stale
554
554
  * and rebuilt by `getOrCreateConversation` once that work finishes. Idle