@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
@@ -159,3 +159,31 @@ describe("serializeConversationSummary · enabledPlugins", () => {
159
159
  expect("enabledPlugins" in summary).toBe(false);
160
160
  });
161
161
  });
162
+
163
+ describe("serializeConversationSummary · title keys", () => {
164
+ test("resolves a stored title key for the locale", () => {
165
+ const summary = serializeConversationSummary({
166
+ conversation: makeConversationRow({
167
+ title: "conversation.title.generating",
168
+ }),
169
+ displayMeta: { displayOrder: null, isPinned: false, groupId: null },
170
+ parentCache: new Map(),
171
+ isProcessing: false,
172
+ locale: "es",
173
+ });
174
+ expect(summary.title).toBe("Generando título...");
175
+ expect("titleState" in summary).toBe(false);
176
+ });
177
+
178
+ test("passes a stored display string through", () => {
179
+ const summary = serialize(
180
+ makeConversationRow({ title: "Untitled Conversation" }),
181
+ );
182
+ expect(summary.title).toBe("Untitled Conversation");
183
+ });
184
+
185
+ test("passes a custom title through", () => {
186
+ const summary = serialize(makeConversationRow({ title: "Weekly planning" }));
187
+ expect(summary.title).toBe("Weekly planning");
188
+ });
189
+ });
@@ -8,6 +8,10 @@
8
8
 
9
9
  import { parseChannelId } from "../../channels/types.js";
10
10
  import { normalizeConversationType } from "../../daemon/message-types/shared.js";
11
+ import {
12
+ resolveConversationTitle,
13
+ type SupportedLocale,
14
+ } from "../../i18n/index.js";
11
15
  import { buildChannelBindingMetadata } from "../../messaging/channel-binding-metadata.js";
12
16
  import {
13
17
  type AttentionState,
@@ -78,6 +82,7 @@ interface ForkLineage {
78
82
  function buildForkLineage(
79
83
  conversation: ConversationRow,
80
84
  parentCache: Map<string, ConversationRow | null>,
85
+ locale?: SupportedLocale,
81
86
  ): ForkLineage {
82
87
  const parentConversationId = conversation.forkParentConversationId;
83
88
  const parentMessageId = conversation.forkParentMessageId;
@@ -101,7 +106,7 @@ function buildForkLineage(
101
106
  forkParent: {
102
107
  conversationId: parentConversationId,
103
108
  messageId: parentMessageId,
104
- title: parentConversation.title ?? "Untitled",
109
+ title: resolveConversationTitle(parentConversation.title, locale),
105
110
  },
106
111
  };
107
112
  }
@@ -178,6 +183,11 @@ export function serializeConversationSummary(params: {
178
183
  * with no daemon-store coupling.
179
184
  */
180
185
  isProcessing: boolean;
186
+ /**
187
+ * Locale used to resolve stored title keys. Omitted locale is English
188
+ * inside {@link resolveConversationTitle}.
189
+ */
190
+ locale?: SupportedLocale;
181
191
  }) {
182
192
  const {
183
193
  conversation,
@@ -186,14 +196,15 @@ export function serializeConversationSummary(params: {
186
196
  displayMeta,
187
197
  parentCache,
188
198
  isProcessing,
199
+ locale,
189
200
  } = params;
190
201
  const originChannel = parseChannelId(conversation.originChannel);
191
202
  const assistantAttention = buildAssistantAttention(attentionState);
192
- const forkLineage = buildForkLineage(conversation, parentCache);
203
+ const forkLineage = buildForkLineage(conversation, parentCache, locale);
193
204
 
194
205
  return {
195
206
  id: conversation.id,
196
- title: conversation.title ?? "Untitled",
207
+ title: resolveConversationTitle(conversation.title, locale),
197
208
  createdAt: conversation.createdAt,
198
209
  updatedAt: conversation.updatedAt,
199
210
  lastMessageAt: conversation.lastMessageAt,
@@ -253,6 +264,7 @@ export function serializeConversationSummary(params: {
253
264
  */
254
265
  export function buildConversationDetailResponse(
255
266
  conversationId: string,
267
+ locale?: SupportedLocale,
256
268
  ): { conversation: ReturnType<typeof serializeConversationSummary> } | null {
257
269
  const conversation = getConversation(conversationId);
258
270
  if (!conversation || conversation.conversationType === "private") {
@@ -274,6 +286,7 @@ export function buildConversationDetailResponse(
274
286
  // Checks in-memory flag first (hot path), falls back to the
275
287
  // persisted `processing_started_at` column for cold conversations.
276
288
  isProcessing: isConversationProcessing(conversation.id),
289
+ locale,
277
290
  }),
278
291
  };
279
292
  }
@@ -4,6 +4,7 @@ import {
4
4
  conversationMetadataSyncTag,
5
5
  SYNC_TAGS,
6
6
  } from "../../daemon/message-types/sync.js";
7
+ import { resolveConversationTitle } from "../../i18n/index.js";
7
8
  import { syncAvatarToPlatform } from "../../platform/sync-avatar.js";
8
9
  import { getAvatarImagePath } from "../../util/platform.js";
9
10
  import { broadcastMessage } from "../assistant-event-hub.js";
@@ -257,7 +258,7 @@ export function publishConversationTitleChanged(
257
258
  {
258
259
  type: "conversation_title_updated",
259
260
  conversationId,
260
- title,
261
+ title: resolveConversationTitle(title),
261
262
  },
262
263
  conversationId,
263
264
  );
@@ -5,6 +5,7 @@ import {
5
5
  } from "../util/host-process.js";
6
6
  import { getLogger } from "../util/logger.js";
7
7
  import { getWorkspaceDir } from "../util/platform.js";
8
+ import { safeStringSlice } from "../util/unicode.js";
8
9
 
9
10
  const log = getLogger("run-script");
10
11
 
@@ -200,7 +201,7 @@ function truncate(text: string): string {
200
201
  if (text.length <= MAX_OUTPUT_BYTES) {
201
202
  return text;
202
203
  }
203
- return text.slice(0, MAX_OUTPUT_BYTES) + "\n... (truncated)";
204
+ return safeStringSlice(text, 0, MAX_OUTPUT_BYTES) + "\n... (truncated)";
204
205
  }
205
206
 
206
207
  /**
@@ -23,6 +23,7 @@ import { publishSchedulesChanged } from "../runtime/sync/resource-sync-events.js
23
23
  import { UserError } from "../util/errors.js";
24
24
  import { getLogger } from "../util/logger.js";
25
25
  import { withSqliteRetry } from "../util/sqlite-retry.js";
26
+ import { safeStringSlice } from "../util/unicode.js";
26
27
  import {
27
28
  hasOwnerDeferProvenance,
28
29
  isDeferSchedule,
@@ -1511,8 +1512,14 @@ async function finishScheduleRunRow(
1511
1512
  status: result.status,
1512
1513
  finishedAt: now,
1513
1514
  durationMs,
1514
- output: result.output?.slice(0, 10_000) ?? null,
1515
- error: result.error?.slice(0, 2000) ?? null,
1515
+ output:
1516
+ result.output === undefined
1517
+ ? null
1518
+ : safeStringSlice(result.output, 0, 10_000),
1519
+ error:
1520
+ result.error === undefined
1521
+ ? null
1522
+ : safeStringSlice(result.error, 0, 2000),
1516
1523
  })
1517
1524
  .where(eq(scheduleRuns.id, runId))
1518
1525
  .run(),
@@ -195,7 +195,7 @@ async function main(): Promise<void> {
195
195
  //
196
196
  // Bounded by MCP_STARTUP_GRACE_MS: unreachable servers must not hold the
197
197
  // schedules that do not use them. The connect runs on past the deadline.
198
- const mcpStartup = startConfiguredMcpServers(getConfig().mcp);
198
+ const mcpStartup = startConfiguredMcpServers();
199
199
  await Promise.race([
200
200
  mcpStartup,
201
201
  // Unref'd so a connect that beats the deadline leaves nothing pending.
@@ -0,0 +1,137 @@
1
+ import { afterEach, describe, expect, spyOn, test } from "bun:test";
2
+
3
+ import type { CredentialRecord } from "@vellumai/service-contracts/credential-rpc";
4
+
5
+ import * as envRegistry from "../../config/env-registry.js";
6
+ import {
7
+ CesHttpRecordBackend,
8
+ createCesHttpRecordBackendIfConfigured,
9
+ } from "../ces-http-record-backend.js";
10
+
11
+ const ORIGINAL_URL = process.env.CES_CREDENTIAL_URL;
12
+ const ORIGINAL_TOKEN = process.env.CES_SERVICE_TOKEN;
13
+
14
+ function restoreCesHttpEnv(): void {
15
+ if (ORIGINAL_URL === undefined) {
16
+ delete process.env.CES_CREDENTIAL_URL;
17
+ } else {
18
+ process.env.CES_CREDENTIAL_URL = ORIGINAL_URL;
19
+ }
20
+ if (ORIGINAL_TOKEN === undefined) {
21
+ delete process.env.CES_SERVICE_TOKEN;
22
+ } else {
23
+ process.env.CES_SERVICE_TOKEN = ORIGINAL_TOKEN;
24
+ }
25
+ }
26
+
27
+ function fakeHttpClient(options: {
28
+ listRecords: () => Promise<{
29
+ records: Array<{ account: string; record: CredentialRecord }>;
30
+ unreachable: boolean;
31
+ }>;
32
+ }) {
33
+ return {
34
+ getRecord: async () => ({ record: undefined, unreachable: false }),
35
+ setRecord: async () => false,
36
+ deleteRecord: async () => "error" as const,
37
+ listRecords: options.listRecords,
38
+ bulkSetRecords: async (records: Array<{ account: string }>) =>
39
+ records.map((entry) => ({ account: entry.account, ok: false })),
40
+ };
41
+ }
42
+
43
+ describe("CES HTTP credential record backend", () => {
44
+ afterEach(() => {
45
+ restoreCesHttpEnv();
46
+ });
47
+
48
+ test("factory returns null when the assistant is not containerized", () => {
49
+ const spy = spyOn(envRegistry, "getIsContainerized").mockReturnValue(false);
50
+ process.env.CES_CREDENTIAL_URL = "http://ces-container:8090";
51
+ process.env.CES_SERVICE_TOKEN = "token-123";
52
+ try {
53
+ expect(createCesHttpRecordBackendIfConfigured()).toBeNull();
54
+ } finally {
55
+ spy.mockRestore();
56
+ }
57
+ });
58
+
59
+ test("factory returns null when CES_CREDENTIAL_URL is unset", () => {
60
+ const spy = spyOn(envRegistry, "getIsContainerized").mockReturnValue(true);
61
+ delete process.env.CES_CREDENTIAL_URL;
62
+ process.env.CES_SERVICE_TOKEN = "token-123";
63
+ try {
64
+ expect(createCesHttpRecordBackendIfConfigured()).toBeNull();
65
+ } finally {
66
+ spy.mockRestore();
67
+ }
68
+ });
69
+
70
+ test("factory returns null when CES_SERVICE_TOKEN is unset", () => {
71
+ const spy = spyOn(envRegistry, "getIsContainerized").mockReturnValue(true);
72
+ process.env.CES_CREDENTIAL_URL = "http://ces-container:8090";
73
+ delete process.env.CES_SERVICE_TOKEN;
74
+ try {
75
+ expect(createCesHttpRecordBackendIfConfigured()).toBeNull();
76
+ } finally {
77
+ spy.mockRestore();
78
+ }
79
+ });
80
+
81
+ test("factory returns a backend when containerized with CES HTTP env", () => {
82
+ const spy = spyOn(envRegistry, "getIsContainerized").mockReturnValue(true);
83
+ process.env.CES_CREDENTIAL_URL = "http://ces-container:8090";
84
+ process.env.CES_SERVICE_TOKEN = "token-123";
85
+ try {
86
+ const backend = createCesHttpRecordBackendIfConfigured();
87
+ expect(backend?.name).toBe("ces-http");
88
+ expect(backend?.isAvailable()).toBe(true);
89
+ } finally {
90
+ spy.mockRestore();
91
+ }
92
+ });
93
+
94
+ test("list returns records when HTTP reports a reachable catalog", async () => {
95
+ const record: CredentialRecord = {
96
+ credentialId: "cred-http",
97
+ service: "github",
98
+ field: "token",
99
+ allowedTools: ["bash"],
100
+ allowedDomains: [],
101
+ createdAt: 1,
102
+ updatedAt: 2,
103
+ };
104
+ const backend = new CesHttpRecordBackend(
105
+ fakeHttpClient({
106
+ listRecords: async () => ({
107
+ records: [{ account: "github:token", record }],
108
+ unreachable: false,
109
+ }),
110
+ }),
111
+ );
112
+
113
+ const listed = await backend.list();
114
+ expect(listed).toEqual([{ account: "github:token", record }]);
115
+ });
116
+
117
+ test("list returns an empty catalog when HTTP is reachable and empty", async () => {
118
+ const backend = new CesHttpRecordBackend(
119
+ fakeHttpClient({
120
+ listRecords: async () => ({ records: [], unreachable: false }),
121
+ }),
122
+ );
123
+
124
+ const listed = await backend.list();
125
+ expect(listed).toEqual([]);
126
+ });
127
+
128
+ test("list returns null when HTTP reports the vault unreachable", async () => {
129
+ const backend = new CesHttpRecordBackend(
130
+ fakeHttpClient({
131
+ listRecords: async () => ({ records: [], unreachable: true }),
132
+ }),
133
+ );
134
+
135
+ expect(await backend.list()).toBeNull();
136
+ });
137
+ });
@@ -0,0 +1,120 @@
1
+ /**
2
+ * CES HTTP backend for non-secret credential records (identity + policy).
3
+ *
4
+ * Used when the CES RPC socket is down but the CES HTTP sidecar is
5
+ * reachable (containerized deployments with CES_CREDENTIAL_URL).
6
+ */
7
+
8
+ import {
9
+ type CesHttpCredentialClient,
10
+ createCesHttpCredentialClient,
11
+ } from "@vellumai/ces-client/http-credentials";
12
+ import type { CredentialRecord } from "@vellumai/service-contracts/credential-rpc";
13
+
14
+ import { getIsContainerized } from "../config/env-registry.js";
15
+ import { getLogger } from "../util/logger.js";
16
+ import type { CredentialRecordBackend } from "./ces-rpc-record-backend.js";
17
+
18
+ const log = getLogger("ces-http-record-backend");
19
+
20
+ type RecordHttpClient = Pick<
21
+ CesHttpCredentialClient,
22
+ | "getRecord"
23
+ | "setRecord"
24
+ | "deleteRecord"
25
+ | "listRecords"
26
+ | "bulkSetRecords"
27
+ >;
28
+
29
+ function resolveEnvHttpClient(): RecordHttpClient | undefined {
30
+ const baseUrl = process.env.CES_CREDENTIAL_URL?.trim();
31
+ const serviceToken = process.env.CES_SERVICE_TOKEN?.trim();
32
+ if (!baseUrl || !serviceToken) {
33
+ return undefined;
34
+ }
35
+ return createCesHttpCredentialClient({ baseUrl, serviceToken }, log);
36
+ }
37
+
38
+ export class CesHttpRecordBackend implements CredentialRecordBackend {
39
+ readonly name = "ces-http";
40
+
41
+ constructor(private readonly client?: RecordHttpClient) {}
42
+
43
+ private resolveClient(): RecordHttpClient | undefined {
44
+ return this.client ?? resolveEnvHttpClient();
45
+ }
46
+
47
+ isAvailable(): boolean {
48
+ return this.resolveClient() !== undefined;
49
+ }
50
+
51
+ async get(account: string): Promise<CredentialRecord | undefined> {
52
+ const client = this.resolveClient();
53
+ if (!client) {
54
+ return undefined;
55
+ }
56
+ const result = await client.getRecord(account);
57
+ return result.record;
58
+ }
59
+
60
+ async set(account: string, record: CredentialRecord): Promise<boolean> {
61
+ const client = this.resolveClient();
62
+ if (!client) {
63
+ return false;
64
+ }
65
+ return client.setRecord(account, record);
66
+ }
67
+
68
+ async delete(
69
+ account: string,
70
+ ): Promise<"deleted" | "not-found" | "error"> {
71
+ const client = this.resolveClient();
72
+ if (!client) {
73
+ return "error";
74
+ }
75
+ return client.deleteRecord(account);
76
+ }
77
+
78
+ async list(): Promise<Array<{
79
+ account: string;
80
+ record: CredentialRecord;
81
+ }> | null> {
82
+ const client = this.resolveClient();
83
+ if (!client) {
84
+ return null;
85
+ }
86
+ const result = await client.listRecords();
87
+ if (result.unreachable) {
88
+ return null;
89
+ }
90
+ return result.records ?? [];
91
+ }
92
+
93
+ async bulkSet(
94
+ records: Array<{ account: string; record: CredentialRecord }>,
95
+ ): Promise<Array<{ account: string; ok: boolean }>> {
96
+ const client = this.resolveClient();
97
+ if (!client) {
98
+ return records.map((entry) => ({ account: entry.account, ok: false }));
99
+ }
100
+ return client.bulkSetRecords(records);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * CES HTTP record backend when the assistant is containerized and the
106
+ * CES HTTP sidecar URL is configured. Returns null otherwise.
107
+ */
108
+ export function createCesHttpRecordBackendIfConfigured(): CredentialRecordBackend | null {
109
+ if (!getIsContainerized()) {
110
+ return null;
111
+ }
112
+ if (!process.env.CES_CREDENTIAL_URL?.trim()) {
113
+ return null;
114
+ }
115
+ const backend = new CesHttpRecordBackend();
116
+ if (!backend.isAvailable()) {
117
+ return null;
118
+ }
119
+ return backend;
120
+ }
@@ -11,6 +11,11 @@ import { getLogger } from "../util/logger.js";
11
11
  const log = getLogger("ces-rpc-record-backend");
12
12
 
13
13
  export interface CredentialRecordBackend {
14
+ /**
15
+ * Transport id. `"ces-rpc"` is the attached CES socket backend; live
16
+ * catalog listing fails over to CES HTTP when that transport is down.
17
+ */
18
+ readonly name?: string;
14
19
  isAvailable(): boolean;
15
20
  get(account: string): Promise<CredentialRecord | undefined>;
16
21
  set(account: string, record: CredentialRecord): Promise<boolean>;
@@ -29,6 +34,8 @@ export interface CredentialRecordBackend {
29
34
  }
30
35
 
31
36
  export class CesRpcRecordBackend implements CredentialRecordBackend {
37
+ readonly name = "ces-rpc";
38
+
32
39
  constructor(private readonly client: CesClient) {}
33
40
 
34
41
  isAvailable(): boolean {
@@ -11,6 +11,8 @@
11
11
  * 3. Enforces per-source character budgets to prevent context flooding.
12
12
  */
13
13
 
14
+ import { safeStringSlice } from "../util/unicode.js";
15
+
14
16
  // ---------------------------------------------------------------------------
15
17
  // Types
16
18
  // ---------------------------------------------------------------------------
@@ -156,7 +158,7 @@ export function escapeContentBoundaries(content: string): string {
156
158
 
157
159
  /** Sanitize a value for use as an XML attribute (no quotes, angle brackets, newlines). */
158
160
  function sanitizeAttr(value: string): string {
159
- return value.replace(/[<>"&\r\n]/g, "").slice(0, 200);
161
+ return safeStringSlice(value.replace(/[<>"&\r\n]/g, ""), 0, 200);
160
162
  }
161
163
 
162
164
  function parseExternalContentAttributes(
@@ -0,0 +1,60 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, utimesSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, describe, expect, test } from "bun:test";
5
+
6
+ import { readWorkspaceSkillMdSignature } from "../workspace-skill-md-signature.js";
7
+
8
+ const ROOT = mkdtempSync(join(tmpdir(), "skill-md-signature-"));
9
+
10
+ afterEach(() => {
11
+ rmSync(ROOT, { recursive: true, force: true });
12
+ mkdirSync(ROOT, { recursive: true });
13
+ });
14
+
15
+ function writeSkill(id: string, body = "body"): string {
16
+ const dir = join(ROOT, id);
17
+ mkdirSync(dir, { recursive: true });
18
+ const skillMd = join(dir, "SKILL.md");
19
+ writeFileSync(skillMd, `---\nname: ${id}\ndescription: ${id}\n---\n${body}\n`);
20
+ return skillMd;
21
+ }
22
+
23
+ describe("readWorkspaceSkillMdSignature", () => {
24
+ test("empty or missing skills dir is the empty signature", () => {
25
+ expect(readWorkspaceSkillMdSignature(join(ROOT, "missing"))).toBe("");
26
+ mkdirSync(ROOT, { recursive: true });
27
+ expect(readWorkspaceSkillMdSignature(ROOT)).toBe("");
28
+ });
29
+
30
+ test("includes SKILL.md mtimes and ignores TOOLS.json", () => {
31
+ const skillMd = writeSkill("alpha");
32
+ const first = readWorkspaceSkillMdSignature(ROOT);
33
+ expect(first).toMatch(/^alpha:\d+$/);
34
+
35
+ writeFileSync(join(ROOT, "alpha", "TOOLS.json"), '{"version":1,"tools":[]}');
36
+ expect(readWorkspaceSkillMdSignature(ROOT)).toBe(first);
37
+
38
+ const later = new Date(Date.now() + 5_000);
39
+ utimesSync(skillMd, later, later);
40
+ expect(readWorkspaceSkillMdSignature(ROOT)).not.toBe(first);
41
+ });
42
+
43
+ test("add and remove skill directories change the signature", () => {
44
+ writeSkill("alpha");
45
+ const one = readWorkspaceSkillMdSignature(ROOT);
46
+ writeSkill("bravo");
47
+ const two = readWorkspaceSkillMdSignature(ROOT);
48
+ expect(two).not.toBe(one);
49
+ expect(two.split("\n")).toHaveLength(2);
50
+
51
+ rmSync(join(ROOT, "bravo"), { recursive: true, force: true });
52
+ expect(readWorkspaceSkillMdSignature(ROOT)).toBe(one);
53
+ });
54
+
55
+ test("directories without SKILL.md are omitted", () => {
56
+ mkdirSync(join(ROOT, "not-a-skill"), { recursive: true });
57
+ writeFileSync(join(ROOT, "README.md"), "nope");
58
+ expect(readWorkspaceSkillMdSignature(ROOT)).toBe("");
59
+ });
60
+ });
@@ -0,0 +1,50 @@
1
+ import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { getWorkspaceSkillsDir } from "../util/platform.js";
5
+
6
+ /**
7
+ * Cheap change detector for workspace skill capability cards: the sorted set
8
+ * of immediate skill directories that contain a top-level `SKILL.md`, each
9
+ * paired with that file's mtime. Capability cards are frontmatter fields from
10
+ * `SKILL.md`. Body files, `TOOLS.json`, scripts, and `references/` are not part
11
+ * of this signature.
12
+ *
13
+ * Listing the skills directory (not only statting known paths) is what makes
14
+ * install and delete visible: a new or removed skill directory changes the
15
+ * signature even when no previously known `SKILL.md` mtime moved.
16
+ */
17
+ export function readWorkspaceSkillMdSignature(
18
+ skillsDir: string = getWorkspaceSkillsDir(),
19
+ ): string {
20
+ if (!existsSync(skillsDir)) {
21
+ return "";
22
+ }
23
+
24
+ let entries;
25
+ try {
26
+ entries = readdirSync(skillsDir, { withFileTypes: true });
27
+ } catch {
28
+ return "";
29
+ }
30
+
31
+ const parts: string[] = [];
32
+ for (const entry of entries) {
33
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) {
34
+ continue;
35
+ }
36
+ const skillMdPath = join(skillsDir, entry.name, "SKILL.md");
37
+ try {
38
+ const st = statSync(skillMdPath);
39
+ if (!st.isFile()) {
40
+ continue;
41
+ }
42
+ parts.push(`${entry.name}:${Math.trunc(st.mtimeMs)}`);
43
+ } catch {
44
+ // No readable SKILL.md: not a catalog skill.
45
+ }
46
+ }
47
+
48
+ parts.sort();
49
+ return parts.join("\n");
50
+ }
@@ -33,7 +33,6 @@
33
33
  import { readdir } from "node:fs/promises";
34
34
  import { join } from "node:path";
35
35
 
36
- import type { SkillSummary } from "../config/skills.js";
37
36
  import type { TrustContext } from "../daemon/trust-context-types.js";
38
37
  import type { TrustClass } from "../runtime/actor-trust-resolver.js";
39
38
  import { truncate as truncateText } from "../util/truncate.js";
@@ -56,15 +55,6 @@ export interface AdvisorContextSources {
56
55
  * from the catalog section, mirroring the `skill_load` gate.
57
56
  */
58
57
  enabledPluginSet?: ReadonlySet<string> | null;
59
- /**
60
- * Pre-resolved skill catalog, typically the parent conversation's warm
61
- * `skillProjectionCache.catalog`. Passing it keeps the synchronous on-disk
62
- * catalog scan out of the consult path (and matches the catalog view the
63
- * parent turn's tool projection used). When absent, the section falls back
64
- * to a fresh `loadSkillCatalog()` scan, the same call every agent turn's
65
- * projection already makes.
66
- */
67
- skillCatalog?: readonly SkillSummary[];
68
58
  }
69
59
 
70
60
  /** Cap a block so the assembled context never balloons the consult prompt. */
@@ -114,7 +104,6 @@ async function buildToolsSection(
114
104
  */
115
105
  async function buildSkillsSection(
116
106
  enabledPluginSet: ReadonlySet<string> | null | undefined,
117
- preResolvedCatalog: readonly SkillSummary[] | undefined,
118
107
  ): Promise<string | null> {
119
108
  try {
120
109
  const [
@@ -130,19 +119,17 @@ async function buildSkillsSection(
130
119
  ]);
131
120
  const config = getConfig();
132
121
  const pluginScope = enabledPluginSet ?? null;
133
- const catalog = (preResolvedCatalog ?? loadSkillCatalog()).filter(
134
- (skill) => {
135
- if (
136
- pluginScope !== null &&
137
- skill.owner?.kind === "plugin" &&
138
- !pluginScope.has(skill.owner.id)
139
- ) {
140
- return false;
141
- }
142
- const flagKey = skillFlagKey(skill);
143
- return !flagKey || isAssistantFeatureFlagEnabled(flagKey, config);
144
- },
145
- );
122
+ const catalog = loadSkillCatalog().filter((skill) => {
123
+ if (
124
+ pluginScope !== null &&
125
+ skill.owner?.kind === "plugin" &&
126
+ !pluginScope.has(skill.owner.id)
127
+ ) {
128
+ return false;
129
+ }
130
+ const flagKey = skillFlagKey(skill);
131
+ return !flagKey || isAssistantFeatureFlagEnabled(flagKey, config);
132
+ });
146
133
  if (catalog.length === 0) {
147
134
  return null;
148
135
  }
@@ -384,7 +371,7 @@ export async function buildAdvisorContext(
384
371
  const sections = await Promise.all(
385
372
  [
386
373
  buildToolsSection(sources.allowedToolNames),
387
- buildSkillsSection(sources.enabledPluginSet, sources.skillCatalog),
374
+ buildSkillsSection(sources.enabledPluginSet),
388
375
  buildWorkspaceSection(sources),
389
376
  ].map((section) => withSectionTimeout(section, sectionTimeoutMs)),
390
377
  );
@@ -158,6 +158,7 @@ const SKILL_TOOLS: GuardedTool[] = [
158
158
  proxied("computer_use_drag"),
159
159
  proxied("computer_use_open_app"),
160
160
  proxied("computer_use_run_applescript"),
161
+ proxied("computer_use_sequence"),
161
162
  proxied("computer_use_respond"),
162
163
 
163
164
  { name: "contact_merge", input: { keep_id: "c-1", merge_id: "c-2" } },
@@ -90,7 +90,9 @@ describe("executeAcpListAgents", () => {
90
90
  const codex = parsed.agents.find((a: { id: string }) => a.id === "codex");
91
91
  expect(codex.available).toBe(false);
92
92
  expect(codex.unavailableReason).toBe("'codex-acp' is not on PATH");
93
- expect(codex.setupHint).toBe("bun add -g @agentclientprotocol/codex-acp");
93
+ expect(codex.setupHint).toBe(
94
+ "bun add -g @agentclientprotocol/codex-acp@1.10.0",
95
+ );
94
96
 
95
97
  const claude = parsed.agents.find((a: { id: string }) => a.id === "claude");
96
98
  expect(claude.available).toBe(true);