@vellumai/assistant 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docker-entrypoint.sh +12 -2
  4. package/docs/architecture/memory.md +1 -1
  5. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  6. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  7. package/openapi.yaml +184 -69
  8. package/package.json +41 -41
  9. package/scripts/generate-openapi.ts +1 -2
  10. package/src/__tests__/acp-session.test.ts +43 -0
  11. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  12. package/src/__tests__/app-executors.test.ts +1 -0
  13. package/src/__tests__/app-source-watcher.test.ts +37 -11
  14. package/src/__tests__/approval-routes-http.test.ts +178 -1
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  17. package/src/__tests__/browser-manager.test.ts +40 -27
  18. package/src/__tests__/catalog-files.test.ts +862 -0
  19. package/src/__tests__/channel-approvals.test.ts +53 -0
  20. package/src/__tests__/checker.test.ts +104 -170
  21. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  22. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  23. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  24. package/src/__tests__/config-schema.test.ts +125 -48
  25. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  26. package/src/__tests__/context-overflow-approval.test.ts +21 -6
  27. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  28. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  29. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  30. package/src/__tests__/conversation-attachments.test.ts +80 -4
  31. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  32. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  33. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  34. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  35. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  36. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  37. package/src/__tests__/conversation-queue.test.ts +45 -2
  38. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  39. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  40. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  41. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  42. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  43. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  44. package/src/__tests__/conversation-store.test.ts +195 -0
  45. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  46. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -3
  47. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  48. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  49. package/src/__tests__/credential-vault.test.ts +152 -13
  50. package/src/__tests__/credentials-cli.test.ts +2 -2
  51. package/src/__tests__/date-context.test.ts +4 -4
  52. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  53. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  54. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  55. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  56. package/src/__tests__/gemini-provider.test.ts +2 -2
  57. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  58. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  59. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  60. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  61. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  62. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  63. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  64. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  65. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  66. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  67. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  68. package/src/__tests__/host-browser-routes.test.ts +198 -0
  69. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  70. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  71. package/src/__tests__/host-file-proxy.test.ts +185 -1
  72. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  73. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  74. package/src/__tests__/host-shell-tool.test.ts +1 -11
  75. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  76. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  77. package/src/__tests__/inline-command-runner.test.ts +7 -5
  78. package/src/__tests__/integration-status.test.ts +6 -7
  79. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  80. package/src/__tests__/log-export-workspace.test.ts +190 -0
  81. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  82. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  83. package/src/__tests__/mcp-health-check.test.ts +10 -3
  84. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  85. package/src/__tests__/migration-export-http.test.ts +61 -2
  86. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  87. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  88. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  89. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  90. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  91. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  92. package/src/__tests__/oauth-cli.test.ts +707 -60
  93. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  94. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  95. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  96. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  97. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  98. package/src/__tests__/oauth-store.test.ts +1386 -182
  99. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  100. package/src/__tests__/onboarding-template-contract.test.ts +74 -55
  101. package/src/__tests__/openai-provider.test.ts +2 -2
  102. package/src/__tests__/outlook-categories.test.ts +1 -1
  103. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  104. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  105. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  106. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  107. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  108. package/src/__tests__/outlook-trash.test.ts +1 -1
  109. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  110. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  111. package/src/__tests__/permission-mode.test.ts +28 -56
  112. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  113. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  114. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  115. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  116. package/src/__tests__/require-fresh-approval.test.ts +40 -3
  117. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  118. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  119. package/src/__tests__/schedule-routes.test.ts +162 -0
  120. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  121. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  123. package/src/__tests__/set-permission-mode.test.ts +13 -250
  124. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  125. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  126. package/src/__tests__/slack-channel-config.test.ts +12 -15
  127. package/src/__tests__/subagent-detail.test.ts +44 -2
  128. package/src/__tests__/subagent-disposal.test.ts +1 -0
  129. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  130. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  131. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  132. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  133. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  134. package/src/__tests__/subagent-tools.test.ts +1 -0
  135. package/src/__tests__/subagent-types.test.ts +1 -0
  136. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  137. package/src/__tests__/system-prompt.test.ts +72 -1
  138. package/src/__tests__/task-scheduler.test.ts +32 -6
  139. package/src/__tests__/telegram-config.test.ts +10 -13
  140. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  141. package/src/__tests__/terminal-tools.test.ts +11 -5
  142. package/src/__tests__/test-preload.ts +14 -0
  143. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  144. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  145. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  146. package/src/__tests__/tool-executor.test.ts +0 -1
  147. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  148. package/src/__tests__/top-level-renderer.test.ts +73 -1
  149. package/src/__tests__/transport-hints-queue.test.ts +62 -0
  150. package/src/__tests__/trust-store.test.ts +4 -4
  151. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  152. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  153. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  154. package/src/__tests__/workspace-policy.test.ts +2 -7
  155. package/src/acp/client-handler.ts +30 -4
  156. package/src/agent/loop.ts +12 -35
  157. package/src/approvals/guardian-request-resolvers.ts +21 -15
  158. package/src/browser-session/__tests__/manager.test.ts +297 -0
  159. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  160. package/src/browser-session/backends/extension.ts +26 -0
  161. package/src/browser-session/backends/local.ts +24 -0
  162. package/src/browser-session/events.ts +164 -0
  163. package/src/browser-session/index.ts +27 -0
  164. package/src/browser-session/manager.ts +159 -0
  165. package/src/browser-session/types.ts +28 -0
  166. package/src/channels/__tests__/types.test.ts +134 -0
  167. package/src/channels/types.ts +55 -0
  168. package/src/cli/__tests__/run-assistant-command.ts +34 -7
  169. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  170. package/src/cli/commands/browser-relay.ts +339 -409
  171. package/src/cli/commands/credentials.ts +3 -3
  172. package/src/cli/commands/default-action.ts +68 -1
  173. package/src/cli/commands/email.ts +18 -13
  174. package/src/cli/commands/mcp.ts +16 -4
  175. package/src/cli/commands/oauth/__tests__/connect.test.ts +68 -41
  176. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  177. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  178. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  179. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  180. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  181. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  182. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  183. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  184. package/src/cli/commands/oauth/apps.ts +7 -4
  185. package/src/cli/commands/oauth/connect.ts +16 -2
  186. package/src/cli/commands/oauth/disconnect.ts +1 -1
  187. package/src/cli/commands/oauth/providers.ts +200 -36
  188. package/src/cli/commands/oauth/shared.ts +5 -5
  189. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  190. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  191. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  192. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  193. package/src/cli/commands/platform/index.ts +107 -10
  194. package/src/cli/commands/usage.ts +10 -9
  195. package/src/cli/lib/daemon-credential-client.ts +4 -0
  196. package/src/cli/program.ts +10 -3
  197. package/src/config/assistant-feature-flags.ts +59 -55
  198. package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
  199. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  200. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  201. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  202. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  203. package/src/config/bundled-skills/document/SKILL.md +4 -0
  204. package/src/config/bundled-skills/gmail/SKILL.md +12 -7
  205. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  206. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  207. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  208. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  209. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  210. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  211. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  212. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  213. package/src/config/env-registry.ts +14 -0
  214. package/src/config/env.ts +21 -0
  215. package/src/config/feature-flag-registry.json +46 -7
  216. package/src/config/loader.ts +56 -1
  217. package/src/config/sanitize-for-transfer.ts +47 -0
  218. package/src/config/schema.ts +46 -5
  219. package/src/config/schemas/host-browser.ts +66 -0
  220. package/src/config/schemas/memory-lifecycle.ts +1 -1
  221. package/src/config/schemas/memory-retrieval.ts +103 -0
  222. package/src/config/schemas/security.ts +0 -6
  223. package/src/config/schemas/services.ts +16 -0
  224. package/src/config/types.ts +0 -1
  225. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  226. package/src/context/window-manager.ts +19 -1
  227. package/src/credential-execution/approval-bridge.ts +49 -16
  228. package/src/credential-execution/managed-catalog.ts +3 -7
  229. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  230. package/src/daemon/app-source-watcher.ts +35 -0
  231. package/src/daemon/config-watcher.ts +6 -2
  232. package/src/daemon/context-overflow-approval.ts +5 -1
  233. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  234. package/src/daemon/conversation-agent-loop.ts +74 -19
  235. package/src/daemon/conversation-attachments.ts +40 -1
  236. package/src/daemon/conversation-messaging.ts +3 -0
  237. package/src/daemon/conversation-process.ts +66 -3
  238. package/src/daemon/conversation-queue-manager.ts +8 -0
  239. package/src/daemon/conversation-runtime-assembly.ts +159 -20
  240. package/src/daemon/conversation-surfaces.ts +78 -12
  241. package/src/daemon/conversation-tool-setup.ts +74 -11
  242. package/src/daemon/conversation-workspace.ts +12 -0
  243. package/src/daemon/conversation.ts +227 -11
  244. package/src/daemon/date-context.ts +10 -10
  245. package/src/daemon/first-greeting.ts +3 -2
  246. package/src/daemon/handlers/conversations.ts +9 -139
  247. package/src/daemon/handlers/shared.ts +65 -0
  248. package/src/daemon/handlers/skills.ts +232 -37
  249. package/src/daemon/host-bash-proxy.ts +48 -13
  250. package/src/daemon/host-browser-proxy.ts +191 -0
  251. package/src/daemon/host-cu-proxy.ts +36 -11
  252. package/src/daemon/host-file-proxy.ts +57 -9
  253. package/src/daemon/lifecycle.ts +86 -12
  254. package/src/daemon/message-protocol.ts +7 -0
  255. package/src/daemon/message-types/conversations.ts +59 -13
  256. package/src/daemon/message-types/host-browser.ts +100 -0
  257. package/src/daemon/message-types/messages.ts +5 -6
  258. package/src/daemon/message-types/notifications.ts +12 -0
  259. package/src/daemon/message-types/settings.ts +12 -0
  260. package/src/daemon/message-types/skills.ts +10 -0
  261. package/src/daemon/message-types/subagents.ts +2 -0
  262. package/src/daemon/server.ts +112 -35
  263. package/src/daemon/tool-side-effects.ts +6 -0
  264. package/src/daemon/transport-hints.ts +14 -0
  265. package/src/inbound/platform-callback-registration.ts +18 -17
  266. package/src/index.ts +1 -1
  267. package/src/mcp/client.ts +59 -24
  268. package/src/memory/app-store.ts +31 -1
  269. package/src/memory/conversation-crud.ts +38 -10
  270. package/src/memory/conversation-directories.ts +39 -0
  271. package/src/memory/conversation-group-migration.ts +65 -5
  272. package/src/memory/conversation-starters-cadence.ts +76 -0
  273. package/src/memory/conversation-title-service.ts +5 -2
  274. package/src/memory/db-init.ts +12 -0
  275. package/src/memory/embedding-backend.test.ts +75 -0
  276. package/src/memory/embedding-backend.ts +131 -5
  277. package/src/memory/embedding-gemini.test.ts +54 -0
  278. package/src/memory/embedding-gemini.ts +20 -9
  279. package/src/memory/embedding-local.ts +177 -18
  280. package/src/memory/graph/capability-seed.ts +3 -5
  281. package/src/memory/graph/consolidation.ts +10 -23
  282. package/src/memory/graph/extraction-job.ts +15 -0
  283. package/src/memory/graph/retriever.ts +40 -22
  284. package/src/memory/graph/store.test.ts +7 -3
  285. package/src/memory/graph/store.ts +47 -12
  286. package/src/memory/group-crud.ts +25 -9
  287. package/src/memory/llm-usage-store.ts +45 -4
  288. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  289. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  290. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  291. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  292. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  293. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  294. package/src/memory/migrations/index.ts +6 -0
  295. package/src/memory/migrations/registry.ts +8 -0
  296. package/src/memory/schema/conversations.ts +1 -0
  297. package/src/memory/schema/oauth.ts +18 -13
  298. package/src/messaging/provider.ts +1 -1
  299. package/src/notifications/broadcaster.ts +6 -0
  300. package/src/notifications/conversation-pairing.ts +12 -4
  301. package/src/notifications/emit-signal.ts +14 -0
  302. package/src/notifications/signal.ts +11 -0
  303. package/src/oauth/AGENTS.md +76 -0
  304. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  305. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  306. package/src/oauth/byo-connection.test.ts +8 -8
  307. package/src/oauth/byo-connection.ts +7 -7
  308. package/src/oauth/connect-orchestrator.ts +23 -21
  309. package/src/oauth/connect-types.ts +3 -3
  310. package/src/oauth/connection-resolver.test.ts +17 -4
  311. package/src/oauth/connection-resolver.ts +16 -16
  312. package/src/oauth/connection.ts +1 -1
  313. package/src/oauth/manual-token-connection.ts +13 -13
  314. package/src/oauth/oauth-store.ts +214 -100
  315. package/src/oauth/platform-connection.test.ts +5 -5
  316. package/src/oauth/platform-connection.ts +4 -4
  317. package/src/oauth/provider-serializer.ts +31 -5
  318. package/src/oauth/revoke.ts +76 -0
  319. package/src/oauth/seed-providers.ts +127 -87
  320. package/src/oauth/token-persistence.ts +1 -1
  321. package/src/permissions/checker.ts +3 -3
  322. package/src/permissions/defaults.ts +7 -8
  323. package/src/permissions/permission-mode.ts +4 -11
  324. package/src/permissions/prompter.ts +13 -3
  325. package/src/permissions/v2-consent-policy.ts +87 -0
  326. package/src/platform/client.ts +1 -1
  327. package/src/prompts/system-prompt.ts +18 -21
  328. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  329. package/src/prompts/templates/BOOTSTRAP.md +59 -96
  330. package/src/prompts/templates/SOUL.md +11 -11
  331. package/src/providers/anthropic/client.ts +1 -0
  332. package/src/providers/types.ts +1 -1
  333. package/src/runtime/AGENTS.md +23 -0
  334. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  335. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  336. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  337. package/src/runtime/assistant-event-hub.ts +24 -2
  338. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  339. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  341. package/src/runtime/auth/middleware.ts +98 -0
  342. package/src/runtime/auth/route-policy.ts +6 -7
  343. package/src/runtime/auth/token-service.ts +8 -0
  344. package/src/runtime/capability-tokens.ts +414 -0
  345. package/src/runtime/channel-approvals.ts +18 -5
  346. package/src/runtime/chrome-extension-registry.ts +332 -0
  347. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  348. package/src/runtime/guardian-decision-types.ts +7 -0
  349. package/src/runtime/http-server.ts +425 -70
  350. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  351. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  352. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  353. package/src/runtime/migrations/migration-transport.ts +6 -0
  354. package/src/runtime/migrations/migration-wizard.ts +22 -2
  355. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  356. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  357. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  358. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  359. package/src/runtime/pending-interactions.ts +29 -13
  360. package/src/runtime/routes/approval-routes.ts +90 -16
  361. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  362. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  363. package/src/runtime/routes/conversation-analysis-routes.ts +18 -5
  364. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  365. package/src/runtime/routes/conversation-routes.ts +308 -28
  366. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  367. package/src/runtime/routes/group-routes.ts +22 -8
  368. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  369. package/src/runtime/routes/host-browser-routes.ts +279 -0
  370. package/src/runtime/routes/host-file-routes.ts +9 -1
  371. package/src/runtime/routes/identity-routes.ts +259 -16
  372. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  373. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  374. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  375. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  376. package/src/runtime/routes/log-export-routes.ts +60 -25
  377. package/src/runtime/routes/memory-item-routes.ts +1 -7
  378. package/src/runtime/routes/migration-routes.ts +87 -2
  379. package/src/runtime/routes/oauth-apps.ts +15 -17
  380. package/src/runtime/routes/oauth-providers.ts +4 -0
  381. package/src/runtime/routes/schedule-routes.ts +24 -11
  382. package/src/runtime/routes/settings-routes.ts +9 -97
  383. package/src/runtime/routes/skills-routes.ts +52 -2
  384. package/src/runtime/routes/subagents-routes.ts +14 -10
  385. package/src/runtime/routes/usage-routes.ts +8 -7
  386. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  387. package/src/runtime/routes/workspace-routes.ts +8 -1
  388. package/src/runtime/routes/workspace-utils.ts +2 -0
  389. package/src/schedule/scheduler.ts +7 -5
  390. package/src/security/ces-credential-client.ts +20 -0
  391. package/src/security/ces-rpc-credential-backend.ts +17 -0
  392. package/src/security/credential-backend.ts +5 -0
  393. package/src/security/oauth2.ts +42 -25
  394. package/src/security/secure-keys.ts +118 -25
  395. package/src/security/token-manager.ts +23 -10
  396. package/src/skills/catalog-files.ts +492 -0
  397. package/src/skills/inline-command-runner.ts +12 -14
  398. package/src/subagent/manager.ts +131 -26
  399. package/src/subagent/types.ts +19 -0
  400. package/src/tools/apps/executors.ts +11 -2
  401. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  402. package/src/tools/browser/auth-detector.ts +43 -12
  403. package/src/tools/browser/browser-execution.ts +645 -340
  404. package/src/tools/browser/browser-manager.ts +36 -12
  405. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  406. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  407. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  408. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  409. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  410. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  411. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  412. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  413. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  414. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  415. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  416. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  417. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  418. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  419. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  420. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  421. package/src/tools/browser/cdp-client/errors.ts +34 -0
  422. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  423. package/src/tools/browser/cdp-client/factory.ts +204 -0
  424. package/src/tools/browser/cdp-client/index.ts +14 -0
  425. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  426. package/src/tools/browser/cdp-client/types.ts +52 -0
  427. package/src/tools/filesystem/edit.ts +1 -1
  428. package/src/tools/filesystem/list.ts +1 -1
  429. package/src/tools/filesystem/read.ts +1 -1
  430. package/src/tools/filesystem/write.ts +2 -1
  431. package/src/tools/host-filesystem/edit.ts +1 -1
  432. package/src/tools/host-filesystem/read.ts +12 -15
  433. package/src/tools/host-filesystem/write.ts +1 -1
  434. package/src/tools/host-terminal/host-shell.ts +21 -16
  435. package/src/tools/permission-checker.ts +77 -100
  436. package/src/tools/registry.ts +0 -2
  437. package/src/tools/secret-detection-handler.ts +34 -1
  438. package/src/tools/shared/filesystem/image-read.ts +61 -40
  439. package/src/tools/skills/sandbox-runner.ts +3 -6
  440. package/src/tools/subagent/spawn.ts +47 -3
  441. package/src/tools/subagent/status.ts +2 -0
  442. package/src/tools/system/register.ts +2 -16
  443. package/src/tools/terminal/safe-env.ts +7 -0
  444. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  445. package/src/tools/terminal/sandbox.ts +4 -1
  446. package/src/tools/terminal/shell.ts +24 -21
  447. package/src/tools/tool-approval-handler.ts +48 -2
  448. package/src/tools/types.ts +2 -3
  449. package/src/util/platform.ts +14 -19
  450. package/src/watcher/provider-types.ts +1 -1
  451. package/src/workspace/migrations/029-seed-pkb.ts +1 -0
  452. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  453. package/src/workspace/migrations/registry.ts +2 -0
  454. package/src/workspace/top-level-renderer.ts +19 -1
  455. package/src/__tests__/chrome-cdp.test.ts +0 -419
  456. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  457. package/src/__tests__/permission-mode-store.test.ts +0 -277
  458. package/src/browser-extension-relay/protocol.ts +0 -63
  459. package/src/browser-extension-relay/server.ts +0 -203
  460. package/src/config/schemas/sandbox.ts +0 -14
  461. package/src/permissions/permission-mode-store.ts +0 -180
  462. package/src/tools/browser/chrome-cdp.ts +0 -239
  463. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -0,0 +1,159 @@
1
+ import { v4 as uuid } from "uuid";
2
+
3
+ import { consumeInvalidatedTargetId } from "./events.js";
4
+ import type {
5
+ BrowserBackend,
6
+ BrowserSession,
7
+ CdpCommand,
8
+ CdpResult,
9
+ } from "./types.js";
10
+
11
+ export interface BrowserSessionManagerOptions {
12
+ /** Ordered list of backends to try; first available wins. */
13
+ backends: BrowserBackend[];
14
+ }
15
+
16
+ export class BrowserSessionManager {
17
+ private backends: BrowserBackend[];
18
+ private sessions = new Map<string, BrowserSession>();
19
+
20
+ constructor(opts: BrowserSessionManagerOptions) {
21
+ this.backends = opts.backends;
22
+ }
23
+
24
+ /** Pick an available backend or throw. */
25
+ selectBackend(): BrowserBackend {
26
+ const b = this.backends.find((x) => x.isAvailable());
27
+ if (!b) throw new Error("No available browser backend");
28
+ return b;
29
+ }
30
+
31
+ createSession(): BrowserSession {
32
+ const backend = this.selectBackend();
33
+ const session: BrowserSession = { id: uuid(), backendKind: backend.kind };
34
+ this.sessions.set(session.id, session);
35
+ return session;
36
+ }
37
+
38
+ getSession(id: string): BrowserSession | undefined {
39
+ return this.sessions.get(id);
40
+ }
41
+
42
+ /**
43
+ * Dispatch a CDP command.
44
+ *
45
+ * - If `sessionId` is provided, the session must exist in the manager; otherwise this throws.
46
+ * The command is routed through the backend whose `kind` matches the session's `backendKind`,
47
+ * ensuring per-session backend isolation and making `disposeSession()` an actual enforcement
48
+ * boundary against stale ids. If the session has an opaque `targetId` and the command does
49
+ * not already carry its own CDP `sessionId`, the manager injects the session's `targetId`
50
+ * as the CDP `sessionId` so backends can multiplex commands across multiple tabs/targets.
51
+ * - If `sessionId` is `undefined`, the first available backend is selected for one-off
52
+ * commands without a session handle (e.g. transport health probes).
53
+ */
54
+ async send(
55
+ sessionId: string | undefined,
56
+ command: CdpCommand,
57
+ signal?: AbortSignal,
58
+ ): Promise<CdpResult> {
59
+ let backend: BrowserBackend;
60
+ let outgoing = command;
61
+ if (sessionId !== undefined) {
62
+ const session = this.sessions.get(sessionId);
63
+ if (!session) {
64
+ throw new Error(`Unknown browser session: ${sessionId}`);
65
+ }
66
+ // If the chrome extension has reported this session's target
67
+ // as detached since the last dispatch, evict the session and
68
+ // throw so the caller can create a fresh one. Reading (and
69
+ // consuming) the invalidation flag here keeps the "next
70
+ // command forces reattach" semantics in lockstep with the
71
+ // host_browser_session_invalidated envelope handler — without
72
+ // this check the manager would happily forward a CDP command
73
+ // against a torn-down target and hit a permanent failure.
74
+ if (
75
+ session.targetId !== undefined &&
76
+ consumeInvalidatedTargetId(session.targetId)
77
+ ) {
78
+ this.sessions.delete(sessionId);
79
+ throw new Error(
80
+ `Browser session ${sessionId} was invalidated (target ${session.targetId} detached)`,
81
+ );
82
+ }
83
+ const matched = this.backends.find((b) => b.kind === session.backendKind);
84
+ if (!matched) {
85
+ throw new Error(
86
+ `No backend available for session kind: ${session.backendKind}`,
87
+ );
88
+ }
89
+ backend = matched;
90
+ // If the session has an opaque targetId and the command does not
91
+ // carry its own CDP sessionId, inject the session's targetId as
92
+ // the CDP sessionId. Backends that support multi-target routing
93
+ // will forward it; backends that ignore it will treat the call
94
+ // as "most-recent-tab" as before.
95
+ if (session.targetId !== undefined && command.sessionId === undefined) {
96
+ outgoing = { ...command, sessionId: session.targetId };
97
+ }
98
+ } else {
99
+ backend = this.selectBackend();
100
+ }
101
+ return backend.send(outgoing, signal);
102
+ }
103
+
104
+ disposeSession(id: string): void {
105
+ this.sessions.delete(id);
106
+ }
107
+
108
+ /**
109
+ * Evict a session that the backend has informed us is no longer
110
+ * valid — e.g. the chrome extension dispatched a
111
+ * `host_browser_session_invalidated` envelope after Chrome detached
112
+ * the debugger from the underlying tab/target.
113
+ *
114
+ * Functionally equivalent to {@link disposeSession} today (both
115
+ * remove the session from the manager's map so a subsequent
116
+ * `send()` throws "Unknown browser session") but preserved as a
117
+ * distinct method so call sites can stay explicit about intent.
118
+ * Callers that receive a detach/invalidated signal should use this
119
+ * method; callers that are cleaning up at end-of-lifecycle should
120
+ * use {@link disposeSession}.
121
+ *
122
+ * Returns `true` when a session was actually removed, `false` when
123
+ * no session with that id was tracked. Returning a boolean lets
124
+ * transport-level dispatchers (see
125
+ * `resolveHostBrowserSessionInvalidated`) log at the right level
126
+ * based on whether the invalidation had any effect.
127
+ */
128
+ invalidateSession(id: string): boolean {
129
+ return this.sessions.delete(id);
130
+ }
131
+
132
+ /**
133
+ * Evict every session whose opaque `targetId` matches the supplied
134
+ * id. Used by the WS dispatcher when a `host_browser_session_invalidated`
135
+ * envelope arrives without a manager-level session id: the
136
+ * extension-side dispatcher only knows its own `tabId` / `targetId`
137
+ * and does not carry our uuid session handle.
138
+ *
139
+ * Returns the number of sessions removed. A zero return does not
140
+ * necessarily indicate an error — the target may not have a
141
+ * runtime-side session attached to it yet, or the session may
142
+ * already have been disposed by its owning tool.
143
+ */
144
+ invalidateByTargetId(targetId: string): number {
145
+ let removed = 0;
146
+ for (const [id, session] of this.sessions) {
147
+ if (session.targetId === targetId) {
148
+ this.sessions.delete(id);
149
+ removed += 1;
150
+ }
151
+ }
152
+ return removed;
153
+ }
154
+
155
+ disposeAll(): void {
156
+ for (const b of this.backends) b.dispose();
157
+ this.sessions.clear();
158
+ }
159
+ }
@@ -0,0 +1,28 @@
1
+ export type BrowserBackendKind = "extension" | "local" | "cdp-inspect";
2
+
3
+ export interface CdpCommand {
4
+ method: string;
5
+ params?: Record<string, unknown>;
6
+ sessionId?: string;
7
+ }
8
+
9
+ export interface CdpResult {
10
+ /** Raw CDP result object; opaque to the manager. */
11
+ result?: unknown;
12
+ /** CDP error envelope if the command failed. */
13
+ error?: { code: number; message: string; data?: unknown };
14
+ }
15
+
16
+ export interface BrowserSession {
17
+ id: string;
18
+ backendKind: BrowserBackendKind;
19
+ /** Opaque target/sessionId from the backend. Omitted for "most-recent-tab" commands. */
20
+ targetId?: string;
21
+ }
22
+
23
+ export interface BrowserBackend {
24
+ kind: BrowserBackendKind;
25
+ isAvailable(): boolean;
26
+ send(command: CdpCommand, signal?: AbortSignal): Promise<CdpResult>;
27
+ dispose(): void;
28
+ }
@@ -0,0 +1,134 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ INTERACTIVE_INTERFACES,
5
+ INTERFACE_IDS,
6
+ isInterfaceId,
7
+ supportsHostProxy,
8
+ } from "../types.js";
9
+
10
+ describe("INTERFACE_IDS", () => {
11
+ test("includes chrome-extension", () => {
12
+ expect(
13
+ (INTERFACE_IDS as readonly string[]).includes("chrome-extension"),
14
+ ).toBe(true);
15
+ });
16
+
17
+ test("still includes macos and other existing interfaces", () => {
18
+ for (const id of [
19
+ "macos",
20
+ "ios",
21
+ "cli",
22
+ "telegram",
23
+ "phone",
24
+ "vellum",
25
+ "whatsapp",
26
+ "slack",
27
+ "email",
28
+ ]) {
29
+ expect((INTERFACE_IDS as readonly string[]).includes(id)).toBe(true);
30
+ }
31
+ });
32
+ });
33
+
34
+ describe("INTERACTIVE_INTERFACES", () => {
35
+ test("does NOT include chrome-extension", () => {
36
+ // Chrome extensions don't render SSE-backed prompter UI, so they must
37
+ // stay out of the interactive set even though they have an InterfaceId.
38
+ expect(INTERACTIVE_INTERFACES.has("chrome-extension" as never)).toBe(false);
39
+ });
40
+
41
+ test("still includes macos", () => {
42
+ expect(INTERACTIVE_INTERFACES.has("macos")).toBe(true);
43
+ });
44
+ });
45
+
46
+ describe("isInterfaceId", () => {
47
+ test("returns true for chrome-extension", () => {
48
+ expect(isInterfaceId("chrome-extension")).toBe(true);
49
+ });
50
+
51
+ test("returns true for macos", () => {
52
+ expect(isInterfaceId("macos")).toBe(true);
53
+ });
54
+
55
+ test("returns false for unknown interface", () => {
56
+ expect(isInterfaceId("safari-extension")).toBe(false);
57
+ });
58
+ });
59
+
60
+ describe("supportsHostProxy", () => {
61
+ // ── macOS: supports host_bash / host_file / host_cu, but NOT host_browser. ──
62
+ test("macos returns true (no capability)", () => {
63
+ expect(supportsHostProxy("macos")).toBe(true);
64
+ });
65
+
66
+ test("macos returns true for host_bash", () => {
67
+ expect(supportsHostProxy("macos", "host_bash")).toBe(true);
68
+ });
69
+
70
+ test("macos returns true for host_file", () => {
71
+ expect(supportsHostProxy("macos", "host_file")).toBe(true);
72
+ });
73
+
74
+ test("macos returns true for host_cu", () => {
75
+ expect(supportsHostProxy("macos", "host_cu")).toBe(true);
76
+ });
77
+
78
+ test("macos returns false for host_browser", () => {
79
+ expect(supportsHostProxy("macos", "host_browser")).toBe(false);
80
+ });
81
+
82
+ // ── chrome-extension: only host_browser. ──
83
+ test("chrome-extension returns false (no capability)", () => {
84
+ // Chrome extension does not support "any host proxy at all" — it only
85
+ // supports host_browser, so the no-arg form must return false to keep
86
+ // existing call sites that guard desktop-only behavior unchanged.
87
+ expect(supportsHostProxy("chrome-extension")).toBe(false);
88
+ });
89
+
90
+ test("chrome-extension returns true for host_browser", () => {
91
+ expect(supportsHostProxy("chrome-extension", "host_browser")).toBe(true);
92
+ });
93
+
94
+ test("chrome-extension returns false for host_bash", () => {
95
+ expect(supportsHostProxy("chrome-extension", "host_bash")).toBe(false);
96
+ });
97
+
98
+ test("chrome-extension returns false for host_file", () => {
99
+ expect(supportsHostProxy("chrome-extension", "host_file")).toBe(false);
100
+ });
101
+
102
+ test("chrome-extension returns false for host_cu", () => {
103
+ expect(supportsHostProxy("chrome-extension", "host_cu")).toBe(false);
104
+ });
105
+
106
+ // ── Non-supporting interfaces: false in all forms. ──
107
+ test("cli returns false (no capability)", () => {
108
+ expect(supportsHostProxy("cli")).toBe(false);
109
+ });
110
+
111
+ test("cli returns false for host_bash", () => {
112
+ expect(supportsHostProxy("cli", "host_bash")).toBe(false);
113
+ });
114
+
115
+ test("cli returns false for host_browser", () => {
116
+ expect(supportsHostProxy("cli", "host_browser")).toBe(false);
117
+ });
118
+
119
+ test("telegram returns false (no capability)", () => {
120
+ expect(supportsHostProxy("telegram")).toBe(false);
121
+ });
122
+
123
+ test("telegram returns false for host_browser", () => {
124
+ expect(supportsHostProxy("telegram", "host_browser")).toBe(false);
125
+ });
126
+
127
+ test("vellum returns false (no capability)", () => {
128
+ expect(supportsHostProxy("vellum")).toBe(false);
129
+ });
130
+
131
+ test("email returns false for host_browser", () => {
132
+ expect(supportsHostProxy("email", "host_browser")).toBe(false);
133
+ });
134
+ });
@@ -48,6 +48,7 @@ export const INTERFACE_IDS = [
48
48
  "whatsapp",
49
49
  "slack",
50
50
  "email",
51
+ "chrome-extension",
51
52
  ] as const;
52
53
 
53
54
  export type InterfaceId = (typeof INTERFACE_IDS)[number];
@@ -90,6 +91,60 @@ export function isInteractiveInterface(id: InterfaceId): boolean {
90
91
  return INTERACTIVE_INTERFACES.has(id);
91
92
  }
92
93
 
94
+ /**
95
+ * Host proxy capabilities that an interface can support. The macOS client
96
+ * supports all four; the chrome-extension interface only supports
97
+ * host_browser (via the Chrome DevTools Protocol proxy).
98
+ */
99
+ export type HostProxyCapability =
100
+ | "host_bash"
101
+ | "host_file"
102
+ | "host_cu"
103
+ | "host_browser";
104
+
105
+ /**
106
+ * Interfaces that support the full desktop host-proxy set (all four
107
+ * `HostProxyCapability` values). This is the capability-level identity used
108
+ * by the discriminated transport metadata union and by the
109
+ * `supportsHostProxy(id)` type predicate.
110
+ *
111
+ * Extend this literal type AND the `supportsHostProxy` implementation
112
+ * below in lock-step when adding a new host-capable client (e.g. a native
113
+ * Linux or Windows desktop).
114
+ */
115
+ export type HostProxyInterfaceId = "macos";
116
+
117
+ /**
118
+ * Whether the interface supports a host proxy capability.
119
+ *
120
+ * The no-arg form `supportsHostProxy(id)` asks "is this interface a desktop
121
+ * host-proxy client?" — it returns `true` only for macOS and is the type
122
+ * predicate that narrows `InterfaceId` to `HostProxyInterfaceId`. It returns
123
+ * `false` for chrome-extension because chrome-extension only supports
124
+ * `host_browser`, and the no-arg form is the gate that legacy desktop-only
125
+ * call sites use (e.g. preactivating computer-use, restoring host proxies
126
+ * in the drain queue). Callers that want to check a single capability —
127
+ * for example, to decide whether to keep `hostBrowserProxy` available for
128
+ * chrome-extension — should pass the capability explicitly:
129
+ * `supportsHostProxy(id, "host_browser")`.
130
+ */
131
+ export function supportsHostProxy(id: InterfaceId): id is HostProxyInterfaceId;
132
+ export function supportsHostProxy(
133
+ id: InterfaceId,
134
+ capability: HostProxyCapability,
135
+ ): boolean;
136
+ export function supportsHostProxy(
137
+ id: InterfaceId,
138
+ capability?: HostProxyCapability,
139
+ ): boolean {
140
+ // host_browser is excluded for macos because the proxy path requires a
141
+ // Chrome extension that isn't guaranteed to be attached; browser tools
142
+ // fall back to the local Playwright Chromium instead.
143
+ if (id === "macos") return capability !== "host_browser";
144
+ if (id === "chrome-extension" && capability === "host_browser") return true;
145
+ return false;
146
+ }
147
+
93
148
  export interface TurnInterfaceContext {
94
149
  userMessageInterface: InterfaceId;
95
150
  assistantMessageInterface: InterfaceId;
@@ -1,17 +1,32 @@
1
+ export interface AssistantCommandResult {
2
+ stdout: string;
3
+ stderr: string;
4
+ }
5
+
1
6
  /**
2
7
  * CLI test utility — run an assistant CLI command via the real program,
3
- * capturing stdout.
8
+ * capturing stdout and stderr.
9
+ *
10
+ * Returns both stdout and stderr. For backward compatibility, the function
11
+ * is also callable with just a string return (use `runAssistantCommand`).
4
12
  */
5
- export async function runAssistantCommand(...args: string[]): Promise<string> {
13
+ export async function runAssistantCommandFull(
14
+ ...args: string[]
15
+ ): Promise<AssistantCommandResult> {
6
16
  const { buildCliProgram } = await import("../program.js");
7
- const program = buildCliProgram();
17
+ const program = await buildCliProgram();
8
18
  program.exitOverride();
9
- program.configureOutput({ writeErr: () => {}, writeOut: () => {} });
10
19
 
11
- const chunks: string[] = [];
20
+ const stderrChunks: string[] = [];
21
+ program.configureOutput({
22
+ writeErr: (str: string) => stderrChunks.push(str),
23
+ writeOut: () => {},
24
+ });
25
+
26
+ const stdoutChunks: string[] = [];
12
27
  const originalWrite = process.stdout.write;
13
28
  process.stdout.write = ((chunk: string | Uint8Array) => {
14
- chunks.push(
29
+ stdoutChunks.push(
15
30
  typeof chunk === "string" ? chunk : new TextDecoder().decode(chunk),
16
31
  );
17
32
  return true;
@@ -25,5 +40,17 @@ export async function runAssistantCommand(...args: string[]): Promise<string> {
25
40
  process.stdout.write = originalWrite;
26
41
  }
27
42
 
28
- return chunks.join("");
43
+ return {
44
+ stdout: stdoutChunks.join(""),
45
+ stderr: stderrChunks.join(""),
46
+ };
47
+ }
48
+
49
+ /**
50
+ * CLI test utility — run an assistant CLI command via the real program,
51
+ * capturing stdout (backward-compatible wrapper).
52
+ */
53
+ export async function runAssistantCommand(...args: string[]): Promise<string> {
54
+ const result = await runAssistantCommandFull(...args);
55
+ return result.stdout;
29
56
  }
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from "bun:test";
2
+
3
+ import { runAssistantCommandFull } from "./run-assistant-command.js";
4
+
5
+ describe("unknown command handling", () => {
6
+ it("reports an error for an unknown subcommand", async () => {
7
+ const { stderr } = await runAssistantCommandFull("invalid");
8
+
9
+ expect(stderr).toContain("unknown command 'invalid'");
10
+ expect(stderr).toContain("Run 'assistant --help'");
11
+ });
12
+
13
+ it("reports an error for an unknown subcommand with extra arguments", async () => {
14
+ const { stderr } = await runAssistantCommandFull("invalid", "something");
15
+
16
+ expect(stderr).toContain("unknown command 'invalid'");
17
+ expect(stderr).toContain("Run 'assistant --help'");
18
+ });
19
+
20
+ it("suggests a similar command when the input is close", async () => {
21
+ const { stderr } = await runAssistantCommandFull("confg");
22
+
23
+ expect(stderr).toContain("unknown command 'confg'");
24
+ expect(stderr).toContain("Did you mean 'config'");
25
+ });
26
+
27
+ it("does not suggest a command when the input is too far off", async () => {
28
+ const { stderr } = await runAssistantCommandFull("xyzzy");
29
+
30
+ expect(stderr).toContain("unknown command 'xyzzy'");
31
+ expect(stderr).not.toContain("Did you mean");
32
+ });
33
+ });