@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
@@ -18,6 +18,8 @@
18
18
  */
19
19
 
20
20
  import { AsyncLocalStorage } from "node:async_hooks";
21
+ import { existsSync } from "node:fs";
22
+ import { join } from "node:path";
21
23
 
22
24
  import type {
23
25
  SecureKeyBackend,
@@ -28,6 +30,7 @@ import { getIsContainerized } from "../config/env-registry.js";
28
30
  import type { CesClient } from "../credential-execution/client.js";
29
31
  import { getAnyProviderEnvVar } from "../providers/provider-env-vars.js";
30
32
  import { getLogger } from "../util/logger.js";
33
+ import { getProtectedDir } from "../util/platform.js";
31
34
  import { createCesCredentialBackend } from "./ces-credential-client.js";
32
35
  import { CesRpcCredentialBackend } from "./ces-rpc-credential-backend.js";
33
36
  import type {
@@ -584,6 +587,58 @@ export function getActiveBackendName(): string {
584
587
  return _resolvedBackend?.name ?? "none";
585
588
  }
586
589
 
590
+ // ---------------------------------------------------------------------------
591
+ // Backend introspection
592
+ // ---------------------------------------------------------------------------
593
+
594
+ export type BackendInfo =
595
+ | {
596
+ backend: "encrypted-store";
597
+ storePath: string;
598
+ storeKeyPath: string;
599
+ storeExists: boolean;
600
+ storeKeyExists: boolean;
601
+ }
602
+ | { backend: "ces-rpc"; ready: boolean }
603
+ | { backend: "ces-http"; url: string }
604
+ | { backend: "none" };
605
+
606
+ /**
607
+ * Resolve the active credential backend (triggering resolution if not yet
608
+ * done) and return introspection details specific to that backend.
609
+ *
610
+ * Useful for `credentials status` — shows which store this process is talking
611
+ * to, so path/socket mismatches between the CLI and daemon are immediately
612
+ * visible.
613
+ */
614
+ export function getActiveBackendInfoAsync(): Promise<BackendInfo> {
615
+ return withCredentialTimeout(async () => {
616
+ const backend = await resolveBackendAsync();
617
+ if (backend.name === "encrypted-store") {
618
+ const protectedDir = getProtectedDir();
619
+ const storePath = join(protectedDir, "keys.enc");
620
+ const storeKeyPath = join(protectedDir, "store.key");
621
+ return {
622
+ backend: "encrypted-store" as const,
623
+ storePath,
624
+ storeKeyPath,
625
+ storeExists: existsSync(storePath),
626
+ storeKeyExists: existsSync(storeKeyPath),
627
+ };
628
+ }
629
+ if (backend.name === "ces-rpc") {
630
+ return { backend: "ces-rpc" as const, ready: backend.isAvailable() };
631
+ }
632
+ if (backend.name === "ces-http") {
633
+ return {
634
+ backend: "ces-http" as const,
635
+ url: process.env.CES_CREDENTIAL_URL ?? "",
636
+ };
637
+ }
638
+ return { backend: "none" as const };
639
+ }, { backend: "none" as const });
640
+ }
641
+
587
642
  /** @internal Test-only: reset the cached backends so they're re-created. */
588
643
  export function _resetBackend(): void {
589
644
  _cesClient = undefined;
@@ -1,12 +1,6 @@
1
1
  type RemoteSkillProvider = "clawhub" | "skillssh";
2
2
 
3
- export type SkillsShRisk =
4
- | "safe"
5
- | "low"
6
- | "medium"
7
- | "high"
8
- | "critical"
9
- | "unknown";
3
+ type SkillsShRisk = "safe" | "low" | "medium" | "high" | "critical" | "unknown";
10
4
  export type SkillsShRiskThreshold = Exclude<SkillsShRisk, "unknown">;
11
5
 
12
6
  export interface RemoteSkillPolicy {
@@ -50,17 +44,17 @@ interface SkillsShRemoteSkillCandidate extends RemoteSkillCandidateBase {
50
44
  audit?: SkillsShAuditState | null;
51
45
  }
52
46
 
53
- export type RemoteSkillCandidate =
47
+ type RemoteSkillCandidate =
54
48
  | ClawhubRemoteSkillCandidate
55
49
  | SkillsShRemoteSkillCandidate;
56
50
 
57
- export type RemoteSkillDenyReason =
51
+ type RemoteSkillDenyReason =
58
52
  | "clawhub_suspicious"
59
53
  | "clawhub_malware_blocked"
60
54
  | "clawhub_moderation_missing"
61
55
  | "skillssh_risk_exceeds_threshold";
62
56
 
63
- export type RemoteSkillInstallDecision =
57
+ type RemoteSkillInstallDecision =
64
58
  | { ok: true }
65
59
  | { ok: false; reason: RemoteSkillDenyReason };
66
60
 
@@ -1,11 +1,5 @@
1
1
  export { mergeSkillIds } from "./manager.js";
2
- export type {
3
- SubagentConfig,
4
- SubagentRole,
5
- SubagentRoleConfig,
6
- SubagentState,
7
- SubagentStatus,
8
- } from "./types.js";
2
+ export type { SubagentRole } from "./types.js";
9
3
  export { SUBAGENT_ROLE_REGISTRY, TERMINAL_STATUSES } from "./types.js";
10
4
 
11
5
  import { SubagentManager } from "./manager.js";
@@ -11,10 +11,7 @@
11
11
  import { v4 as uuid } from "uuid";
12
12
 
13
13
  import { getConfig } from "../config/loader.js";
14
- import {
15
- Conversation,
16
- type ConversationMemoryPolicy,
17
- } from "../daemon/conversation.js";
14
+ import { Conversation } from "../daemon/conversation.js";
18
15
  import { findConversation } from "../daemon/conversation-store.js";
19
16
  import type { ServerMessage } from "../daemon/message-protocol.js";
20
17
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
@@ -231,16 +228,6 @@ export class SubagentManager {
231
228
  const maxTokens = appConfig.llm.default.maxTokens;
232
229
  const workingDir = getSandboxWorkingDir();
233
230
 
234
- const memoryPolicy: ConversationMemoryPolicy = isFork
235
- ? {
236
- scopeId: "default",
237
- includeDefaultFallback: false,
238
- }
239
- : {
240
- scopeId: `subagent:${subagentId}`,
241
- includeDefaultFallback: true,
242
- };
243
-
244
231
  // ── Initialise state ────────────────────────────────────────────
245
232
  const now = Date.now();
246
233
  // For forks, default sendResultToUser to false (silent) unless explicitly true.
@@ -289,7 +276,6 @@ export class SubagentManager {
289
276
  maxTokens,
290
277
  wrappedSendToClient,
291
278
  workingDir,
292
- memoryPolicy,
293
279
  undefined, // sharedCesClient
294
280
  undefined, // speedOverride
295
281
  "5m", // cacheTtl — subagents run tight tool-use loops, 5m is always hot
@@ -67,7 +67,6 @@ export async function runTask(
67
67
 
68
68
  updateTaskRun(run.id, {
69
69
  conversationId: conversation.id,
70
- memoryScopeId: `task:${task.id}`,
71
70
  });
72
71
 
73
72
  try {
@@ -27,7 +27,6 @@ export interface TaskRun {
27
27
  finishedAt: number | null;
28
28
  error: string | null;
29
29
  principalId: string | null;
30
- memoryScopeId: string | null;
31
30
  createdAt: number;
32
31
  }
33
32
 
@@ -109,7 +108,6 @@ export function createTaskRun(taskId: string): TaskRun {
109
108
  finishedAt: null,
110
109
  error: null,
111
110
  principalId: null,
112
- memoryScopeId: null,
113
111
  createdAt: now,
114
112
  };
115
113
  db.insert(taskRuns).values(run).run();
@@ -125,7 +123,6 @@ export function updateTaskRun(
125
123
  | "conversationId"
126
124
  | "error"
127
125
  | "principalId"
128
- | "memoryScopeId"
129
126
  | "startedAt"
130
127
  | "finishedAt"
131
128
  >
@@ -19,7 +19,7 @@ export interface BackgroundTool {
19
19
  command: string;
20
20
  startedAt: number;
21
21
  /** Kills the process (bash) or aborts the proxy (host_bash). */
22
- cancel: () => void;
22
+ cancel: (reason?: string) => void;
23
23
  }
24
24
 
25
25
  /** Maximum number of concurrent background tools allowed. */
@@ -61,16 +61,30 @@ export function listBackgroundTools(conversationId?: string): BackgroundTool[] {
61
61
  * Cancels a background tool by ID: calls `tool.cancel()`, removes the entry,
62
62
  * and returns `true`. Returns `false` if the ID is not found.
63
63
  */
64
- export function cancelBackgroundTool(id: string): boolean {
64
+ export function cancelBackgroundTool(id: string, reason?: string): boolean {
65
65
  const tool = registry.get(id);
66
66
  if (!tool) {
67
67
  return false;
68
68
  }
69
- tool.cancel();
69
+ tool.cancel(reason);
70
70
  registry.delete(id);
71
71
  return true;
72
72
  }
73
73
 
74
+ export function cancelBackgroundTools(
75
+ shouldCancel: (tool: BackgroundTool) => boolean,
76
+ reason?: string,
77
+ ): BackgroundTool[] {
78
+ const cancelled: BackgroundTool[] = [];
79
+ for (const tool of Array.from(registry.values())) {
80
+ if (!shouldCancel(tool)) continue;
81
+ tool.cancel(reason);
82
+ registry.delete(tool.id);
83
+ cancelled.push(tool);
84
+ }
85
+ return cancelled;
86
+ }
87
+
74
88
  /**
75
89
  * Generates a short prefixed ID for a background tool.
76
90
  * Format: `bg-<8 hex chars>` (e.g. `bg-a1b2c3d4`).
@@ -0,0 +1,151 @@
1
+ import { mkdtempSync, realpathSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, describe, expect, mock, test } from "bun:test";
5
+
6
+ import type { ToolContext } from "../types.js";
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Singleton mocks — must precede the tool import so bun's module mock applies.
10
+ // ---------------------------------------------------------------------------
11
+
12
+ let mockProxyAvailable = false;
13
+
14
+ mock.module("../../daemon/host-file-proxy.js", () => ({
15
+ HostFileProxy: {
16
+ get instance() {
17
+ return {
18
+ isAvailable: () => mockProxyAvailable,
19
+ request: () => Promise.resolve({ content: "ok", isError: false }),
20
+ };
21
+ },
22
+ },
23
+ }));
24
+
25
+ mock.module("../../runtime/assistant-event-hub.js", () => ({
26
+ assistantEventHub: {
27
+ listClientsByCapability: () => [],
28
+ },
29
+ }));
30
+
31
+ const { hostFileEditTool } = await import("./edit.js");
32
+
33
+ const testDirs: string[] = [];
34
+
35
+ afterEach(() => {
36
+ mockProxyAvailable = false;
37
+ for (const dir of testDirs.splice(0)) {
38
+ rmSync(dir, { recursive: true, force: true });
39
+ }
40
+ });
41
+
42
+ function makeTempDir(): string {
43
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "host-edit-test-")));
44
+ testDirs.push(dir);
45
+ return dir;
46
+ }
47
+
48
+ function makeContext(
49
+ workingDir: string,
50
+ transportInterface: ToolContext["transportInterface"],
51
+ ): ToolContext {
52
+ return {
53
+ workingDir,
54
+ conversationId: "test-conv",
55
+ trustClass: "guardian",
56
+ transportInterface,
57
+ };
58
+ }
59
+
60
+ describe("host_file_edit cross-client guards", () => {
61
+ test("returns 'no client' error on web transport when proxy unavailable and no targetClientId", async () => {
62
+ const workingDir = makeTempDir();
63
+ const result = await hostFileEditTool.execute(
64
+ {
65
+ path: "/some/host/path.txt",
66
+ old_string: "foo",
67
+ new_string: "bar",
68
+ },
69
+ makeContext(workingDir, "web"),
70
+ );
71
+ expect(result.isError).toBe(true);
72
+ expect(result.content).toContain(
73
+ "no client with host_file capability is connected",
74
+ );
75
+ });
76
+
77
+ test("returns 'specified client disconnected' error when targetClientId set but proxy unavailable on web", async () => {
78
+ const workingDir = makeTempDir();
79
+ const result = await hostFileEditTool.execute(
80
+ {
81
+ path: "/some/host/path.txt",
82
+ old_string: "foo",
83
+ new_string: "bar",
84
+ target_client_id: "abc-123",
85
+ },
86
+ makeContext(workingDir, "web"),
87
+ );
88
+ expect(result.isError).toBe(true);
89
+ expect(result.content).toContain(
90
+ 'target client "abc-123" is no longer connected',
91
+ );
92
+ });
93
+
94
+ test("falls through to local fs on macos transport when proxy unavailable", async () => {
95
+ const workingDir = makeTempDir();
96
+ const result = await hostFileEditTool.execute(
97
+ {
98
+ path: "/nonexistent/x.txt",
99
+ old_string: "foo",
100
+ new_string: "bar",
101
+ },
102
+ makeContext(workingDir, "macos"),
103
+ );
104
+ // Proves the guard did NOT fire on macOS — instead we got the
105
+ // local FileSystemOps error path (file not found / IO error).
106
+ expect(result.isError).toBe(true);
107
+ expect(result.content.toLowerCase()).toMatch(/not found|enoent|editing/);
108
+ });
109
+
110
+ test("does NOT reject on macos transport with a stale target_client_id when proxy unavailable (regression: P2 fix)", async () => {
111
+ const workingDir = makeTempDir();
112
+ const result = await hostFileEditTool.execute(
113
+ {
114
+ path: "/nonexistent/x.txt",
115
+ old_string: "foo",
116
+ new_string: "bar",
117
+ target_client_id: "stale-mac",
118
+ },
119
+ makeContext(workingDir, "macos"),
120
+ );
121
+ // The disconnected-target guard is scoped to non-host-proxy transports
122
+ // (!supportsHostProxy). On macos, a stale target_client_id auto-filled
123
+ // from a prior cross-client turn must be silently ignored and the call
124
+ // must fall through to local FileSystemOps, NOT reject with "target
125
+ // client ... is no longer connected".
126
+ expect(result.isError).toBe(true);
127
+ expect(result.content).not.toContain("is no longer connected");
128
+ expect(result.content.toLowerCase()).toMatch(/not found|enoent|editing/);
129
+ });
130
+
131
+ test("rejects when target_client_id is set but transport metadata is missing (legacy/backwards-compat path)", async () => {
132
+ const workingDir = makeTempDir();
133
+ const result = await hostFileEditTool.execute(
134
+ {
135
+ path: "/some/host/path.txt",
136
+ old_string: "foo",
137
+ new_string: "bar",
138
+ target_client_id: "abc-123",
139
+ },
140
+ // transportInterface intentionally undefined (legacy callers).
141
+ makeContext(workingDir, undefined),
142
+ );
143
+ // Without transport metadata, falling through to local fs would
144
+ // silently target the daemon container. The guard fires for undefined
145
+ // transport AND non-host-proxy transports — only macos turns skip it.
146
+ expect(result.isError).toBe(true);
147
+ expect(result.content).toContain(
148
+ 'target client "abc-123" is no longer connected',
149
+ );
150
+ });
151
+ });
@@ -92,7 +92,8 @@ class HostFileEditTool implements Tool {
92
92
  const replaceAll = input.replace_all === true;
93
93
 
94
94
  const targetClientId =
95
- typeof input.target_client_id === "string" && input.target_client_id !== ""
95
+ typeof input.target_client_id === "string" &&
96
+ input.target_client_id !== ""
96
97
  ? input.target_client_id
97
98
  : undefined;
98
99
 
@@ -109,6 +110,45 @@ class HostFileEditTool implements Tool {
109
110
  };
110
111
  }
111
112
 
113
+ // Guard: non-host-proxy interfaces with no capable clients connected.
114
+ // Without this guard, the request would fall through to local
115
+ // FileSystemOps below and read the daemon container's filesystem
116
+ // instead of the user's host machine.
117
+ if (
118
+ targetClientId == null &&
119
+ transportInterface != null &&
120
+ !supportsHostProxy(transportInterface) &&
121
+ !HostFileProxy.instance.isAvailable()
122
+ ) {
123
+ return {
124
+ content:
125
+ "Error: no client with host_file capability is connected. Connect a macOS client to use host_file from a non-desktop interface.",
126
+ isError: true,
127
+ };
128
+ }
129
+
130
+ // Guard: explicit targetClientId provided but proxy is unavailable.
131
+ // Fires on non-host-proxy transports (web, ios) AND on legacy callers
132
+ // without transport metadata, where falling through to local fs would
133
+ // silently target the daemon container's filesystem instead of the
134
+ // intended host client. Skips only when transport is explicitly
135
+ // host-proxy-capable (macos), where local-fs fallback IS the intended
136
+ // offline behavior — a stale target_client_id auto-filled from a prior
137
+ // cross-client turn is silently ignored on those turns.
138
+ // Note: this scoping deliberately differs from host_bash
139
+ // (host-shell.ts:239-247), which rejects unconditionally for any
140
+ // stale target_client_id regardless of transport.
141
+ if (
142
+ targetClientId != null &&
143
+ !HostFileProxy.instance.isAvailable() &&
144
+ (transportInterface == null || !supportsHostProxy(transportInterface))
145
+ ) {
146
+ return {
147
+ content: `Error: target client "${targetClientId}" is no longer connected. The specified client may have disconnected since the tool was called. Run \`assistant clients list --capability host_file\` to see currently connected clients.`,
148
+ isError: true,
149
+ };
150
+ }
151
+
112
152
  // Proxy to connected client for execution on the user's machine
113
153
  // when a capable client is available (managed/cloud-hosted mode).
114
154
  if (HostFileProxy.instance.isAvailable()) {
@@ -123,6 +163,8 @@ class HostFileEditTool implements Tool {
123
163
  },
124
164
  context.conversationId,
125
165
  context.signal,
166
+ targetClientId,
167
+ context.sourceActorPrincipalId,
126
168
  );
127
169
  }
128
170
 
@@ -0,0 +1,129 @@
1
+ import { mkdtempSync, realpathSync, rmSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, describe, expect, mock, test } from "bun:test";
5
+
6
+ import type { ToolContext } from "../types.js";
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // Singleton mocks — must precede the tool import so bun's module mock applies.
10
+ // ---------------------------------------------------------------------------
11
+
12
+ let mockProxyAvailable = false;
13
+
14
+ mock.module("../../daemon/host-file-proxy.js", () => ({
15
+ HostFileProxy: {
16
+ get instance() {
17
+ return {
18
+ isAvailable: () => mockProxyAvailable,
19
+ request: () => Promise.resolve({ content: "ok", isError: false }),
20
+ };
21
+ },
22
+ },
23
+ }));
24
+
25
+ mock.module("../../runtime/assistant-event-hub.js", () => ({
26
+ assistantEventHub: {
27
+ listClientsByCapability: () => [],
28
+ },
29
+ }));
30
+
31
+ const { hostFileReadTool } = await import("./read.js");
32
+
33
+ const testDirs: string[] = [];
34
+
35
+ afterEach(() => {
36
+ mockProxyAvailable = false;
37
+ for (const dir of testDirs.splice(0)) {
38
+ rmSync(dir, { recursive: true, force: true });
39
+ }
40
+ });
41
+
42
+ function makeTempDir(): string {
43
+ const dir = realpathSync(mkdtempSync(join(tmpdir(), "host-read-test-")));
44
+ testDirs.push(dir);
45
+ return dir;
46
+ }
47
+
48
+ function makeContext(
49
+ workingDir: string,
50
+ transportInterface: ToolContext["transportInterface"],
51
+ ): ToolContext {
52
+ return {
53
+ workingDir,
54
+ conversationId: "test-conv",
55
+ trustClass: "guardian",
56
+ transportInterface,
57
+ };
58
+ }
59
+
60
+ describe("host_file_read cross-client guards", () => {
61
+ test("returns 'no client' error on web transport when proxy unavailable and no targetClientId", async () => {
62
+ const workingDir = makeTempDir();
63
+ const result = await hostFileReadTool.execute(
64
+ { path: "/some/host/path.txt" },
65
+ makeContext(workingDir, "web"),
66
+ );
67
+ expect(result.isError).toBe(true);
68
+ expect(result.content).toContain(
69
+ "no client with host_file capability is connected",
70
+ );
71
+ });
72
+
73
+ test("returns 'specified client disconnected' error when targetClientId set but proxy unavailable on web", async () => {
74
+ const workingDir = makeTempDir();
75
+ const result = await hostFileReadTool.execute(
76
+ { path: "/some/host/path.txt", target_client_id: "abc-123" },
77
+ makeContext(workingDir, "web"),
78
+ );
79
+ expect(result.isError).toBe(true);
80
+ expect(result.content).toContain(
81
+ 'target client "abc-123" is no longer connected',
82
+ );
83
+ });
84
+
85
+ test("falls through to local fs on macos transport when proxy unavailable and path is non-image", async () => {
86
+ const workingDir = makeTempDir();
87
+ const result = await hostFileReadTool.execute(
88
+ { path: "/nonexistent/x.txt" },
89
+ makeContext(workingDir, "macos"),
90
+ );
91
+ // Proves the guard did NOT fire on macOS — instead we got the
92
+ // local FileSystemOps NOT_FOUND error.
93
+ expect(result.isError).toBe(true);
94
+ expect(result.content).toContain("File not found");
95
+ });
96
+
97
+ test("does NOT reject on macos transport with a stale target_client_id when proxy unavailable (regression: P2 fix)", async () => {
98
+ const workingDir = makeTempDir();
99
+ const result = await hostFileReadTool.execute(
100
+ { path: "/nonexistent/x.txt", target_client_id: "stale-mac" },
101
+ makeContext(workingDir, "macos"),
102
+ );
103
+ // The disconnected-target guard is scoped to non-host-proxy transports
104
+ // (!supportsHostProxy). On macos, a stale target_client_id auto-filled
105
+ // from a prior cross-client turn must be silently ignored and the call
106
+ // must fall through to local FileSystemOps (NOT_FOUND for a fake path),
107
+ // NOT reject with "target client ... is no longer connected".
108
+ expect(result.isError).toBe(true);
109
+ expect(result.content).toContain("File not found");
110
+ expect(result.content).not.toContain("is no longer connected");
111
+ });
112
+
113
+ test("rejects when target_client_id is set but transport metadata is missing (legacy/backwards-compat path)", async () => {
114
+ const workingDir = makeTempDir();
115
+ const result = await hostFileReadTool.execute(
116
+ { path: "/some/host/path.txt", target_client_id: "abc-123" },
117
+ // transportInterface intentionally undefined (legacy callers).
118
+ makeContext(workingDir, undefined),
119
+ );
120
+ // When transport metadata is missing we cannot rule out a non-host-proxy
121
+ // turn, so falling through to local fs would silently target the daemon
122
+ // container. The guard fires for both undefined transport AND
123
+ // non-host-proxy transports — only macos turns skip it.
124
+ expect(result.isError).toBe(true);
125
+ expect(result.content).toContain(
126
+ 'target client "abc-123" is no longer connected',
127
+ );
128
+ });
129
+ });
@@ -63,7 +63,8 @@ class HostFileReadTool implements Tool {
63
63
  }
64
64
 
65
65
  const targetClientId =
66
- typeof input.target_client_id === "string" && input.target_client_id !== ""
66
+ typeof input.target_client_id === "string" &&
67
+ input.target_client_id !== ""
67
68
  ? input.target_client_id
68
69
  : undefined;
69
70
 
@@ -80,6 +81,45 @@ class HostFileReadTool implements Tool {
80
81
  };
81
82
  }
82
83
 
84
+ // Guard: non-host-proxy interfaces with no capable clients connected.
85
+ // Without this guard, the request would fall through to local
86
+ // FileSystemOps below and read the daemon container's filesystem
87
+ // instead of the user's host machine.
88
+ if (
89
+ targetClientId == null &&
90
+ transportInterface != null &&
91
+ !supportsHostProxy(transportInterface) &&
92
+ !HostFileProxy.instance.isAvailable()
93
+ ) {
94
+ return {
95
+ content:
96
+ "Error: no client with host_file capability is connected. Connect a macOS client to use host_file from a non-desktop interface.",
97
+ isError: true,
98
+ };
99
+ }
100
+
101
+ // Guard: explicit targetClientId provided but proxy is unavailable.
102
+ // Fires on non-host-proxy transports (web, ios) AND on legacy callers
103
+ // without transport metadata, where falling through to local fs would
104
+ // silently target the daemon container's filesystem instead of the
105
+ // intended host client. Skips only when transport is explicitly
106
+ // host-proxy-capable (macos), where local-fs fallback IS the intended
107
+ // offline behavior — a stale target_client_id auto-filled from a prior
108
+ // cross-client turn is silently ignored on those turns.
109
+ // Note: this scoping deliberately differs from host_bash
110
+ // (host-shell.ts:239-247), which rejects unconditionally for any
111
+ // stale target_client_id regardless of transport.
112
+ if (
113
+ targetClientId != null &&
114
+ !HostFileProxy.instance.isAvailable() &&
115
+ (transportInterface == null || !supportsHostProxy(transportInterface))
116
+ ) {
117
+ return {
118
+ content: `Error: target client "${targetClientId}" is no longer connected. The specified client may have disconnected since the tool was called. Run \`assistant clients list --capability host_file\` to see currently connected clients.`,
119
+ isError: true,
120
+ };
121
+ }
122
+
83
123
  // Proxy to connected client for execution on the user's machine
84
124
  // when a capable client is available (managed/cloud-hosted mode),
85
125
  // including image reads that need the host filesystem view.
@@ -94,6 +134,8 @@ class HostFileReadTool implements Tool {
94
134
  },
95
135
  context.conversationId,
96
136
  context.signal,
137
+ targetClientId,
138
+ context.sourceActorPrincipalId,
97
139
  );
98
140
  }
99
141