@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
@@ -3,7 +3,7 @@ import { createServer, type Server } from "node:http";
3
3
  import type { Command } from "commander";
4
4
 
5
5
  import { getIsContainerized } from "../../../config/env-registry.js";
6
- import { orchestrateOAuthConnect } from "../../../oauth/connect-orchestrator.js";
6
+ import { cliIpcCall } from "../../../ipc/cli-client.js";
7
7
  import {
8
8
  getAppByProviderAndClientId,
9
9
  getMostRecentAppByProvider,
@@ -69,6 +69,39 @@ function startManagedRedirectServer(provider: string): Promise<{
69
69
  });
70
70
  }
71
71
 
72
+ // ---------------------------------------------------------------------------
73
+ // IPC polling helpers
74
+ // ---------------------------------------------------------------------------
75
+
76
+ type OAuthConnectStatusResponse =
77
+ | { status: "pending"; service: string }
78
+ | { status: "complete"; service: string; account_info?: string; granted_scopes?: string[] }
79
+ | { status: "error"; service: string; error?: string };
80
+
81
+ async function pollOAuthConnectStatus(
82
+ state: string,
83
+ opts: { intervalMs: number; timeoutMs: number },
84
+ ): Promise<OAuthConnectStatusResponse> {
85
+ const deadline = Date.now() + opts.timeoutMs;
86
+ while (Date.now() < deadline) {
87
+ const r = await cliIpcCall<OAuthConnectStatusResponse>(
88
+ "internal_oauth_connect_status",
89
+ { pathParams: { state } },
90
+ );
91
+ if (r.ok && r.result) {
92
+ const { status } = r.result;
93
+ if (status === "complete" || status === "error") {
94
+ return r.result;
95
+ }
96
+ }
97
+ if (!r.ok && r.statusCode !== undefined) {
98
+ return { status: "error", service: "?", error: r.error ?? "assistant error during OAuth status poll" };
99
+ }
100
+ await new Promise<void>((res) => setTimeout(res, opts.intervalMs));
101
+ }
102
+ return { status: "error", service: "?", error: "Timed out waiting for OAuth callback" };
103
+ }
104
+
72
105
  // ---------------------------------------------------------------------------
73
106
  // Command registration
74
107
  // ---------------------------------------------------------------------------
@@ -392,53 +425,104 @@ Examples:
392
425
  }
393
426
  }
394
427
 
395
- // e. Call the orchestrator
396
- const result = await orchestrateOAuthConnect({
397
- service: provider,
398
- clientId,
399
- clientSecret,
400
- callbackTransport: opts.callbackTransport,
401
- isInteractive: opts.browser !== false,
402
- openUrl: opts.browser !== false ? openInHostBrowser : undefined,
403
- ...(opts.scopes ? { requestedScopes: opts.scopes } : {}),
404
- });
405
-
406
- // f. Handle results
407
- if (!result.success) {
408
- writeError(result.error ?? "OAuth connect failed");
409
- return;
410
- }
428
+ // e. Try daemon-orchestrated path first (fixes heap-split for gateway transport).
429
+ const startResult = await cliIpcCall<{ auth_url: string; state: string }>(
430
+ "internal_oauth_connect_start",
431
+ {
432
+ body: {
433
+ service: provider,
434
+ clientId,
435
+ ...(clientSecret !== undefined ? { clientSecret } : {}),
436
+ callbackTransport: opts.callbackTransport,
437
+ ...(opts.scopes ? { requestedScopes: opts.scopes } : {}),
438
+ },
439
+ },
440
+ );
441
+
442
+ if (startResult.ok && startResult.result?.auth_url) {
443
+ const { auth_url, state } = startResult.result;
444
+
445
+ if (opts.browser !== false) {
446
+ await openInHostBrowser(auth_url);
411
447
 
412
- if (result.deferred) {
413
- if (jsonMode) {
414
- writeOutput(cmd, {
415
- ok: true,
416
- deferred: true,
417
- // Wire key stays `authUrl` for backward compatibility with
418
- // existing CLI script consumers; the internal field on
419
- // `result` is `authorizeUrl`.
420
- authUrl: result.authorizeUrl,
421
- service: result.service,
448
+ if (!jsonMode) {
449
+ log.info("Waiting for authorization in browser... (press Ctrl+C to cancel)");
450
+ }
451
+ const final = await pollOAuthConnectStatus(state, {
452
+ intervalMs: 2000,
453
+ timeoutMs: 5 * 60 * 1000, // match LOOPBACK_TIMEOUT_MS in oauth2.ts (5 min)
422
454
  });
455
+
456
+ if (final.status === "complete") {
457
+ if (jsonMode) {
458
+ writeOutput(cmd, {
459
+ ok: true,
460
+ grantedScopes: final.granted_scopes ?? [],
461
+ accountInfo: final.account_info,
462
+ });
463
+ } else {
464
+ process.stdout.write(
465
+ `Connected to ${provider}${final.account_info ? ` as ${final.account_info}` : ""}\n`,
466
+ );
467
+ }
468
+ return;
469
+ }
470
+
471
+ if (final.status === "error") {
472
+ // Includes the timeout sentinel emitted by pollOAuthConnectStatus.
473
+ writeError(final.error ?? "OAuth connect failed");
474
+ return;
475
+ }
476
+
477
+ // Defensive: pollOAuthConnectStatus should never return pending,
478
+ // but TS narrowing requires us to handle it.
479
+ writeError("OAuth connect ended in an unexpected pending state");
480
+ return;
423
481
  } else {
424
- process.stdout.write(
425
- `\nAuthorize with ${provider}:\n\n${result.authorizeUrl}\n\nThe connection will complete automatically once you authorize.\n`,
426
- );
482
+ // --no-browser: return the URL immediately, matching existing deferred behavior.
483
+ if (jsonMode) {
484
+ writeOutput(cmd, {
485
+ ok: true,
486
+ deferred: true,
487
+ authUrl: auth_url,
488
+ state,
489
+ service: provider,
490
+ });
491
+ } else {
492
+ process.stdout.write(
493
+ `\nAuthorize with ${provider}:\n\n${auth_url}\n\nThe connection will complete automatically once you authorize.\n`,
494
+ );
495
+ }
496
+ return;
427
497
  }
498
+ }
499
+
500
+ // ok:true but no auth_url means a malformed daemon response — surface an error rather
501
+ // than falling back to in-process (which would re-introduce the heap-split bug for
502
+ // gateway transport).
503
+ if (startResult.ok && !startResult.result?.auth_url) {
504
+ writeError("assistant returned unexpected response for OAuth connect start");
428
505
  return;
429
506
  }
430
507
 
431
- // Interactive mode completed
432
- if (jsonMode) {
433
- writeOutput(cmd, {
434
- ok: true,
435
- grantedScopes: result.grantedScopes,
436
- accountInfo: result.accountInfo,
437
- });
438
- } else {
439
- const msg = `Connected to ${provider}${result.accountInfo ? ` as ${result.accountInfo}` : ""}`;
440
- process.stdout.write(msg + "\n");
508
+ // If the daemon was reachable but returned an error, surface it rather than
509
+ // falling back to in-process (which would re-introduce the heap-split bug for
510
+ // gateway transport).
511
+ if (!startResult.ok && startResult.statusCode !== undefined) {
512
+ writeError(startResult.error ?? "OAuth connect failed (assistant error)");
513
+ return;
441
514
  }
515
+
516
+ // IPC unavailable: the assistant must be running for OAuth connect. The
517
+ // gateway-routed callback lands in the assistant's process, and any tokens
518
+ // acquired need the assistant to store and use them — so an unreachable
519
+ // assistant is a fatal precondition. Surface a clear error and exit 1.
520
+ writeError(
521
+ startResult.error
522
+ ? `Could not reach the assistant: ${startResult.error}. Is the assistant running?`
523
+ : "Could not reach the assistant. Is the assistant running?",
524
+ );
525
+ return;
442
526
  }
443
527
  } catch (err) {
444
528
  const message = err instanceof Error ? err.message : String(err);
@@ -10,7 +10,6 @@ let mockResolvePlatformCallbackRegistrationContext: () => Promise<
10
10
  isPlatform: false,
11
11
  platformBaseUrl: "",
12
12
  assistantId: "",
13
- hasInternalApiKey: false,
14
13
  hasAssistantApiKey: false,
15
14
  authHeader: null,
16
15
  enabled: false,
@@ -93,7 +92,6 @@ function connectedContext(
93
92
  isPlatform: false,
94
93
  platformBaseUrl: "https://dev-platform.vellum.ai",
95
94
  assistantId: "019d6d4f-6dbd-779f-91d3-cb273b9429a5",
96
- hasInternalApiKey: false,
97
95
  hasAssistantApiKey: true,
98
96
  authHeader: "Api-Key vak_test123",
99
97
  enabled: false,
@@ -181,7 +179,6 @@ describe("assistant platform callback-routes list", () => {
181
179
  isPlatform: false,
182
180
  platformBaseUrl: "",
183
181
  assistantId: "",
184
- hasInternalApiKey: false,
185
182
  hasAssistantApiKey: false,
186
183
  authHeader: null,
187
184
  enabled: false,
@@ -32,6 +32,13 @@ mock.module("../../../../security/secure-keys.js", () => ({
32
32
  onCesClientChanged: () => ({ unsubscribe: () => {} }),
33
33
  setCesReconnect: () => {},
34
34
  getActiveBackendName: () => "file",
35
+ getActiveBackendInfoAsync: async () => ({
36
+ backend: "encrypted-store",
37
+ storePath: "/tmp/keys.enc",
38
+ storeKeyPath: "/tmp/store.key",
39
+ storeExists: false,
40
+ storeKeyExists: false,
41
+ }),
35
42
  _resetBackend: () => {},
36
43
  }));
37
44
 
@@ -45,7 +52,6 @@ mock.module("../../../../inbound/platform-callback-registration.js", () => ({
45
52
  isPlatform: false,
46
53
  platformBaseUrl: "",
47
54
  assistantId: "",
48
- hasInternalApiKey: false,
49
55
  hasAssistantApiKey: false,
50
56
  authHeader: null,
51
57
  enabled: false,
@@ -39,7 +39,6 @@ mock.module("../../../../inbound/platform-callback-registration.js", () => ({
39
39
  isPlatform: false,
40
40
  platformBaseUrl: "",
41
41
  assistantId: "",
42
- hasInternalApiKey: false,
43
42
  hasAssistantApiKey: false,
44
43
  authHeader: null,
45
44
  enabled: false,
@@ -64,6 +63,13 @@ mock.module("../../../../security/secure-keys.js", () => ({
64
63
  onCesClientChanged: () => ({ unsubscribe: () => {} }),
65
64
  setCesReconnect: () => {},
66
65
  getActiveBackendName: () => "file",
66
+ getActiveBackendInfoAsync: async () => ({
67
+ backend: "encrypted-store",
68
+ storePath: "/tmp/keys.enc",
69
+ storeKeyPath: "/tmp/store.key",
70
+ storeExists: false,
71
+ storeKeyExists: false,
72
+ }),
67
73
  _resetBackend: () => {},
68
74
  }));
69
75
 
@@ -14,12 +14,16 @@ let mockResolvePlatformCallbackRegistrationContext: () => Promise<
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,
21
20
  });
22
21
 
22
+ let mockIpcGetVelayStatus: () => Promise<{
23
+ connected: boolean;
24
+ publicUrl: string | null;
25
+ } | null> = async () => null;
26
+
23
27
  // ---------------------------------------------------------------------------
24
28
  // Mocks
25
29
  // ---------------------------------------------------------------------------
@@ -47,6 +51,13 @@ mock.module("../../../../security/secure-keys.js", () => ({
47
51
  onCesClientChanged: () => ({ unsubscribe: () => {} }),
48
52
  setCesReconnect: () => {},
49
53
  getActiveBackendName: () => "file",
54
+ getActiveBackendInfoAsync: async () => ({
55
+ backend: "encrypted-store",
56
+ storePath: "/tmp/keys.enc",
57
+ storeKeyPath: "/tmp/store.key",
58
+ storeExists: false,
59
+ storeKeyExists: false,
60
+ }),
50
61
  _resetBackend: () => {},
51
62
  }));
52
63
 
@@ -100,6 +111,15 @@ mock.module("../../../../config/loader.js", () => ({
100
111
  mergeDefaultWorkspaceConfig: () => {},
101
112
  }));
102
113
 
114
+ mock.module("../../../../ipc/gateway-client.js", () => ({
115
+ ipcGetVelayStatus: () => mockIpcGetVelayStatus(),
116
+ ipcCall: async () => undefined,
117
+ ipcCallPersistent: async () => undefined,
118
+ resetPersistentClient: () => {},
119
+ ipcGetFeatureFlags: async () => ({}),
120
+ ipcClassifyRisk: async () => undefined,
121
+ }));
122
+
103
123
  // ---------------------------------------------------------------------------
104
124
  // Import module under test (after mocks are registered)
105
125
  // ---------------------------------------------------------------------------
@@ -158,11 +178,11 @@ describe("assistant platform status", () => {
158
178
  isPlatform: false,
159
179
  platformBaseUrl: "",
160
180
  assistantId: "",
161
- hasInternalApiKey: false,
162
181
  hasAssistantApiKey: false,
163
182
  authHeader: null,
164
183
  enabled: false,
165
184
  });
185
+ mockIpcGetVelayStatus = async () => null;
166
186
  process.exitCode = 0;
167
187
  });
168
188
 
@@ -179,9 +199,8 @@ describe("assistant platform status", () => {
179
199
  isPlatform: true,
180
200
  platformBaseUrl: "https://platform.vellum.ai",
181
201
  assistantId: "asst-abc-123",
182
- hasInternalApiKey: true,
183
202
  hasAssistantApiKey: true,
184
- authHeader: "Bearer internal-key",
203
+ authHeader: "Api-Key assistant-key",
185
204
  enabled: true,
186
205
  });
187
206
 
@@ -209,12 +228,91 @@ describe("assistant platform status", () => {
209
228
  expect(parsed.isPlatform).toBe(true);
210
229
  expect(parsed.baseUrl).toBe("https://platform.vellum.ai");
211
230
  expect(parsed.assistantId).toBe("asst-abc-123");
212
- expect(parsed.hasInternalApiKey).toBe(true);
213
231
  expect(parsed.hasAssistantApiKey).toBe(true);
214
232
  expect(parsed.hasWebhookSecret).toBe(true);
215
233
  expect(parsed.available).toBe(true);
216
234
  expect(parsed.organizationId).toBe("org-456");
217
235
  expect(parsed.userId).toBe("user-789");
236
+ // velayTunnel is null when gateway is unreachable
237
+ expect(parsed.velayTunnel).toBeNull();
238
+ });
239
+
240
+ test("velayTunnel connected with publicUrl is returned when gateway is live", async () => {
241
+ /**
242
+ * When the gateway is running and the Velay tunnel is connected, the
243
+ * status command includes velayTunnel.connected=true and the public URL.
244
+ */
245
+
246
+ // GIVEN a connected Velay tunnel reported by the gateway IPC
247
+ mockIpcGetVelayStatus = async () => ({
248
+ connected: true,
249
+ publicUrl: "https://abc123.vellum.ai",
250
+ });
251
+
252
+ // WHEN the status command is run with --json
253
+ const { exitCode, stdout } = await runCommand([
254
+ "platform",
255
+ "status",
256
+ "--json",
257
+ ]);
258
+
259
+ // THEN the command succeeds
260
+ expect(exitCode).toBe(0);
261
+
262
+ // AND velayTunnel reflects the live connection
263
+ const parsed = JSON.parse(stdout);
264
+ expect(parsed.velayTunnel).toEqual({
265
+ connected: true,
266
+ publicUrl: "https://abc123.vellum.ai",
267
+ });
268
+ });
269
+
270
+ test("velayTunnel disconnected when gateway reports no active connection", async () => {
271
+ /**
272
+ * When the gateway is running but Velay is not connected (e.g.
273
+ * reconnecting after disconnect), velayTunnel.connected is false.
274
+ */
275
+
276
+ // GIVEN a disconnected Velay tunnel
277
+ mockIpcGetVelayStatus = async () => ({
278
+ connected: false,
279
+ publicUrl: null,
280
+ });
281
+
282
+ // WHEN the status command is run with --json
283
+ const { exitCode, stdout } = await runCommand([
284
+ "platform",
285
+ "status",
286
+ "--json",
287
+ ]);
288
+
289
+ expect(exitCode).toBe(0);
290
+ const parsed = JSON.parse(stdout);
291
+ expect(parsed.velayTunnel).toEqual({ connected: false, publicUrl: null });
292
+ });
293
+
294
+ test("velayTunnel is null when gateway IPC is unreachable", async () => {
295
+ /**
296
+ * When the gateway IPC socket is not available (assistant not running),
297
+ * velayTunnel is null rather than causing the status command to fail.
298
+ */
299
+
300
+ // GIVEN the gateway IPC throws
301
+ mockIpcGetVelayStatus = async () => {
302
+ throw new Error("ENOENT");
303
+ };
304
+
305
+ // WHEN the status command is run with --json
306
+ const { exitCode, stdout } = await runCommand([
307
+ "platform",
308
+ "status",
309
+ "--json",
310
+ ]);
311
+
312
+ // THEN the command still succeeds (graceful fallback)
313
+ expect(exitCode).toBe(0);
314
+ const parsed = JSON.parse(stdout);
315
+ expect(parsed.velayTunnel).toBeNull();
218
316
  });
219
317
 
220
318
  test("plain text mode does not emit JSON to stdout", async () => {
@@ -229,7 +327,6 @@ describe("assistant platform status", () => {
229
327
  isPlatform: false,
230
328
  platformBaseUrl: "",
231
329
  assistantId: "",
232
- hasInternalApiKey: false,
233
330
  hasAssistantApiKey: false,
234
331
  authHeader: null,
235
332
  enabled: false,
@@ -4,6 +4,7 @@ import {
4
4
  registerCallbackRoute,
5
5
  resolvePlatformCallbackRegistrationContext,
6
6
  } from "../../../inbound/platform-callback-registration.js";
7
+ import { ipcGetVelayStatus } from "../../../ipc/gateway-client.js";
7
8
  import { credentialKey } from "../../../security/credential-key.js";
8
9
  import { getSecureKeyAsync } from "../../../security/secure-keys.js";
9
10
  import { log } from "../../logger.js";
@@ -62,14 +63,14 @@ Fields:
62
63
  isPlatform Whether IS_PLATFORM is set (boolean)
63
64
  baseUrl VELLUM_PLATFORM_URL — the platform gateway base URL
64
65
  assistantId This assistant's platform UUID
65
- hasInternalApiKey Whether PLATFORM_INTERNAL_API_KEY is set (boolean,
66
- value not disclosed)
67
66
  hasAssistantApiKey Whether a stored assistant API key is available
68
67
  hasWebhookSecret Whether a stored webhook secret is available (needed
69
68
  for email and other inbound webhook channels)
70
69
  available Whether callback registration prerequisites are satisfied
71
70
  organizationId The platform organization ID (from stored credentials)
72
71
  userId The platform user ID (from stored credentials)
72
+ velayTunnel Live Velay tunnel status from the gateway IPC socket
73
+ (null when the gateway is not running)
73
74
 
74
75
  Examples:
75
76
  $ assistant platform status
@@ -77,7 +78,10 @@ Examples:
77
78
  )
78
79
  .action(async (_opts: Record<string, unknown>, cmd: Command) => {
79
80
  try {
80
- const context = await resolvePlatformCallbackRegistrationContext();
81
+ const [context, velayTunnel] = await Promise.all([
82
+ resolvePlatformCallbackRegistrationContext(),
83
+ ipcGetVelayStatus().catch(() => null),
84
+ ]);
81
85
 
82
86
  const organizationId =
83
87
  (
@@ -106,12 +110,12 @@ Examples:
106
110
  isPlatform: context.isPlatform,
107
111
  baseUrl: context.platformBaseUrl,
108
112
  assistantId: context.assistantId,
109
- hasInternalApiKey: context.hasInternalApiKey,
110
113
  hasAssistantApiKey: context.hasAssistantApiKey,
111
114
  hasWebhookSecret,
112
115
  available: context.enabled,
113
116
  organizationId: organizationId || null,
114
117
  userId: userId || null,
118
+ velayTunnel,
115
119
  };
116
120
 
117
121
  if (shouldOutputJson(cmd)) {
@@ -120,9 +124,6 @@ Examples:
120
124
  log.info(`Platform: ${result.isPlatform}`);
121
125
  log.info(`Base URL: ${result.baseUrl || "(not set)"}`);
122
126
  log.info(`Assistant ID: ${result.assistantId || "(not set)"}`);
123
- log.info(
124
- `Internal API key: ${result.hasInternalApiKey ? "set" : "not set"}`,
125
- );
126
127
  log.info(
127
128
  `Assistant API key: ${result.hasAssistantApiKey ? "set" : "not set"}`,
128
129
  );
@@ -134,6 +135,14 @@ Examples:
134
135
  );
135
136
  log.info(`Organization ID: ${organizationId || "(not set)"}`);
136
137
  log.info(`User ID: ${userId || "(not set)"}`);
138
+ if (result.velayTunnel !== null) {
139
+ const tunnelState = result.velayTunnel.connected
140
+ ? `connected${result.velayTunnel.publicUrl ? ` (${result.velayTunnel.publicUrl})` : ""}`
141
+ : "disconnected";
142
+ log.info(`Velay tunnel: ${tunnelState}`);
143
+ } else {
144
+ log.info(`Velay tunnel: (gateway not running)`);
145
+ }
137
146
  }
138
147
  } catch (err) {
139
148
  const message = err instanceof Error ? err.message : String(err);
@@ -0,0 +1,57 @@
1
+ import type { Command } from "commander";
2
+
3
+ import { cliIpcCall } from "../../ipc/cli-client.js";
4
+ import { getWorkspaceDirDisplay } from "../../util/platform.js";
5
+ import { log } from "../logger.js";
6
+
7
+ interface HealthResponse {
8
+ version: string;
9
+ memory: { currentMb: number; maxMb: number };
10
+ disk: { freeMb: number; totalMb: number } | null;
11
+ }
12
+
13
+ function fmtMb(mb: number): string {
14
+ if (mb >= 1024) return `${(mb / 1024).toFixed(1)} GB`;
15
+ return `${Math.round(mb)} MB`;
16
+ }
17
+
18
+ export function registerStatusCommand(program: Command): void {
19
+ program
20
+ .command("status")
21
+ .description("Show assistant version, workspace, and runtime health")
22
+ .action(async () => {
23
+ const result = await cliIpcCall<HealthResponse>("health");
24
+
25
+ if (!result.ok || !result.result) {
26
+ log.error(
27
+ result.error ??
28
+ "Assistant not running — could not connect to IPC socket.",
29
+ );
30
+ process.exit(1);
31
+ }
32
+
33
+ const h = result.result;
34
+ const workspace = getWorkspaceDirDisplay();
35
+
36
+ const rows: [string, string][] = [
37
+ ["Version", h.version],
38
+ ["Workspace", workspace],
39
+ ["", ""],
40
+ ["Memory", `${fmtMb(h.memory.currentMb)} / ${fmtMb(h.memory.maxMb)}`],
41
+ ...(h.disk
42
+ ? ([["Disk", `${fmtMb(h.disk.freeMb)} free`]] as [string, string][])
43
+ : []),
44
+ ];
45
+
46
+ const labelWidth = Math.max(
47
+ ...rows.filter(([l]) => l).map(([l]) => l.length),
48
+ );
49
+ for (const [label, value] of rows) {
50
+ if (!label) {
51
+ log.info("");
52
+ continue;
53
+ }
54
+ log.info(`${label.padEnd(labelWidth)} ${value}`);
55
+ }
56
+ });
57
+ }
@@ -41,6 +41,7 @@ import { registerPlatformCommand } from "./commands/platform/index.js";
41
41
  import { registerRoutesCommand } from "./commands/routes.js";
42
42
  import { registerSequenceCommand } from "./commands/sequence.js";
43
43
  import { registerSkillsCommand } from "./commands/skills.js";
44
+ import { registerStatusCommand } from "./commands/status.js";
44
45
  import { registerSttCommand } from "./commands/stt.js";
45
46
  import { registerTaskCommand } from "./commands/task.js";
46
47
  import { registerTrustCommand } from "./commands/trust.js";
@@ -56,7 +57,7 @@ import { log } from "./logger.js";
56
57
  * the gateway so flag-gated commands are registered correctly.
57
58
  */
58
59
  export async function buildCliProgram(): Promise<Command> {
59
- await initFeatureFlagOverrides();
60
+ await initFeatureFlagOverrides({ retryBackoffsMs: [], callTimeoutMs: 200 });
60
61
  const program = new Command();
61
62
 
62
63
  program
@@ -110,6 +111,7 @@ Examples:
110
111
  registerPlatformCommand(program);
111
112
  registerRoutesCommand(program);
112
113
  registerSequenceCommand(program);
114
+ registerStatusCommand(program);
113
115
  registerSkillsCommand(program);
114
116
  registerSttCommand(program);
115
117
  registerTaskCommand(program);
@@ -126,7 +128,7 @@ Examples:
126
128
  // remain available even without a workspace.
127
129
  // Workspace-independent commands are exempt:
128
130
  // completions — pure shell-script generation, no workspace files needed
129
- const workspaceExemptCommands = new Set(["completions"]);
131
+ const workspaceExemptCommands = new Set(["completions", "status"]);
130
132
  program.hook("preAction", (_thisCommand, actionCommand) => {
131
133
  if (workspaceExemptCommands.has(actionCommand.name())) {
132
134
  return;
@@ -135,9 +135,11 @@ let cachedOverridesFromGateway = false;
135
135
  * timeout, parse error). No auth needed — the IPC socket is
136
136
  * access-controlled by file-system permissions on the shared volume.
137
137
  */
138
- async function fetchOverridesFromGateway(): Promise<Record<string, boolean>> {
138
+ async function fetchOverridesFromGateway(
139
+ timeoutMs?: number,
140
+ ): Promise<Record<string, boolean>> {
139
141
  try {
140
- return await ipcGetFeatureFlags();
142
+ return await ipcGetFeatureFlags(timeoutMs);
141
143
  } catch {
142
144
  return {};
143
145
  }
@@ -182,10 +184,18 @@ const DEFAULT_INIT_RETRY_BACKOFFS_MS: readonly number[] = [
182
184
  */
183
185
  export async function initFeatureFlagOverrides(options?: {
184
186
  retryBackoffsMs?: readonly number[];
187
+ /**
188
+ * Timeout (ms) for each IPC call to the gateway. When omitted the
189
+ * transport defaults apply (3 s connect + 5 s call). CLI callers should
190
+ * pass a small value (e.g. 200) so a slow/absent gateway fails fast
191
+ * instead of blocking startup.
192
+ */
193
+ callTimeoutMs?: number;
185
194
  }): Promise<void> {
186
195
  if (cachedOverridesFromGateway) return;
187
196
 
188
197
  const backoffs = options?.retryBackoffsMs ?? DEFAULT_INIT_RETRY_BACKOFFS_MS;
198
+ const callTimeoutMs = options?.callTimeoutMs;
189
199
 
190
200
  // First attempt has no preceding delay; subsequent attempts wait per the
191
201
  // backoff schedule. An empty result is treated as a transient miss
@@ -201,7 +211,7 @@ export async function initFeatureFlagOverrides(options?: {
201
211
  if (cachedOverridesFromGateway) return;
202
212
  }
203
213
 
204
- const gatewayOverrides = await fetchOverridesFromGateway();
214
+ const gatewayOverrides = await fetchOverridesFromGateway(callTimeoutMs);
205
215
  if (Object.keys(gatewayOverrides).length > 0) {
206
216
  cachedOverrides = gatewayOverrides;
207
217
  cachedOverridesFromGateway = true;
@@ -6,8 +6,6 @@ metadata:
6
6
  emoji: "🏗️"
7
7
  vellum:
8
8
  display-name: "App Builder"
9
- includes:
10
- - "frontend-design"
11
9
  activation-hints:
12
10
  - "User asks to build an app, landing page, website, dashboard, tool, calculator, game, tracker, or interactive page"
13
11
  - "User asks to visualize data or says 'let's visualize this' — use the app sandbox to build interactive visualizations"
@@ -20,7 +18,7 @@ You are an expert app builder and visual designer. When the user asks you to cre
20
18
 
21
19
  **Your default behavior:** Build immediately. The user types "build me a habit tracker" and you deliver a complete, polished app with a domain-matched color palette, atmospheric background, and thoughtful interactions. Don't ask what colors they want. Don't show wireframes. Just build something stunning and let them refine from there.
22
20
 
23
- **Design quality is delegated to the `frontend-design` skill.** That skill defines your aesthetic principles: typography, color strategy, motion, spatial composition, and visual detail. Follow it completely for every build. This skill (app-builder) handles the technical infrastructure: sandbox constraints, data bridge, widget API, app lifecycle, and interaction patterns.
21
+ **Design quality is delegated to the `frontend-design` skill, so you must also load/install that before proceeding.** That skill defines your aesthetic principles: typography, color strategy, motion, spatial composition, and visual detail. Follow it completely for every build. This skill (app-builder) handles the technical infrastructure: sandbox constraints, data bridge, widget API, app lifecycle, and interaction patterns.
24
22
 
25
23
  ## Filesystem Layout
26
24