@vellumai/assistant 0.7.2 → 0.7.3

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 (347) hide show
  1. package/ARCHITECTURE.md +16 -1
  2. package/docs/architecture/memory.md +5 -2
  3. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  4. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  5. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  6. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  7. package/openapi.yaml +449 -22
  8. package/package.json +1 -1
  9. package/src/__tests__/app-control-flow.test.ts +21 -11
  10. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  11. package/src/__tests__/assistant-event.test.ts +0 -10
  12. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  13. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  14. package/src/__tests__/auto-analysis-end-to-end.test.ts +62 -1
  15. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  16. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  18. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  19. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  20. package/src/__tests__/config-schema.test.ts +1 -0
  21. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  22. package/src/__tests__/config-watcher.test.ts +140 -69
  23. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  24. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  25. package/src/__tests__/context-search-fanout.test.ts +0 -1
  26. package/src/__tests__/context-search-memory-source.test.ts +3 -7
  27. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  28. package/src/__tests__/context-search-pkb-source.test.ts +0 -1
  29. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  30. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  31. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  32. package/src/__tests__/conversation-agent-loop.test.ts +454 -5
  33. package/src/__tests__/conversation-error.test.ts +150 -3
  34. package/src/__tests__/conversation-process-callsite.test.ts +43 -0
  35. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  36. package/src/__tests__/conversation-runtime-assembly.test.ts +65 -0
  37. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  38. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  39. package/src/__tests__/conversation-store.test.ts +0 -18
  40. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  41. package/src/__tests__/conversation-surfaces-data-persist.test.ts +404 -0
  42. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +2 -5
  43. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  44. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  45. package/src/__tests__/credentials-cli.test.ts +7 -0
  46. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  47. package/src/__tests__/date-context.test.ts +164 -2
  48. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  49. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  50. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  51. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  52. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  53. package/src/__tests__/disk-usage.test.ts +150 -0
  54. package/src/__tests__/events-client-registration.test.ts +52 -0
  55. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  56. package/src/__tests__/file-write-tool.test.ts +4 -10
  57. package/src/__tests__/filing-service.test.ts +3 -4
  58. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  59. package/src/__tests__/heartbeat-service.test.ts +260 -11
  60. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  61. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  62. package/src/__tests__/host-bash-routes.test.ts +178 -13
  63. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  64. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  65. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  66. package/src/__tests__/host-file-proxy.test.ts +268 -6
  67. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  68. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  69. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  70. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  71. package/src/__tests__/injector-chain.test.ts +18 -6
  72. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  73. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  74. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  75. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  76. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  77. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  78. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  79. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  80. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  81. package/src/__tests__/openai-provider.test.ts +45 -8
  82. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  83. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  84. package/src/__tests__/platform.test.ts +2 -1
  85. package/src/__tests__/playbook-execution.test.ts +0 -43
  86. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  87. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  88. package/src/__tests__/provider-tool-name.test.ts +23 -0
  89. package/src/__tests__/relay-server.test.ts +15 -4
  90. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  91. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  92. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  93. package/src/__tests__/suggestion-routes.test.ts +46 -0
  94. package/src/__tests__/twilio-validation.test.ts +2 -2
  95. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  96. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  97. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  98. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +90 -0
  99. package/src/approvals/guardian-decision-primitive.ts +13 -0
  100. package/src/approvals/guardian-request-resolvers.ts +16 -17
  101. package/src/backup/snapshot-lock.ts +2 -27
  102. package/src/bundler/compiler-tools.ts +3 -2
  103. package/src/calls/call-conversation-messages.ts +46 -10
  104. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  105. package/src/cli/commands/bash.ts +35 -108
  106. package/src/cli/commands/contacts.ts +64 -25
  107. package/src/cli/commands/credentials.ts +56 -0
  108. package/src/cli/commands/memory-v2.ts +7 -6
  109. package/src/cli/commands/oauth/__tests__/connect.test.ts +437 -1
  110. package/src/cli/commands/oauth/connect.ts +127 -1
  111. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  112. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  113. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  114. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  115. package/src/cli/commands/platform/index.ts +16 -7
  116. package/src/cli/commands/status.ts +57 -0
  117. package/src/cli/program.ts +4 -2
  118. package/src/config/assistant-feature-flags.ts +13 -3
  119. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  120. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  121. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  122. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  123. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  124. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  125. package/src/config/env.ts +0 -8
  126. package/src/config/feature-flag-registry.json +27 -3
  127. package/src/config/loader.ts +127 -8
  128. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  129. package/src/config/schemas/call-site-catalog.ts +14 -0
  130. package/src/config/schemas/channels.ts +0 -5
  131. package/src/config/schemas/heartbeat.ts +1 -1
  132. package/src/config/schemas/llm.ts +2 -0
  133. package/src/config/schemas/memory-lifecycle.ts +13 -0
  134. package/src/config/schemas/memory-v2.ts +75 -11
  135. package/src/config/schemas/platform.ts +43 -3
  136. package/src/config/schemas/services.ts +28 -0
  137. package/src/config/seed-inference-profiles.ts +230 -33
  138. package/src/contacts/contact-store.ts +0 -25
  139. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  140. package/src/daemon/assistant-attachments.ts +4 -4
  141. package/src/daemon/config-watcher.ts +85 -57
  142. package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
  143. package/src/daemon/conversation-agent-loop.ts +170 -33
  144. package/src/daemon/conversation-error.ts +87 -15
  145. package/src/daemon/conversation-lifecycle.ts +1 -3
  146. package/src/daemon/conversation-process.ts +8 -0
  147. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  148. package/src/daemon/conversation-store.ts +2 -2
  149. package/src/daemon/conversation-surfaces.ts +195 -15
  150. package/src/daemon/conversation-tool-setup.ts +57 -14
  151. package/src/daemon/conversation.ts +17 -22
  152. package/src/daemon/date-context.ts +71 -22
  153. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  154. package/src/daemon/disk-pressure-guard.ts +343 -0
  155. package/src/daemon/disk-pressure-policy.ts +163 -0
  156. package/src/daemon/handlers/shared.ts +0 -1
  157. package/src/daemon/handlers/skills.ts +3 -4
  158. package/src/daemon/host-app-control-proxy.ts +137 -41
  159. package/src/daemon/host-bash-proxy.ts +46 -21
  160. package/src/daemon/host-cu-proxy.ts +49 -3
  161. package/src/daemon/host-file-proxy.ts +43 -7
  162. package/src/daemon/host-transfer-proxy.ts +95 -4
  163. package/src/daemon/lifecycle.ts +79 -28
  164. package/src/daemon/meet-host-supervisor.ts +4 -4
  165. package/src/daemon/meet-manifest-loader.ts +0 -1
  166. package/src/daemon/memory-v2-startup.ts +14 -4
  167. package/src/daemon/message-protocol.ts +3 -0
  168. package/src/daemon/message-types/conversations.ts +4 -0
  169. package/src/daemon/message-types/disk-pressure.ts +9 -0
  170. package/src/daemon/message-types/messages.ts +3 -0
  171. package/src/daemon/profiler-run-store.ts +5 -5
  172. package/src/daemon/tool-setup-types.ts +2 -2
  173. package/src/documents/document-store.ts +85 -0
  174. package/src/filing/filing-service.ts +30 -5
  175. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  176. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  177. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  178. package/src/heartbeat/heartbeat-service.ts +205 -31
  179. package/src/home/feed-scheduler.ts +18 -0
  180. package/src/inbound/platform-callback-registration.ts +8 -15
  181. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  182. package/src/ipc/assistant-server.ts +56 -2
  183. package/src/ipc/gateway-client.ts +37 -3
  184. package/src/live-voice/live-voice-archive.ts +4 -4
  185. package/src/live-voice/protocol.ts +5 -7
  186. package/src/media/image-service.ts +1 -7
  187. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  188. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +52 -22
  189. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  190. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  191. package/src/memory/admin.ts +5 -9
  192. package/src/memory/context-search/agent-runner.ts +19 -2
  193. package/src/memory/context-search/sources/conversations.ts +2 -11
  194. package/src/memory/context-search/sources/memory-v2.ts +5 -4
  195. package/src/memory/context-search/sources/memory.ts +0 -1
  196. package/src/memory/context-search/types.ts +0 -1
  197. package/src/memory/conversation-crud.ts +4 -12
  198. package/src/memory/db-init.ts +2 -0
  199. package/src/memory/embedding-runtime-manager.ts +119 -5
  200. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +32 -21
  201. package/src/memory/graph/conversation-graph-memory.ts +42 -54
  202. package/src/memory/graph/extraction.ts +1 -3
  203. package/src/memory/graph/graph-search.test.ts +10 -67
  204. package/src/memory/graph/graph-search.ts +1 -20
  205. package/src/memory/graph/retriever.test.ts +6 -0
  206. package/src/memory/graph/retriever.ts +6 -10
  207. package/src/memory/indexer.ts +54 -45
  208. package/src/memory/job-handlers/backfill.ts +2 -11
  209. package/src/memory/job-handlers/cleanup.ts +43 -0
  210. package/src/memory/job-handlers/embedding.ts +6 -8
  211. package/src/memory/job-handlers/summarization.ts +2 -7
  212. package/src/memory/jobs-store.ts +48 -0
  213. package/src/memory/jobs-worker.ts +81 -43
  214. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  215. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  216. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  217. package/src/memory/migrations/index.ts +1 -0
  218. package/src/memory/pkb/pkb-search.test.ts +6 -0
  219. package/src/memory/qdrant-client.ts +0 -13
  220. package/src/memory/rerank-local.ts +374 -0
  221. package/src/memory/search/semantic.ts +6 -67
  222. package/src/memory/trace-event-store.ts +1 -17
  223. package/src/memory/v2/__tests__/activation.test.ts +311 -250
  224. package/src/memory/v2/__tests__/consolidation-job.test.ts +40 -8
  225. package/src/memory/v2/__tests__/injection.test.ts +157 -167
  226. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  227. package/src/memory/v2/__tests__/qdrant.test.ts +16 -0
  228. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  229. package/src/memory/v2/__tests__/sim.test.ts +5 -199
  230. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  231. package/src/memory/v2/__tests__/static-context.test.ts +76 -1
  232. package/src/memory/v2/activation.ts +149 -156
  233. package/src/memory/v2/consolidation-job.ts +62 -12
  234. package/src/memory/v2/injection.ts +47 -60
  235. package/src/memory/v2/prompts/consolidation.ts +36 -1
  236. package/src/memory/v2/qdrant.ts +99 -0
  237. package/src/memory/v2/reranker.ts +177 -0
  238. package/src/memory/v2/sim.ts +10 -84
  239. package/src/memory/v2/skill-content.ts +4 -3
  240. package/src/memory/v2/skill-store.ts +82 -59
  241. package/src/memory/v2/static-context.ts +22 -0
  242. package/src/memory/v2/types.ts +10 -10
  243. package/src/notifications/copy-composer.ts +13 -0
  244. package/src/notifications/signal.ts +4 -0
  245. package/src/oauth/AGENTS.md +3 -1
  246. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  247. package/src/oauth/connect-orchestrator.ts +2 -0
  248. package/src/oauth/connection-resolver.test.ts +66 -1
  249. package/src/oauth/connection-resolver.ts +55 -1
  250. package/src/oauth/oauth-connect-state.ts +77 -0
  251. package/src/oauth/seed-providers.ts +58 -1
  252. package/src/plugins/defaults/injectors.ts +35 -2
  253. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  254. package/src/plugins/types.ts +7 -0
  255. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  256. package/src/proactive-artifact/decision.test.ts +226 -0
  257. package/src/proactive-artifact/decision.ts +165 -0
  258. package/src/proactive-artifact/index.ts +7 -0
  259. package/src/proactive-artifact/job.test.ts +867 -0
  260. package/src/proactive-artifact/job.ts +352 -0
  261. package/src/proactive-artifact/message-copy.ts +41 -0
  262. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  263. package/src/proactive-artifact/trigger-state.ts +119 -0
  264. package/src/prompts/normalize-onboarding.ts +80 -0
  265. package/src/prompts/persona-resolver.ts +101 -9
  266. package/src/prompts/system-prompt.ts +21 -7
  267. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  268. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  269. package/src/providers/model-intents.ts +7 -0
  270. package/src/providers/openrouter/client.ts +8 -0
  271. package/src/providers/retry.ts +50 -0
  272. package/src/providers/types.ts +1 -0
  273. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  274. package/src/runtime/agent-wake.ts +238 -100
  275. package/src/runtime/assistant-event-hub.ts +36 -6
  276. package/src/runtime/assistant-event.ts +0 -1
  277. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  278. package/src/runtime/auth/route-policy.ts +14 -1
  279. package/src/runtime/auth/same-actor.ts +216 -0
  280. package/src/runtime/channel-retry-sweep.ts +65 -1
  281. package/src/runtime/guardian-reply-router.ts +10 -0
  282. package/src/runtime/local-actor-identity.ts +52 -11
  283. package/src/runtime/pending-interactions.ts +8 -0
  284. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  285. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  286. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  287. package/src/runtime/routes/client-routes.ts +20 -2
  288. package/src/runtime/routes/contact-routes.ts +0 -25
  289. package/src/runtime/routes/conversation-routes.ts +35 -26
  290. package/src/runtime/routes/debug-bash-routes.ts +163 -0
  291. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  292. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  293. package/src/runtime/routes/documents-routes.ts +2 -75
  294. package/src/runtime/routes/events-routes.ts +41 -9
  295. package/src/runtime/routes/host-bash-routes.ts +23 -3
  296. package/src/runtime/routes/host-cu-routes.ts +33 -6
  297. package/src/runtime/routes/host-file-routes.ts +32 -6
  298. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  299. package/src/runtime/routes/identity-routes.ts +7 -138
  300. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  301. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -0
  302. package/src/runtime/routes/index.ts +6 -0
  303. package/src/runtime/routes/memory-item-routes.test.ts +41 -15
  304. package/src/runtime/routes/memory-v2-routes.ts +33 -0
  305. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  306. package/src/runtime/verification-outbound-actions.ts +4 -4
  307. package/src/schedule/run-script.ts +37 -5
  308. package/src/schedule/scheduler.ts +20 -1
  309. package/src/security/encrypted-store.ts +2 -0
  310. package/src/security/secure-keys.ts +55 -0
  311. package/src/skills/remote-skill-policy.ts +4 -10
  312. package/src/subagent/index.ts +1 -7
  313. package/src/subagent/manager.ts +1 -15
  314. package/src/tasks/task-runner.ts +0 -1
  315. package/src/tasks/task-store.ts +0 -3
  316. package/src/tools/background-tool-registry.ts +17 -3
  317. package/src/tools/host-filesystem/edit.test.ts +151 -0
  318. package/src/tools/host-filesystem/edit.ts +43 -1
  319. package/src/tools/host-filesystem/read.test.ts +129 -0
  320. package/src/tools/host-filesystem/read.ts +43 -1
  321. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  322. package/src/tools/host-filesystem/transfer.ts +56 -11
  323. package/src/tools/host-filesystem/write.test.ts +134 -0
  324. package/src/tools/host-filesystem/write.ts +43 -1
  325. package/src/tools/host-terminal/host-shell.ts +13 -6
  326. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  327. package/src/tools/memory/register.test.ts +12 -9
  328. package/src/tools/memory/register.ts +1 -2
  329. package/src/tools/provider-tool-name.ts +28 -0
  330. package/src/tools/registry.ts +30 -9
  331. package/src/tools/terminal/shell.ts +9 -1
  332. package/src/tools/tool-approval-handler.ts +31 -6
  333. package/src/tools/types.ts +24 -2
  334. package/src/tts/provider-catalog.ts +3 -5
  335. package/src/util/disk-usage.ts +138 -0
  336. package/src/util/platform.ts +21 -11
  337. package/src/util/process-liveness.ts +26 -0
  338. package/src/workspace/heartbeat-service.ts +19 -0
  339. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  340. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  341. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +72 -0
  342. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  343. package/src/workspace/migrations/registry.ts +8 -0
  344. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  345. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  346. package/src/memory/v2/skill-qdrant.ts +0 -404
  347. package/src/signals/bash.ts +0 -198
@@ -36,6 +36,23 @@ const NETWORK_PATTERNS = [
36
36
  // Rate limit patterns (HTTP 429 or explicit rate limit messages)
37
37
  const RATE_LIMIT_PATTERNS = [/429/, /rate.?limit/i, /too many requests/i];
38
38
 
39
+ // Managed usage-limit responses are generated by Vellum, even though they can
40
+ // travel through provider SDKs and get wrapped as ProviderError.
41
+ const MANAGED_USAGE_LIMIT_PATTERNS = [
42
+ /"code"\s*:\s*"daily_quota_exceeded"/i,
43
+ /"code"\s*:\s*"rate_limit_exceeded"/i,
44
+ /system credential proxy rate limit/i,
45
+ /you've reached your usage limit for today/i,
46
+ /current plan allows/i,
47
+ ];
48
+
49
+ const PROVIDER_BILLING_PATTERNS = [
50
+ /credit balance is too low/i,
51
+ /insufficient.*credits?/i,
52
+ /requires more credits/i,
53
+ /can only afford/i,
54
+ ];
55
+
39
56
  // Overloaded patterns — provider is capacity-constrained (distinct from rate limiting)
40
57
  const OVERLOADED_PATTERNS = [/overloaded/i];
41
58
 
@@ -259,15 +276,21 @@ function classifyCore(
259
276
  };
260
277
  }
261
278
  if (error.statusCode === 402) {
262
- return {
263
- code: "PROVIDER_BILLING",
264
- userMessage:
265
- "You've run out of credits. Add funds to continue using the assistant.",
266
- retryable: false,
267
- errorCategory: "credits_exhausted",
268
- };
279
+ if (isManagedBalanceError(error)) {
280
+ return managedBalanceClassification();
281
+ }
282
+ return providerBillingClassification();
269
283
  }
270
284
  if (error.statusCode === 429) {
285
+ if (isManagedUsageLimitError(error, message)) {
286
+ return {
287
+ code: "MANAGED_USAGE_LIMIT",
288
+ userMessage:
289
+ "Vellum managed inference is rate limited. This is a Vellum-side usage limit, not an AI provider outage.",
290
+ retryable: true,
291
+ errorCategory: "managed_usage_limit",
292
+ };
293
+ }
271
294
  return {
272
295
  code: "PROVIDER_RATE_LIMIT",
273
296
  userMessage:
@@ -331,13 +354,8 @@ function classifyCore(
331
354
  errorCategory: "tool_ordering",
332
355
  };
333
356
  }
334
- if (/credit balance is too low|insufficient.*credits?/i.test(message)) {
335
- return {
336
- code: "PROVIDER_BILLING",
337
- userMessage: "Your API key has insufficient credits.",
338
- retryable: false,
339
- errorCategory: "provider_billing",
340
- };
357
+ if (isProviderBillingError(message)) {
358
+ return providerBillingClassification();
341
359
  }
342
360
  if (
343
361
  /invalid.*api.?key|invalid.*x-api-key|authentication.?error|invalid.authentication/i.test(
@@ -362,7 +380,7 @@ function classifyCore(
362
380
  }
363
381
 
364
382
  // Regex fallback for non-ProviderError or ProviderError without statusCode
365
- return classifyByMessage(message);
383
+ return classifyByMessage(error, message);
366
384
  }
367
385
 
368
386
  /** Check whether an error message indicates a context-too-large failure. */
@@ -394,7 +412,52 @@ function isStreamingError(message: string): boolean {
394
412
  return STREAMING_ERROR_PATTERNS.some((p) => p.test(message));
395
413
  }
396
414
 
415
+ function isManagedUsageLimitError(error: unknown, message: string): boolean {
416
+ if (
417
+ error instanceof ProviderError &&
418
+ getProviderRoutingSource(error.provider) === "managed-proxy"
419
+ ) {
420
+ return true;
421
+ }
422
+ return MANAGED_USAGE_LIMIT_PATTERNS.some((p) => p.test(message));
423
+ }
424
+
425
+ function isManagedBalanceError(error: ProviderError): boolean {
426
+ return getProviderRoutingSource(error.provider) === "managed-proxy";
427
+ }
428
+
429
+ function isProviderBillingError(message: string): boolean {
430
+ return PROVIDER_BILLING_PATTERNS.some((p) => p.test(message));
431
+ }
432
+
433
+ function managedBalanceClassification(): Omit<
434
+ ClassifiedConversationError,
435
+ "debugDetails"
436
+ > {
437
+ return {
438
+ code: "PROVIDER_BILLING",
439
+ userMessage:
440
+ "You've run out of credits. Add funds to continue using the assistant.",
441
+ retryable: false,
442
+ errorCategory: "credits_exhausted",
443
+ };
444
+ }
445
+
446
+ function providerBillingClassification(): Omit<
447
+ ClassifiedConversationError,
448
+ "debugDetails"
449
+ > {
450
+ return {
451
+ code: "PROVIDER_BILLING",
452
+ userMessage:
453
+ "Your API provider account or key needs credits. Add funds with the provider or update the key in Settings.",
454
+ retryable: false,
455
+ errorCategory: "provider_billing",
456
+ };
457
+ }
458
+
397
459
  function classifyByMessage(
460
+ error: unknown,
398
461
  message: string,
399
462
  ): Omit<ClassifiedConversationError, "debugDetails"> {
400
463
  // Check context-too-large before other patterns
@@ -411,6 +474,15 @@ function classifyByMessage(
411
474
  // Check rate limit first (before network, since 429 could match both)
412
475
  for (const pattern of RATE_LIMIT_PATTERNS) {
413
476
  if (pattern.test(message)) {
477
+ if (isManagedUsageLimitError(error, message)) {
478
+ return {
479
+ code: "MANAGED_USAGE_LIMIT",
480
+ userMessage:
481
+ "Vellum managed inference is rate limited. This is a Vellum-side usage limit, not an AI provider outage.",
482
+ retryable: true,
483
+ errorCategory: "managed_usage_limit",
484
+ };
485
+ }
414
486
  return {
415
487
  code: "PROVIDER_RATE_LIMIT",
416
488
  userMessage:
@@ -153,8 +153,6 @@ export interface DisposeContext extends AbortContext {
153
153
  trustContext?: { trustClass: TrustClass };
154
154
  /** Active memory node IDs snapshotted from the conversation's InContextTracker before disposal. */
155
155
  activeContextNodeIds?: string[];
156
- /** Memory scope for extraction — defaults to "default" if omitted. */
157
- memoryScopeId?: string;
158
156
  abort(): void;
159
157
  }
160
158
 
@@ -378,7 +376,7 @@ export function disposeConversation(ctx: DisposeContext): void {
378
376
  try {
379
377
  enqueueMemoryJob("graph_extract", {
380
378
  conversationId: ctx.conversationId,
381
- scopeId: ctx.memoryScopeId ?? "default",
379
+ scopeId: "default",
382
380
  ...(ctx.activeContextNodeIds?.length
383
381
  ? { activeContextNodeIds: ctx.activeContextNodeIds }
384
382
  : {}),
@@ -182,6 +182,8 @@ export interface ProcessConversationContext {
182
182
  forceCompact(): Promise<ContextWindowResult>;
183
183
  /** Set transport-derived hints for the conversation. */
184
184
  setTransportHints(hints: string[] | undefined): void;
185
+ /** IANA timezone reported by the active client for the current turn. */
186
+ clientTimezone?: string;
185
187
  /**
186
188
  * Apply client-reported host env (home dir, username) from transport
187
189
  * metadata, gating on `supportsHostProxy` so non-host-proxy interfaces
@@ -189,6 +191,10 @@ export interface ProcessConversationContext {
189
191
  * `DaemonServer.applyTransportMetadata` and the queue-drain path below.
190
192
  */
191
193
  applyHostEnvFromTransport(transport: ConversationTransportMetadata): void;
194
+ /** Apply the per-turn client timezone reported by transport metadata. */
195
+ applyClientTimezoneFromTransport(
196
+ transport: ConversationTransportMetadata,
197
+ ): void;
192
198
  }
193
199
 
194
200
  function resolveQueuedTurnContext(
@@ -423,6 +429,7 @@ async function drainSingleMessage(
423
429
  // setter used by DaemonServer.applyTransportMetadata so create/reuse
424
430
  // and queue-drain stay in sync without duplicating the gate logic.
425
431
  conversation.applyHostEnvFromTransport(next.transport);
432
+ conversation.applyClientTimezoneFromTransport(next.transport);
426
433
  }
427
434
 
428
435
  // Re-preactivate host-proxy skills for interactive desktop turns. The
@@ -865,6 +872,7 @@ async function drainBatch(
865
872
  if (head.transport) {
866
873
  conversation.setTransportHints(buildTransportHints(head.transport));
867
874
  conversation.applyHostEnvFromTransport(head.transport);
875
+ conversation.applyClientTimezoneFromTransport(head.transport);
868
876
  }
869
877
 
870
878
  // Re-preactivate host-proxy skills for interactive desktop turns.
@@ -33,6 +33,7 @@ import {
33
33
  } from "../messaging/providers/slack/render-transcript.js";
34
34
  import { getInjectors } from "../plugins/registry.js";
35
35
  import type {
36
+ DiskPressureInjectionContext,
36
37
  InjectionBlock,
37
38
  InjectionPlacement,
38
39
  TurnContext,
@@ -788,6 +789,9 @@ export interface UnifiedTurnContextOptions {
788
789
  interfaceName?: string;
789
790
  channelName?: string;
790
791
  actorContext?: InboundActorContext | null;
792
+ configuredUserTimezone?: string | null;
793
+ clientTimezone?: string | null;
794
+ detectedTimezone?: string | null;
791
795
  /**
792
796
  * Human-readable duration since the previous user message (e.g. "14h ago",
793
797
  * "yesterday", "3d ago"). Only populated when the gap exceeds 12 hours so
@@ -830,6 +834,25 @@ export function buildUnifiedTurnContextBlock(
830
834
 
831
835
  const lines: string[] = ["<turn_context>"];
832
836
  lines.push(`current_time: ${options.timestamp}`);
837
+ const configuredUserTimezone = options.configuredUserTimezone ?? null;
838
+ const clientDeviceTimezone =
839
+ options.clientTimezone ?? options.detectedTimezone ?? null;
840
+ const hasTimezoneMismatch =
841
+ configuredUserTimezone !== null &&
842
+ clientDeviceTimezone !== null &&
843
+ configuredUserTimezone !== clientDeviceTimezone;
844
+ if (hasTimezoneMismatch) {
845
+ const sanitizedConfiguredTimezone = sanitizeInlineContextValue(
846
+ configuredUserTimezone,
847
+ );
848
+ const sanitizedClientDeviceTimezone =
849
+ sanitizeInlineContextValue(clientDeviceTimezone);
850
+ lines.push(`configured_user_timezone: ${sanitizedConfiguredTimezone}`);
851
+ lines.push(`client_device_timezone: ${sanitizedClientDeviceTimezone}`);
852
+ lines.push(
853
+ `timezone_update_available: after explicit user confirmation, persist client_device_timezone with \`assistant config set ui.userTimezone "${sanitizedClientDeviceTimezone}"\``,
854
+ );
855
+ }
833
856
  if (options.timeSinceLastMessage) {
834
857
  lines.push(`time_since_last_message: ${options.timeSinceLastMessage}`);
835
858
  }
@@ -1609,6 +1632,7 @@ export function loadSlackActiveThreadFocusBlock(
1609
1632
  const RUNTIME_INJECTION_PREFIXES = [
1610
1633
  "<channel_capabilities>",
1611
1634
  "<channel_command_context>",
1635
+ "<disk_pressure_warning>",
1612
1636
  "<channel_turn_context>", // backward-compat: strip legacy separate channel blocks
1613
1637
  "<guardian_context>",
1614
1638
  "<inbound_actor_context>", // backward-compat: strip legacy separate actor blocks
@@ -1872,6 +1896,7 @@ function applyInjectionBlock(
1872
1896
  * plugin-overridable default injectors.
1873
1897
  */
1874
1898
  export interface RuntimeInjectionOptions {
1899
+ diskPressureContext?: DiskPressureInjectionContext | null;
1875
1900
  /**
1876
1901
  * Active dashboard-surface context (read from `<active_workspace>`). Kept
1877
1902
  * on the options bag rather than an injector because it is a
@@ -1990,6 +2015,7 @@ function buildTurnInjectionInputs(
1990
2015
  ): TurnInjectionInputs {
1991
2016
  return {
1992
2017
  mode: options.mode,
2018
+ diskPressureContext: options.diskPressureContext,
1993
2019
  workspaceTopLevelContext: options.workspaceTopLevelContext,
1994
2020
  unifiedTurnContext: options.unifiedTurnContext,
1995
2021
  pkbContext: options.pkbContext,
@@ -22,7 +22,7 @@ import { RateLimitProvider } from "../providers/ratelimit.js";
22
22
  import { getProvider } from "../providers/registry.js";
23
23
  import { getSubagentManager } from "../subagent/index.js";
24
24
  import { getSandboxWorkingDir } from "../util/platform.js";
25
- import { Conversation, DEFAULT_MEMORY_POLICY } from "./conversation.js";
25
+ import { Conversation } from "./conversation.js";
26
26
  import type { ConversationEvictor } from "./conversation-evictor.js";
27
27
  import type { ConversationCreateOptions } from "./handlers/shared.js";
28
28
  import { buildTransportHints } from "./transport-hints.js";
@@ -180,6 +180,7 @@ function applyTransportMetadata(
180
180
  if (!transport) return;
181
181
  conversation.setTransportHints(buildTransportHints(transport));
182
182
  conversation.applyHostEnvFromTransport(transport);
183
+ conversation.applyClientTimezoneFromTransport(transport);
183
184
  }
184
185
 
185
186
  /**
@@ -253,7 +254,6 @@ export async function getOrCreateConversation(
253
254
  maxTokens,
254
255
  sendToClient,
255
256
  workingDir,
256
- DEFAULT_MEMORY_POLICY,
257
257
  sharedCesClient,
258
258
  storedOptions?.speed,
259
259
  undefined,
@@ -18,6 +18,7 @@ import {
18
18
  assistantEventHub,
19
19
  broadcastMessage,
20
20
  } from "../runtime/assistant-event-hub.js";
21
+ import { enforceSameActorOrErrorResult } from "../runtime/auth/same-actor.js";
21
22
  import type {
22
23
  InteractiveUiRequest,
23
24
  InteractiveUiResult,
@@ -53,6 +54,148 @@ const log = getLogger("conversation-surfaces");
53
54
 
54
55
  const MAX_UNDO_DEPTH = 10;
55
56
 
57
+ /**
58
+ * Debounce window for persisting `ui_surface_update` data back to the
59
+ * message row. Surfaces typically receive bursts of updates (e.g. a
60
+ * Workspace Health Check ticking off items rapidly) — collapsing them
61
+ * to a single DB write avoids hammering SQLite while still bounding the
62
+ * "lost work on crash" window to ~half a second.
63
+ */
64
+ const SURFACE_PERSIST_DEBOUNCE_MS = 500;
65
+
66
+ /**
67
+ * In-flight debounced persist timers keyed by `surfaceId`. Surface IDs
68
+ * are UUIDs and globally unique, so a module-level map is safe across
69
+ * conversations. Each entry holds the latest data snapshot — newer
70
+ * updates clobber older ones since the persisted row carries the full
71
+ * merged state, not a delta.
72
+ */
73
+ const pendingSurfacePersists = new Map<
74
+ string,
75
+ {
76
+ timer: ReturnType<typeof setTimeout>;
77
+ conversationId: string;
78
+ data: SurfaceData;
79
+ }
80
+ >();
81
+
82
+ /**
83
+ * Persist the latest `data` for a `ui_surface` content block by
84
+ * scanning the conversation's messages for one containing the given
85
+ * `surfaceId` and patching its `data` field. Mirrors the scan-and-patch
86
+ * pattern in `markSurfaceCompleted`.
87
+ *
88
+ * Safe to call before the assistant message has been persisted (mid-stream):
89
+ * the scan simply finds nothing and bails. The next update after
90
+ * `handleMessageComplete` runs will pick up the now-persisted row.
91
+ */
92
+ function persistSurfaceData(
93
+ conversationId: string,
94
+ surfaceId: string,
95
+ data: SurfaceData,
96
+ ): void {
97
+ try {
98
+ const rows = getMessages(conversationId);
99
+ for (let r = rows.length - 1; r >= 0; r--) {
100
+ let parsed: unknown[];
101
+ try {
102
+ const result = JSON.parse(rows[r].content);
103
+ if (!Array.isArray(result)) continue;
104
+ parsed = result;
105
+ } catch {
106
+ // Plain-text content rows — skip and keep scanning.
107
+ continue;
108
+ }
109
+ let found = false;
110
+ for (const pb of parsed) {
111
+ const rb = pb as Record<string, unknown>;
112
+ if (rb.type === "ui_surface" && rb.surfaceId === surfaceId) {
113
+ rb.data = data;
114
+ found = true;
115
+ break;
116
+ }
117
+ }
118
+ if (found) {
119
+ updateMessageContent(rows[r].id, JSON.stringify(parsed));
120
+ return;
121
+ }
122
+ }
123
+ } catch (err) {
124
+ log.debug(
125
+ { err, surfaceId, conversationId },
126
+ "Failed to persist surface data update",
127
+ );
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Schedule a debounced write of the merged surface data back to the
133
+ * persisted message row. Repeated calls within the debounce window
134
+ * collapse to a single write carrying the latest data.
135
+ */
136
+ export function scheduleSurfaceDataPersist(
137
+ conversationId: string,
138
+ surfaceId: string,
139
+ data: SurfaceData,
140
+ ): void {
141
+ const existing = pendingSurfacePersists.get(surfaceId);
142
+ if (existing) {
143
+ clearTimeout(existing.timer);
144
+ }
145
+ const timer = setTimeout(() => {
146
+ pendingSurfacePersists.delete(surfaceId);
147
+ persistSurfaceData(conversationId, surfaceId, data);
148
+ }, SURFACE_PERSIST_DEBOUNCE_MS);
149
+ pendingSurfacePersists.set(surfaceId, { timer, conversationId, data });
150
+ }
151
+
152
+ /**
153
+ * Force-flush any pending debounced persist for `surfaceId`. Called on
154
+ * surface completion so the final state is durable before the surface
155
+ * record transitions to `completed`.
156
+ */
157
+ export function flushSurfaceDataPersist(surfaceId: string): void {
158
+ const pending = pendingSurfacePersists.get(surfaceId);
159
+ if (!pending) return;
160
+ clearTimeout(pending.timer);
161
+ pendingSurfacePersists.delete(surfaceId);
162
+ persistSurfaceData(pending.conversationId, surfaceId, pending.data);
163
+ }
164
+
165
+ /**
166
+ * Cancel all pending debounced persists. Called on conversation
167
+ * teardown to avoid timers firing against torn-down state.
168
+ *
169
+ * Use `flushPendingSurfaceDataPersists` instead on a clean shutdown
170
+ * path where the latest in-flight surface state should still be
171
+ * written before teardown.
172
+ */
173
+ export function cancelPendingSurfaceDataPersists(
174
+ conversationId?: string,
175
+ ): void {
176
+ for (const [surfaceId, pending] of pendingSurfacePersists) {
177
+ if (conversationId && pending.conversationId !== conversationId) continue;
178
+ clearTimeout(pending.timer);
179
+ pendingSurfacePersists.delete(surfaceId);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Synchronously flush all pending debounced persists, optionally scoped
185
+ * to a single conversation. Called on clean conversation teardown so an
186
+ * update that arrived inside the 500ms debounce window still lands in
187
+ * the DB before the conversation goes away. Each entry is removed from
188
+ * the pending map after its write fires.
189
+ */
190
+ export function flushPendingSurfaceDataPersists(conversationId?: string): void {
191
+ for (const [surfaceId, pending] of pendingSurfacePersists) {
192
+ if (conversationId && pending.conversationId !== conversationId) continue;
193
+ clearTimeout(pending.timer);
194
+ pendingSurfacePersists.delete(surfaceId);
195
+ persistSurfaceData(pending.conversationId, surfaceId, pending.data);
196
+ }
197
+ }
198
+
56
199
  /**
57
200
  * Mark a `ui_surface` content block as completed in the database so that
58
201
  * history reconstruction preserves the completion state. Also updates
@@ -63,6 +206,10 @@ export function markSurfaceCompleted(
63
206
  surfaceId: string,
64
207
  summary: string,
65
208
  ): void {
209
+ // Force-flush any pending debounced data persist so the completion
210
+ // patch lands on top of the latest data instead of racing with it.
211
+ flushSurfaceDataPersist(surfaceId);
212
+
66
213
  // Update in-memory messages when available so subsequent reads within
67
214
  // this session see the change without waiting for DB.
68
215
  if (ctx.messages) {
@@ -1783,15 +1930,19 @@ export async function surfaceProxyResolver(
1783
1930
  // Record the action and proxy to the connected desktop client
1784
1931
  const reasoning =
1785
1932
  typeof input.reasoning === "string" ? input.reasoning : undefined;
1786
- const targetClientId =
1787
- typeof input.target_client_id === "string" && input.target_client_id !== ""
1933
+ let targetClientId: string | undefined =
1934
+ typeof input.target_client_id === "string" &&
1935
+ input.target_client_id !== ""
1788
1936
  ? input.target_client_id
1789
1937
  : undefined;
1790
1938
 
1791
- // Validate targetClientId before recordAction so an invalid ID does not
1792
- // burn a step or pollute action history. (HostBashProxy / HostFileProxy
1793
- // both validate at the tool-resolution layer before incrementing any
1794
- // state; this mirrors that behaviour for CU.)
1939
+ // Validate targetClientId existence, capability, and same-user binding
1940
+ // before recordAction so an invalid or cross-user ID does not burn a
1941
+ // step or pollute action history. HostBashProxy / HostFileProxy
1942
+ // validate at the tool-resolution layer for the same reason. The proxy
1943
+ // re-checks same-user (single authoritative gate); using the shared
1944
+ // helper keeps log payload and error wording identical at both layers.
1945
+ const sourceActorPrincipalId = ctx.trustContext?.guardianPrincipalId;
1795
1946
  if (targetClientId != null) {
1796
1947
  const client = assistantEventHub.getClientById(targetClientId);
1797
1948
  if (!client) {
@@ -1806,14 +1957,24 @@ export async function surfaceProxyResolver(
1806
1957
  isError: true,
1807
1958
  };
1808
1959
  }
1960
+ const rejection = enforceSameActorOrErrorResult({
1961
+ hub: assistantEventHub,
1962
+ sourceActorPrincipalId,
1963
+ targetClientId,
1964
+ op: "host_cu",
1965
+ });
1966
+ if (rejection) return rejection;
1809
1967
  }
1810
1968
 
1811
- // Guard: require explicit targeting when multiple CU-capable clients are
1812
- // connected. The tool schemas document target_client_id as "required when
1813
- // multiple clients support host_cu" but nothing enforced it at runtime
1814
- // until now. Without this guard, the request would broadcast to all
1815
- // capable clients simultaneously, causing the same CU action to execute
1816
- // on multiple machines.
1969
+ // Guard: require explicit targeting when multiple same-user CU-capable
1970
+ // clients are connected. The tool schemas document target_client_id as
1971
+ // "required when multiple clients support host_cu" but nothing enforced
1972
+ // it at runtime until now. Without this guard, the request would
1973
+ // broadcast to all capable clients simultaneously, causing the same CU
1974
+ // action to execute on multiple machines. The filter mirrors
1975
+ // HostFileProxy's auto-resolve: only same-user clients participate, so
1976
+ // a cross-user client connected to the same daemon does not falsely
1977
+ // trigger this ambiguity error.
1817
1978
  //
1818
1979
  // Asymmetry with host_bash / host_file (host-shell.ts): the bash/file
1819
1980
  // guard additionally checks `transportInterface != null &&
@@ -1824,13 +1985,25 @@ export async function surfaceProxyResolver(
1824
1985
  // host_cu-capable transport for which auto-routing-to-self would be
1825
1986
  // appropriate. We therefore fire whenever there is genuine ambiguity.
1826
1987
  if (targetClientId == null) {
1827
- const cuClients = assistantEventHub.listClientsByCapability("host_cu");
1828
- if (cuClients.length > 1) {
1988
+ const allCuClients = assistantEventHub.listClientsByCapability("host_cu");
1989
+ const sameUserCuClients = allCuClients.filter(
1990
+ (c) => c.actorPrincipalId === sourceActorPrincipalId,
1991
+ );
1992
+ if (sameUserCuClients.length > 1) {
1829
1993
  return {
1830
1994
  content: `Error: multiple clients support host_cu. Specify which client to target with \`target_client_id\`. Run \`assistant clients list --capability host_cu\` to see client IDs and labels.`,
1831
1995
  isError: true,
1832
1996
  };
1833
1997
  }
1998
+ // When cross-user host_cu clients are connected, we MUST auto-resolve
1999
+ // to the unique same-user client (or fail explicitly) — otherwise the
2000
+ // proxy would broadcast untargeted and the CU action would reach the
2001
+ // cross-user client too. Setting targetClientId here forces the proxy
2002
+ // to deliver only to that client, with the same-user check below as
2003
+ // belt-and-suspenders.
2004
+ if (sameUserCuClients.length === 1 && allCuClients.length > 1) {
2005
+ targetClientId = sameUserCuClients[0].clientId;
2006
+ }
1834
2007
  }
1835
2008
 
1836
2009
  ctx.hostCuProxy.recordAction(toolName, input, reasoning);
@@ -1842,6 +2015,7 @@ export async function surfaceProxyResolver(
1842
2015
  reasoning,
1843
2016
  signal,
1844
2017
  targetClientId,
2018
+ sourceActorPrincipalId,
1845
2019
  );
1846
2020
  }
1847
2021
 
@@ -1876,7 +2050,7 @@ export async function surfaceProxyResolver(
1876
2050
  // union on `tool` ("start" | "observe" | "press" | …). The agent's raw
1877
2051
  // tool input only carries the action-specific payload (app, x/y, text,
1878
2052
  // …) — the discriminator is implied by `toolName` (`app_control_<tool>`).
1879
- // Inject it here so the proxy's singleton-lock guard (`input.tool ===
2053
+ // Inject it here so the proxy's session-lock guard (`input.tool ===
1880
2054
  // "start"`) and the Swift client's discriminated-union decoder both see
1881
2055
  // the field they require.
1882
2056
  const tool = toolName.slice("app_control_".length);
@@ -2077,6 +2251,12 @@ export async function surfaceProxyResolver(
2077
2251
  ctx.currentTurnSurfaces[idx].data = mergedData;
2078
2252
  }
2079
2253
 
2254
+ // Persist the merged data back to the assistant message's
2255
+ // `ui_surface` content block so a refresh / restart shows the
2256
+ // current state instead of the original creation-time snapshot.
2257
+ // Debounced to coalesce bursts of rapid updates.
2258
+ scheduleSurfaceDataPersist(ctx.conversationId, surfaceId, mergedData);
2259
+
2080
2260
  return { content: "Surface updated", isError: false };
2081
2261
  }
2082
2262