@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
@@ -11,13 +11,22 @@
11
11
  * (PNG-hash loop guard) and the result-payload → ToolExecutionResult
12
12
  * translation on top.
13
13
  *
14
- * **Singleton lock.** Only one conversation may hold an active app-control
15
- * session at a time. The lock is module-level (`activeAppControlConversationId`)
16
- * because a session targets the user's actual desktop application, which
17
- * is a host-wide resource. The lock is acquired on a successful
18
- * `app_control_start` and released when the owning proxy's `dispose()`
19
- * fires. A second conversation that calls `start` while the lock is held
20
- * receives an `isError: true` tool result naming the holding conversation.
14
+ * **Session lock.** Only one conversation may hold an active app-control
15
+ * session at a time, and that session is bound to a specific target app.
16
+ * The lock is module-level (`activeAppControlSession`) because the session
17
+ * targets the user's actual desktop application, which is a host-wide
18
+ * resource. It is acquired on a successful `app_control_start` (storing
19
+ * `(conversationId, app)`) and released when the owning proxy's
20
+ * `dispose()` fires.
21
+ *
22
+ * `app_control_start` is the only tool that can acquire the lock — the
23
+ * user's medium-risk approval at start time is the consent boundary. All
24
+ * other tools (observe / press / combo / sequence / type / click / drag)
25
+ * require the calling conversation to own an active session targeting the
26
+ * same `app`; otherwise the call is rejected before any host dispatch.
27
+ * This prevents prompt-injected tool calls from sending raw input to
28
+ * arbitrary apps without the user having approved control of that
29
+ * specific app.
21
30
  *
22
31
  * **No step cap.** Unlike {@link HostCuProxy} which enforces a per-session
23
32
  * step ceiling via `loadConfig().maxStepsPerSession`, app-control sessions
@@ -51,36 +60,111 @@ const REQUEST_TIMEOUT_MS = 60 * 1000;
51
60
  const STUCK_REPEAT_THRESHOLD = 4;
52
61
 
53
62
  // ---------------------------------------------------------------------------
54
- // Tool name constants
63
+ // Module-level session lock
55
64
  // ---------------------------------------------------------------------------
56
- //
57
- // Kept here (rather than imported from PR 5's tool registrations) so the
58
- // proxy is independently testable. PR 5 must use these same string values.
59
-
60
- const TOOL_START = "app_control_start";
61
65
 
62
- // ---------------------------------------------------------------------------
63
- // Module-level singleton lock
64
- // ---------------------------------------------------------------------------
66
+ /**
67
+ * Active app-control session: the conversation that owns the lock and the
68
+ * `app` it was approved against. Set on a successful `app_control_start`;
69
+ * cleared by the owning proxy's `dispose()`.
70
+ */
71
+ export interface ActiveAppControlSession {
72
+ conversationId: string;
73
+ /**
74
+ * The exact `app` string the user approved at start time (bundle ID or
75
+ * process name — preserved as-is). Compared case-insensitively against
76
+ * the `app` of subsequent non-start tool calls.
77
+ */
78
+ app: string;
79
+ }
65
80
 
66
81
  /**
67
- * Conversation id that currently owns the active app-control session, or
68
- * `undefined` if no session is active. Set on a successful
69
- * `app_control_start`; cleared by the owning proxy's `dispose()`.
82
+ * Currently active session, or `undefined` when no session is held.
70
83
  *
71
84
  * Exported for test inspection only. Production code paths must not read
72
85
  * or mutate this directly — use the proxy methods.
73
86
  */
74
- let activeAppControlConversationId: string | undefined;
87
+ let activeAppControlSession: ActiveAppControlSession | undefined;
75
88
 
76
- /** Test-only helper: read current lock owner. */
77
- export function _getActiveAppControlConversationId(): string | undefined {
78
- return activeAppControlConversationId;
89
+ /** Test-only helper: read current session. */
90
+ export function _getActiveAppControlSession():
91
+ | ActiveAppControlSession
92
+ | undefined {
93
+ return activeAppControlSession;
79
94
  }
80
95
 
81
- /** Test-only helper: clear lock between test cases. */
82
- export function _resetActiveAppControlConversationId(): void {
83
- activeAppControlConversationId = undefined;
96
+ /** Test-only helper: clear session between test cases. */
97
+ export function _resetActiveAppControlSession(): void {
98
+ activeAppControlSession = undefined;
99
+ }
100
+
101
+ /**
102
+ * Test-only helper: prime the active session without a full `start` round-trip.
103
+ * Useful for tests that exercise non-start tool paths and don't need to
104
+ * verify the start flow itself.
105
+ */
106
+ export function _setActiveAppControlSession(
107
+ session: ActiveAppControlSession,
108
+ ): void {
109
+ activeAppControlSession = session;
110
+ }
111
+
112
+ /**
113
+ * Validate a non-start tool call against the active session. Returns a
114
+ * `ToolExecutionResult` (with `isError: true`) when the call should be
115
+ * rejected; returns `null` when the call is authorized to dispatch.
116
+ *
117
+ * `app` matching is case-insensitive (macOS bundle IDs are
118
+ * case-insensitive in practice) but strict on form: `"Safari"` and
119
+ * `"com.apple.Safari"` do not match — the user approved a specific string
120
+ * and substituting a different form requires a new approval.
121
+ */
122
+ function checkNonStartAuthorization(
123
+ input: HostAppControlInput,
124
+ conversationId: string,
125
+ ): ToolExecutionResult | null {
126
+ if (activeAppControlSession == null) {
127
+ return {
128
+ content:
129
+ "No app-control session is active. Call app_control_start to request " +
130
+ "user approval to control the target app, then retry.",
131
+ isError: true,
132
+ };
133
+ }
134
+ if (activeAppControlSession.conversationId !== conversationId) {
135
+ return {
136
+ content:
137
+ `Another conversation (${activeAppControlSession.conversationId}) currently ` +
138
+ `holds the app-control session. Wait for it to finish, or call ` +
139
+ `app_control_stop from that conversation first.`,
140
+ isError: true,
141
+ };
142
+ }
143
+ // `app` is required on every non-start variant of HostAppControlInput
144
+ // except `stop`, and `stop` short-circuits in conversation-surfaces and
145
+ // does not reach this method in production. A stop reaching here would
146
+ // be a defensive bug — surface it explicitly rather than dispatch.
147
+ const requestedApp = (input as { app?: string }).app;
148
+ if (requestedApp == null) {
149
+ return {
150
+ content:
151
+ "Tool input missing required 'app' field; cannot validate against " +
152
+ "the active app-control session.",
153
+ isError: true,
154
+ };
155
+ }
156
+ if (
157
+ requestedApp.toLowerCase() !== activeAppControlSession.app.toLowerCase()
158
+ ) {
159
+ return {
160
+ content:
161
+ `Active app-control session targets ${activeAppControlSession.app}; ` +
162
+ `cannot send actions to ${requestedApp}. Call app_control_stop and ` +
163
+ `app_control_start to switch apps.`,
164
+ isError: true,
165
+ };
166
+ }
167
+ return null;
84
168
  }
85
169
 
86
170
  // ---------------------------------------------------------------------------
@@ -91,7 +175,7 @@ export class HostAppControlProxy extends HostProxyBase<
91
175
  HostAppControlInput,
92
176
  HostAppControlResultPayload
93
177
  > {
94
- /** Conversation that owns this proxy instance. Used by `dispose()` to release the singleton lock only when this proxy is the holder. */
178
+ /** Conversation that owns this proxy instance. Used by `dispose()` to release the session lock only when this proxy is the holder. */
95
179
  private readonly conversationId: string;
96
180
 
97
181
  /** sha256 hex of the most recent observation's `pngBase64`, or undefined. */
@@ -143,21 +227,29 @@ export class HostAppControlProxy extends HostProxyBase<
143
227
  return { content: "Aborted", isError: true };
144
228
  }
145
229
 
146
- // Singleton-lock guard for `start`. Other tools assume a session
147
- // already exists and are not gated here.
148
- if (toolName === TOOL_START) {
230
+ // Authorization gate. `start` acquires the session lock (the user's
231
+ // medium-risk approval is the consent boundary); all other tools must
232
+ // belong to the active session and target the same `app`. Without this
233
+ // gate, prompt-injected calls would bypass the start-time approval and
234
+ // send raw input to arbitrary apps.
235
+ if (input.tool === "start") {
149
236
  if (
150
- activeAppControlConversationId != null &&
151
- activeAppControlConversationId !== conversationId
237
+ activeAppControlSession != null &&
238
+ activeAppControlSession.conversationId !== conversationId
152
239
  ) {
153
240
  return {
154
241
  content:
155
- `Another conversation (${activeAppControlConversationId}) currently holds the ` +
242
+ `Another conversation (${activeAppControlSession.conversationId}) currently holds the ` +
156
243
  `app-control session. Wait for it to finish, or call app_control_stop ` +
157
244
  `from that conversation first.`,
158
245
  isError: true,
159
246
  };
160
247
  }
248
+ } else {
249
+ const sessionError = checkNonStartAuthorization(input, conversationId);
250
+ if (sessionError != null) {
251
+ return sessionError;
252
+ }
161
253
  }
162
254
 
163
255
  try {
@@ -167,7 +259,7 @@ export class HostAppControlProxy extends HostProxyBase<
167
259
  conversationId,
168
260
  signal,
169
261
  );
170
- return this.handleSuccess(toolName, payload);
262
+ return this.handleSuccess(input, payload);
171
263
  } catch (err) {
172
264
  if (err instanceof HostProxyRequestError) {
173
265
  if (err.reason === "timeout") {
@@ -192,7 +284,7 @@ export class HostAppControlProxy extends HostProxyBase<
192
284
  // ---------------------------------------------------------------------------
193
285
 
194
286
  private handleSuccess(
195
- toolName: string,
287
+ input: HostAppControlInput,
196
288
  payload: HostAppControlResultPayload,
197
289
  ): ToolExecutionResult {
198
290
  // Update PNG-hash loop tracking only for the "running" state — other
@@ -212,9 +304,13 @@ export class HostAppControlProxy extends HostProxyBase<
212
304
  }
213
305
  }
214
306
 
215
- // Acquire the singleton lock on a successful `start`.
216
- if (toolName === TOOL_START && payload.state === "running") {
217
- activeAppControlConversationId = this.conversationId;
307
+ // Store the exact `app` form for validation against subsequent
308
+ // non-start tool calls.
309
+ if (input.tool === "start" && payload.state === "running") {
310
+ activeAppControlSession = {
311
+ conversationId: this.conversationId,
312
+ app: input.app,
313
+ };
218
314
  }
219
315
 
220
316
  return this.formatResult(payload, stuck);
@@ -281,13 +377,13 @@ export class HostAppControlProxy extends HostProxyBase<
281
377
  // ---------------------------------------------------------------------------
282
378
 
283
379
  /**
284
- * Reject pending requests via the base, then release the singleton lock
380
+ * Reject pending requests via the base, then release the session lock
285
381
  * if this proxy is the holder. Idempotent: safe to call multiple times.
286
382
  */
287
383
  override dispose(): void {
288
384
  super.dispose();
289
- if (activeAppControlConversationId === this.conversationId) {
290
- activeAppControlConversationId = undefined;
385
+ if (activeAppControlSession?.conversationId === this.conversationId) {
386
+ activeAppControlSession = undefined;
291
387
  }
292
388
  }
293
389
  }
@@ -5,6 +5,11 @@ import {
5
5
  assistantEventHub,
6
6
  broadcastMessage,
7
7
  } from "../runtime/assistant-event-hub.js";
8
+ import {
9
+ ambiguousSameUserError,
10
+ enforceSameActorOrErrorResult,
11
+ pickSameUserAutoResolve,
12
+ } from "../runtime/auth/same-actor.js";
8
13
  import * as pendingInteractions from "../runtime/pending-interactions.js";
9
14
  import { formatShellOutput } from "../tools/shared/shell-output.js";
10
15
  import type { ToolExecutionResult } from "../tools/types.js";
@@ -13,7 +18,6 @@ import { getLogger } from "../util/logger.js";
13
18
 
14
19
  const log = getLogger("host-bash-proxy");
15
20
 
16
-
17
21
  export class HostBashProxy {
18
22
  private static _instance: HostBashProxy | null = null;
19
23
 
@@ -62,14 +66,14 @@ export class HostBashProxy {
62
66
  },
63
67
  conversationId: string,
64
68
  signal?: AbortSignal,
69
+ // Principal ID of the actor on whose behalf this request is initiated.
70
+ sourceActorPrincipalId?: string,
65
71
  ): Promise<ToolExecutionResult> {
66
72
  if (signal?.aborted) {
67
73
  const result = formatShellOutput("", "Aborted", null, false, 0);
68
74
  return Promise.resolve(result);
69
75
  }
70
76
 
71
- const capableClients = assistantEventHub.listClientsByCapability("host_bash");
72
-
73
77
  let resolvedTargetClientId: string | undefined;
74
78
 
75
79
  if (input.targetClientId) {
@@ -81,14 +85,37 @@ export class HostBashProxy {
81
85
  });
82
86
  }
83
87
  resolvedTargetClientId = input.targetClientId;
84
- } else if (capableClients.length === 1) {
85
- // Auto-resolve when exactly one capable client is connected.
86
- resolvedTargetClientId = capableClients[0].clientId;
88
+ } else {
89
+ // Auto-resolve to the unique same-user client. Reject (rather than
90
+ // broadcast) when multiple same-user clients are connected so that
91
+ // a single targeted-style request cannot fan out across every one
92
+ // of the user's machines. Zero same-user matches falls through to
93
+ // the existing untargeted code path.
94
+ const resolved = pickSameUserAutoResolve({
95
+ hub: assistantEventHub,
96
+ capability: "host_bash",
97
+ sourceActorPrincipalId,
98
+ });
99
+ if (resolved.kind === "ambiguous") {
100
+ return Promise.resolve(ambiguousSameUserError("host_bash"));
101
+ }
102
+ resolvedTargetClientId =
103
+ resolved.kind === "match" ? resolved.clientId : undefined;
104
+ }
105
+
106
+ // Targeted requests must be bound to the same authenticated user as the
107
+ // target client. Fail closed at request time — before pendingInteractions
108
+ // registration and before broadcast — so a same-daemon caller cannot
109
+ // execute on another user's connected client.
110
+ if (resolvedTargetClientId != null) {
111
+ const rejection = enforceSameActorOrErrorResult({
112
+ hub: assistantEventHub,
113
+ sourceActorPrincipalId,
114
+ targetClientId: resolvedTargetClientId,
115
+ op: "host_bash",
116
+ });
117
+ if (rejection) return Promise.resolve(rejection);
87
118
  }
88
- // capableClients.length === 0 or > 1 without explicit target: resolvedTargetClientId
89
- // stays undefined and falls through to untargeted broadcast — the existing timeout/error
90
- // path handles the zero-client case, and multi-client ambiguity is enforced at the tool
91
- // executor layer (not here) once target_client_id is exposed in the tool schema.
92
119
 
93
120
  const requestId = uuid();
94
121
 
@@ -108,15 +135,7 @@ export class HostBashProxy {
108
135
  const timeoutMessage = resolvedTargetClientId
109
136
  ? `Host bash proxy timed out waiting for response from client ${resolvedTargetClientId}`
110
137
  : "Host bash proxy timed out waiting for client response";
111
- resolve(
112
- formatShellOutput(
113
- "",
114
- timeoutMessage,
115
- null,
116
- true,
117
- timeoutSec,
118
- ),
119
- );
138
+ resolve(formatShellOutput("", timeoutMessage, null, true, timeoutSec));
120
139
  }, proxyTimeoutSec * 1000);
121
140
 
122
141
  if (signal) {
@@ -152,6 +171,12 @@ export class HostBashProxy {
152
171
  timer,
153
172
  detachAbort,
154
173
  targetClientId: resolvedTargetClientId,
174
+ targetActorPrincipalId:
175
+ resolvedTargetClientId != null
176
+ ? assistantEventHub.getActorPrincipalIdForClient(
177
+ resolvedTargetClientId,
178
+ )
179
+ : undefined,
155
180
  metadata: { timeoutSec },
156
181
  });
157
182
 
@@ -166,8 +191,8 @@ export class HostBashProxy {
166
191
  timeout_seconds: input.timeout_seconds,
167
192
  targetClientId: resolvedTargetClientId,
168
193
  ...(input.env && Object.keys(input.env).length > 0
169
- ? { env: input.env }
170
- : {}),
194
+ ? { env: input.env }
195
+ : {}),
171
196
  },
172
197
  conversationId,
173
198
  { targetClientId: resolvedTargetClientId },
@@ -23,6 +23,7 @@ import {
23
23
  assistantEventHub,
24
24
  broadcastMessage,
25
25
  } from "../runtime/assistant-event-hub.js";
26
+ import { enforceSameActorOrErrorResult } from "../runtime/auth/same-actor.js";
26
27
  import * as pendingInteractions from "../runtime/pending-interactions.js";
27
28
  import type { ToolExecutionResult } from "../tools/types.js";
28
29
  import { AssistantError, ErrorCode } from "../util/errors.js";
@@ -121,6 +122,16 @@ export class HostCuProxy {
121
122
  // Request / resolve lifecycle
122
123
  // ---------------------------------------------------------------------------
123
124
 
125
+ /**
126
+ * Send a CU request to the connected desktop client.
127
+ *
128
+ * When `targetClientId` is supplied, the proxy validates that the target
129
+ * exists and advertises the `host_cu` capability, mirroring HostFileProxy's
130
+ * resolver-side checks so that the proxy is safe to call as a standalone
131
+ * API. It additionally enforces that the caller (`sourceActorPrincipalId`)
132
+ * and the target client share the same actor principal — cross-user
133
+ * targeted dispatch is rejected.
134
+ */
124
135
  request(
125
136
  toolName: string,
126
137
  input: Record<string, unknown>,
@@ -129,6 +140,7 @@ export class HostCuProxy {
129
140
  reasoning?: string,
130
141
  signal?: AbortSignal,
131
142
  targetClientId?: string,
143
+ sourceActorPrincipalId?: string,
132
144
  ): Promise<ToolExecutionResult> {
133
145
  if (signal?.aborted) {
134
146
  return Promise.resolve({
@@ -144,6 +156,38 @@ export class HostCuProxy {
144
156
  });
145
157
  }
146
158
 
159
+ // Existence + capability validation for explicit targets. Mirrors
160
+ // HostFileProxy's resolver-side guard so that the proxy is safe even
161
+ // when called outside the conversation-surfaces dispatch (which has
162
+ // its own validation layer).
163
+ if (targetClientId != null) {
164
+ const client = assistantEventHub.getClientById(targetClientId);
165
+ if (!client) {
166
+ return Promise.resolve({
167
+ content: `No connected client with id '${targetClientId}' supports host_cu. Run \`assistant clients list --capability host_cu\` to see available clients.`,
168
+ isError: true,
169
+ });
170
+ }
171
+ if (!client.capabilities.includes("host_cu")) {
172
+ return Promise.resolve({
173
+ content: `Client '${targetClientId}' does not support host_cu. Run \`assistant clients list --capability host_cu\` to see available clients.`,
174
+ isError: true,
175
+ });
176
+ }
177
+
178
+ // Same-user enforcement: targeted CU dispatch must be owned by the
179
+ // same actor on both sides. This is the authoritative gate — the
180
+ // dispatch layer (conversation-surfaces.ts) skips its own check
181
+ // and relies on the proxy.
182
+ const rejection = enforceSameActorOrErrorResult({
183
+ hub: assistantEventHub,
184
+ sourceActorPrincipalId,
185
+ targetClientId,
186
+ op: "host_cu",
187
+ });
188
+ if (rejection) return Promise.resolve(rejection);
189
+ }
190
+
147
191
  const requestId = uuid();
148
192
 
149
193
  return new Promise<ToolExecutionResult>((resolve, reject) => {
@@ -170,9 +214,7 @@ export class HostCuProxy {
170
214
  type: "host_cu_cancel",
171
215
  requestId,
172
216
  conversationId,
173
- ...(targetClientId != null
174
- ? { targetClientId }
175
- : {}),
217
+ ...(targetClientId != null ? { targetClientId } : {}),
176
218
  },
177
219
  conversationId,
178
220
  { targetClientId },
@@ -193,6 +235,10 @@ export class HostCuProxy {
193
235
  conversationId,
194
236
  kind: "host_cu",
195
237
  targetClientId,
238
+ targetActorPrincipalId:
239
+ targetClientId != null
240
+ ? assistantEventHub.getActorPrincipalIdForClient(targetClientId)
241
+ : undefined,
196
242
  rpcResolve: resolve,
197
243
  rpcReject: reject,
198
244
  timer,
@@ -4,6 +4,11 @@ import {
4
4
  assistantEventHub,
5
5
  broadcastMessage,
6
6
  } from "../runtime/assistant-event-hub.js";
7
+ import {
8
+ ambiguousSameUserError,
9
+ enforceSameActorOrErrorResult,
10
+ pickSameUserAutoResolve,
11
+ } from "../runtime/auth/same-actor.js";
7
12
  import * as pendingInteractions from "../runtime/pending-interactions.js";
8
13
  import { readImageBase64 } from "../tools/shared/filesystem/image-read.js";
9
14
  import type { ToolExecutionResult } from "../tools/types.js";
@@ -68,15 +73,18 @@ export class HostFileProxy {
68
73
  conversationId: string,
69
74
  signal?: AbortSignal,
70
75
  targetClientId?: string,
76
+ sourceActorPrincipalId?: string,
71
77
  ): Promise<ToolExecutionResult> {
72
78
  if (signal?.aborted) {
73
79
  return Promise.resolve({ content: "Aborted", isError: true });
74
80
  }
75
81
 
76
- // Resolve targetClientId: explicit → validate; single capable client → auto-resolve.
77
- // Callers may embed targetClientId in the input object (tool handlers) or pass it as
78
- // the 4th parameter (legacy). Prefer the explicit param; fall back to input field.
79
- let resolvedTargetClientId: string | undefined = targetClientId ?? input.targetClientId;
82
+ // Resolve targetClientId: explicit → validate; single same-user
83
+ // capable client → auto-resolve. Callers may embed targetClientId in
84
+ // the input object (tool handlers) or pass it as the 4th parameter
85
+ // (legacy). Prefer the explicit param; fall back to input field.
86
+ let resolvedTargetClientId: string | undefined =
87
+ targetClientId ?? input.targetClientId;
80
88
  if (resolvedTargetClientId != null) {
81
89
  const client = assistantEventHub.getClientById(resolvedTargetClientId);
82
90
  if (!client) {
@@ -92,10 +100,32 @@ export class HostFileProxy {
92
100
  });
93
101
  }
94
102
  } else {
95
- const capable = assistantEventHub.listClientsByCapability("host_file");
96
- if (capable.length === 1) {
97
- resolvedTargetClientId = capable[0].clientId;
103
+ // Auto-resolve to the unique same-user client. Reject ambiguous
104
+ // (multi-machine) cases so a single targeted-style request cannot
105
+ // fan out across the user's machines.
106
+ const resolved = pickSameUserAutoResolve({
107
+ hub: assistantEventHub,
108
+ capability: "host_file",
109
+ sourceActorPrincipalId,
110
+ });
111
+ if (resolved.kind === "ambiguous") {
112
+ return Promise.resolve(ambiguousSameUserError("host_file"));
98
113
  }
114
+ resolvedTargetClientId =
115
+ resolved.kind === "match" ? resolved.clientId : undefined;
116
+ }
117
+
118
+ // Same-user check: targeted host_file requests must be bound to the same
119
+ // authenticated user identity that opened the target client's SSE stream.
120
+ // Prevents cross-user routing through actor token mis-targeting.
121
+ if (resolvedTargetClientId != null) {
122
+ const rejection = enforceSameActorOrErrorResult({
123
+ hub: assistantEventHub,
124
+ sourceActorPrincipalId,
125
+ targetClientId: resolvedTargetClientId,
126
+ op: "host_file",
127
+ });
128
+ if (rejection) return Promise.resolve(rejection);
99
129
  }
100
130
 
101
131
  const requestId = uuid();
@@ -150,6 +180,12 @@ export class HostFileProxy {
150
180
  conversationId,
151
181
  kind: "host_file",
152
182
  targetClientId: resolvedTargetClientId,
183
+ targetActorPrincipalId:
184
+ resolvedTargetClientId != null
185
+ ? assistantEventHub.getActorPrincipalIdForClient(
186
+ resolvedTargetClientId,
187
+ )
188
+ : undefined,
153
189
  rpcResolve: resolve,
154
190
  rpcReject: reject,
155
191
  timer,