@vellumai/assistant 0.8.4 → 0.8.5

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 (438) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/docs/browser-use-architecture-phase2.md +1 -1
  3. package/knip.json +2 -1
  4. package/openapi.yaml +809 -11
  5. package/package.json +1 -1
  6. package/src/__tests__/anthropic-provider.test.ts +34 -37
  7. package/src/__tests__/assistant-event-hub-self-exclusion.test.ts +293 -0
  8. package/src/__tests__/assistant-feature-flags-integration.test.ts +3 -3
  9. package/src/__tests__/audit-log-rotation.test.ts +70 -16
  10. package/src/__tests__/background-workers-disk-pressure.test.ts +3 -3
  11. package/src/__tests__/btw-routes.test.ts +2 -3
  12. package/src/__tests__/call-controller.test.ts +0 -1
  13. package/src/__tests__/cancel-resolves-conversation-key.test.ts +1 -1
  14. package/src/__tests__/channel-guardian.test.ts +3 -3
  15. package/src/__tests__/checker.test.ts +6 -15
  16. package/src/__tests__/compaction-events.test.ts +1 -0
  17. package/src/__tests__/compactor-call-site-logging.test.ts +214 -0
  18. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +5 -11
  19. package/src/__tests__/computer-use-tools.test.ts +2 -4
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  21. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -1
  22. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop-overflow.test.ts +197 -2
  24. package/src/__tests__/conversation-agent-loop.test.ts +163 -122
  25. package/src/__tests__/conversation-app-control-instantiation.test.ts +2 -5
  26. package/src/__tests__/conversation-clear-safety.test.ts +25 -25
  27. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +1 -1
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -2
  29. package/src/__tests__/conversation-error.test.ts +31 -0
  30. package/src/__tests__/conversation-fork-crud.test.ts +178 -15
  31. package/src/__tests__/conversation-lifecycle.test.ts +52 -11
  32. package/src/__tests__/{conversation-load-cleaned-at.test.ts → conversation-load-history-stripped.test.ts} +13 -13
  33. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  34. package/src/__tests__/conversation-routes-disk-view.test.ts +109 -0
  35. package/src/__tests__/conversation-routes-slash-commands.test.ts +35 -0
  36. package/src/__tests__/conversation-skill-tools.test.ts +2 -5
  37. package/src/__tests__/conversation-store.test.ts +1 -1
  38. package/src/__tests__/conversation-sync-tags.test.ts +99 -32
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  40. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  41. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  42. package/src/__tests__/credential-execution-feature-gates.test.ts +9 -7
  43. package/src/__tests__/credential-execution-tools.test.ts +6 -6
  44. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  45. package/src/__tests__/credential-vault-unit.test.ts +2 -2
  46. package/src/__tests__/dynamic-page-surface.test.ts +2 -2
  47. package/src/__tests__/email-html-renderer.test.ts +12 -0
  48. package/src/__tests__/gateway-flag-listener.test.ts +237 -0
  49. package/src/__tests__/gemini-provider.test.ts +78 -0
  50. package/src/__tests__/guardian-dispatch.test.ts +0 -1
  51. package/src/__tests__/guardian-outbound-http.test.ts +7 -5
  52. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  53. package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -0
  54. package/src/__tests__/heartbeat-service.test.ts +4 -0
  55. package/src/__tests__/host-shell-tool.test.ts +1 -1
  56. package/src/__tests__/init-feature-flag-overrides.test.ts +5 -6
  57. package/src/__tests__/list-messages-tool-merge.test.ts +70 -11
  58. package/src/__tests__/llm-request-log-call-site.test.ts +136 -0
  59. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +26 -0
  60. package/src/__tests__/llm-resolver.test.ts +77 -9
  61. package/src/__tests__/llm-usage-store.test.ts +66 -0
  62. package/src/__tests__/logger.test.ts +89 -0
  63. package/src/__tests__/mcp-abort-signal.test.ts +2 -2
  64. package/src/__tests__/media-generate-image.test.ts +31 -0
  65. package/src/__tests__/memory-v2-static-injector.test.ts +7 -7
  66. package/src/__tests__/model-intents.test.ts +2 -4
  67. package/src/__tests__/notification-guardian-path.test.ts +0 -1
  68. package/src/__tests__/onboarding-template-contract.test.ts +1 -1
  69. package/src/__tests__/openai-provider.test.ts +46 -0
  70. package/src/__tests__/openai-responses-provider.test.ts +114 -12
  71. package/src/__tests__/pending-interactions-resolved-event.test.ts +0 -1
  72. package/src/__tests__/platform-bash-auto-approve.test.ts +2 -2
  73. package/src/__tests__/platform.test.ts +2 -2
  74. package/src/__tests__/plugin-api-tool-definition.test.ts +92 -0
  75. package/src/__tests__/plugin-bootstrap.test.ts +2 -2
  76. package/src/__tests__/plugin-tool-contribution.test.ts +13 -6
  77. package/src/__tests__/plugin-types.test.ts +3 -2
  78. package/src/__tests__/prechat-onboarding-contract.test.ts +131 -98
  79. package/src/__tests__/pricing.test.ts +12 -0
  80. package/src/__tests__/prune-jobs-changes-parser.test.ts +61 -0
  81. package/src/__tests__/registry.test.ts +2 -8
  82. package/src/__tests__/require-fresh-approval.test.ts +2 -2
  83. package/src/__tests__/runtime-events-sse-bilingual.test.ts +154 -0
  84. package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -1
  85. package/src/__tests__/skill-feature-flags.test.ts +2 -2
  86. package/src/__tests__/skill-projection-feature-flag.test.ts +4 -7
  87. package/src/__tests__/skill-projection.benchmark.test.ts +2 -6
  88. package/src/__tests__/skill-tool-factory.test.ts +1 -1
  89. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  90. package/src/__tests__/suggestion-routes.test.ts +1 -0
  91. package/src/__tests__/sync-message-contract.test.ts +59 -0
  92. package/src/__tests__/system-prompt.test.ts +145 -131
  93. package/src/__tests__/terminal-tools.test.ts +1 -1
  94. package/src/__tests__/tool-approval-handler.test.ts +1 -5
  95. package/src/__tests__/tool-execute-pipeline.test.ts +2 -2
  96. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -5
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +15 -5
  98. package/src/__tests__/tool-executor.test.ts +9 -62
  99. package/src/__tests__/tool-grant-request-escalation.test.ts +1 -6
  100. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  101. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -6
  102. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -1
  103. package/src/__tests__/ui-file-upload-surface.test.ts +2 -2
  104. package/src/__tests__/usage-routes.test.ts +3 -0
  105. package/src/__tests__/verification-control-plane-policy.test.ts +2 -2
  106. package/src/__tests__/workspace-git-service.test.ts +6 -5
  107. package/src/__tests__/workspace-migration-089-move-memory-tree-out-of-v3.test.ts +86 -0
  108. package/src/acp/__tests__/prepare-agent-env.test.ts +146 -0
  109. package/src/acp/prepare-agent-env.ts +78 -0
  110. package/src/acp/session-manager.ts +1 -1
  111. package/src/agent/loop.ts +8 -0
  112. package/src/api/README.md +5 -0
  113. package/src/api/index.ts +4 -0
  114. package/src/api/package.json +10 -0
  115. package/src/background-wake/background-wake-routes.test.ts +233 -0
  116. package/src/background-wake/runtime-registry.ts +24 -0
  117. package/src/cli/commands/__tests__/browser.test.ts +23 -5
  118. package/src/cli/commands/__tests__/domain-register.test.ts +110 -0
  119. package/src/cli/commands/__tests__/domain-status.test.ts +33 -33
  120. package/src/cli/commands/__tests__/inference-send.test.ts +108 -5
  121. package/src/cli/commands/__tests__/memory-v2-compare-render.test.ts +98 -0
  122. package/src/cli/commands/__tests__/memory-v2.test.ts +1 -0
  123. package/src/cli/commands/__tests__/memory-v3-render.test.ts +340 -0
  124. package/src/cli/commands/browser.ts +247 -0
  125. package/src/cli/commands/domain.ts +91 -41
  126. package/src/cli/commands/inference.ts +93 -40
  127. package/src/cli/commands/memory-v2-compare-render.ts +115 -0
  128. package/src/cli/commands/memory-v2.ts +176 -1
  129. package/src/cli/commands/memory-v3-render.ts +344 -0
  130. package/src/cli/commands/memory-v3.ts +316 -0
  131. package/src/cli/program.ts +2 -0
  132. package/src/config/assistant-feature-flags.ts +21 -9
  133. package/src/config/bundled-skills/document-editor/SKILL.md +11 -2
  134. package/src/config/bundled-skills/document-editor/TOOLS.json +18 -0
  135. package/src/config/bundled-skills/document-editor/tools/document-open.ts +12 -0
  136. package/src/config/bundled-skills/image-studio/SKILL.md +4 -0
  137. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -2
  138. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +13 -8
  139. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +10 -3
  140. package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +16 -14
  141. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +7 -2
  142. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +7 -2
  143. package/src/config/bundled-tool-registry.ts +2 -0
  144. package/src/config/call-site-defaults.ts +7 -6
  145. package/src/config/feature-flag-registry.json +16 -0
  146. package/src/config/schemas/__tests__/memory-v2.test.ts +213 -1
  147. package/src/config/schemas/call-site-catalog.ts +21 -7
  148. package/src/config/schemas/llm.ts +12 -1
  149. package/src/config/schemas/memory-v2.ts +246 -0
  150. package/src/config/schemas/memory.ts +2 -1
  151. package/src/context/compactor.ts +52 -0
  152. package/src/conversations/__tests__/message-consolidation.test.ts +350 -0
  153. package/src/conversations/message-consolidation.ts +404 -0
  154. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -1
  155. package/src/daemon/__tests__/meet-manifest-loader.test.ts +1 -1
  156. package/src/daemon/conversation-agent-loop-handlers.ts +2 -13
  157. package/src/daemon/conversation-agent-loop.ts +126 -76
  158. package/src/daemon/conversation-error.ts +31 -1
  159. package/src/daemon/conversation-lifecycle.ts +27 -22
  160. package/src/daemon/conversation-runtime-assembly.ts +10 -9
  161. package/src/daemon/conversation-tool-setup.ts +63 -3
  162. package/src/daemon/conversation-usage.ts +2 -0
  163. package/src/daemon/conversation.ts +14 -29
  164. package/src/daemon/disk-pressure-guard.ts +14 -2
  165. package/src/daemon/handlers/config-model.test.ts +1 -0
  166. package/src/daemon/handlers/conversations.ts +11 -3
  167. package/src/daemon/host-browser-proxy.ts +5 -5
  168. package/src/daemon/host-cu-proxy.ts +4 -4
  169. package/src/daemon/host-file-proxy.ts +4 -4
  170. package/src/daemon/host-proxy-base.ts +4 -4
  171. package/src/daemon/host-transfer-proxy.ts +10 -10
  172. package/src/daemon/lifecycle.ts +23 -20
  173. package/src/daemon/meet-manifest-loader.ts +1 -7
  174. package/src/daemon/message-types/conversations.ts +6 -9
  175. package/src/daemon/message-types/home.ts +1 -13
  176. package/src/daemon/message-types/messages.ts +6 -14
  177. package/src/daemon/message-types/sync.ts +14 -0
  178. package/src/daemon/shutdown-handlers.ts +24 -5
  179. package/src/daemon/switch-inference-profile-tool.ts +52 -0
  180. package/src/daemon/tool-setup-types.ts +13 -0
  181. package/src/events/relationship-state-updated.ts +25 -0
  182. package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
  183. package/src/home/home-greeting.ts +0 -9
  184. package/src/home/suggested-prompts.ts +0 -9
  185. package/src/ipc/gateway-flag-listener.ts +123 -0
  186. package/src/ipc/skill-routes/registries.ts +8 -12
  187. package/src/memory/__tests__/db-async-query.test.ts +165 -0
  188. package/src/memory/__tests__/db-maintenance.test.ts +115 -0
  189. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +241 -0
  190. package/src/memory/__tests__/jobs-store-job-classes.test.ts +28 -1
  191. package/src/memory/__tests__/memory-retrospective-job.test.ts +7 -0
  192. package/src/memory/auto-analysis-enqueue.ts +5 -1
  193. package/src/memory/conversation-crud.ts +71 -70
  194. package/src/memory/conversation-starters-cadence.ts +3 -1
  195. package/src/memory/conversation-title-service.ts +19 -3
  196. package/src/memory/db-async-query.ts +214 -0
  197. package/src/memory/db-init.ts +10 -0
  198. package/src/memory/db-maintenance.ts +30 -21
  199. package/src/memory/graph/bootstrap.ts +8 -1
  200. package/src/memory/graph/capability-seed.ts +7 -3
  201. package/src/memory/graph/conversation-graph-memory.ts +100 -17
  202. package/src/memory/graph/extraction.ts +1 -5
  203. package/src/memory/graph/graph-search.ts +7 -1
  204. package/src/memory/indexer.ts +28 -18
  205. package/src/memory/job-handlers/cleanup.ts +76 -18
  206. package/src/memory/job-handlers/conversation-starters.ts +1 -4
  207. package/src/memory/jobs/embed-pkb-file.ts +6 -1
  208. package/src/memory/jobs-store.ts +14 -0
  209. package/src/memory/jobs-worker.ts +55 -22
  210. package/src/memory/llm-request-log-source-clickhouse.ts +42 -2
  211. package/src/memory/llm-request-log-source-local.ts +7 -0
  212. package/src/memory/llm-request-log-source.ts +9 -2
  213. package/src/memory/llm-request-log-store.ts +43 -1
  214. package/src/memory/llm-usage-store.ts +24 -0
  215. package/src/memory/memory-retrospective-enqueue.ts +8 -1
  216. package/src/memory/memory-retrospective-job.ts +5 -0
  217. package/src/memory/memory-v2-activation-log-store.ts +15 -6
  218. package/src/memory/migrations/260-rename-cleaned-at.ts +44 -0
  219. package/src/memory/migrations/261-llm-usage-add-raw-usage.ts +36 -0
  220. package/src/memory/migrations/262-memory-v3-coactivation.ts +57 -0
  221. package/src/memory/migrations/263-memory-v3-auto-edges.ts +50 -0
  222. package/src/memory/migrations/264-llm-request-log-call-site.ts +29 -0
  223. package/src/memory/migrations/index.ts +17 -0
  224. package/src/memory/migrations/registry.ts +33 -0
  225. package/src/memory/schema/conversations.ts +1 -1
  226. package/src/memory/schema/infrastructure.ts +21 -0
  227. package/src/memory/tool-usage-store.ts +36 -8
  228. package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -0
  229. package/src/memory/v2/__tests__/harness-compare.test.ts +186 -0
  230. package/src/memory/v2/__tests__/harness-metrics.test.ts +74 -0
  231. package/src/memory/v2/__tests__/harness-oracle.test.ts +257 -0
  232. package/src/memory/v2/__tests__/harness-replay-input.test.ts +225 -0
  233. package/src/memory/v2/__tests__/harness-runner.test.ts +109 -0
  234. package/src/memory/v2/__tests__/injection.test.ts +127 -98
  235. package/src/memory/v2/__tests__/qdrant.test.ts +36 -0
  236. package/src/memory/v2/__tests__/router.test.ts +171 -3
  237. package/src/memory/v2/harness/compare.ts +57 -0
  238. package/src/memory/v2/harness/metrics.ts +124 -0
  239. package/src/memory/v2/harness/oracle.ts +145 -0
  240. package/src/memory/v2/harness/replay-input.ts +224 -0
  241. package/src/memory/v2/harness/retriever.ts +74 -0
  242. package/src/memory/v2/harness/router-retriever.ts +43 -0
  243. package/src/memory/v2/harness/runner.ts +106 -0
  244. package/src/memory/v2/harness/trace.ts +58 -0
  245. package/src/memory/v2/injection.ts +21 -15
  246. package/src/memory/v2/prompts/router.ts +26 -1
  247. package/src/memory/v2/qdrant.ts +14 -2
  248. package/src/memory/v2/router.ts +171 -18
  249. package/src/memory/v3/__tests__/coactivation-store.test.ts +422 -0
  250. package/src/memory/v3/__tests__/consolidation-job.test.ts +468 -0
  251. package/src/memory/v3/__tests__/edge-learning-job.test.ts +324 -0
  252. package/src/memory/v3/__tests__/edges.test.ts +563 -0
  253. package/src/memory/v3/__tests__/filter.test.ts +512 -0
  254. package/src/memory/v3/__tests__/gate.test.ts +574 -0
  255. package/src/memory/v3/__tests__/index-composition.test.ts +233 -0
  256. package/src/memory/v3/__tests__/loop.test.ts +530 -0
  257. package/src/memory/v3/__tests__/retriever.test.ts +226 -0
  258. package/src/memory/v3/__tests__/scouts.test.ts +440 -0
  259. package/src/memory/v3/__tests__/shadow-middleware.test.ts +312 -0
  260. package/src/memory/v3/__tests__/system-prompts.test.ts +154 -0
  261. package/src/memory/v3/__tests__/traversal.test.ts +469 -0
  262. package/src/memory/v3/__tests__/tree-index.test.ts +280 -0
  263. package/src/memory/v3/__tests__/tree-store.test.ts +529 -0
  264. package/src/memory/v3/__tests__/tree-walk.test.ts +707 -0
  265. package/src/memory/v3/__tests__/validate.test.ts +245 -0
  266. package/src/memory/v3/auto-edges.ts +223 -0
  267. package/src/memory/v3/coactivation-store.ts +124 -0
  268. package/src/memory/v3/consolidation-job.ts +323 -0
  269. package/src/memory/v3/edge-learning-job.ts +160 -0
  270. package/src/memory/v3/edges.ts +249 -0
  271. package/src/memory/v3/filter.ts +281 -0
  272. package/src/memory/v3/gate.ts +334 -0
  273. package/src/memory/v3/index-composition.ts +113 -0
  274. package/src/memory/v3/llm-capture.ts +46 -0
  275. package/src/memory/v3/loop.ts +382 -0
  276. package/src/memory/v3/maintenance.ts +144 -0
  277. package/src/memory/v3/prompt-context.ts +33 -0
  278. package/src/memory/v3/prompts/consolidation.ts +458 -0
  279. package/src/memory/v3/prompts/system-prompts.ts +196 -0
  280. package/src/memory/v3/retriever.ts +33 -0
  281. package/src/memory/v3/scouts.ts +420 -0
  282. package/src/memory/v3/shadow-middleware.ts +305 -0
  283. package/src/memory/v3/traversal.ts +206 -0
  284. package/src/memory/v3/tree-index.ts +237 -0
  285. package/src/memory/v3/tree-store.ts +394 -0
  286. package/src/memory/v3/tree-walk.ts +351 -0
  287. package/src/memory/v3/types.ts +65 -0
  288. package/src/memory/v3/validate.ts +300 -0
  289. package/src/notifications/adapters/macos.ts +18 -1
  290. package/src/notifications/adapters/platform.ts +1 -1
  291. package/src/notifications/decision-engine.ts +1 -4
  292. package/src/notifications/emit-signal.ts +29 -49
  293. package/src/permissions/prompter.ts +3 -3
  294. package/src/permissions/question-prompter.ts +5 -2
  295. package/src/permissions/secret-prompter.ts +2 -2
  296. package/src/plugin-api/index.ts +4 -0
  297. package/src/plugin-api/types.ts +7 -33
  298. package/src/plugins/defaults/index.ts +6 -0
  299. package/src/plugins/defaults/injectors.ts +18 -11
  300. package/src/plugins/external-plugin-loader.ts +5 -68
  301. package/src/plugins/types.ts +11 -16
  302. package/src/proactive-artifact/aux-message-injector.ts +17 -4
  303. package/src/prompts/__tests__/task-progress-hint-section.test.ts +3 -9
  304. package/src/prompts/persona-resolver.ts +36 -21
  305. package/src/prompts/sections.ts +39 -7
  306. package/src/prompts/system-prompt.ts +50 -185
  307. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  308. package/src/prompts/templates/system-sections.ts +230 -8
  309. package/src/providers/__tests__/connection-model-compat.test.ts +234 -0
  310. package/src/providers/__tests__/retry-callsite.test.ts +85 -5
  311. package/src/providers/anthropic/client.ts +32 -66
  312. package/src/providers/call-site-routing.ts +14 -2
  313. package/src/providers/connection-model-compat.ts +38 -0
  314. package/src/providers/connection-resolution.ts +16 -2
  315. package/src/providers/gemini/client.ts +49 -6
  316. package/src/providers/inference/adapter-factory.ts +3 -0
  317. package/src/providers/minimax/client.ts +106 -0
  318. package/src/providers/model-catalog.ts +43 -0
  319. package/src/providers/model-intents.ts +1 -1
  320. package/src/providers/openai/chat-completions-provider.ts +6 -3
  321. package/src/providers/openai/codex-models.ts +18 -0
  322. package/src/providers/openai/responses-provider.ts +78 -21
  323. package/src/providers/provider-send-message.ts +7 -1
  324. package/src/providers/retry.ts +34 -3
  325. package/src/providers/thinking-config.ts +26 -1
  326. package/src/providers/usage-tracking.ts +2 -0
  327. package/src/runtime/AGENTS.md +2 -2
  328. package/src/runtime/agent-wake.ts +1 -0
  329. package/src/runtime/assistant-event-hub.ts +76 -6
  330. package/src/runtime/auth/route-policy.ts +36 -0
  331. package/src/runtime/btw-sidechain.ts +0 -6
  332. package/src/runtime/http-types.ts +0 -2
  333. package/src/runtime/migrations/vbundle-builder.ts +10 -3
  334. package/src/runtime/pending-interactions.ts +0 -1
  335. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +106 -0
  336. package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +25 -6
  337. package/src/runtime/routes/__tests__/plugins-routes.test.ts +512 -0
  338. package/src/runtime/routes/acp-routes.test.ts +255 -6
  339. package/src/runtime/routes/acp-routes.ts +8 -1
  340. package/src/runtime/routes/avatar-routes.ts +10 -10
  341. package/src/runtime/routes/background-wake-routes.ts +188 -0
  342. package/src/runtime/routes/browser-tabs-routes.ts +200 -0
  343. package/src/runtime/routes/btw-routes.ts +0 -6
  344. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  345. package/src/runtime/routes/conversation-list-routes.ts +12 -4
  346. package/src/runtime/routes/conversation-management-routes.ts +77 -20
  347. package/src/runtime/routes/conversation-query-routes.ts +142 -36
  348. package/src/runtime/routes/conversation-routes.ts +252 -410
  349. package/src/runtime/routes/conversation-starter-routes.ts +6 -3
  350. package/src/runtime/routes/disk-pressure-routes.ts +1 -1
  351. package/src/runtime/routes/domain-routes.ts +60 -10
  352. package/src/runtime/routes/email-routes.ts +5 -2
  353. package/src/runtime/routes/events-routes.ts +54 -10
  354. package/src/runtime/routes/group-routes.ts +24 -8
  355. package/src/runtime/routes/host-browser-routes.ts +10 -2
  356. package/src/runtime/routes/host-cu-routes.ts +2 -2
  357. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +96 -3
  358. package/src/runtime/routes/index.ts +8 -0
  359. package/src/runtime/routes/inference-profile-session-handler.ts +22 -12
  360. package/src/runtime/routes/inference-profile-session-routes.ts +7 -1
  361. package/src/runtime/routes/llm-call-sites-routes.ts +32 -5
  362. package/src/runtime/routes/memory-item-routes.ts +8 -3
  363. package/src/runtime/routes/memory-v2-routes.ts +215 -5
  364. package/src/runtime/routes/memory-v3-routes.ts +316 -0
  365. package/src/runtime/routes/migration-routes.ts +21 -24
  366. package/src/runtime/routes/plugins-routes.ts +337 -0
  367. package/src/runtime/routes/rename-conversation-routes.ts +6 -2
  368. package/src/runtime/routes/secret-routes.ts +25 -5
  369. package/src/runtime/routes/settings-routes.ts +12 -11
  370. package/src/runtime/routes/slack-channel-routes.ts +5 -4
  371. package/src/runtime/routes/workspace-routes.ts +25 -10
  372. package/src/runtime/sync/resource-sync-events.ts +106 -38
  373. package/src/runtime/sync/sync-publisher.test.ts +49 -0
  374. package/src/runtime/sync/sync-publisher.ts +2 -1
  375. package/src/runtime/verification-outbound-actions.ts +73 -1
  376. package/src/telemetry/types.ts +12 -0
  377. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  378. package/src/telemetry/usage-telemetry-reporter.ts +1 -0
  379. package/src/tools/acp/spawn.test.ts +119 -0
  380. package/src/tools/acp/spawn.ts +15 -2
  381. package/src/tools/apps/definitions.ts +2 -8
  382. package/src/tools/ask-question/ask-question-tool.test.ts +3 -3
  383. package/src/tools/ask-question/ask-question-tool.ts +38 -45
  384. package/src/tools/browser/__tests__/pinned-tabs.test.ts +70 -0
  385. package/src/tools/browser/browser-execution.ts +16 -3
  386. package/src/tools/browser/cdp-client/__tests__/browser-tabs-factory.test.ts +402 -0
  387. package/src/tools/browser/cdp-client/__tests__/types.test.ts +3 -0
  388. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +12 -0
  389. package/src/tools/browser/cdp-client/extension-cdp-client.ts +27 -1
  390. package/src/tools/browser/cdp-client/factory.ts +100 -17
  391. package/src/tools/browser/cdp-client/local-cdp-client.ts +12 -0
  392. package/src/tools/browser/cdp-client/types.ts +65 -0
  393. package/src/tools/browser/pinned-tabs.ts +96 -40
  394. package/src/tools/computer-use/definitions.ts +22 -78
  395. package/src/tools/credential-execution/make-authenticated-request.ts +3 -9
  396. package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -9
  397. package/src/tools/credential-execution/run-authenticated-command.ts +3 -9
  398. package/src/tools/credentials/vault.ts +3 -9
  399. package/src/tools/document/document-tool.ts +59 -0
  400. package/src/tools/execution-target.ts +21 -23
  401. package/src/tools/executor.ts +6 -1
  402. package/src/tools/filesystem/edit.ts +3 -9
  403. package/src/tools/filesystem/list.ts +3 -9
  404. package/src/tools/filesystem/read.ts +3 -9
  405. package/src/tools/filesystem/write.ts +3 -9
  406. package/src/tools/host-filesystem/edit.ts +3 -9
  407. package/src/tools/host-filesystem/read.ts +3 -9
  408. package/src/tools/host-filesystem/transfer.ts +3 -9
  409. package/src/tools/host-filesystem/write.ts +3 -9
  410. package/src/tools/host-terminal/host-shell.ts +3 -9
  411. package/src/tools/mcp/mcp-tool-factory.ts +1 -8
  412. package/src/tools/memory/register.test.ts +1 -1
  413. package/src/tools/memory/register.ts +4 -9
  414. package/src/tools/network/web-fetch.ts +3 -9
  415. package/src/tools/network/web-search.ts +25 -32
  416. package/src/tools/registry.ts +7 -23
  417. package/src/tools/schema-transforms.ts +1 -1
  418. package/src/tools/skills/execute.ts +3 -9
  419. package/src/tools/skills/load.ts +3 -9
  420. package/src/tools/skills/skill-tool-factory.ts +1 -8
  421. package/src/tools/subagent/notify-parent.ts +3 -9
  422. package/src/tools/system/request-permission.ts +3 -9
  423. package/src/tools/terminal/shell.ts +3 -9
  424. package/src/tools/tool-defaults.ts +94 -0
  425. package/src/tools/types.ts +27 -98
  426. package/src/tools/ui-surface/definitions.ts +6 -22
  427. package/src/usage/pricing.ts +23 -0
  428. package/src/usage/types.ts +12 -0
  429. package/src/util/logger.ts +16 -7
  430. package/src/util/platform.ts +7 -2
  431. package/src/util/sqlite3-runtime.ts +65 -0
  432. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +1 -0
  433. package/src/workspace/migrations/089-move-memory-tree-out-of-v3.ts +86 -0
  434. package/src/workspace/migrations/registry.ts +2 -0
  435. package/src/__tests__/compaction-strip-metadata-clear.test.ts +0 -206
  436. package/src/__tests__/message-complete-display-id.test.ts +0 -175
  437. package/src/daemon/query-complexity-router.ts +0 -75
  438. package/src/prompts/cache-boundary.ts +0 -8
@@ -412,6 +412,253 @@ Examples:
412
412
  for (const meta of BROWSER_OPERATION_META) {
413
413
  buildSubcommand(browser, meta);
414
414
  }
415
+
416
+ // -- tabs subcommand group
417
+ const tabs = browser
418
+ .command("tabs")
419
+ .description("Manage browser tabs");
420
+
421
+ tabs
422
+ .command("list")
423
+ .description("List all open browser tabs")
424
+ .option("--pretty", "Human-readable table output instead of JSON")
425
+ .action(async (opts: { pretty?: boolean }) => {
426
+ const parentOpts = browser.opts() as {
427
+ session?: string;
428
+ json?: boolean;
429
+ targetClientId?: string;
430
+ };
431
+ const sessionId = parentOpts.session ?? "default";
432
+ const jsonMode = parentOpts.json ?? false;
433
+ const conversationId = resolveContextConversationId();
434
+ const targetClientId = parentOpts.targetClientId;
435
+
436
+ const ipcResult = await cliIpcCall<{
437
+ ok: boolean;
438
+ tabs?: Array<{
439
+ tabId?: number;
440
+ windowId?: number;
441
+ url?: string;
442
+ title?: string;
443
+ active: boolean;
444
+ pinned: boolean;
445
+ }>;
446
+ }>(
447
+ "browser_tabs",
448
+ {
449
+ body: {
450
+ command: "list",
451
+ sessionId,
452
+ ...(conversationId ? { conversationId } : {}),
453
+ ...(targetClientId ? { targetClientId } : {}),
454
+ },
455
+ },
456
+ { timeoutMs: 30_000 },
457
+ );
458
+
459
+ if (!ipcResult.ok) {
460
+ if (jsonMode) {
461
+ process.stdout.write(
462
+ JSON.stringify({ ok: false, error: ipcResult.error }) + "\n",
463
+ );
464
+ } else {
465
+ log.error(`Error: ${ipcResult.error}`);
466
+ }
467
+ process.exitCode = 1;
468
+ return;
469
+ }
470
+
471
+ const tabList = ipcResult.result?.tabs ?? [];
472
+
473
+ if (jsonMode || !opts.pretty) {
474
+ process.stdout.write(JSON.stringify(tabList) + "\n");
475
+ } else {
476
+ if (tabList.length === 0) {
477
+ log.info("No tabs found.");
478
+ return;
479
+ }
480
+ log.info(
481
+ `${"ID".padEnd(8)} ${"WIN".padEnd(5)} ${"ACT".padEnd(4)} ${"PIN".padEnd(4)} URL/Title`,
482
+ );
483
+ log.info("-".repeat(80));
484
+ for (const tab of tabList) {
485
+ const id = String(tab.tabId ?? "?").padEnd(8);
486
+ const win = String(tab.windowId ?? "?").padEnd(5);
487
+ const act = (tab.active ? "yes" : "no").padEnd(4);
488
+ const pin = (tab.pinned ? "yes" : "no").padEnd(4);
489
+ const label = tab.url ?? tab.title ?? "(untitled)";
490
+ log.info(`${id} ${win} ${act} ${pin} ${label}`);
491
+ }
492
+ }
493
+ });
494
+
495
+ tabs
496
+ .command("select")
497
+ .description("Select (activate) a browser tab by ID")
498
+ .requiredOption("--tab-id <id>", "Tab ID to select", Number)
499
+ .action(async (opts: { tabId: number }) => {
500
+ const parentOpts = browser.opts() as {
501
+ session?: string;
502
+ json?: boolean;
503
+ targetClientId?: string;
504
+ };
505
+ const sessionId = parentOpts.session ?? "default";
506
+ const jsonMode = parentOpts.json ?? false;
507
+ const conversationId = resolveContextConversationId();
508
+ const targetClientId = parentOpts.targetClientId;
509
+
510
+ const ipcResult = await cliIpcCall<{ ok: boolean; tab?: unknown }>(
511
+ "browser_tabs",
512
+ {
513
+ body: {
514
+ command: "select",
515
+ sessionId,
516
+ tabId: opts.tabId,
517
+ ...(conversationId ? { conversationId } : {}),
518
+ ...(targetClientId ? { targetClientId } : {}),
519
+ },
520
+ },
521
+ { timeoutMs: 30_000 },
522
+ );
523
+
524
+ if (!ipcResult.ok) {
525
+ if (jsonMode) {
526
+ process.stdout.write(
527
+ JSON.stringify({ ok: false, error: ipcResult.error }) + "\n",
528
+ );
529
+ } else {
530
+ log.error(`Error: ${ipcResult.error}`);
531
+ }
532
+ process.exitCode = 1;
533
+ return;
534
+ }
535
+
536
+ if (jsonMode) {
537
+ process.stdout.write(
538
+ JSON.stringify({ ok: true, tab: ipcResult.result?.tab }) + "\n",
539
+ );
540
+ } else {
541
+ log.info(`Selected tab ${opts.tabId}`);
542
+ }
543
+ });
544
+
545
+ tabs
546
+ .command("new")
547
+ .description("Open a new browser tab")
548
+ .option("--url <url>", "URL to navigate the new tab to")
549
+ .action(async (opts: { url?: string }) => {
550
+ const parentOpts = browser.opts() as {
551
+ session?: string;
552
+ json?: boolean;
553
+ targetClientId?: string;
554
+ };
555
+ const sessionId = parentOpts.session ?? "default";
556
+ const jsonMode = parentOpts.json ?? false;
557
+ const conversationId = resolveContextConversationId();
558
+ const targetClientId = parentOpts.targetClientId;
559
+
560
+ const ipcResult = await cliIpcCall<{
561
+ ok: boolean;
562
+ tabId?: string;
563
+ clientId?: string;
564
+ }>(
565
+ "browser_tabs",
566
+ {
567
+ body: {
568
+ command: "new",
569
+ sessionId,
570
+ ...(opts.url ? { url: opts.url } : {}),
571
+ ...(conversationId ? { conversationId } : {}),
572
+ ...(targetClientId ? { targetClientId } : {}),
573
+ },
574
+ },
575
+ { timeoutMs: 30_000 },
576
+ );
577
+
578
+ if (!ipcResult.ok) {
579
+ if (jsonMode) {
580
+ process.stdout.write(
581
+ JSON.stringify({ ok: false, error: ipcResult.error }) + "\n",
582
+ );
583
+ } else {
584
+ log.error(`Error: ${ipcResult.error}`);
585
+ }
586
+ process.exitCode = 1;
587
+ return;
588
+ }
589
+
590
+ if (jsonMode) {
591
+ process.stdout.write(
592
+ JSON.stringify({
593
+ ok: true,
594
+ tabId: ipcResult.result?.tabId,
595
+ clientId: ipcResult.result?.clientId,
596
+ }) + "\n",
597
+ );
598
+ } else {
599
+ log.info(
600
+ `Opened new tab${ipcResult.result?.tabId ? ` (ID: ${ipcResult.result.tabId})` : ""}`,
601
+ );
602
+ }
603
+ });
604
+
605
+ tabs
606
+ .command("close")
607
+ .description("Close a browser tab by ID")
608
+ .requiredOption("--tab-id <id>", "Tab ID to close", Number)
609
+ .action(async (opts: { tabId: number }) => {
610
+ const parentOpts = browser.opts() as {
611
+ session?: string;
612
+ json?: boolean;
613
+ targetClientId?: string;
614
+ };
615
+ const sessionId = parentOpts.session ?? "default";
616
+ const jsonMode = parentOpts.json ?? false;
617
+ const conversationId = resolveContextConversationId();
618
+ const targetClientId = parentOpts.targetClientId;
619
+
620
+ const ipcResult = await cliIpcCall<{
621
+ ok: boolean;
622
+ closed?: boolean;
623
+ tabId?: number;
624
+ }>(
625
+ "browser_tabs",
626
+ {
627
+ body: {
628
+ command: "close",
629
+ sessionId,
630
+ tabId: opts.tabId,
631
+ ...(conversationId ? { conversationId } : {}),
632
+ ...(targetClientId ? { targetClientId } : {}),
633
+ },
634
+ },
635
+ { timeoutMs: 30_000 },
636
+ );
637
+
638
+ if (!ipcResult.ok) {
639
+ if (jsonMode) {
640
+ process.stdout.write(
641
+ JSON.stringify({ ok: false, error: ipcResult.error }) + "\n",
642
+ );
643
+ } else {
644
+ log.error(`Error: ${ipcResult.error}`);
645
+ }
646
+ process.exitCode = 1;
647
+ return;
648
+ }
649
+
650
+ if (jsonMode) {
651
+ process.stdout.write(
652
+ JSON.stringify({
653
+ ok: true,
654
+ closed: ipcResult.result?.closed,
655
+ tabId: opts.tabId,
656
+ }) + "\n",
657
+ );
658
+ } else {
659
+ log.info(`Closed tab ${opts.tabId}`);
660
+ }
661
+ });
415
662
  },
416
663
  });
417
664
  }
@@ -38,12 +38,17 @@ for email and web presence. DNS managed by the Vellum platform.
38
38
 
39
39
  Examples:
40
40
  $ assistant domain register velly
41
+ $ assistant domain register velly --email-username hello
41
42
  $ assistant domain register --json
42
- $ assistant domain status`,
43
+ $ assistant domain status velly`,
43
44
  );
44
45
 
45
46
  domain
46
47
  .command("register [subdomain]")
48
+ .option(
49
+ "--email-username <username>",
50
+ "Also register an email address (e.g. --email-username hello → hello@<subdomain>.domain)",
51
+ )
47
52
  .description(
48
53
  `Register a custom subdomain on ${baseDomain} for this assistant`,
49
54
  )
@@ -54,6 +59,10 @@ Arguments:
54
59
  subdomain The subdomain to register (e.g. "velly" → velly.${baseDomain}).
55
60
  If omitted, the platform derives it from the assistant's name.
56
61
 
62
+ Options:
63
+ --email-username <username> Also register an email address for the domain
64
+ (e.g. --email-username hello → hello@velly.${baseDomain})
65
+
57
66
  Registers a subdomain at <subdomain>.${baseDomain}. DNS managed by the
58
67
  Vellum platform — no manual DNS changes needed.
59
68
 
@@ -61,27 +70,36 @@ Examples:
61
70
  $ assistant domain register velly
62
71
  ✓ Registered velly.${baseDomain}
63
72
 
73
+ $ assistant domain register velly --email-username hello
74
+ ✓ Registered velly.${baseDomain} (email: hello@velly.${baseDomain})
75
+
64
76
  $ assistant domain register
65
77
  ✓ Registered my-assistant.${baseDomain}
66
78
 
67
79
  $ assistant domain register velly --json
68
- {"domain":"velly.${baseDomain}","id":"...","status":"active","verified":true}`,
80
+ {"domain":"velly.${baseDomain}","id":"..."}`,
69
81
  )
70
82
  .action(
71
- async (subdomain: string | undefined, _opts: unknown, cmd: Command) => {
83
+ async (
84
+ subdomain: string | undefined,
85
+ opts: { emailUsername?: string },
86
+ cmd: Command,
87
+ ) => {
72
88
  const body: Record<string, string> = {};
73
89
  if (subdomain) {
74
90
  body.subdomain = subdomain;
75
91
  }
92
+ if (opts.emailUsername) {
93
+ body.email_username = opts.emailUsername;
94
+ }
76
95
 
77
96
  const r = await cliIpcCall<{
78
97
  id: string;
79
98
  subdomain?: string;
80
99
  domain?: string;
81
- status?: string;
82
- verified?: boolean;
83
100
  created_at?: string;
84
101
  created?: string;
102
+ email_error?: { detail: string; code: string };
85
103
  }>("domain_register", { body });
86
104
 
87
105
  if (!r.ok)
@@ -104,10 +122,16 @@ Examples:
104
122
  if (shouldOutputJson(cmd)) {
105
123
  writeOutput(cmd, data);
106
124
  } else {
107
- log.info(`✓ Registered ${displayDomain}`);
108
- if (data.verified === false) {
125
+ if (opts.emailUsername && !data.email_error) {
109
126
  log.info(
110
- " ⚠ Domain verification pending — this usually resolves within a few seconds.",
127
+ `✓ Registered ${displayDomain} (email: ${opts.emailUsername}@${displayDomain})`,
128
+ );
129
+ } else {
130
+ log.info(`✓ Registered ${displayDomain}`);
131
+ }
132
+ if (data.email_error) {
133
+ log.warn(
134
+ `⚠ Email registration failed: ${data.email_error.detail}`,
111
135
  );
112
136
  }
113
137
  }
@@ -115,32 +139,35 @@ Examples:
115
139
  );
116
140
 
117
141
  domain
118
- .command("status")
119
- .description("Show this assistant's domain registration and health")
142
+ .command("status <subdomain>")
143
+ .description(
144
+ "Show registration and DNS verification status for a subdomain",
145
+ )
120
146
  .addHelpText(
121
147
  "after",
122
148
  `
123
- Shows the domain currently registered for this assistant, including
124
- verification status and DNS health.
149
+ Arguments:
150
+ subdomain The subdomain to check (e.g. "velly").
151
+
152
+ Shows the domain's registration details and live DNS verification
153
+ status from the email provider.
125
154
 
126
155
  Examples:
127
- $ assistant domain status
128
- Domain: velly.${baseDomain}
129
- Status: active
130
- Verified: yes
131
- Created: 2026-04-15
132
-
133
- $ assistant domain status --json
134
- {"domain":"velly.${baseDomain}","status":"active","verified":true,...}`,
156
+ $ assistant domain status velly
157
+ Domain: velly.${baseDomain}
158
+ Verification: verified
159
+ DNS records have been verified. Your domain is ready to send and receive email.
160
+ Created: 2026-04-15
161
+
162
+ $ assistant domain status velly --json
163
+ {"domain":{"subdomain":"velly","id":"..."},"verification":{"status":"verified","message":"..."}}`,
135
164
  )
136
- .action(async (_opts: unknown, cmd: Command) => {
165
+ .action(async (subdomain: string, _opts: unknown, cmd: Command) => {
137
166
  const r = await cliIpcCall<{
138
167
  results: {
139
168
  id: string;
140
169
  subdomain?: string;
141
170
  domain?: string;
142
- status?: string;
143
- verified?: boolean;
144
171
  created_at?: string;
145
172
  created?: string;
146
173
  }[];
@@ -154,28 +181,51 @@ Examples:
154
181
 
155
182
  const data = r.result!;
156
183
  const domains = data.results ?? [];
184
+ const d = domains.find(
185
+ (entry) => entry.subdomain === subdomain,
186
+ );
187
+
188
+ if (!d) {
189
+ if (shouldOutputJson(cmd)) {
190
+ writeOutput(cmd, { error: `Domain "${subdomain}" not found` });
191
+ process.exitCode = 1;
192
+ } else {
193
+ log.error(
194
+ `Domain "${subdomain}" is not registered for this assistant.`,
195
+ );
196
+ process.exitCode = 1;
197
+ }
198
+ return;
199
+ }
200
+
201
+ // Fetch live verification status
202
+ const v = await cliIpcCall<{
203
+ domain: string;
204
+ status: string;
205
+ message: string;
206
+ }>("domain_verification_status", {
207
+ body: { domain_id: d.id },
208
+ });
209
+
210
+ const verification = v.ok ? v.result : undefined;
157
211
 
158
212
  if (shouldOutputJson(cmd)) {
159
- writeOutput(cmd, data);
160
- } else if (domains.length === 0) {
161
- log.info(
162
- "No domain registered for this assistant. Run: assistant domain register [subdomain]",
163
- );
213
+ writeOutput(cmd, { domain: d, verification: verification ?? null });
164
214
  } else {
165
- for (const d of domains) {
166
- const displayDomain =
167
- d.domain ??
168
- (d.subdomain ? `${d.subdomain}.${apexDomain}` : "unknown");
169
- const createdRaw = d.created_at ?? d.created;
170
- const createdDate = createdRaw
171
- ? createdRaw.split("T")[0]
172
- : "unknown";
173
- log.info(`Domain: ${displayDomain}`);
174
- if (d.status != null) log.info(`Status: ${d.status}`);
175
- if (d.verified != null)
176
- log.info(`Verified: ${d.verified ? "yes" : "no"}`);
177
- log.info(`Created: ${createdDate}`);
215
+ const displayDomain =
216
+ d.domain ??
217
+ (d.subdomain ? `${d.subdomain}.${apexDomain}` : "unknown");
218
+ const createdRaw = d.created_at ?? d.created;
219
+ const createdDate = createdRaw
220
+ ? createdRaw.split("T")[0]
221
+ : "unknown";
222
+ log.info(`Domain: ${displayDomain}`);
223
+ const vStatus = verification?.status ?? "unknown";
224
+ log.info(`Verification: ${vStatus}`);
225
+ if (verification?.message) {
226
+ log.info(` ${verification.message}`);
178
227
  }
228
+ log.info(`Created: ${createdDate}`);
179
229
  }
180
230
  });
181
231
  },
@@ -30,6 +30,54 @@ interface InferenceSendResult {
30
30
  };
31
31
  }
32
32
 
33
+ const DEFAULT_INFERENCE_IPC_TIMEOUT_MS = 32 * 60 * 1000;
34
+ const MAX_TIMER_TIMEOUT_MS = 2_147_483_647;
35
+ const MAX_INFERENCE_TIMEOUT_SECONDS = Math.floor(MAX_TIMER_TIMEOUT_MS / 1000);
36
+
37
+ function parsePositiveIntegerOption(
38
+ raw: string | undefined,
39
+ flagName: string,
40
+ options: { max?: number } = {},
41
+ ): { ok: true; value: number | undefined } | { ok: false; error: string } {
42
+ if (raw === undefined) {
43
+ return { ok: true, value: undefined };
44
+ }
45
+
46
+ const trimmed = raw.trim();
47
+ if (!/^\d+$/.test(trimmed)) {
48
+ return {
49
+ ok: false,
50
+ error: `Invalid ${flagName} value. Must be a positive integer.`,
51
+ };
52
+ }
53
+
54
+ const value = Number(trimmed);
55
+ if (
56
+ !Number.isSafeInteger(value) ||
57
+ value < 1 ||
58
+ (options.max !== undefined && value > options.max)
59
+ ) {
60
+ return {
61
+ ok: false,
62
+ error:
63
+ `Invalid ${flagName} value. Must be a positive integer` +
64
+ (options.max !== undefined ? ` no greater than ${options.max}` : "") +
65
+ ".",
66
+ };
67
+ }
68
+
69
+ return { ok: true, value };
70
+ }
71
+
72
+ function writeCliError(message: string, jsonOutput?: boolean): void {
73
+ if (jsonOutput) {
74
+ process.stdout.write(JSON.stringify({ ok: false, error: message }) + "\n");
75
+ } else {
76
+ log.error(message);
77
+ }
78
+ process.exitCode = 1;
79
+ }
80
+
33
81
  // ── Send subcommand ──────────────────────────────────────────────────
34
82
 
35
83
  /**
@@ -47,6 +95,11 @@ function attachSendSubcommand(group: Command): void {
47
95
  "Apply a named inference profile from llm.profiles for this single call",
48
96
  )
49
97
  .option("--max-tokens <n>", "Max response tokens", undefined)
98
+ .option(
99
+ "--timeout-seconds <seconds>",
100
+ "Maximum time to wait for the inference response",
101
+ undefined,
102
+ )
50
103
  .option("--json", "Output structured JSON")
51
104
  .argument("[message...]", "User message (joined with spaces)")
52
105
  .addHelpText(
@@ -59,12 +112,15 @@ Behavioral notes:
59
112
  only. It does NOT open a session — to pin a profile to a conversation,
60
113
  use 'assistant inference profile open <name>'.
61
114
  - --profile layers below --model: --model still wins on the model field.
115
+ - Long-running requests wait up to 32 minutes by default. Use
116
+ --timeout-seconds to adjust the wait budget for this call.
62
117
  - Requires a configured LLM provider (see 'assistant config set').
63
118
 
64
119
  Examples:
65
120
  $ assistant inference send "What is 2+2?"
66
121
  $ echo "Summarize this" | assistant inference send
67
122
  $ assistant llm send --system-prompt "You are a poet" "Write a haiku"
123
+ $ assistant inference send --timeout-seconds 300 "Draft a long memo"
68
124
  $ assistant inference send --model claude-sonnet-4-20250514 --json "Hello"
69
125
  $ assistant inference send --profile balanced "Explain RFC 1149"`,
70
126
  )
@@ -76,30 +132,37 @@ Examples:
76
132
  model?: string;
77
133
  profile?: string;
78
134
  maxTokens?: string;
135
+ timeoutSeconds?: string;
79
136
  json?: boolean;
80
137
  },
81
138
  ) => {
82
139
  const { systemPrompt, model, profile, json: jsonOutput } = opts;
83
- const maxTokens = opts.maxTokens
84
- ? parseInt(opts.maxTokens, 10)
85
- : undefined;
86
-
87
- if (
88
- opts.maxTokens !== undefined &&
89
- (!Number.isFinite(maxTokens) || maxTokens! < 1)
90
- ) {
91
- const msg = "Invalid --max-tokens value. Must be a positive integer.";
92
- if (jsonOutput) {
93
- process.stdout.write(
94
- JSON.stringify({ ok: false, error: msg }) + "\n",
95
- );
96
- } else {
97
- log.error(msg);
98
- }
99
- process.exitCode = 1;
140
+ const parsedMaxTokens = parsePositiveIntegerOption(
141
+ opts.maxTokens,
142
+ "--max-tokens",
143
+ );
144
+ const parsedTimeoutSeconds = parsePositiveIntegerOption(
145
+ opts.timeoutSeconds,
146
+ "--timeout-seconds",
147
+ { max: MAX_INFERENCE_TIMEOUT_SECONDS },
148
+ );
149
+
150
+ if (!parsedMaxTokens.ok) {
151
+ writeCliError(parsedMaxTokens.error, jsonOutput);
152
+ return;
153
+ }
154
+
155
+ if (!parsedTimeoutSeconds.ok) {
156
+ writeCliError(parsedTimeoutSeconds.error, jsonOutput);
100
157
  return;
101
158
  }
102
159
 
160
+ const maxTokens = parsedMaxTokens.value;
161
+ const timeoutMs =
162
+ parsedTimeoutSeconds.value !== undefined
163
+ ? parsedTimeoutSeconds.value * 1000
164
+ : DEFAULT_INFERENCE_IPC_TIMEOUT_MS;
165
+
103
166
  // Determine user message: positional args or stdin.
104
167
  let messageText = messageParts.length > 0 ? messageParts.join(" ") : "";
105
168
 
@@ -114,14 +177,7 @@ Examples:
114
177
  if (!messageText) {
115
178
  const msg =
116
179
  "No message provided. Pass a message as an argument or pipe via stdin.";
117
- if (jsonOutput) {
118
- process.stdout.write(
119
- JSON.stringify({ ok: false, error: msg }) + "\n",
120
- );
121
- } else {
122
- log.error(msg);
123
- }
124
- process.exitCode = 1;
180
+ writeCliError(msg, jsonOutput);
125
181
  return;
126
182
  }
127
183
 
@@ -135,17 +191,14 @@ Examples:
135
191
  const ipcResult = await cliIpcCall<InferenceSendResult>(
136
192
  "inference_send",
137
193
  { body },
194
+ { timeoutMs },
138
195
  );
139
196
 
140
197
  if (!ipcResult.ok) {
141
- if (jsonOutput) {
142
- process.stdout.write(
143
- JSON.stringify({ ok: false, error: ipcResult.error }) + "\n",
144
- );
145
- } else {
146
- log.error(ipcResult.error ?? "Unknown error occurred");
147
- }
148
- process.exitCode = 1;
198
+ writeCliError(
199
+ ipcResult.error ?? "Unknown error occurred",
200
+ jsonOutput,
201
+ );
149
202
  return;
150
203
  }
151
204
 
@@ -179,9 +232,9 @@ export function registerInferenceCommand(program: Command): void {
179
232
  transport: "ipc",
180
233
  description: "LLM inference operations",
181
234
  build: (inference) => {
182
- inference.addHelpText(
183
- "after",
184
- `
235
+ inference.addHelpText(
236
+ "after",
237
+ `
185
238
  The inference command group sends requests to your configured LLM provider.
186
239
  The provider is resolved from your assistant config (llm.default.provider).
187
240
 
@@ -191,11 +244,11 @@ Examples:
191
244
  $ assistant llm send --system-prompt "Be concise" "What is TCP?"
192
245
  $ assistant inference send --model claude-sonnet-4-20250514 --json "Hello"
193
246
  $ assistant inference send --profile balanced "Explain RFC 1149"`,
194
- );
247
+ );
195
248
 
196
- attachSendSubcommand(inference);
197
- attachSessionSubcommand(inference);
198
- attachProvidersSubcommand(inference);
249
+ attachSendSubcommand(inference);
250
+ attachSessionSubcommand(inference);
251
+ attachProvidersSubcommand(inference);
199
252
  },
200
253
  });
201
254