@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
@@ -26,6 +26,7 @@ import type { TtsProvider, TtsProviderId } from "../tts/types.js";
26
26
  import { getLogger } from "../util/logger.js";
27
27
  import { createStreamingEntry } from "./audio-store.js";
28
28
  import {
29
+ getEndCallListenWindowMs,
29
30
  getMaxCallDurationMs,
30
31
  getSilenceTimeoutMs,
31
32
  getUserConsultationTimeoutMs,
@@ -37,6 +38,7 @@ import {
37
38
  registerCallController,
38
39
  unregisterCallController,
39
40
  } from "./call-state.js";
41
+ import { isTerminalState } from "./call-state-machine.js";
40
42
  import {
41
43
  createPendingQuestion,
42
44
  expirePendingQuestions,
@@ -93,6 +95,7 @@ export class CallController {
93
95
  private currentTurnPromise: Promise<void> | null = null;
94
96
  private destroyed = false;
95
97
  private silenceTimer: ReturnType<typeof setTimeout> | null = null;
98
+ private endCallListenTimer: ReturnType<typeof setTimeout> | null = null;
96
99
  private durationTimer: ReturnType<typeof setTimeout> | null = null;
97
100
  private durationWarningTimer: ReturnType<typeof setTimeout> | null = null;
98
101
  /**
@@ -244,6 +247,8 @@ export class CallController {
244
247
  transcript: string,
245
248
  speaker?: PromptSpeakerContext,
246
249
  ): Promise<void> {
250
+ this.cancelPendingEndCall();
251
+
247
252
  const interruptedInFlight =
248
253
  this.state === "processing" || this.state === "speaking";
249
254
  // If we're already processing or speaking, abort the in-flight generation
@@ -295,6 +300,8 @@ export class CallController {
295
300
  return false;
296
301
  }
297
302
 
303
+ this.cancelPendingEndCall();
304
+
298
305
  // Clear the consultation timeout and record
299
306
  clearTimeout(this.pendingGuardianInput.timer);
300
307
  this.pendingGuardianInput = null;
@@ -326,6 +333,8 @@ export class CallController {
326
333
  * position once the current turn completes.
327
334
  */
328
335
  async handleUserInstruction(instructionText: string): Promise<void> {
336
+ this.cancelPendingEndCall();
337
+
329
338
  recordCallEvent(this.callSessionId, "user_instruction_relayed", {
330
339
  instruction: instructionText,
331
340
  });
@@ -408,6 +417,7 @@ export class CallController {
408
417
  destroy(): void {
409
418
  this.destroyed = true;
410
419
  if (this.silenceTimer) clearTimeout(this.silenceTimer);
420
+ if (this.endCallListenTimer) clearTimeout(this.endCallListenTimer);
411
421
  if (this.durationTimer) clearTimeout(this.durationTimer);
412
422
  if (this.durationWarningTimer) clearTimeout(this.durationWarningTimer);
413
423
  if (this.pendingGuardianInput) {
@@ -419,6 +429,7 @@ export class CallController {
419
429
  this.durationEndTimer = null;
420
430
  }
421
431
  this.pendingInstructions = [];
432
+ this.endCallListenTimer = null;
422
433
  this.llmRunVersion++;
423
434
  this.abortCurrentTurn();
424
435
  if (this.activeSynthesisAbort) {
@@ -1075,73 +1086,7 @@ export class CallController {
1075
1086
 
1076
1087
  // Check for END_CALL marker
1077
1088
  if (responseText.includes(END_CALL_MARKER)) {
1078
- // Clear any pending consultation before completing the call.
1079
- // Without this, the consultation timeout can fire on an already-ended
1080
- // call, overwriting 'completed' status back to 'in_progress' and
1081
- // starting a new LLM turn on a dead conversation. Similarly, a late
1082
- // handleUserAnswer could be accepted since pendingGuardianInput is
1083
- // still non-null.
1084
- if (this.pendingGuardianInput) {
1085
- clearTimeout(this.pendingGuardianInput.timer);
1086
-
1087
- // Expire store-side consultation records so clients don't observe
1088
- // a completed call with a dangling pendingQuestion, and guardian
1089
- // replies are cleanly rejected instead of hitting answerCall failures.
1090
- expirePendingQuestions(this.callSessionId);
1091
- const previousRequest = getPendingCanonicalRequestByCallSessionId(
1092
- this.callSessionId,
1093
- );
1094
- if (previousRequest) {
1095
- expireCanonicalGuardianRequest(previousRequest.id);
1096
- }
1097
-
1098
- this.pendingGuardianInput = null;
1099
- }
1100
-
1101
- const currentSession = getCallSession(this.callSessionId);
1102
- const shouldNotifyCompletion = currentSession
1103
- ? currentSession.status !== "completed" &&
1104
- currentSession.status !== "failed" &&
1105
- currentSession.status !== "cancelled"
1106
- : false;
1107
-
1108
- this.transport.endSession("Call completed");
1109
- updateCallSession(this.callSessionId, {
1110
- status: "completed",
1111
- endedAt: Date.now(),
1112
- });
1113
- recordCallEvent(this.callSessionId, "call_ended", {
1114
- reason: "completed",
1115
- });
1116
-
1117
- // Notify the voice conversation
1118
- if (shouldNotifyCompletion && currentSession) {
1119
- finalizeCall(this.callSessionId, currentSession.conversationId);
1120
- }
1121
-
1122
- // Post a pointer message in the initiating conversation
1123
- if (currentSession?.initiatedFromConversationId) {
1124
- const durationMs = currentSession.startedAt
1125
- ? Date.now() - currentSession.startedAt
1126
- : 0;
1127
- addPointerMessage(
1128
- currentSession.initiatedFromConversationId,
1129
- "completed",
1130
- currentSession.toNumber,
1131
- {
1132
- duration: durationMs > 0 ? formatDuration(durationMs) : undefined,
1133
- },
1134
- ).catch((err) => {
1135
- log.warn(
1136
- {
1137
- conversationId: currentSession.initiatedFromConversationId,
1138
- err,
1139
- },
1140
- "Skipping pointer write — origin conversation may no longer exist",
1141
- );
1142
- });
1143
- }
1144
- this.state = "idle";
1089
+ this.scheduleEndCallAfterListenWindow();
1145
1090
  return;
1146
1091
  }
1147
1092
 
@@ -1153,6 +1098,124 @@ export class CallController {
1153
1098
  this.flushPendingInstructions();
1154
1099
  }
1155
1100
 
1101
+ private scheduleEndCallAfterListenWindow(): void {
1102
+ const currentSession = getCallSession(this.callSessionId);
1103
+ if (currentSession && isTerminalState(currentSession.status)) {
1104
+ this.state = "idle";
1105
+ this.currentTurnHandle = null;
1106
+ return;
1107
+ }
1108
+
1109
+ const clearedPendingGuardianInput =
1110
+ this.clearPendingGuardianInputForCallEnd();
1111
+ this.state = "idle";
1112
+ this.currentTurnHandle = null;
1113
+
1114
+ if (this.endCallListenTimer) {
1115
+ clearTimeout(this.endCallListenTimer);
1116
+ this.endCallListenTimer = null;
1117
+ }
1118
+
1119
+ const listenWindowMs = getEndCallListenWindowMs();
1120
+ const callContinues =
1121
+ this.pendingInstructions.length > 0 || listenWindowMs > 0;
1122
+ if (clearedPendingGuardianInput && callContinues) {
1123
+ updateCallSession(this.callSessionId, { status: "in_progress" });
1124
+ }
1125
+
1126
+ if (this.pendingInstructions.length > 0) {
1127
+ this.flushPendingInstructions();
1128
+ return;
1129
+ }
1130
+
1131
+ if (listenWindowMs <= 0) {
1132
+ this.completeCallFromEndMarker();
1133
+ return;
1134
+ }
1135
+
1136
+ this.resetSilenceTimer();
1137
+ this.endCallListenTimer = setTimeout(() => {
1138
+ this.endCallListenTimer = null;
1139
+ this.completeCallFromEndMarker();
1140
+ }, listenWindowMs);
1141
+ }
1142
+
1143
+ private cancelPendingEndCall(): void {
1144
+ if (!this.endCallListenTimer) return;
1145
+ clearTimeout(this.endCallListenTimer);
1146
+ this.endCallListenTimer = null;
1147
+ }
1148
+
1149
+ private clearPendingGuardianInputForCallEnd(): boolean {
1150
+ if (!this.pendingGuardianInput) return false;
1151
+
1152
+ clearTimeout(this.pendingGuardianInput.timer);
1153
+
1154
+ // Expire store-side consultation records so clients don't observe
1155
+ // a completed call with a dangling pendingQuestion, and guardian
1156
+ // replies are cleanly rejected instead of hitting answerCall failures.
1157
+ expirePendingQuestions(this.callSessionId);
1158
+ const previousRequest = getPendingCanonicalRequestByCallSessionId(
1159
+ this.callSessionId,
1160
+ );
1161
+ if (previousRequest) {
1162
+ expireCanonicalGuardianRequest(previousRequest.id);
1163
+ }
1164
+
1165
+ this.pendingGuardianInput = null;
1166
+ return true;
1167
+ }
1168
+
1169
+ private completeCallFromEndMarker(): void {
1170
+ if (this.destroyed) return;
1171
+
1172
+ const currentSession = getCallSession(this.callSessionId);
1173
+ if (currentSession && isTerminalState(currentSession.status)) {
1174
+ this.state = "idle";
1175
+ return;
1176
+ }
1177
+
1178
+ const shouldNotifyCompletion = !!currentSession;
1179
+
1180
+ this.transport.endSession("Call completed");
1181
+ updateCallSession(this.callSessionId, {
1182
+ status: "completed",
1183
+ endedAt: Date.now(),
1184
+ });
1185
+ recordCallEvent(this.callSessionId, "call_ended", {
1186
+ reason: "completed",
1187
+ });
1188
+
1189
+ // Notify the voice conversation
1190
+ if (shouldNotifyCompletion && currentSession) {
1191
+ finalizeCall(this.callSessionId, currentSession.conversationId);
1192
+ }
1193
+
1194
+ // Post a pointer message in the initiating conversation
1195
+ if (currentSession?.initiatedFromConversationId) {
1196
+ const durationMs = currentSession.startedAt
1197
+ ? Date.now() - currentSession.startedAt
1198
+ : 0;
1199
+ addPointerMessage(
1200
+ currentSession.initiatedFromConversationId,
1201
+ "completed",
1202
+ currentSession.toNumber,
1203
+ {
1204
+ duration: durationMs > 0 ? formatDuration(durationMs) : undefined,
1205
+ },
1206
+ ).catch((err) => {
1207
+ log.warn(
1208
+ {
1209
+ conversationId: currentSession.initiatedFromConversationId,
1210
+ err,
1211
+ },
1212
+ "Skipping pointer write — origin conversation may no longer exist",
1213
+ );
1214
+ });
1215
+ }
1216
+ this.state = "idle";
1217
+ }
1218
+
1156
1219
  private isExpectedAbortError(err: unknown): boolean {
1157
1220
  if (!(err instanceof Error)) return false;
1158
1221
  return err.name === "AbortError" || err.name === "APIUserAbortError";
@@ -6,6 +6,21 @@
6
6
  import { addMessage } from "../memory/conversation-crud.js";
7
7
  import { getCallEvents, getCallSession } from "./call-store.js";
8
8
 
9
+ function buildCallSummaryLabel(
10
+ status: string | undefined,
11
+ duration: number | null,
12
+ eventCount: number,
13
+ ): string {
14
+ const statusLabel =
15
+ status === "failed"
16
+ ? "Call failed"
17
+ : status === "cancelled"
18
+ ? "Call cancelled"
19
+ : "Call completed";
20
+ const durationStr = duration != null ? ` (${duration}s)` : "";
21
+ return `**${statusLabel}**${durationStr}. ${eventCount} event(s) recorded.`;
22
+ }
23
+
9
24
  export function buildCallCompletionMessage(callSessionId: string): string {
10
25
  const callSession = getCallSession(callSessionId);
11
26
  const events = getCallEvents(callSessionId);
@@ -13,25 +28,46 @@ export function buildCallCompletionMessage(callSessionId: string): string {
13
28
  callSession?.endedAt && callSession?.startedAt
14
29
  ? Math.round((callSession.endedAt - callSession.startedAt) / 1000)
15
30
  : null;
16
- const durationStr = duration != null ? ` (${duration}s)` : "";
17
- const statusLabel =
18
- callSession?.status === "failed"
19
- ? "Call failed"
20
- : callSession?.status === "cancelled"
21
- ? "Call cancelled"
22
- : "Call completed";
23
- return `**${statusLabel}**${durationStr}. ${events.length} event(s) recorded.`;
31
+ return buildCallSummaryLabel(callSession?.status, duration, events.length);
24
32
  }
25
33
 
26
34
  export async function persistCallCompletionMessage(
27
35
  conversationId: string,
28
36
  callSessionId: string,
29
37
  ): Promise<string> {
30
- const summaryText = buildCallCompletionMessage(callSessionId);
38
+ const callSession = getCallSession(callSessionId);
39
+ const events = getCallEvents(callSessionId);
40
+ const duration =
41
+ callSession?.endedAt && callSession?.startedAt
42
+ ? Math.round((callSession.endedAt - callSession.startedAt) / 1000)
43
+ : null;
44
+ const summaryText = buildCallSummaryLabel(
45
+ callSession?.status,
46
+ duration,
47
+ events.length,
48
+ );
49
+
31
50
  await addMessage(
32
51
  conversationId,
33
52
  "assistant",
34
- JSON.stringify([{ type: "text", text: summaryText }]),
53
+ JSON.stringify([
54
+ {
55
+ type: "ui_surface",
56
+ surfaceType: "call_summary",
57
+ surfaceId: crypto.randomUUID(),
58
+ completed: true,
59
+ data: {
60
+ summaryText,
61
+ status: callSession?.status ?? "completed",
62
+ duration,
63
+ events: events.map((e) => ({
64
+ eventType: e.eventType,
65
+ payloadJson: e.payloadJson,
66
+ createdAt: e.createdAt,
67
+ })),
68
+ },
69
+ },
70
+ ]),
35
71
  {
36
72
  userMessageChannel: "phone",
37
73
  assistantMessageChannel: "phone",
@@ -66,6 +66,8 @@ import {
66
66
  } from "./speaker-identification.js";
67
67
 
68
68
  const log = getLogger("relay-server");
69
+ const UUID_SHAPED_NAME =
70
+ /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
69
71
 
70
72
  // ── ConversationRelay message types ──────────────────────────────────
71
73
 
@@ -1615,7 +1617,11 @@ export class RelayConnection {
1615
1617
  private resolveAssistantLabel(): string | null {
1616
1618
  try {
1617
1619
  const name = getAssistantName();
1618
- return name?.trim() || null;
1620
+ const trimmedName = name?.trim();
1621
+ if (!trimmedName || UUID_SHAPED_NAME.test(trimmedName)) {
1622
+ return null;
1623
+ }
1624
+ return trimmedName;
1619
1625
  } catch {
1620
1626
  return null;
1621
1627
  }
@@ -233,7 +233,7 @@ function buildVoiceCallControlPrompt(opts: {
233
233
  );
234
234
  } else {
235
235
  lines.push(
236
- '7. If the latest user turn is "(call connected — deliver opening greeting)", this is an inbound call you are answering (not a call you initiated). Greet the caller warmly and ask how you can help. Introduce yourself once at the start using your assistant name if you know it (for example: "Hey there, this is Ava, Sam\'s assistant. How can I help?"). If your assistant name is not known, skip the name and just identify yourself as the guardian\'s assistant. Do NOT say "I\'m calling" or "I\'m calling on behalf of". Vary the wording; do not use a fixed template.',
236
+ '7. If the latest user turn is "(call connected — deliver opening greeting)", this is an inbound call you are answering (not a call you initiated). Greet the caller warmly and ask how you can help. Introduce yourself once at the start using your assistant name if you know it (for example: "Hey there, this is Ava, Sam\'s assistant. How can I help?"). If your assistant name is not known, skip the name and just identify yourself as the guardian\'s assistant. Never use a UUID-shaped internal assistant ID as your spoken name. Do NOT say "I\'m calling" or "I\'m calling on behalf of". Vary the wording; do not use a fixed template.',
237
237
  );
238
238
  }
239
239
  lines.push(
@@ -14,7 +14,6 @@ let mockPlatformContext: Record<string, unknown> = {
14
14
  isPlatform: false,
15
15
  platformBaseUrl: "",
16
16
  assistantId: "",
17
- hasInternalApiKey: false,
18
17
  hasAssistantApiKey: false,
19
18
  authHeader: null,
20
19
  enabled: false,
@@ -128,7 +127,6 @@ function connectedContext(
128
127
  isPlatform: false,
129
128
  platformBaseUrl: "https://test-platform.vellum.ai",
130
129
  assistantId: "019d6d4f-6dbd-779f-91d3-cb273b9429a5",
131
- hasInternalApiKey: false,
132
130
  hasAssistantApiKey: true,
133
131
  authHeader: "Api-Key vak_test123",
134
132
  enabled: true,
@@ -145,7 +143,6 @@ describe("assistant webhooks register", () => {
145
143
  isPlatform: false,
146
144
  platformBaseUrl: "",
147
145
  assistantId: "",
148
- hasInternalApiKey: false,
149
146
  hasAssistantApiKey: false,
150
147
  authHeader: null,
151
148
  enabled: false,
@@ -466,7 +463,6 @@ describe("assistant webhooks list", () => {
466
463
  isPlatform: false,
467
464
  platformBaseUrl: "",
468
465
  assistantId: "",
469
- hasInternalApiKey: false,
470
466
  hasAssistantApiKey: false,
471
467
  authHeader: null,
472
468
  enabled: false,
@@ -1,23 +1,11 @@
1
- import { randomUUID } from "node:crypto";
2
- import {
3
- existsSync,
4
- mkdirSync,
5
- readFileSync,
6
- unlinkSync,
7
- writeFileSync,
8
- } from "node:fs";
9
- import { join } from "node:path";
10
-
11
1
  import type { Command } from "commander";
12
2
 
13
- import { getSignalsDir } from "../../util/platform.js";
3
+ import { cliIpcCall } from "../../ipc/cli-client.js";
14
4
  import { log } from "../logger.js";
15
5
 
16
6
  const DEFAULT_TIMEOUT_MS = 30_000;
17
- const POLL_INTERVAL_MS = 100;
18
7
 
19
- interface BashSignalResult {
20
- requestId: string;
8
+ interface DebugBashResult {
21
9
  stdout: string;
22
10
  stderr: string;
23
11
  exitCode: number | null;
@@ -39,20 +27,16 @@ export function registerBashCommand(program: Command): void {
39
27
  .addHelpText(
40
28
  "after",
41
29
  `
42
- Sends a shell command to the running assistant for execution via the
43
- signals directory. The assistant spawns the command in its own process environment
44
- and returns stdout, stderr, and the exit code.
30
+ Sends a shell command to the running assistant for execution via the IPC
31
+ socket. The assistant spawns the command in its own process environment and
32
+ returns stdout, stderr, and the exit code.
45
33
 
46
34
  This is a developer debugging tool for inspecting how the assistant invokes and
47
35
  observes shell commands. The command runs with the assistant's environment, working
48
36
  directory, and process context — not the caller's shell.
49
37
 
50
38
  Requires the assistant to be running with VELLUM_DEBUG=1. When debug mode is off
51
- (the default), the assistant ignores bash signal files and returns an error.
52
-
53
- The CLI writes the command to signals/bash.<requestId> and polls
54
- signals/bash.<requestId>.result for the output. The assistant must be running
55
- for this to work.
39
+ (the default), the assistant returns an error immediately.
56
40
 
57
41
  Arguments:
58
42
  command The shell command string to execute (e.g. "echo hello", "ls -la").
@@ -64,7 +48,7 @@ Examples:
64
48
  $ assistant bash "env | grep PATH" --timeout 10000
65
49
  $ assistant bash "ls -la"`,
66
50
  )
67
- .action((command: string, opts: { timeout: string }) => {
51
+ .action(async (command: string, opts: { timeout: string }) => {
68
52
  const timeoutMs = parseInt(opts.timeout, 10);
69
53
  if (!Number.isFinite(timeoutMs) || timeoutMs < 1) {
70
54
  log.error("Invalid timeout value. Must be a positive integer.");
@@ -72,105 +56,48 @@ Examples:
72
56
  return;
73
57
  }
74
58
 
75
- const requestId = randomUUID();
76
- const signalsDir = getSignalsDir();
59
+ const result = await cliIpcCall<DebugBashResult>(
60
+ "debug_bash",
61
+ { body: { command, timeoutMs } },
62
+ { timeoutMs: timeoutMs + 10_000 },
63
+ );
77
64
 
78
- try {
79
- mkdirSync(signalsDir, { recursive: true });
80
- } catch {
81
- log.error("Failed to create signals directory.");
65
+ if (!result.ok) {
66
+ log.error(result.error ?? "Failed to reach the assistant.");
82
67
  process.exitCode = 1;
83
68
  return;
84
69
  }
85
70
 
86
- // Write the command signal for the assistant to pick up.
87
- const signalPath = join(signalsDir, `bash.${requestId}`);
88
- const resultPath = join(signalsDir, `bash.${requestId}.result`);
89
-
90
- try {
91
- writeFileSync(
92
- signalPath,
93
- JSON.stringify({ requestId, command, timeoutMs }),
94
- );
95
- } catch {
96
- log.error("Failed to write bash signal file.");
71
+ const data = result.result!;
72
+
73
+ if (data.error) {
74
+ log.error(data.error);
97
75
  process.exitCode = 1;
98
76
  return;
99
77
  }
100
78
 
101
- log.info(`Sent command to assistant (requestId: ${requestId})`);
102
- log.info("Waiting for result...");
103
-
104
- // Poll for the result file until timeout.
105
- const deadline = Date.now() + timeoutMs + 5_000; // extra buffer for assistant overhead
106
-
107
- const poll = setInterval(() => {
108
- if (Date.now() > deadline) {
109
- clearInterval(poll);
110
- cleanupSignalFiles();
111
- log.error(
112
- "Timed out waiting for response. Is the assistant running?",
113
- );
114
- process.exitCode = 1;
115
- return;
116
- }
117
-
118
- if (!existsSync(resultPath)) return;
119
-
120
- let result: BashSignalResult;
121
- try {
122
- const content = readFileSync(resultPath, "utf-8");
123
- result = JSON.parse(content) as BashSignalResult;
124
- } catch {
125
- // File may be partially written; retry on next poll.
126
- return;
127
- }
128
-
129
- // Ignore stale results from a previous invocation.
130
- if (result.requestId !== requestId) return;
131
-
132
- clearInterval(poll);
133
- cleanupSignalFiles();
134
-
135
- if (result.error) {
136
- log.error(result.error);
137
- process.exitCode = 1;
138
- return;
139
- }
140
-
141
- if (result.stdout) {
142
- process.stdout.write(result.stdout);
143
- if (!result.stdout.endsWith("\n")) {
144
- process.stdout.write("\n");
145
- }
146
- }
147
-
148
- if (result.stderr) {
149
- process.stderr.write(result.stderr);
150
- if (!result.stderr.endsWith("\n")) {
151
- process.stderr.write("\n");
152
- }
153
- }
154
-
155
- if (result.timedOut) {
156
- log.info(`Command timed out in assistant.`);
79
+ if (data.stdout) {
80
+ process.stdout.write(data.stdout);
81
+ if (!data.stdout.endsWith("\n")) {
82
+ process.stdout.write("\n");
157
83
  }
84
+ }
158
85
 
159
- if (result.exitCode != null && result.exitCode !== 0) {
160
- log.info(`Exit code: ${result.exitCode}`);
86
+ if (data.stderr) {
87
+ process.stderr.write(data.stderr);
88
+ if (!data.stderr.endsWith("\n")) {
89
+ process.stderr.write("\n");
161
90
  }
91
+ }
162
92
 
163
- process.exitCode = result.exitCode ?? 1;
164
- }, POLL_INTERVAL_MS);
93
+ if (data.timedOut) {
94
+ log.info(`Command timed out in assistant.`);
95
+ }
165
96
 
166
- function cleanupSignalFiles(): void {
167
- for (const p of [signalPath, resultPath]) {
168
- try {
169
- unlinkSync(p);
170
- } catch {
171
- // Best-effort cleanup; the file may already be gone.
172
- }
173
- }
97
+ if (data.exitCode != null && data.exitCode !== 0) {
98
+ log.info(`Exit code: ${data.exitCode}`);
174
99
  }
100
+
101
+ process.exitCode = data.exitCode ?? 1;
175
102
  });
176
103
  }