@vellumai/assistant 0.7.2 → 0.8.0

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 (424) hide show
  1. package/ARCHITECTURE.md +45 -29
  2. package/Dockerfile +1 -0
  3. package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
  4. package/bun.lock +3 -0
  5. package/docs/architecture/memory.md +5 -2
  6. package/knip.json +1 -0
  7. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  8. package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
  9. package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
  10. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
  11. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
  12. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
  13. package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  15. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  16. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  17. package/openapi.yaml +470 -25
  18. package/package.json +3 -1
  19. package/src/__tests__/annotate-risk-options.test.ts +291 -0
  20. package/src/__tests__/app-control-flow.test.ts +21 -11
  21. package/src/__tests__/approval-cascade.test.ts +8 -16
  22. package/src/__tests__/approval-routes-http.test.ts +6 -0
  23. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  24. package/src/__tests__/assistant-event.test.ts +0 -10
  25. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  26. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  27. package/src/__tests__/auto-analysis-end-to-end.test.ts +48 -0
  28. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  29. package/src/__tests__/call-constants.test.ts +10 -1
  30. package/src/__tests__/call-controller.test.ts +127 -0
  31. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  32. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  33. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  34. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
  35. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  36. package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
  37. package/src/__tests__/config-schema.test.ts +1 -0
  38. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  39. package/src/__tests__/config-watcher.test.ts +140 -69
  40. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  41. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  42. package/src/__tests__/context-search-fanout.test.ts +0 -1
  43. package/src/__tests__/context-search-memory-source.test.ts +6 -33
  44. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  45. package/src/__tests__/context-search-pkb-source.test.ts +12 -7
  46. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  47. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
  48. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  51. package/src/__tests__/conversation-agent-loop.test.ts +457 -8
  52. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
  53. package/src/__tests__/conversation-error.test.ts +150 -3
  54. package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
  55. package/src/__tests__/conversation-process-callsite.test.ts +38 -0
  56. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
  58. package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
  59. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  60. package/src/__tests__/conversation-store.test.ts +0 -18
  61. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
  62. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  63. package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
  65. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  66. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  67. package/src/__tests__/credentials-cli.test.ts +7 -0
  68. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  69. package/src/__tests__/date-context.test.ts +164 -2
  70. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  71. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  72. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  73. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  74. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  75. package/src/__tests__/disk-usage.test.ts +150 -0
  76. package/src/__tests__/events-client-registration.test.ts +52 -0
  77. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  78. package/src/__tests__/file-write-tool.test.ts +4 -10
  79. package/src/__tests__/filing-service.test.ts +2 -20
  80. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  82. package/src/__tests__/heartbeat-service.test.ts +260 -11
  83. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  84. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  85. package/src/__tests__/host-bash-routes.test.ts +178 -13
  86. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  87. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  88. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  89. package/src/__tests__/host-file-proxy.test.ts +268 -6
  90. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  91. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  92. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  93. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  94. package/src/__tests__/injector-chain.test.ts +36 -16
  95. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  96. package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
  98. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  99. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  100. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  101. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  102. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  103. package/src/__tests__/notification-decision-fallback.test.ts +91 -0
  104. package/src/__tests__/notification-decision-strategy.test.ts +22 -0
  105. package/src/__tests__/oauth-cli.test.ts +121 -0
  106. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  107. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  108. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  109. package/src/__tests__/openai-provider.test.ts +45 -8
  110. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  111. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  112. package/src/__tests__/platform.test.ts +2 -1
  113. package/src/__tests__/playbook-execution.test.ts +0 -43
  114. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  115. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  116. package/src/__tests__/provider-tool-name.test.ts +23 -0
  117. package/src/__tests__/relay-server.test.ts +60 -5
  118. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  119. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  120. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  121. package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
  122. package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
  123. package/src/__tests__/secret-response-routing.test.ts +7 -5
  124. package/src/__tests__/server-history-render.test.ts +82 -0
  125. package/src/__tests__/skill-include-graph.test.ts +31 -0
  126. package/src/__tests__/skill-load-tool.test.ts +44 -16
  127. package/src/__tests__/skills.test.ts +39 -0
  128. package/src/__tests__/suggestion-routes.test.ts +46 -0
  129. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
  130. package/src/__tests__/tool-executor.test.ts +155 -0
  131. package/src/__tests__/twilio-validation.test.ts +2 -2
  132. package/src/__tests__/voice-session-bridge.test.ts +3 -0
  133. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  134. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  136. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
  137. package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
  138. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
  139. package/src/agent/loop.ts +11 -0
  140. package/src/approvals/guardian-request-resolvers.ts +3 -32
  141. package/src/backup/snapshot-lock.ts +2 -27
  142. package/src/bundler/compiler-tools.ts +3 -2
  143. package/src/calls/call-constants.ts +5 -8
  144. package/src/calls/call-controller.ts +130 -67
  145. package/src/calls/call-conversation-messages.ts +46 -10
  146. package/src/calls/relay-server.ts +7 -1
  147. package/src/calls/voice-session-bridge.ts +1 -1
  148. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  149. package/src/cli/commands/bash.ts +35 -108
  150. package/src/cli/commands/contacts.ts +64 -25
  151. package/src/cli/commands/credentials.ts +56 -0
  152. package/src/cli/commands/memory-v2.ts +11 -10
  153. package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
  154. package/src/cli/commands/oauth/connect.ts +124 -40
  155. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  156. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  157. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  158. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  159. package/src/cli/commands/platform/index.ts +16 -7
  160. package/src/cli/commands/status.ts +57 -0
  161. package/src/cli/program.ts +4 -2
  162. package/src/config/assistant-feature-flags.ts +13 -3
  163. package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
  164. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  165. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  166. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  167. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  168. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  169. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  170. package/src/config/env.ts +0 -8
  171. package/src/config/feature-flag-registry.json +13 -5
  172. package/src/config/loader.ts +199 -27
  173. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  174. package/src/config/schemas/call-site-catalog.ts +14 -0
  175. package/src/config/schemas/channels.ts +0 -5
  176. package/src/config/schemas/heartbeat.ts +1 -1
  177. package/src/config/schemas/llm.ts +2 -0
  178. package/src/config/schemas/memory-lifecycle.ts +13 -0
  179. package/src/config/schemas/memory-v2.ts +76 -12
  180. package/src/config/schemas/platform.ts +43 -3
  181. package/src/config/schemas/services.ts +28 -0
  182. package/src/config/seed-inference-profiles.ts +230 -33
  183. package/src/contacts/contact-store.ts +0 -25
  184. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
  185. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  186. package/src/daemon/assistant-attachments.ts +4 -4
  187. package/src/daemon/config-watcher.ts +85 -57
  188. package/src/daemon/conversation-agent-loop-handlers.ts +38 -0
  189. package/src/daemon/conversation-agent-loop.ts +183 -43
  190. package/src/daemon/conversation-error.ts +87 -15
  191. package/src/daemon/conversation-lifecycle.ts +22 -10
  192. package/src/daemon/conversation-process.ts +8 -0
  193. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  194. package/src/daemon/conversation-store.ts +2 -2
  195. package/src/daemon/conversation-surfaces.ts +211 -29
  196. package/src/daemon/conversation-tool-setup.ts +66 -19
  197. package/src/daemon/conversation.ts +18 -23
  198. package/src/daemon/date-context.ts +71 -22
  199. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  200. package/src/daemon/disk-pressure-guard.ts +343 -0
  201. package/src/daemon/disk-pressure-policy.ts +163 -0
  202. package/src/daemon/handlers/shared.ts +26 -1
  203. package/src/daemon/handlers/skills.ts +3 -4
  204. package/src/daemon/host-app-control-proxy.ts +137 -41
  205. package/src/daemon/host-bash-proxy.ts +47 -22
  206. package/src/daemon/host-browser-proxy.ts +1 -1
  207. package/src/daemon/host-cu-proxy.ts +50 -4
  208. package/src/daemon/host-file-proxy.ts +44 -8
  209. package/src/daemon/host-transfer-proxy.ts +97 -6
  210. package/src/daemon/lifecycle.ts +167 -101
  211. package/src/daemon/meet-host-supervisor.ts +4 -4
  212. package/src/daemon/meet-manifest-loader.ts +0 -1
  213. package/src/daemon/memory-v2-startup.ts +66 -15
  214. package/src/daemon/message-protocol.ts +3 -0
  215. package/src/daemon/message-types/conversations.ts +4 -0
  216. package/src/daemon/message-types/disk-pressure.ts +9 -0
  217. package/src/daemon/message-types/messages.ts +22 -1
  218. package/src/daemon/profiler-run-store.ts +5 -5
  219. package/src/daemon/tool-setup-types.ts +2 -2
  220. package/src/documents/document-store.ts +119 -0
  221. package/src/filing/filing-service.ts +29 -5
  222. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  223. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  224. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  225. package/src/heartbeat/heartbeat-service.ts +205 -31
  226. package/src/home/feed-scheduler.ts +18 -0
  227. package/src/inbound/platform-callback-registration.ts +8 -15
  228. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  229. package/src/ipc/assistant-server.ts +149 -38
  230. package/src/ipc/gateway-client.ts +37 -3
  231. package/src/ipc/skill-server.ts +99 -42
  232. package/src/live-voice/live-voice-archive.ts +4 -4
  233. package/src/live-voice/protocol.ts +5 -7
  234. package/src/media/image-service.ts +1 -7
  235. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  236. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +34 -51
  237. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  238. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  239. package/src/memory/admin.ts +5 -9
  240. package/src/memory/context-search/agent-runner.ts +19 -2
  241. package/src/memory/context-search/sources/conversations.ts +2 -11
  242. package/src/memory/context-search/sources/memory-v2.ts +1 -16
  243. package/src/memory/context-search/sources/memory.ts +2 -3
  244. package/src/memory/context-search/sources/pkb.ts +2 -3
  245. package/src/memory/context-search/types.ts +0 -1
  246. package/src/memory/conversation-crud.ts +4 -12
  247. package/src/memory/db-init.ts +2 -0
  248. package/src/memory/embedding-runtime-manager.ts +119 -5
  249. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +136 -82
  250. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
  251. package/src/memory/graph/conversation-graph-memory.ts +72 -61
  252. package/src/memory/graph/extraction.ts +1 -3
  253. package/src/memory/graph/graph-search.test.ts +11 -67
  254. package/src/memory/graph/graph-search.ts +4 -24
  255. package/src/memory/graph/retriever.test.ts +12 -1
  256. package/src/memory/graph/retriever.ts +10 -15
  257. package/src/memory/graph/tool-handlers.ts +3 -4
  258. package/src/memory/graph/tools.ts +4 -4
  259. package/src/memory/indexer.ts +53 -45
  260. package/src/memory/job-handlers/backfill.ts +2 -11
  261. package/src/memory/job-handlers/cleanup.ts +43 -0
  262. package/src/memory/job-handlers/embedding.ts +6 -8
  263. package/src/memory/job-handlers/summarization.ts +2 -7
  264. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
  265. package/src/memory/jobs/embed-concept-page.ts +223 -87
  266. package/src/memory/jobs-store.ts +48 -0
  267. package/src/memory/jobs-worker.ts +85 -43
  268. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  269. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  270. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  271. package/src/memory/migrations/index.ts +1 -0
  272. package/src/memory/pkb/pkb-search.test.ts +7 -0
  273. package/src/memory/pkb/pkb-search.ts +4 -5
  274. package/src/memory/qdrant-client.ts +3 -13
  275. package/src/memory/rerank-local.ts +374 -0
  276. package/src/memory/search/semantic.ts +10 -72
  277. package/src/memory/trace-event-store.ts +1 -17
  278. package/src/memory/v2/__tests__/activation.test.ts +346 -255
  279. package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
  280. package/src/memory/v2/__tests__/injection.test.ts +297 -190
  281. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  282. package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
  283. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  284. package/src/memory/v2/__tests__/sim.test.ts +113 -196
  285. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  286. package/src/memory/v2/__tests__/static-context.test.ts +77 -14
  287. package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
  288. package/src/memory/v2/activation.ts +149 -156
  289. package/src/memory/v2/consolidation-job.ts +69 -20
  290. package/src/memory/v2/injection.ts +75 -68
  291. package/src/memory/v2/page-store.ts +39 -0
  292. package/src/memory/v2/prompts/consolidation.ts +41 -1
  293. package/src/memory/v2/qdrant.ts +306 -46
  294. package/src/memory/v2/reranker.ts +177 -0
  295. package/src/memory/v2/sim.ts +77 -110
  296. package/src/memory/v2/skill-content.ts +4 -3
  297. package/src/memory/v2/skill-store.ts +82 -59
  298. package/src/memory/v2/static-context.ts +26 -8
  299. package/src/memory/v2/sweep-job.ts +5 -6
  300. package/src/memory/v2/types.ts +17 -10
  301. package/src/notifications/copy-composer.ts +47 -0
  302. package/src/notifications/decision-engine.ts +46 -0
  303. package/src/notifications/signal.ts +4 -0
  304. package/src/oauth/AGENTS.md +3 -1
  305. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  306. package/src/oauth/connect-orchestrator.ts +2 -0
  307. package/src/oauth/connection-resolver.test.ts +66 -1
  308. package/src/oauth/connection-resolver.ts +55 -1
  309. package/src/oauth/oauth-connect-state.ts +77 -0
  310. package/src/oauth/seed-providers.ts +58 -1
  311. package/src/permissions/gateway-threshold-reader.ts +116 -8
  312. package/src/permissions/prompter.ts +86 -96
  313. package/src/permissions/secret-prompter.ts +31 -31
  314. package/src/plugins/defaults/injectors.ts +36 -4
  315. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  316. package/src/plugins/types.ts +7 -0
  317. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  318. package/src/proactive-artifact/decision.test.ts +226 -0
  319. package/src/proactive-artifact/decision.ts +165 -0
  320. package/src/proactive-artifact/index.ts +7 -0
  321. package/src/proactive-artifact/job.test.ts +914 -0
  322. package/src/proactive-artifact/job.ts +366 -0
  323. package/src/proactive-artifact/message-copy.ts +58 -0
  324. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  325. package/src/proactive-artifact/trigger-state.ts +119 -0
  326. package/src/prompts/normalize-onboarding.ts +80 -0
  327. package/src/prompts/persona-resolver.ts +101 -9
  328. package/src/prompts/system-prompt.ts +21 -7
  329. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  330. package/src/prompts/templates/SOUL.md +13 -28
  331. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  332. package/src/providers/model-intents.ts +7 -0
  333. package/src/providers/openrouter/client.ts +8 -0
  334. package/src/providers/retry.ts +50 -0
  335. package/src/providers/types.ts +1 -0
  336. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  337. package/src/runtime/agent-wake.ts +238 -100
  338. package/src/runtime/assistant-event-hub.ts +36 -6
  339. package/src/runtime/assistant-event.ts +0 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  341. package/src/runtime/auth/route-policy.ts +15 -1
  342. package/src/runtime/auth/same-actor.ts +216 -0
  343. package/src/runtime/channel-approvals.ts +3 -2
  344. package/src/runtime/channel-retry-sweep.ts +65 -1
  345. package/src/runtime/local-actor-identity.ts +52 -11
  346. package/src/runtime/pending-interactions.ts +27 -15
  347. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  348. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  349. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  350. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
  351. package/src/runtime/routes/approval-routes.ts +7 -3
  352. package/src/runtime/routes/client-routes.ts +20 -2
  353. package/src/runtime/routes/consolidation-routes.ts +8 -9
  354. package/src/runtime/routes/contact-routes.ts +0 -25
  355. package/src/runtime/routes/conversation-query-routes.ts +44 -1
  356. package/src/runtime/routes/conversation-routes.ts +35 -26
  357. package/src/runtime/routes/debug-bash-routes.ts +165 -0
  358. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  359. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  360. package/src/runtime/routes/documents-routes.ts +2 -75
  361. package/src/runtime/routes/events-routes.ts +41 -9
  362. package/src/runtime/routes/filing-routes.ts +2 -3
  363. package/src/runtime/routes/host-bash-routes.ts +23 -3
  364. package/src/runtime/routes/host-cu-routes.ts +33 -6
  365. package/src/runtime/routes/host-file-routes.ts +32 -6
  366. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  367. package/src/runtime/routes/identity-routes.ts +7 -138
  368. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  369. package/src/runtime/routes/index.ts +6 -0
  370. package/src/runtime/routes/memory-item-routes.test.ts +37 -17
  371. package/src/runtime/routes/memory-item-routes.ts +5 -6
  372. package/src/runtime/routes/memory-v2-routes.ts +136 -17
  373. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  374. package/src/runtime/verification-outbound-actions.ts +4 -4
  375. package/src/schedule/run-script.ts +37 -5
  376. package/src/schedule/scheduler.ts +20 -1
  377. package/src/security/encrypted-store.ts +2 -0
  378. package/src/security/secure-keys.ts +55 -0
  379. package/src/skills/include-graph.ts +35 -13
  380. package/src/skills/remote-skill-policy.ts +4 -10
  381. package/src/subagent/index.ts +1 -7
  382. package/src/subagent/manager.ts +1 -15
  383. package/src/tasks/task-runner.ts +0 -1
  384. package/src/tasks/task-store.ts +0 -3
  385. package/src/tools/background-tool-registry.ts +17 -3
  386. package/src/tools/document/document-tool.ts +20 -0
  387. package/src/tools/executor.ts +18 -2
  388. package/src/tools/host-filesystem/edit.test.ts +151 -0
  389. package/src/tools/host-filesystem/edit.ts +43 -1
  390. package/src/tools/host-filesystem/read.test.ts +129 -0
  391. package/src/tools/host-filesystem/read.ts +43 -1
  392. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  393. package/src/tools/host-filesystem/transfer.ts +56 -11
  394. package/src/tools/host-filesystem/write.test.ts +134 -0
  395. package/src/tools/host-filesystem/write.ts +43 -1
  396. package/src/tools/host-terminal/host-shell.ts +13 -6
  397. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  398. package/src/tools/memory/register.test.ts +14 -9
  399. package/src/tools/memory/register.ts +1 -2
  400. package/src/tools/permission-checker.ts +15 -0
  401. package/src/tools/provider-tool-name.ts +28 -0
  402. package/src/tools/registry.ts +30 -9
  403. package/src/tools/skills/load.ts +24 -20
  404. package/src/tools/terminal/shell.ts +9 -1
  405. package/src/tools/tool-approval-handler.ts +31 -6
  406. package/src/tools/tool-name-aliases.ts +19 -0
  407. package/src/tools/types.ts +43 -3
  408. package/src/tts/provider-catalog.ts +3 -5
  409. package/src/util/disk-usage.ts +138 -0
  410. package/src/util/platform.ts +21 -11
  411. package/src/util/process-liveness.ts +26 -0
  412. package/src/workspace/heartbeat-service.ts +19 -0
  413. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  414. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  415. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +14 -0
  416. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  417. package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
  418. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
  419. package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
  420. package/src/workspace/migrations/registry.ts +14 -0
  421. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  422. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  423. package/src/memory/v2/skill-qdrant.ts +0 -404
  424. package/src/signals/bash.ts +0 -198
@@ -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) {
@@ -1012,6 +1159,7 @@ export async function handleSurfaceAction(
1012
1159
  summary,
1013
1160
  submittedData: data,
1014
1161
  });
1162
+ markSurfaceCompleted(ctx, surfaceId, summary);
1015
1163
 
1016
1164
  // Cleanup and resolve — order matters: cleanup clears the timer
1017
1165
  // before resolve() unblocks the caller.
@@ -1358,20 +1506,6 @@ export async function handleSurfaceAction(
1358
1506
  surfaceData,
1359
1507
  );
1360
1508
 
1361
- // Forms are one-shot surfaces — auto-complete immediately so the client
1362
- // transitions from the "Submitting…" spinner to a completion chip without
1363
- // requiring the LLM to call ui_dismiss.
1364
- if (pending.surfaceType === "form") {
1365
- broadcastMessage({
1366
- type: "ui_surface_complete",
1367
- conversationId: ctx.conversationId,
1368
- surfaceId,
1369
- summary,
1370
- submittedData: mergedData,
1371
- });
1372
- markSurfaceCompleted(ctx, surfaceId, summary);
1373
- }
1374
-
1375
1509
  // Extract file attachments from action data so they are sent as proper
1376
1510
  // image/file content blocks instead of dumping base64 into the text.
1377
1511
  let pendingAttachments: UserMessageAttachment[] = [];
@@ -1501,6 +1635,21 @@ export async function handleSurfaceAction(
1501
1635
  return;
1502
1636
  }
1503
1637
 
1638
+ // One-shot interactive surfaces — auto-complete now that the message has
1639
+ // been accepted. Deferred until after rejection check so the surface stays
1640
+ // active and retryable if the queue was full.
1641
+ const ONE_SHOT_SURFACE_TYPES = ["form", "confirmation", "file_upload"];
1642
+ if (ONE_SHOT_SURFACE_TYPES.includes(pending.surfaceType)) {
1643
+ broadcastMessage({
1644
+ type: "ui_surface_complete",
1645
+ conversationId: ctx.conversationId,
1646
+ surfaceId,
1647
+ summary,
1648
+ submittedData: mergedDataForText,
1649
+ });
1650
+ markSurfaceCompleted(ctx, surfaceId, summary);
1651
+ }
1652
+
1504
1653
  // One-shot: clear accumulated state now that the message has been accepted.
1505
1654
  // Deferred until after rejection check so state is preserved for retry on rejection.
1506
1655
  if (accumulatedState && Object.keys(accumulatedState).length > 0) {
@@ -1783,15 +1932,19 @@ export async function surfaceProxyResolver(
1783
1932
  // Record the action and proxy to the connected desktop client
1784
1933
  const reasoning =
1785
1934
  typeof input.reasoning === "string" ? input.reasoning : undefined;
1786
- const targetClientId =
1787
- typeof input.target_client_id === "string" && input.target_client_id !== ""
1935
+ let targetClientId: string | undefined =
1936
+ typeof input.target_client_id === "string" &&
1937
+ input.target_client_id !== ""
1788
1938
  ? input.target_client_id
1789
1939
  : undefined;
1790
1940
 
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.)
1941
+ // Validate targetClientId existence, capability, and same-user binding
1942
+ // before recordAction so an invalid or cross-user ID does not burn a
1943
+ // step or pollute action history. HostBashProxy / HostFileProxy
1944
+ // validate at the tool-resolution layer for the same reason. The proxy
1945
+ // re-checks same-user (single authoritative gate); using the shared
1946
+ // helper keeps log payload and error wording identical at both layers.
1947
+ const sourceActorPrincipalId = ctx.trustContext?.guardianPrincipalId;
1795
1948
  if (targetClientId != null) {
1796
1949
  const client = assistantEventHub.getClientById(targetClientId);
1797
1950
  if (!client) {
@@ -1806,14 +1959,24 @@ export async function surfaceProxyResolver(
1806
1959
  isError: true,
1807
1960
  };
1808
1961
  }
1962
+ const rejection = enforceSameActorOrErrorResult({
1963
+ hub: assistantEventHub,
1964
+ sourceActorPrincipalId,
1965
+ targetClientId,
1966
+ op: "host_cu",
1967
+ });
1968
+ if (rejection) return rejection;
1809
1969
  }
1810
1970
 
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.
1971
+ // Guard: require explicit targeting when multiple same-user CU-capable
1972
+ // clients are connected. The tool schemas document target_client_id as
1973
+ // "required when multiple clients support host_cu" but nothing enforced
1974
+ // it at runtime until now. Without this guard, the request would
1975
+ // broadcast to all capable clients simultaneously, causing the same CU
1976
+ // action to execute on multiple machines. The filter mirrors
1977
+ // HostFileProxy's auto-resolve: only same-user clients participate, so
1978
+ // a cross-user client connected to the same daemon does not falsely
1979
+ // trigger this ambiguity error.
1817
1980
  //
1818
1981
  // Asymmetry with host_bash / host_file (host-shell.ts): the bash/file
1819
1982
  // guard additionally checks `transportInterface != null &&
@@ -1824,13 +1987,25 @@ export async function surfaceProxyResolver(
1824
1987
  // host_cu-capable transport for which auto-routing-to-self would be
1825
1988
  // appropriate. We therefore fire whenever there is genuine ambiguity.
1826
1989
  if (targetClientId == null) {
1827
- const cuClients = assistantEventHub.listClientsByCapability("host_cu");
1828
- if (cuClients.length > 1) {
1990
+ const allCuClients = assistantEventHub.listClientsByCapability("host_cu");
1991
+ const sameUserCuClients = allCuClients.filter(
1992
+ (c) => c.actorPrincipalId === sourceActorPrincipalId,
1993
+ );
1994
+ if (sameUserCuClients.length > 1) {
1829
1995
  return {
1830
1996
  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
1997
  isError: true,
1832
1998
  };
1833
1999
  }
2000
+ // When cross-user host_cu clients are connected, we MUST auto-resolve
2001
+ // to the unique same-user client (or fail explicitly) — otherwise the
2002
+ // proxy would broadcast untargeted and the CU action would reach the
2003
+ // cross-user client too. Setting targetClientId here forces the proxy
2004
+ // to deliver only to that client, with the same-user check below as
2005
+ // belt-and-suspenders.
2006
+ if (sameUserCuClients.length === 1 && allCuClients.length > 1) {
2007
+ targetClientId = sameUserCuClients[0].clientId;
2008
+ }
1834
2009
  }
1835
2010
 
1836
2011
  ctx.hostCuProxy.recordAction(toolName, input, reasoning);
@@ -1842,6 +2017,7 @@ export async function surfaceProxyResolver(
1842
2017
  reasoning,
1843
2018
  signal,
1844
2019
  targetClientId,
2020
+ sourceActorPrincipalId,
1845
2021
  );
1846
2022
  }
1847
2023
 
@@ -1876,7 +2052,7 @@ export async function surfaceProxyResolver(
1876
2052
  // union on `tool` ("start" | "observe" | "press" | …). The agent's raw
1877
2053
  // tool input only carries the action-specific payload (app, x/y, text,
1878
2054
  // …) — the discriminator is implied by `toolName` (`app_control_<tool>`).
1879
- // Inject it here so the proxy's singleton-lock guard (`input.tool ===
2055
+ // Inject it here so the proxy's session-lock guard (`input.tool ===
1880
2056
  // "start"`) and the Swift client's discriminated-union decoder both see
1881
2057
  // the field they require.
1882
2058
  const tool = toolName.slice("app_control_".length);
@@ -2077,6 +2253,12 @@ export async function surfaceProxyResolver(
2077
2253
  ctx.currentTurnSurfaces[idx].data = mergedData;
2078
2254
  }
2079
2255
 
2256
+ // Persist the merged data back to the assistant message's
2257
+ // `ui_surface` content block so a refresh / restart shows the
2258
+ // current state instead of the original creation-time snapshot.
2259
+ // Debounced to coalesce bursts of rapid updates.
2260
+ scheduleSurfaceDataPersist(ctx.conversationId, surfaceId, mergedData);
2261
+
2080
2262
  return { content: "Surface updated", isError: false };
2081
2263
  }
2082
2264
 
@@ -30,12 +30,14 @@ import {
30
30
  ACTIVITY_SKIP_SET,
31
31
  injectActivityField,
32
32
  } from "../tools/schema-transforms.js";
33
- import type {
34
- ProxyApprovalCallback,
35
- ProxyApprovalRequest,
36
- ToolContext,
37
- ToolExecutionResult,
38
- ToolLifecycleEventHandler,
33
+ import { resolveToolNameAlias } from "../tools/tool-name-aliases.js";
34
+ import {
35
+ isDiskPressureCleanupToolName,
36
+ type ProxyApprovalCallback,
37
+ type ProxyApprovalRequest,
38
+ type ToolContext,
39
+ type ToolExecutionResult,
40
+ type ToolLifecycleEventHandler,
39
41
  } from "../tools/types.js";
40
42
  import { allUiSurfaceTools } from "../tools/ui-surface/definitions.js";
41
43
  import { getLogger } from "../util/logger.js";
@@ -121,7 +123,9 @@ export function createToolExecutor(
121
123
  toolUseId?: string,
122
124
  turnContext?: import("../plugins/types.js").TurnContext,
123
125
  ) => {
124
- if (isDoordashCommand(name, input)) {
126
+ const executionName = resolveToolNameAlias(name, ctx.allowedToolNames);
127
+
128
+ if (isDoordashCommand(executionName, input)) {
125
129
  markDoordashStepInProgress(ctx, input);
126
130
  }
127
131
 
@@ -135,6 +139,7 @@ export function createToolExecutor(
135
139
  taskRunId: ctx.taskRunId,
136
140
  trustClass: resolveTrustClass(ctx.trustContext),
137
141
  executionChannel: ctx.trustContext?.sourceChannel,
142
+ sourceActorPrincipalId: ctx.trustContext?.guardianPrincipalId,
138
143
  callSessionId: ctx.callSessionId,
139
144
  triggeredBySurfaceAction:
140
145
  ctx.surfaceActionRequestIds?.has(ctx.currentRequestId ?? "") ?? false,
@@ -151,7 +156,7 @@ export function createToolExecutor(
151
156
  onOutput,
152
157
  signal: ctx.abortController?.signal,
153
158
  allowedToolNames: ctx.allowedToolNames,
154
- memoryScopeId: ctx.memoryPolicy.scopeId,
159
+ diskPressureCleanupModeActive: ctx.diskPressureCleanupModeActive,
155
160
  toolUseId,
156
161
  isPlatformHosted: getIsPlatform(),
157
162
  cesClient: ctx.cesClient,
@@ -206,8 +211,9 @@ export function createToolExecutor(
206
211
  // route through the full executor pipeline so the underlying tool's
207
212
  // risk level, permission checks, hooks, and lifecycle events all fire
208
213
  // with the real tool name.
209
- if (name === "skill_execute") {
210
- const toolName = typeof input.tool === "string" ? input.tool : "";
214
+ if (executionName === "skill_execute") {
215
+ const rawToolName = typeof input.tool === "string" ? input.tool : "";
216
+ const toolName = resolveToolNameAlias(rawToolName, ctx.allowedToolNames);
211
217
  const rawToolInput =
212
218
  input.input != null && typeof input.input === "object"
213
219
  ? (input.input as Record<string, unknown>)
@@ -240,7 +246,7 @@ export function createToolExecutor(
240
246
  }
241
247
 
242
248
  const result = await executor.execute(
243
- name,
249
+ executionName,
244
250
  input,
245
251
  toolContext,
246
252
  turnContext,
@@ -249,7 +255,7 @@ export function createToolExecutor(
249
255
  ctx.approvedViaPromptThisTurn = true;
250
256
  }
251
257
 
252
- runPostExecutionSideEffects(name, input, result, { ctx });
258
+ runPostExecutionSideEffects(executionName, input, result, { ctx });
253
259
 
254
260
  return result;
255
261
  };
@@ -305,6 +311,8 @@ export interface SkillProjectionContext {
305
311
  readonly hasNoClient?: boolean;
306
312
  /** When set, only tools in this set are included in the resolved tool list (subagent delegation). */
307
313
  subagentAllowedTools?: Set<string>;
314
+ /** True when the current turn is restricted to disk-pressure cleanup-safe tools. */
315
+ diskPressureCleanupModeActive?: boolean;
308
316
  /** True when this conversation belongs to a subagent spawned by SubagentManager. */
309
317
  readonly isSubagent?: boolean;
310
318
  /**
@@ -351,6 +359,29 @@ export const HOST_TOOL_TO_CAPABILITY = new Map<string, HostProxyCapability>([
351
359
  // Derived from HOST_TOOL_TO_CAPABILITY so the invariant "every host tool has
352
360
  // a capability mapping" is a structural fact — no runtime assertion needed.
353
361
  export const HOST_TOOL_NAMES = new Set(HOST_TOOL_TO_CAPABILITY.keys());
362
+ /**
363
+ * Capabilities eligible for cross-client exposure on non-host-proxy
364
+ * transports (e.g. web, ios routing to a connected macOS client).
365
+ * Adding a capability here exposes ALL tools that map to it (per
366
+ * HOST_TOOL_TO_CAPABILITY) on non-host-proxy transports — the daemon then
367
+ * routes the actual invocation to the connected capable client via the
368
+ * proxy's targetClientId path.
369
+ *
370
+ * Inclusions:
371
+ * - host_bash (Phase 1, PR #29322)
372
+ * - host_file (Phases 2 & 3, PRs #29398 + #29440)
373
+ *
374
+ * Exclusions:
375
+ * - host_browser: chrome-extension is its own executor; web turns don't
376
+ * have a CDP target model. Re-evaluate when host browser via macOS
377
+ * host proxy ships (PR #27489).
378
+ * - host_app_control, host_cu: not in HOST_TOOL_TO_CAPABILITY
379
+ * (skill-routed).
380
+ */
381
+ const CROSS_CLIENT_EXPOSED_CAPABILITIES = new Set<HostProxyCapability>([
382
+ "host_bash",
383
+ "host_file",
384
+ ]);
354
385
  const CLIENT_CAPABILITY_TOOL_NAMES = new Set(["app_open"]);
355
386
  const PLATFORM_TOOL_NAMES = new Set(["request_system_permission"]);
356
387
 
@@ -385,16 +416,22 @@ export function isToolActiveForContext(
385
416
  // Per-capability check is authoritative for structural support: if the
386
417
  // transport cannot service this capability, the tool is filtered out.
387
418
  if (transport && capability && !supportsHostProxy(transport, capability)) {
388
- // Cross-client exception: allow host_bash for non-host-proxy interfaces when
389
- // at least one capable client is connected via the event hub.
390
- // Only applies to host_bash (not host_file, host_cu, host_browser Phase 2).
391
- // Excludes chrome-extension (security boundary: extension only gets host_browser)
392
- // and hasNoClient turns (no interactive approval UI available).
419
+ // Cross-client exception: allow host tools whose capabilities have
420
+ // cross-client routing infrastructure (Phases 1–3) to be exposed for
421
+ // non-host-proxy transports (e.g. "web", "ios") when at least one
422
+ // capable client is connected via the event hub. Members of
423
+ // CROSS_CLIENT_EXPOSED_CAPABILITIES (host_bash, host_file) qualify;
424
+ // host_browser is intentionally excluded (chrome-extension is its
425
+ // own executor and web turns don't have a CDP target model).
426
+ // chrome-extension transport is excluded as a security boundary
427
+ // (extension only gets host_browser); hasNoClient turns are excluded
428
+ // (no interactive approval UI available).
393
429
  if (
394
- capability === "host_bash" &&
430
+ capability &&
431
+ CROSS_CLIENT_EXPOSED_CAPABILITIES.has(capability) &&
395
432
  transport !== "chrome-extension" &&
396
433
  !ctx.hasNoClient &&
397
- assistantEventHub.listClientsByCapability("host_bash").length > 0
434
+ assistantEventHub.listClientsByCapability(capability).length > 0
398
435
  ) {
399
436
  return true;
400
437
  }
@@ -516,6 +553,16 @@ export function createResolveToolsCallback(
516
553
  }
517
554
  turnAllowed.add(name);
518
555
  }
556
+ if (ctx.diskPressureCleanupModeActive === true) {
557
+ const cleanupDefs = allBaseDefs.filter((d) =>
558
+ isDiskPressureCleanupToolName(d.name),
559
+ );
560
+ ctx.allowedToolNames = new Set(
561
+ Array.from(turnAllowed).filter(isDiskPressureCleanupToolName),
562
+ );
563
+ return injectActivityField(cleanupDefs, ACTIVITY_SKIP_SET);
564
+ }
565
+
519
566
  ctx.allowedToolNames = turnAllowed;
520
567
  return injectActivityField(allBaseDefs, ACTIVITY_SKIP_SET);
521
568
  };