@vellumai/assistant 0.12.1 → 0.12.2-staging.1

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 (548) 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/openapi.yaml +327 -52
  6. package/package.json +3 -1
  7. package/scripts/smoke-desktop-window-theme.ts +186 -0
  8. package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
  9. package/src/__tests__/anthropic-provider.test.ts +0 -63
  10. package/src/__tests__/app-compiler.test.ts +6 -0
  11. package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -3
  13. package/src/__tests__/assistant-event-hub-machine-name.test.ts +6 -7
  14. package/src/__tests__/assistant-event-hub.test.ts +44 -0
  15. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  16. package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
  17. package/src/__tests__/computer-use-tools.test.ts +19 -7
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +28 -29
  20. package/src/__tests__/config-watcher.test.ts +23 -8
  21. package/src/__tests__/connection-model-compat.test.ts +2 -2
  22. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
  23. package/src/__tests__/conversation-pairing.test.ts +26 -0
  24. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
  25. package/src/__tests__/conversation-queue.test.ts +48 -207
  26. package/src/__tests__/conversation-retry-discard.test.ts +54 -1
  27. package/src/__tests__/conversation-skill-tools.test.ts +27 -0
  28. package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
  29. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  30. package/src/__tests__/conversation-title-service.test.ts +14 -10
  31. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
  32. package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
  33. package/src/__tests__/credential-record-retire.test.ts +171 -1
  34. package/src/__tests__/credential-routes.test.ts +212 -3
  35. package/src/__tests__/credential-security-invariants.test.ts +4 -2
  36. package/src/__tests__/cu-unified-flow.test.ts +8 -2
  37. package/src/__tests__/document-sync-tags.test.ts +25 -0
  38. package/src/__tests__/drain-kick-guard.test.ts +0 -2
  39. package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
  40. package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
  41. package/src/__tests__/git-porcelain.test.ts +47 -0
  42. package/src/__tests__/heartbeat-service.test.ts +56 -40
  43. package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
  44. package/src/__tests__/host-bash-proxy.test.ts +26 -45
  45. package/src/__tests__/host-browser-proxy.test.ts +6 -17
  46. package/src/__tests__/host-cu-proxy.test.ts +539 -0
  47. package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
  48. package/src/__tests__/host-file-proxy.test.ts +11 -23
  49. package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
  50. package/src/__tests__/list-messages-camera-frame.test.ts +121 -0
  51. package/src/__tests__/llm-catalog-parity.test.ts +25 -0
  52. package/src/__tests__/mcp-auth-routes.test.ts +6 -9
  53. package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
  54. package/src/__tests__/mcp-health-check.test.ts +8 -11
  55. package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
  56. package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
  57. package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
  58. package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
  59. package/src/__tests__/messaging-send-tool.test.ts +355 -548
  60. package/src/__tests__/model-intents.test.ts +3 -0
  61. package/src/__tests__/notification-vellum-adapter.test.ts +85 -2
  62. package/src/__tests__/oauth-commands-routes.test.ts +113 -0
  63. package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
  64. package/src/__tests__/openai-provider.test.ts +1 -0
  65. package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
  66. package/src/__tests__/platform-callback-registration.test.ts +8 -16
  67. package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
  68. package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
  69. package/src/__tests__/qdrant-manager.test.ts +132 -0
  70. package/src/__tests__/resolve-app.test.ts +135 -0
  71. package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
  72. package/src/__tests__/script-proxy-certs.test.ts +35 -0
  73. package/src/__tests__/service-account-token.test.ts +247 -0
  74. package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
  75. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  76. package/src/__tests__/skills.test.ts +9 -4
  77. package/src/__tests__/source-fingerprint.test.ts +184 -0
  78. package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
  79. package/src/__tests__/subagent-tools.test.ts +3 -2
  80. package/src/__tests__/telephony-tts-guard.test.ts +38 -3
  81. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  82. package/src/__tests__/unicode.test.ts +13 -0
  83. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
  84. package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
  85. package/src/__tests__/workspace-git-service.test.ts +26 -0
  86. package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
  87. package/src/acp/__tests__/agent-process.test.ts +258 -4
  88. package/src/acp/__tests__/auth-required.test.ts +96 -0
  89. package/src/acp/__tests__/client-handler.test.ts +90 -2
  90. package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
  91. package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
  92. package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
  93. package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
  94. package/src/acp/__tests__/session-manager.test.ts +895 -4
  95. package/src/acp/agent-process.test.ts +4 -1
  96. package/src/acp/agent-process.ts +96 -10
  97. package/src/acp/auth-required.ts +50 -1
  98. package/src/acp/auto-install.test.ts +9 -7
  99. package/src/acp/client-handler.ts +16 -3
  100. package/src/acp/model-config.test.ts +166 -0
  101. package/src/acp/model-config.ts +138 -0
  102. package/src/acp/resolve-agent.test.ts +220 -6
  103. package/src/acp/resolve-agent.ts +88 -23
  104. package/src/acp/session-manager.test.ts +196 -3
  105. package/src/acp/session-manager.ts +441 -18
  106. package/src/acp/types.ts +50 -5
  107. package/src/agent/loop.ts +1 -0
  108. package/src/api/events/acp-session-model-update.test.ts +88 -0
  109. package/src/api/events/acp-session-model-update.ts +46 -0
  110. package/src/api/events/app-files-changed.ts +4 -3
  111. package/src/api/events/notification-intent.ts +2 -0
  112. package/src/api/events/user-message-echo.ts +7 -1
  113. package/src/api/index.ts +6 -0
  114. package/src/api/responses/conversation-message.ts +5 -0
  115. package/src/apps/app-store.ts +1 -1
  116. package/src/apps/resolve-app.ts +81 -0
  117. package/src/apps/source-fingerprint.ts +266 -0
  118. package/src/avatar/identity-avatar.ts +7 -2
  119. package/src/bundler/app-compiler.ts +15 -0
  120. package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
  121. package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
  122. package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
  123. package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
  124. package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
  125. package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
  126. package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
  127. package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
  128. package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
  129. package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
  130. package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
  131. package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
  132. package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
  133. package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
  134. package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
  135. package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
  136. package/src/calls/barge-in-guard.ts +118 -0
  137. package/src/calls/call-constants.ts +24 -4
  138. package/src/calls/call-controller.ts +499 -199
  139. package/src/calls/call-transport.ts +16 -0
  140. package/src/calls/media-stream-output.ts +32 -0
  141. package/src/calls/media-stream-server.ts +82 -30
  142. package/src/calls/media-stream-stt-session.ts +15 -0
  143. package/src/{live-voice → calls}/progress-narration.ts +2 -2
  144. package/src/calls/resolve-call-tts-provider.ts +33 -2
  145. package/src/calls/tts-call-strategy.ts +9 -5
  146. package/src/calls/types.ts +2 -0
  147. package/src/calls/voice-control-protocol.ts +53 -21
  148. package/src/calls/voice-leg-coordinator.ts +189 -0
  149. package/src/calls/voice-progress-cadence.ts +423 -0
  150. package/src/calls/voice-session-bridge.ts +59 -46
  151. package/src/calls/voice-triage-escalate.ts +175 -56
  152. package/src/cli/commands/__tests__/apps.test.ts +245 -0
  153. package/src/cli/commands/__tests__/bash.test.ts +53 -0
  154. package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
  155. package/src/cli/commands/apps.help.ts +67 -13
  156. package/src/cli/commands/apps.ts +183 -0
  157. package/src/cli/commands/bash.help.ts +8 -13
  158. package/src/cli/commands/bash.ts +7 -25
  159. package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
  160. package/src/cli/commands/channels/index.help.ts +70 -2
  161. package/src/cli/commands/channels/index.ts +16 -3
  162. package/src/cli/commands/channels/send.ts +128 -0
  163. package/src/cli/commands/credentials.help.ts +6 -2
  164. package/src/cli/commands/credentials.ts +2 -2
  165. package/src/cli/commands/mcp.help.ts +8 -8
  166. package/src/cli/commands/mcp.ts +1 -1
  167. package/src/cli/commands/roadmap.help.ts +5 -1
  168. package/src/cli/commands/roadmap.ts +1 -1
  169. package/src/cli/lib/bundled-marketplace.json +103 -90
  170. package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
  171. package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
  172. package/src/config/__tests__/platform-identity.test.ts +255 -0
  173. package/src/config/__tests__/platform-rehydration.test.ts +99 -9
  174. package/src/config/acp-agent-write.ts +43 -0
  175. package/src/config/acp-defaults.test.ts +13 -3
  176. package/src/config/acp-defaults.ts +23 -6
  177. package/src/config/acp-schema.test.ts +81 -0
  178. package/src/config/acp-schema.ts +30 -2
  179. package/src/config/bundled-skills/acp/SKILL.md +22 -14
  180. package/src/config/bundled-skills/acp/TOOLS.json +5 -1
  181. package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
  182. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
  183. package/src/config/bundled-skills/computer-use/SKILL.md +20 -2
  184. package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
  185. package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
  186. package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
  187. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
  188. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
  189. package/src/config/bundled-skills/messaging/SKILL.md +3 -3
  190. package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
  191. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
  192. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
  193. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -1
  194. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
  195. package/src/config/bundled-skills/schedule/SKILL.md +3 -4
  196. package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
  197. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
  198. package/src/config/bundled-skills/skill-management/TOOLS.json +6 -2
  199. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
  200. package/src/config/default-profile-catalog.ts +1 -1
  201. package/src/config/env.ts +2 -2
  202. package/src/config/feature-flag-registry.json +24 -12
  203. package/src/config/loader.ts +1 -0
  204. package/src/config/memory-v3-gate.test.ts +31 -0
  205. package/src/config/memory-v3-gate.ts +20 -16
  206. package/src/config/platform-identity.ts +201 -0
  207. package/src/config/platform-rehydration.ts +11 -27
  208. package/src/config/schema.ts +2 -2
  209. package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
  210. package/src/config/schemas/__tests__/voice.test.ts +148 -0
  211. package/src/config/schemas/calls.ts +1 -28
  212. package/src/config/schemas/live-voice.ts +1 -173
  213. package/src/config/schemas/mcp.ts +1 -1
  214. package/src/config/schemas/memory-retrospective.ts +10 -0
  215. package/src/config/schemas/services.ts +8 -0
  216. package/src/config/schemas/skills.ts +0 -14
  217. package/src/config/schemas/voice.ts +150 -0
  218. package/src/credential-execution/process-manager.test.ts +18 -19
  219. package/src/credential-health/credential-health-service.ts +11 -17
  220. package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
  221. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
  222. package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
  223. package/src/daemon/app-change-notify.ts +45 -0
  224. package/src/daemon/config-watcher.ts +13 -194
  225. package/src/daemon/conversation-error.ts +3 -2
  226. package/src/daemon/conversation-media-retry.ts +2 -1
  227. package/src/daemon/conversation-process.ts +35 -64
  228. package/src/daemon/conversation-runtime-assembly.ts +2 -1
  229. package/src/daemon/conversation-skill-tools.ts +13 -31
  230. package/src/daemon/conversation-store.ts +1 -1
  231. package/src/daemon/conversation-surfaces.ts +15 -9
  232. package/src/daemon/conversation-tool-setup.ts +37 -43
  233. package/src/daemon/conversation.ts +0 -3
  234. package/src/daemon/dictation-profile-store.ts +2 -1
  235. package/src/daemon/handlers/shared.ts +2 -1
  236. package/src/daemon/handlers/skills.ts +6 -3
  237. package/src/daemon/host-bash-proxy.ts +8 -12
  238. package/src/daemon/host-browser-proxy.ts +15 -1
  239. package/src/daemon/host-cu-proxy.ts +186 -19
  240. package/src/daemon/host-file-proxy.ts +8 -10
  241. package/src/daemon/host-proxy-preactivation.ts +2 -1
  242. package/src/daemon/host-transfer-proxy.ts +14 -18
  243. package/src/daemon/lifecycle.ts +0 -5
  244. package/src/daemon/mcp-reload-service.ts +5 -5
  245. package/src/daemon/message-types/sync.ts +1 -0
  246. package/src/daemon/orphan-reaper.ts +1 -1
  247. package/src/daemon/providers-setup.ts +1 -1
  248. package/src/daemon/reaction-history-render.ts +2 -1
  249. package/src/daemon/shutdown-handlers.ts +1 -9
  250. package/src/daemon/skill-memory-refresh.ts +64 -0
  251. package/src/daemon/tool-side-effects.ts +7 -18
  252. package/src/desktop/__tests__/fake-desktop.ts +122 -0
  253. package/src/desktop/desktop-chrome-policy.test.ts +79 -0
  254. package/src/desktop/desktop-chrome-policy.ts +47 -0
  255. package/src/desktop/desktop-chrome-session.ts +37 -0
  256. package/src/desktop/desktop-dependencies.test.ts +102 -0
  257. package/src/desktop/desktop-dependencies.ts +308 -0
  258. package/src/desktop/desktop-feature.test.ts +18 -0
  259. package/src/desktop/desktop-feature.ts +19 -0
  260. package/src/desktop/desktop-panel-config.test.ts +135 -0
  261. package/src/desktop/desktop-panel-config.ts +149 -0
  262. package/src/desktop/desktop-session-manager.test.ts +730 -0
  263. package/src/desktop/desktop-session-manager.ts +850 -0
  264. package/src/desktop/desktop-stream-bridge.test.ts +395 -0
  265. package/src/desktop/desktop-stream-bridge.ts +214 -0
  266. package/src/desktop/desktop-wallpaper.test.ts +191 -0
  267. package/src/desktop/desktop-wallpaper.ts +113 -0
  268. package/src/desktop/desktop-window-drag.ts +177 -0
  269. package/src/desktop/desktop-window-manager-config.test.ts +230 -0
  270. package/src/desktop/desktop-window-manager-config.ts +238 -0
  271. package/src/desktop/desktop-window-theme.ts +170 -0
  272. package/src/heartbeat/heartbeat-service.ts +21 -3
  273. package/src/home/__tests__/feed-writer.test.ts +22 -0
  274. package/src/home/feed-writer.ts +11 -2
  275. package/src/hooks/types.ts +33 -0
  276. package/src/i18n/AGENTS.md +22 -0
  277. package/src/i18n/__tests__/i18n.test.ts +108 -0
  278. package/src/i18n/index.ts +16 -0
  279. package/src/i18n/locales.ts +93 -0
  280. package/src/i18n/messages.ts +61 -0
  281. package/src/i18n/title.ts +25 -0
  282. package/src/inbound/platform-callback-registration.ts +4 -5
  283. package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
  284. package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
  285. package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
  286. package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
  287. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
  288. package/src/live-voice/live-voice-photo.ts +25 -6
  289. package/src/live-voice/live-voice-session.ts +213 -693
  290. package/src/live-voice/protocol.ts +0 -2
  291. package/src/mcp/__tests__/effective-config.test.ts +2 -2
  292. package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
  293. package/src/mcp/effective-config.ts +3 -3
  294. package/src/mcp/mcp-header-store.ts +29 -45
  295. package/src/mcp/spec-schema.ts +111 -0
  296. package/src/mcp/startup.ts +8 -4
  297. package/src/mcp/workspace-mcp-config.ts +188 -0
  298. package/src/messaging/provider.ts +10 -3
  299. package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
  300. package/src/messaging/providers/callback-routing.ts +18 -0
  301. package/src/messaging/providers/channel-transport.ts +56 -0
  302. package/src/messaging/providers/discord/transport.ts +17 -0
  303. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
  304. package/src/messaging/providers/slack/adapter.ts +7 -44
  305. package/src/messaging/providers/slack/transport.ts +16 -0
  306. package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
  307. package/src/messaging/providers/telegram-bot/client.ts +0 -26
  308. package/src/messaging/providers/telegram-bot/transport.ts +20 -0
  309. package/src/messaging/providers/whatsapp/adapter.ts +1 -48
  310. package/src/messaging/providers/whatsapp/transport.ts +18 -0
  311. package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
  312. package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
  313. package/src/notifications/__tests__/guardian-feed-projection.test.ts +158 -2
  314. package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
  315. package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
  316. package/src/notifications/adapters/macos.ts +3 -0
  317. package/src/notifications/assistant-reply-producer.ts +8 -3
  318. package/src/notifications/conversation-pairing.ts +36 -16
  319. package/src/notifications/delivered-post-record.ts +11 -1
  320. package/src/notifications/guardian-feed-projection.ts +125 -5
  321. package/src/notifications/home-feed-side-effect.ts +55 -7
  322. package/src/notifications/schedule-result-producer.ts +66 -22
  323. package/src/oauth/AGENTS.md +2 -0
  324. package/src/oauth/__tests__/seed-providers-managed.test.ts +54 -0
  325. package/src/oauth/byo-connection.test.ts +105 -0
  326. package/src/oauth/byo-connection.ts +36 -6
  327. package/src/oauth/connection-resolver.test.ts +73 -0
  328. package/src/oauth/connection-resolver.ts +95 -2
  329. package/src/oauth/scope-utils.ts +24 -0
  330. package/src/oauth/seed-providers.ts +204 -25
  331. package/src/outbound-proxy/certs.ts +3 -5
  332. package/src/permissions/checker.test.ts +11 -6
  333. package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
  334. package/src/persistence/conversation-crud.ts +71 -63
  335. package/src/persistence/conversation-lineage.ts +12 -4
  336. package/src/persistence/conversation-queries.ts +2 -2
  337. package/src/persistence/conversation-title-placeholders.ts +23 -0
  338. package/src/persistence/conversation-title-service.ts +9 -26
  339. package/src/persistence/embeddings/qdrant-manager.ts +163 -18
  340. package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
  341. package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
  342. package/src/persistence/message-cursor.ts +78 -0
  343. package/src/persistence/message-reads.ts +30 -0
  344. package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
  345. package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
  346. package/src/persistence/schema/memory-core.ts +5 -0
  347. package/src/platform/client.test.ts +4 -2
  348. package/src/platform/client.ts +2 -10
  349. package/src/plugin-api/constants.ts +2 -0
  350. package/src/plugin-api/index.ts +1 -0
  351. package/src/plugin-api/types.ts +2 -0
  352. package/src/plugins/defaults/index.ts +2 -0
  353. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
  354. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
  355. package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
  356. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
  357. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
  358. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +5 -0
  359. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
  360. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
  361. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
  362. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
  363. package/src/plugins/defaults/memory/context-search/agent-runner.ts +3 -2
  364. package/src/plugins/defaults/memory/context-search/format.ts +3 -2
  365. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
  366. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
  367. package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
  368. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  369. package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
  370. package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
  371. package/src/plugins/defaults/memory/host-utils.ts +10 -0
  372. package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
  373. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
  374. package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
  375. package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
  376. package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
  377. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
  378. package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
  379. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +1 -1
  380. package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
  381. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
  382. package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
  383. package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
  384. package/src/plugins/defaults/memory/startup.ts +28 -2
  385. package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
  386. package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
  387. package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
  388. package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
  389. package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
  390. package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
  391. package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
  392. package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
  393. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
  394. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
  395. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
  396. package/src/plugins/defaults/memory/v3/card.ts +3 -2
  397. package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
  398. package/src/plugins/defaults/memory/v3/sections.ts +9 -3
  399. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
  400. package/src/plugins/defaults/memory/worker.ts +6 -0
  401. package/src/plugins/mcp-servers.ts +15 -75
  402. package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
  403. package/src/providers/__tests__/retry-callsite.test.ts +41 -0
  404. package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
  405. package/src/providers/anthropic/client.ts +0 -47
  406. package/src/providers/gemini/client.ts +9 -23
  407. package/src/providers/gemini-thought-signature.test.ts +63 -0
  408. package/src/providers/gemini-thought-signature.ts +44 -0
  409. package/src/providers/inference/__tests__/endpoint-probe.test.ts +42 -3
  410. package/src/providers/inference/auth.ts +2 -3
  411. package/src/providers/inference/connection-availability.ts +27 -8
  412. package/src/providers/inference/endpoint-probe.ts +10 -1
  413. package/src/providers/inference/resolve-auth.ts +22 -4
  414. package/src/providers/inference/service-account-token.ts +248 -0
  415. package/src/providers/model-catalog.ts +34 -0
  416. package/src/providers/model-intents.ts +10 -5
  417. package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
  418. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
  419. package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
  420. package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
  421. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
  422. package/src/providers/openai/chat-completions-provider.ts +212 -79
  423. package/src/providers/openai/codex-models.ts +0 -4
  424. package/src/providers/openai/google-thought-signature.ts +320 -0
  425. package/src/providers/opencode/client.test.ts +73 -4
  426. package/src/providers/opencode/client.ts +60 -3
  427. package/src/providers/openrouter/client.ts +20 -0
  428. package/src/providers/outbound-request-sanitize.ts +96 -0
  429. package/src/providers/retry.ts +30 -19
  430. package/src/providers/unparseable-tool-args.ts +5 -1
  431. package/src/runtime/AGENTS.md +3 -1
  432. package/src/runtime/__tests__/channel-send.test.ts +443 -0
  433. package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
  434. package/src/runtime/__tests__/host-observe.test.ts +40 -1
  435. package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
  436. package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
  437. package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
  438. package/src/runtime/agent-wake.ts +2 -1
  439. package/src/runtime/assistant-event-hub.ts +21 -3
  440. package/src/runtime/auth/same-actor.ts +38 -3
  441. package/src/runtime/channel-send.ts +316 -0
  442. package/src/runtime/host-observe.ts +11 -1
  443. package/src/runtime/http-server.ts +160 -151
  444. package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
  445. package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
  446. package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
  447. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
  448. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +2 -2
  449. package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
  450. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
  451. package/src/runtime/routes/acp-routes.test.ts +15 -0
  452. package/src/runtime/routes/acp-routes.ts +72 -9
  453. package/src/runtime/routes/app-management-routes.ts +73 -0
  454. package/src/runtime/routes/app-routes.ts +1 -1
  455. package/src/runtime/routes/channel-send-routes.ts +85 -0
  456. package/src/runtime/routes/conversation-list-routes.ts +14 -3
  457. package/src/runtime/routes/conversation-query-routes.ts +40 -0
  458. package/src/runtime/routes/conversation-routes.ts +16 -3
  459. package/src/runtime/routes/credential-routes.ts +19 -10
  460. package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
  461. package/src/runtime/routes/desktop-setup-routes.ts +35 -0
  462. package/src/runtime/routes/diagnostics-routes.ts +61 -4
  463. package/src/runtime/routes/events-routes.ts +4 -0
  464. package/src/runtime/routes/host-cu-routes.ts +6 -0
  465. package/src/runtime/routes/index.ts +4 -2
  466. package/src/runtime/routes/log-export-routes.ts +2 -0
  467. package/src/runtime/routes/mcp-auth-routes.ts +39 -47
  468. package/src/runtime/routes/migration-routes.ts +6 -20
  469. package/src/runtime/routes/oauth-commands-routes.ts +36 -0
  470. package/src/runtime/routes/platform-routes.ts +18 -15
  471. package/src/runtime/routes/workspace-routes.test.ts +55 -26
  472. package/src/runtime/routes/workspace-routes.ts +46 -63
  473. package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
  474. package/src/runtime/routes/workspace-tree-walk.ts +240 -0
  475. package/src/runtime/routes/workspace-utils.ts +17 -0
  476. package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
  477. package/src/runtime/services/conversation-serializer.ts +16 -3
  478. package/src/runtime/sync/resource-sync-events.ts +2 -1
  479. package/src/schedule/run-script.ts +2 -1
  480. package/src/schedule/schedule-store.ts +9 -2
  481. package/src/schedule/worker.ts +1 -1
  482. package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
  483. package/src/security/ces-http-record-backend.ts +120 -0
  484. package/src/security/ces-rpc-record-backend.ts +7 -0
  485. package/src/security/untrusted-content.ts +3 -1
  486. package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
  487. package/src/skills/workspace-skill-md-signature.ts +50 -0
  488. package/src/subagent/consult-context.ts +12 -25
  489. package/src/tools/__tests__/side-effect-abort-guard.test.ts +1 -0
  490. package/src/tools/acp/list-agents.test.ts +3 -1
  491. package/src/tools/acp/spawn.test.ts +146 -11
  492. package/src/tools/acp/spawn.ts +21 -3
  493. package/src/tools/acp/status.test.ts +94 -0
  494. package/src/tools/acp/status.ts +40 -3
  495. package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
  496. package/src/tools/browser/browser-execution.ts +1 -1
  497. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
  498. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
  499. package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
  500. package/src/tools/browser/network-recorder.ts +2 -1
  501. package/src/tools/capability-offer.test.ts +22 -2
  502. package/src/tools/capability-offer.ts +16 -1
  503. package/src/tools/computer-use/definitions.ts +121 -2
  504. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
  505. package/src/tools/credentials/metadata-store.ts +100 -0
  506. package/src/tools/filesystem/search.ts +2 -1
  507. package/src/tools/host-filesystem/edit.test.ts +2 -1
  508. package/src/tools/host-filesystem/edit.ts +2 -2
  509. package/src/tools/host-filesystem/read.test.ts +2 -1
  510. package/src/tools/host-filesystem/read.ts +2 -2
  511. package/src/tools/host-filesystem/transfer.test.ts +2 -1
  512. package/src/tools/host-filesystem/transfer.ts +2 -2
  513. package/src/tools/host-filesystem/write.test.ts +2 -1
  514. package/src/tools/host-filesystem/write.ts +2 -2
  515. package/src/tools/host-terminal/host-shell.test.ts +16 -0
  516. package/src/tools/host-terminal/host-shell.ts +4 -20
  517. package/src/tools/network/web-search.ts +6 -1
  518. package/src/tools/policy-context.ts +6 -7
  519. package/src/tools/skills/scaffold-managed.ts +61 -3
  520. package/src/tools/subagent/spawn.ts +5 -8
  521. package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
  522. package/src/tools/terminal/sanitized-bash.ts +105 -0
  523. package/src/tools/watch/watch-retro-report.ts +0 -8
  524. package/src/types/host-capabilities.ts +5 -3
  525. package/src/util/__tests__/language-subtag.test.ts +23 -1
  526. package/src/util/ca-bundle.ts +13 -0
  527. package/src/util/language-subtag.ts +16 -0
  528. package/src/util/platform.ts +8 -0
  529. package/src/util/unicode.ts +15 -1
  530. package/src/watch/watch-timeline.ts +2 -2
  531. package/src/workflows/run-manager.ts +2 -1
  532. package/src/workspace/git-porcelain.ts +66 -0
  533. package/src/workspace/git-service.ts +6 -55
  534. package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
  535. package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
  536. package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
  537. package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
  538. package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
  539. package/src/workspace/migrations/registry.ts +6 -0
  540. package/src/__tests__/app-source-watcher.test.ts +0 -218
  541. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
  542. package/src/__tests__/speaker-identification.test.ts +0 -56
  543. package/src/calls/speaker-identification.ts +0 -233
  544. package/src/daemon/app-source-watcher.ts +0 -218
  545. package/src/messaging/providers/whatsapp/client.ts +0 -29
  546. package/src/runtime/routes/debug-bash-routes.ts +0 -173
  547. package/src/util/fs-watcher-error.ts +0 -36
  548. /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
@@ -0,0 +1,240 @@
1
+ /**
2
+ * The directory walk behind GET /v1/workspace/tree.
3
+ *
4
+ * One directory by default. With `recursive`, the whole subtree in one
5
+ * response, so a client can search or expand without a request per folder.
6
+ * The walk yields the event loop between directories and is bounded by an
7
+ * entry cap and a deadline; it does not descend into the runtime-state
8
+ * directories the workspace's own gitignore rules name.
9
+ */
10
+ import type { Dirent, Stats } from "node:fs";
11
+ import { readdir, stat } from "node:fs/promises";
12
+ import { join, sep } from "node:path";
13
+
14
+ import ignore from "ignore";
15
+
16
+ import { WORKSPACE_GITIGNORE_RULES } from "../../workspace/git-service.js";
17
+ import type { WorkspaceEntry } from "./workspace-utils.js";
18
+
19
+ /**
20
+ * Entries a recursive listing returns before it stops descending and
21
+ * reports `truncated`. The root's own listing is always whole, so the
22
+ * answer is never worse than a non-recursive one.
23
+ */
24
+ export const MAX_RECURSIVE_ENTRIES = 20_000;
25
+
26
+ /** Wall-clock budget for one recursive walk. */
27
+ export const MAX_RECURSIVE_WALK_MS = 3_000;
28
+
29
+ /**
30
+ * Directories the recursive walk lists but does not enter: whatever the
31
+ * workspace's own gitignore rules exclude (databases, caches, dependency
32
+ * trees, logs), which is the one maintained statement of what in a
33
+ * workspace is runtime state rather than the user's files. Files those rules
34
+ * match are still listed; only descent is decided here.
35
+ */
36
+ const descentRules = ignore().add(WORKSPACE_GITIGNORE_RULES);
37
+
38
+ /** git never lists its own metadata directory, and neither does this walk. */
39
+ const GIT_DIR = ".git";
40
+
41
+ function isDescentSkipped(relativeDirPath: string, name: string): boolean {
42
+ return name === GIT_DIR || descentRules.ignores(`${relativeDirPath}/`);
43
+ }
44
+
45
+ export interface WorkspaceTreeWalk {
46
+ entries: WorkspaceEntry[];
47
+ /** The entry cap or the deadline stopped a recursive walk early. */
48
+ truncated: boolean;
49
+ /**
50
+ * Workspace-relative directories the walk listed but did not enter, for
51
+ * any reason: a gitignore rule, a symlink, or a directory that could not
52
+ * be read. A client that wants their contents asks for them one at a time.
53
+ */
54
+ skipped: string[];
55
+ }
56
+
57
+ export interface WorkspaceTreeWalkOptions {
58
+ /** Absolute directory to list. */
59
+ rootPath: string;
60
+ /** Absolute workspace root; entry paths are relative to it. */
61
+ workspaceDir: string;
62
+ showHidden: boolean;
63
+ recursive: boolean;
64
+ /**
65
+ * Per-directory size for directory entries in a non-recursive listing.
66
+ * Ignored when `recursive`, where directories report `size: null`.
67
+ */
68
+ directorySize?: (absPath: string) => number | null;
69
+ maxEntries?: number;
70
+ maxWalkMs?: number;
71
+ /** Clock for the deadline. Tests hand in a scripted one. */
72
+ now?: () => number;
73
+ }
74
+
75
+ function byDirectoriesThenName(a: WorkspaceEntry, b: WorkspaceEntry): number {
76
+ if (a.type !== b.type) {
77
+ return a.type === "directory" ? -1 : 1;
78
+ }
79
+ return a.name.localeCompare(b.name);
80
+ }
81
+
82
+ interface ListedDirectory {
83
+ entries: WorkspaceEntry[];
84
+ /** Directory entries in listing order, with whether each is a symlink. */
85
+ subdirectories: Array<{
86
+ absPath: string;
87
+ relativePath: string;
88
+ name: string;
89
+ isSymlink: boolean;
90
+ }>;
91
+ }
92
+
93
+ /**
94
+ * What a nested directory may cost a recursive walk. The listing stops
95
+ * before any stat work when the directory has more entries than the
96
+ * response has room for, and between stats once the deadline passes, so a
97
+ * single wide or slow directory cannot carry the walk past its bound.
98
+ */
99
+ interface ListingBudget {
100
+ room: number;
101
+ deadline: number;
102
+ }
103
+
104
+ /**
105
+ * One directory's entries, sorted directories first then by name. Types
106
+ * follow `stat`, so a symlinked directory reads as a directory; the caller
107
+ * decides whether to enter it. Returns `null` when a budget stops the
108
+ * listing, in which case none of it is reported.
109
+ */
110
+ async function listDirectory(
111
+ absDir: string,
112
+ {
113
+ workspaceDir,
114
+ showHidden,
115
+ directorySize,
116
+ now = Date.now,
117
+ }: WorkspaceTreeWalkOptions,
118
+ budget?: ListingBudget,
119
+ ): Promise<ListedDirectory | null> {
120
+ const dirents: Dirent[] = (
121
+ await readdir(absDir, { withFileTypes: true })
122
+ ).filter((dirent) => showHidden || !dirent.name.startsWith("."));
123
+ if (budget && dirents.length > budget.room) {
124
+ return null;
125
+ }
126
+ const entries: WorkspaceEntry[] = [];
127
+ const subdirectories: ListedDirectory["subdirectories"] = [];
128
+
129
+ for (const dirent of dirents) {
130
+ if (budget && now() > budget.deadline) {
131
+ return null;
132
+ }
133
+ const absPath = join(absDir, dirent.name);
134
+ let stats: Stats;
135
+ try {
136
+ stats = await stat(absPath);
137
+ } catch {
138
+ continue;
139
+ }
140
+ const isDir = stats.isDirectory();
141
+ // Wire paths use "/" on every host; clients key expansion and
142
+ // ancestry on them and never see the daemon's own separator.
143
+ const relativePath = absPath
144
+ .slice(workspaceDir.length + 1)
145
+ .split(sep)
146
+ .join("/");
147
+ entries.push({
148
+ name: dirent.name,
149
+ path: relativePath,
150
+ type: isDir ? "directory" : "file",
151
+ size: isDir ? (directorySize?.(absPath) ?? null) : stats.size,
152
+ mimeType: isDir ? null : Bun.file(absPath).type,
153
+ modifiedAt: stats.mtime.toISOString(),
154
+ });
155
+ if (isDir) {
156
+ subdirectories.push({
157
+ absPath,
158
+ relativePath,
159
+ name: dirent.name,
160
+ isSymlink: dirent.isSymbolicLink(),
161
+ });
162
+ }
163
+ }
164
+
165
+ entries.sort(byDirectoriesThenName);
166
+ subdirectories.sort((a, b) => a.name.localeCompare(b.name));
167
+ return { entries, subdirectories };
168
+ }
169
+
170
+ /**
171
+ * List `rootPath`. A recursive walk is depth-first with each directory's
172
+ * entries in listing order and never part of a directory, so grouping the
173
+ * result by parent path gives every directory's listing exactly as a
174
+ * non-recursive request would, or nothing for it at all.
175
+ *
176
+ * Symlinked directories are listed but never entered, so a link cannot make
177
+ * the walk cycle or leave the workspace; they are reported in `skipped`, as
178
+ * is a directory found mid-walk that cannot be read. The root's own read
179
+ * error is the caller's to report.
180
+ */
181
+ export async function walkWorkspaceTree(
182
+ options: WorkspaceTreeWalkOptions,
183
+ ): Promise<WorkspaceTreeWalk> {
184
+ const {
185
+ rootPath,
186
+ recursive,
187
+ maxEntries = MAX_RECURSIVE_ENTRIES,
188
+ maxWalkMs = MAX_RECURSIVE_WALK_MS,
189
+ now = Date.now,
190
+ } = options;
191
+ // A recursive listing carries every file, so a client sums directory
192
+ // sizes itself and no per-directory sizer runs.
193
+ const listOptions = recursive
194
+ ? { ...options, directorySize: undefined }
195
+ : options;
196
+ // The root is listed without a budget: its listing is always whole.
197
+ const root = (await listDirectory(rootPath, listOptions))!;
198
+ if (!recursive) {
199
+ return { entries: root.entries, truncated: false, skipped: [] };
200
+ }
201
+
202
+ const deadline = now() + maxWalkMs;
203
+ const entries: WorkspaceEntry[] = [...root.entries];
204
+ const skipped: string[] = [];
205
+ // Reversed so the stack pops subdirectories in listing order.
206
+ const stack = root.subdirectories.slice().reverse();
207
+
208
+ while (stack.length > 0) {
209
+ const dir = stack.pop()!;
210
+ if (dir.isSymlink || isDescentSkipped(dir.relativePath, dir.name)) {
211
+ skipped.push(dir.relativePath);
212
+ continue;
213
+ }
214
+ if (entries.length >= maxEntries || now() > deadline) {
215
+ return { entries, truncated: true, skipped };
216
+ }
217
+
218
+ let listed: ListedDirectory | null;
219
+ try {
220
+ listed = await listDirectory(dir.absPath, listOptions, {
221
+ room: maxEntries - entries.length,
222
+ deadline,
223
+ });
224
+ } catch {
225
+ skipped.push(dir.relativePath);
226
+ continue;
227
+ }
228
+ // A directory is carried whole or not at all, so a client grouping the
229
+ // result by parent never mistakes part of a listing for all of it.
230
+ if (listed === null) {
231
+ return { entries, truncated: true, skipped };
232
+ }
233
+ entries.push(...listed.entries);
234
+ for (let i = listed.subdirectories.length - 1; i >= 0; i--) {
235
+ stack.push(listed.subdirectories[i]);
236
+ }
237
+ }
238
+
239
+ return { entries, truncated: false, skipped };
240
+ }
@@ -8,9 +8,26 @@ import {
8
8
  } from "node:fs";
9
9
  import { dirname, resolve, sep } from "node:path";
10
10
 
11
+ import { z } from "zod";
12
+
11
13
  import { getWorkspaceDir } from "../../util/platform.js";
12
14
  import { BadRequestError, ConflictError } from "./errors.js";
13
15
 
16
+ /**
17
+ * One workspace entry as the tree endpoint reports it. `size` is null for a
18
+ * directory whose recursive size was not computed.
19
+ */
20
+ export const workspaceEntrySchema = z.object({
21
+ name: z.string(),
22
+ path: z.string(),
23
+ type: z.enum(["file", "directory"]),
24
+ size: z.number().nullable(),
25
+ mimeType: z.string().nullable(),
26
+ modifiedAt: z.string(),
27
+ });
28
+
29
+ export type WorkspaceEntry = z.infer<typeof workspaceEntrySchema>;
30
+
14
31
  /**
15
32
  * Resolves a user-provided relative path to an absolute path within the workspace.
16
33
  * Returns the resolved absolute path, or undefined if the path escapes the workspace root.
@@ -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
+ });