@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
@@ -1,3 +1,8 @@
1
+ import {
2
+ checkDiskPressureBackgroundGate,
3
+ diskPressureBackgroundSkipLogFields,
4
+ shouldLogDiskPressureBackgroundSkip,
5
+ } from "../daemon/disk-pressure-background-gate.js";
1
6
  import { emitFeedEvent } from "../home/emit-feed-event.js";
2
7
  import { bootstrapConversation } from "../memory/conversation-bootstrap.js";
3
8
  import { getConversation } from "../memory/conversation-crud.js";
@@ -129,7 +134,7 @@ export function startScheduler(
129
134
  };
130
135
  }
131
136
 
132
- async function runScheduleOnce(
137
+ export async function runScheduleOnce(
133
138
  processMessage: ScheduleMessageProcessor,
134
139
  notifyScheduleOneShot: ScheduleNotifyModeNotifier,
135
140
  watcherNotifier?: WatcherNotifier,
@@ -139,6 +144,20 @@ async function runScheduleOnce(
139
144
  const now = Date.now();
140
145
  let processed = 0;
141
146
 
147
+ const diskPressureGate = checkDiskPressureBackgroundGate("background-work");
148
+ if (diskPressureGate.action === "skip") {
149
+ if (shouldLogDiskPressureBackgroundSkip("scheduler")) {
150
+ log.warn(
151
+ {
152
+ source: "schedule",
153
+ ...diskPressureBackgroundSkipLogFields(diskPressureGate),
154
+ },
155
+ "Schedule tick skipped during disk pressure cleanup mode",
156
+ );
157
+ }
158
+ return 0;
159
+ }
160
+
142
161
  // ── Schedules (recurring cron/RRULE + one-shot) ─────────────────────
143
162
  const jobs = claimDueSchedules(now);
144
163
  for (const job of jobs) {
@@ -108,6 +108,8 @@ function getStoreKeyPath(): string {
108
108
  );
109
109
  }
110
110
 
111
+
112
+
111
113
  /**
112
114
  * Read the store.key file. Returns the raw 32-byte key buffer, or null
113
115
  * if the file is missing, wrong size, or unreadable.
@@ -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;
@@ -61,14 +61,10 @@ type IncludeValidationResult =
61
61
  | IncludeValidationError
62
62
  | IncludeValidationCycleError;
63
63
 
64
- /**
65
- * Validate the include graph starting from the given root skill ID.
66
- * Uses three-state DFS (unseen/visiting/done) to detect both missing children
67
- * and cycles. Returns the first error encountered in DFS order.
68
- */
69
- export function validateIncludes(
64
+ function validateIncludeGraph(
70
65
  rootId: string,
71
66
  catalogIndex: Map<string, SkillSummary>,
67
+ options: { failOnMissing: boolean },
72
68
  ): IncludeValidationResult {
73
69
  const visited: string[] = [];
74
70
  type State = "unseen" | "visiting" | "done";
@@ -97,13 +93,16 @@ export function validateIncludes(
97
93
  if (skill?.includes) {
98
94
  for (const childId of skill.includes) {
99
95
  if (!catalogIndex.has(childId)) {
100
- return {
101
- ok: false,
102
- error: "missing",
103
- missingChildId: childId,
104
- parentId: id,
105
- path: [...ancestry],
106
- };
96
+ if (options.failOnMissing) {
97
+ return {
98
+ ok: false,
99
+ error: "missing",
100
+ missingChildId: childId,
101
+ parentId: id,
102
+ path: [...ancestry],
103
+ };
104
+ }
105
+ continue;
107
106
  }
108
107
  const childError = dfs(childId);
109
108
  if (childError) return childError;
@@ -120,6 +119,29 @@ export function validateIncludes(
120
119
  return { ok: true, visited };
121
120
  }
122
121
 
122
+ /**
123
+ * Validate the include graph starting from the given root skill ID.
124
+ * Uses three-state DFS (unseen/visiting/done) to detect both missing children
125
+ * and cycles. Returns the first error encountered in DFS order.
126
+ */
127
+ export function validateIncludes(
128
+ rootId: string,
129
+ catalogIndex: Map<string, SkillSummary>,
130
+ ): IncludeValidationResult {
131
+ return validateIncludeGraph(rootId, catalogIndex, { failOnMissing: true });
132
+ }
133
+
134
+ /**
135
+ * Validate only cycle safety for a graph that may intentionally have missing
136
+ * advisory includes. Missing child IDs are skipped during traversal.
137
+ */
138
+ export function validateIncludeCycles(
139
+ rootId: string,
140
+ catalogIndex: Map<string, SkillSummary>,
141
+ ): IncludeValidationResult {
142
+ return validateIncludeGraph(rootId, catalogIndex, { failOnMissing: false });
143
+ }
144
+
123
145
  /**
124
146
  * Recursively traverse the include graph starting from the given root skill ID.
125
147
  * Returns all visited skill IDs in DFS pre-order.
@@ -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`).
@@ -1,5 +1,9 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
 
3
+ import {
4
+ saveDocument,
5
+ updateDocumentContent,
6
+ } from "../../documents/document-store.js";
3
7
  import type { ToolContext, ToolExecutionResult } from "../types.js";
4
8
 
5
9
  // ── Exported execute functions ──────────────────────────────────────
@@ -12,6 +16,20 @@ export function executeDocumentCreate(
12
16
  const initialContent = (input.initial_content as string | undefined) || "";
13
17
  const surfaceId = `doc-${randomUUID()}`;
14
18
 
19
+ // Persist the document so any client (web or macOS) can fetch it via
20
+ // GET /v1/documents/:id. The macOS client may later update the row
21
+ // via document_save; ON CONFLICT DO UPDATE handles that.
22
+ const wordCount = initialContent
23
+ .split(/\s+/)
24
+ .filter((w) => w.length > 0).length;
25
+ saveDocument({
26
+ surfaceId,
27
+ conversationId: context.conversationId,
28
+ title,
29
+ content: initialContent,
30
+ wordCount,
31
+ });
32
+
15
33
  // Send document_editor_show message to open the built-in RTE
16
34
  if (context.sendToClient) {
17
35
  context.sendToClient({
@@ -67,6 +85,8 @@ export function executeDocumentUpdate(
67
85
  const content = input.content as string;
68
86
  const mode = (input.mode as string | undefined) || "append";
69
87
 
88
+ updateDocumentContent(surfaceId, content, mode);
89
+
70
90
  // Send document_editor_update message to update the built-in RTE
71
91
  if (context.sendToClient) {
72
92
  context.sendToClient({
@@ -27,6 +27,7 @@ import { applyEdit } from "./shared/filesystem/edit-engine.js";
27
27
  import { sandboxPolicy } from "./shared/filesystem/path-policy.js";
28
28
  import { MAX_FILE_SIZE_BYTES } from "./shared/filesystem/size-guard.js";
29
29
  import { ToolApprovalHandler } from "./tool-approval-handler.js";
30
+ import { resolveToolNameAlias } from "./tool-name-aliases.js";
30
31
  import type {
31
32
  ToolContext,
32
33
  ToolExecutionResult,
@@ -76,7 +77,12 @@ export class ToolExecutor {
76
77
  };
77
78
 
78
79
  const middlewares = getMiddlewaresFor("toolExecute");
79
- const pipelineArgs: ToolExecuteArgs = { name, input, context };
80
+ const executionName = resolveToolNameAlias(name, context.allowedToolNames);
81
+ const pipelineArgs: ToolExecuteArgs = {
82
+ name: executionName,
83
+ input,
84
+ context,
85
+ };
80
86
 
81
87
  // No pipeline-level timeout: `executeInternal` already wraps the real
82
88
  // tool invocation in `executeWithTimeout`, which is the sole enforcer
@@ -107,6 +113,11 @@ export class ToolExecutor {
107
113
  riskLevel: string;
108
114
  riskReason: string;
109
115
  riskScopeOptions: Array<{ pattern: string; label: string }>;
116
+ riskAllowlistOptions?: Array<{
117
+ label: string;
118
+ description: string;
119
+ pattern: string;
120
+ }>;
110
121
  riskDirectoryScopeOptions?: Array<{ scope: string; label: string }>;
111
122
  isContainerized?: boolean;
112
123
  }
@@ -205,6 +216,7 @@ export class ToolExecutor {
205
216
  riskLevel: permRiskMeta?.riskLevel,
206
217
  riskReason: permRiskMeta?.riskReason,
207
218
  riskScopeOptions: permRiskMeta?.riskScopeOptions,
219
+ riskAllowlistOptions: permRiskMeta?.riskAllowlistOptions,
208
220
  riskDirectoryScopeOptions: permRiskMeta?.riskDirectoryScopeOptions,
209
221
  isContainerized: permRiskMeta?.isContainerized,
210
222
  matchedTrustRuleId: permMatchedTrustRuleId,
@@ -422,12 +434,16 @@ export class ToolExecutor {
422
434
  riskLevel: permRiskMeta.riskLevel,
423
435
  riskReason: permRiskMeta.riskReason,
424
436
  riskScopeOptions: permRiskMeta.riskScopeOptions,
437
+ riskAllowlistOptions: permRiskMeta.riskAllowlistOptions,
425
438
  riskDirectoryScopeOptions: permRiskMeta.riskDirectoryScopeOptions,
426
439
  isContainerized: permRiskMeta.isContainerized,
427
440
  };
428
441
  }
429
442
  if (permMatchedTrustRuleId) {
430
- execResult = { ...execResult, matchedTrustRuleId: permMatchedTrustRuleId };
443
+ execResult = {
444
+ ...execResult,
445
+ matchedTrustRuleId: permMatchedTrustRuleId,
446
+ };
431
447
  }
432
448
  if (permApprovalMode) {
433
449
  execResult = { ...execResult, approvalMode: permApprovalMode };
@@ -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