@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
@@ -18,6 +18,7 @@
18
18
  import type { ResolvedSystemPrompt } from "../agent/loop.js";
19
19
  import { AgentLoop } from "../agent/loop.js";
20
20
  import type {
21
+ InterfaceId,
21
22
  TurnChannelContext,
22
23
  TurnInterfaceContext,
23
24
  } from "../channels/types.js";
@@ -43,12 +44,20 @@ import {
43
44
  import { registerToolTraceListener } from "../events/tool-trace-listener.js";
44
45
  import { getHookManager } from "../hooks/manager.js";
45
46
  import { resolveCanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
46
- import { updateConversationContextWindow } from "../memory/conversation-crud.js";
47
+ import {
48
+ updateConversationContextWindow,
49
+ updateConversationHostAccess,
50
+ } from "../memory/conversation-crud.js";
47
51
  import { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
48
52
  import { PermissionPrompter } from "../permissions/prompter.js";
49
53
  import { SecretPrompter } from "../permissions/secret-prompter.js";
50
54
  import { patternMatchesCandidate } from "../permissions/trust-store.js";
51
55
  import type { UserDecision } from "../permissions/types.js";
56
+ import {
57
+ isConversationHostAccessDecision,
58
+ isConversationHostAccessEnabled,
59
+ isConversationHostAccessEnablePrompt,
60
+ } from "../permissions/v2-consent-policy.js";
52
61
  import { resolvePersonaContext } from "../prompts/persona-resolver.js";
53
62
  import { buildSystemPrompt } from "../prompts/system-prompt.js";
54
63
  import type { Message } from "../providers/types.js";
@@ -104,6 +113,7 @@ import {
104
113
  } from "./conversation-tool-setup.js";
105
114
  import { refreshWorkspaceTopLevelContextIfNeeded as refreshWorkspaceImpl } from "./conversation-workspace.js";
106
115
  import { HostBashProxy } from "./host-bash-proxy.js";
116
+ import { HostBrowserProxy } from "./host-browser-proxy.js";
107
117
  import type { CuObservationResult } from "./host-cu-proxy.js";
108
118
  import { HostCuProxy } from "./host-cu-proxy.js";
109
119
  import { HostFileProxy } from "./host-file-proxy.js";
@@ -114,6 +124,8 @@ import type {
114
124
  UsageStats,
115
125
  UserMessageAttachment,
116
126
  } from "./message-protocol.js";
127
+ import type { ConversationTransportMetadata } from "./message-types/conversations.js";
128
+ import { isHostProxyTransport } from "./message-types/conversations.js";
117
129
  import type {
118
130
  AssistantActivityState,
119
131
  ConfirmationStateChanged,
@@ -156,7 +168,6 @@ export class Conversation {
156
168
  /** @internal */ sendToClient: (msg: ServerMessage) => void;
157
169
  /** @internal */ eventBus = new EventBus<AssistantDomainEvents>();
158
170
  /** @internal */ workingDir: string;
159
- /** @internal */ sandboxOverride?: boolean;
160
171
  /** @internal */ allowedToolNames?: Set<string>;
161
172
  /** @internal */ toolsDisabledDepth = 0;
162
173
  /** @internal */ preactivatedSkillIds?: string[];
@@ -180,8 +191,22 @@ export class Conversation {
180
191
  /** @internal */ taskRunId?: string;
181
192
  /** @internal */ callSessionId?: string;
182
193
  /** @internal */ hostBashProxy?: HostBashProxy;
194
+ /** @internal */ hostBrowserProxy?: HostBrowserProxy;
183
195
  /** @internal */ hostCuProxy?: HostCuProxy;
184
196
  /** @internal */ hostFileProxy?: HostFileProxy;
197
+ /**
198
+ * Optional override sender used by `restoreBrowserProxyAvailability` so
199
+ * non-SSE transports (e.g. chrome-extension, whose host_browser_request
200
+ * frames flow through the ChromeExtensionRegistry WebSocket rather than
201
+ * the SSE hub) can preserve their registry-routed sender across drain
202
+ * queue restores. When set, `restoreBrowserProxyAvailability()` uses this
203
+ * function instead of `sendToClient` so the drain-queue path doesn't
204
+ * clobber the chrome-extension sender with the SSE hub emitter.
205
+ *
206
+ * Populated by the POST /messages handler for chrome-extension turns and
207
+ * cleared when an unrelated interface takes over (see `updateClient`).
208
+ */
209
+ /** @internal */ hostBrowserSenderOverride?: (msg: ServerMessage) => void;
185
210
  /** @internal */ cesClient?: CesClient;
186
211
  /** @internal */ readonly queue = new MessageQueue();
187
212
  /** @internal */ currentActiveSurfaceId?: string;
@@ -246,8 +271,24 @@ export class Conversation {
246
271
  }> = [];
247
272
  /** @internal */ workspaceTopLevelContext: string | null = null;
248
273
  /** @internal */ workspaceTopLevelDirty = true;
274
+ /**
275
+ * Host home directory reported by the client (e.g. macOS
276
+ * `NSHomeDirectory()`). Populated from `HostProxyTransportMetadata` when
277
+ * a message arrives from an interface that supports host-proxy tools
278
+ * (see `supportsHostProxy`). Consumed by the `<workspace>` block renderer
279
+ * so platform-managed (containerized) daemons show the user's actual
280
+ * client-side home dir instead of the container's `os.homedir()`.
281
+ * @internal
282
+ */
283
+ hostHomeDir?: string;
284
+ /**
285
+ * Host username reported by the client (e.g. macOS `NSUserName()`).
286
+ * See `hostHomeDir`.
287
+ * @internal
288
+ */
289
+ hostUsername?: string;
249
290
  public readonly traceEmitter: TraceEmitter;
250
- public readonly hasSystemPromptOverride: boolean;
291
+ /** @internal */ hasSystemPromptOverride: boolean;
251
292
  public memoryPolicy: ConversationMemoryPolicy;
252
293
  /** @internal */ readonly graphMemory: ConversationGraphMemory;
253
294
  /** @internal */ activeContextNodeIds?: string[];
@@ -387,7 +428,7 @@ export class Conversation {
387
428
  _history: import("../providers/types.js").Message[],
388
429
  ): ResolvedSystemPrompt => {
389
430
  const resolved = {
390
- systemPrompt: hasSystemPromptOverride
431
+ systemPrompt: this.hasSystemPromptOverride
391
432
  ? systemPrompt
392
433
  : (() => {
393
434
  const persona = resolvePersonaContext(
@@ -501,6 +542,7 @@ export class Conversation {
501
542
  this.traceEmitter.updateSender(sendToClient);
502
543
  if (!opts?.skipProxySenderUpdate) {
503
544
  this.hostBashProxy?.updateSender(sendToClient, !hasNoClient);
545
+ this.hostBrowserProxy?.updateSender(sendToClient, !hasNoClient);
504
546
  this.hostCuProxy?.updateSender(sendToClient, !hasNoClient);
505
547
  this.hostFileProxy?.updateSender(sendToClient, !hasNoClient);
506
548
  }
@@ -527,6 +569,7 @@ export class Conversation {
527
569
  /** Mark host proxies as unavailable so tool execution uses local fallback. */
528
570
  clearProxyAvailability(): void {
529
571
  this.hostBashProxy?.updateSender(this.sendToClient, false);
572
+ this.hostBrowserProxy?.updateSender(this.sendToClient, false);
530
573
  this.hostCuProxy?.updateSender(this.sendToClient, false);
531
574
  this.hostFileProxy?.updateSender(this.sendToClient, false);
532
575
  }
@@ -535,13 +578,41 @@ export class Conversation {
535
578
  restoreProxyAvailability(): void {
536
579
  if (!this.hasNoClient) {
537
580
  this.hostBashProxy?.updateSender(this.sendToClient, true);
581
+ this.hostBrowserProxy?.updateSender(this.sendToClient, true);
538
582
  this.hostCuProxy?.updateSender(this.sendToClient, true);
539
583
  this.hostFileProxy?.updateSender(this.sendToClient, true);
540
584
  }
541
585
  }
542
586
 
543
- setSandboxOverride(enabled: boolean | undefined): void {
544
- this.sandboxOverride = enabled;
587
+ /**
588
+ * Restore host browser proxy availability only. Used for non-desktop
589
+ * interfaces (e.g. chrome-extension) that support host_browser but not
590
+ * the full desktop proxy set, so calling restoreProxyAvailability() would
591
+ * incorrectly re-enable bash/file/CU proxies that should stay disabled.
592
+ *
593
+ * Unlike `restoreProxyAvailability()`, this helper does NOT gate on
594
+ * `hasNoClient`. The chrome-extension interface is non-interactive (so
595
+ * `hasNoClient === true`), but it DOES have a connected client that can
596
+ * service `host_browser_request` events. Gating on `hasNoClient` would
597
+ * leave the just-constructed proxy unavailable and the only way to make
598
+ * it available would be to flip `hasNoClient` false, which would
599
+ * incorrectly enable host_bash/host_file/host_cu tool gating downstream.
600
+ *
601
+ * When `hostBrowserSenderOverride` is set, that function is used as the
602
+ * sender instead of `sendToClient`. This is required for the
603
+ * chrome-extension interface whose host_browser frames route through the
604
+ * ChromeExtensionRegistry WebSocket rather than the SSE hub: if the
605
+ * queue-drain path called this helper with `sendToClient`, the
606
+ * registry-routed sender established at turn-start would be clobbered by
607
+ * the SSE hub emitter and host_browser_request frames would stop
608
+ * reaching the extension.
609
+ *
610
+ * Callers must only invoke this when they know the current interface
611
+ * supports host_browser (see `supportsHostProxy(id, "host_browser")`).
612
+ */
613
+ restoreBrowserProxyAvailability(): void {
614
+ const sender = this.hostBrowserSenderOverride ?? this.sendToClient;
615
+ this.hostBrowserProxy?.updateSender(sender, true);
545
616
  }
546
617
 
547
618
  setSubagentAllowedTools(tools: Set<string> | undefined): void {
@@ -552,6 +623,35 @@ export class Conversation {
552
623
  this.isSubagent = value;
553
624
  }
554
625
 
626
+ /**
627
+ * Prepend inherited parent messages into the in-memory message array so that
628
+ * the AgentLoop includes them in provider calls (enabling KV cache sharing).
629
+ *
630
+ * These messages are NOT persisted to the database — they exist only in
631
+ * memory. When the conversation is later read from DB via getMessages(),
632
+ * only the conversation's own persisted messages appear.
633
+ *
634
+ * Must be called before the first persistUserMessage() call — i.e. while
635
+ * `this.messages` is still empty.
636
+ */
637
+ injectInheritedContext(messages: Message[]): void {
638
+ if (this.messages.length !== 0) {
639
+ throw new Error(
640
+ "injectInheritedContext must be called before any messages have been added",
641
+ );
642
+ }
643
+ this.messages = [...messages];
644
+ this.contextWindowManager.nonPersistedPrefixCount = messages.length;
645
+ }
646
+
647
+ /**
648
+ * Return the system prompt string set at construction time (or its override).
649
+ * Fork consumers use this to pass the parent's system prompt to the fork.
650
+ */
651
+ getCurrentSystemPrompt(): string {
652
+ return this.systemPrompt;
653
+ }
654
+
555
655
  isProcessing(): boolean {
556
656
  return this.processing;
557
657
  }
@@ -574,6 +674,7 @@ export class Conversation {
574
674
  dispose(): void {
575
675
  approvalOverrides.clearMode(this.conversationId);
576
676
  this.hostBashProxy?.dispose();
677
+ this.hostBrowserProxy?.dispose();
577
678
  this.hostCuProxy?.dispose();
578
679
  this.hostFileProxy?.dispose();
579
680
  // CES client is owned by DaemonServer — just drop the reference.
@@ -609,6 +710,7 @@ export class Conversation {
609
710
  metadata?: Record<string, unknown>,
610
711
  options?: { isInteractive?: boolean },
611
712
  displayContent?: string,
713
+ transport?: ConversationTransportMetadata,
612
714
  ): { queued: boolean; requestId: string; rejected?: boolean } {
613
715
  return enqueueMessageImpl(
614
716
  this,
@@ -621,6 +723,7 @@ export class Conversation {
621
723
  metadata,
622
724
  options,
623
725
  displayContent,
726
+ transport,
624
727
  );
625
728
  }
626
729
 
@@ -674,12 +777,35 @@ export class Conversation {
674
777
  return;
675
778
  }
676
779
 
780
+ const hostAccessEnablePrompt =
781
+ this.prompter.isHostAccessEnablePrompt(requestId) ||
782
+ isConversationHostAccessEnablePrompt(
783
+ pendingInteractions.get(requestId)?.confirmationDetails,
784
+ );
785
+ const effectiveDecision =
786
+ hostAccessEnablePrompt && !isConversationHostAccessDecision(decision)
787
+ ? "deny"
788
+ : decision;
789
+
790
+ if (
791
+ hostAccessEnablePrompt &&
792
+ effectiveDecision === "allow" &&
793
+ !isConversationHostAccessEnabled(this.conversationId)
794
+ ) {
795
+ updateConversationHostAccess(this.conversationId, true);
796
+ this.sendToClient({
797
+ type: "conversation_host_access_updated",
798
+ conversationId: this.conversationId,
799
+ hostAccess: true,
800
+ });
801
+ }
802
+
677
803
  // Capture toolUseId before resolving (resolution deletes the pending entry)
678
804
  const toolUseId = this.prompter.getToolUseId(requestId);
679
805
 
680
806
  this.prompter.resolveConfirmation(
681
807
  requestId,
682
- decision,
808
+ effectiveDecision,
683
809
  selectedPattern,
684
810
  selectedScope,
685
811
  decisionContext,
@@ -693,7 +819,7 @@ export class Conversation {
693
819
  // so ALL callers (HTTP handlers, /v1/confirm, channel bridges) get
694
820
  // consistent events without duplicating emission logic.
695
821
  const resolvedState =
696
- decision === "deny" || decision === "always_deny"
822
+ effectiveDecision === "deny" || effectiveDecision === "always_deny"
697
823
  ? ("denied" as const)
698
824
  : ("approved" as const);
699
825
  this.emitConfirmationStateChanged({
@@ -737,7 +863,12 @@ export class Conversation {
737
863
  }
738
864
 
739
865
  // Cascade to other pending confirmations that match this decision
740
- this.cascadePendingApprovals(requestId, decision, selectedPattern);
866
+ this.cascadePendingApprovals(
867
+ requestId,
868
+ effectiveDecision,
869
+ selectedPattern,
870
+ hostAccessEnablePrompt,
871
+ );
741
872
  }
742
873
 
743
874
  /**
@@ -753,9 +884,14 @@ export class Conversation {
753
884
  primaryRequestId: string,
754
885
  decision: UserDecision,
755
886
  selectedPattern?: string,
887
+ hostAccessEnablePrompt = false,
756
888
  ): void {
757
889
  // Single-action decisions don't cascade
758
- if (decision === "allow" || decision === "deny") return;
890
+ if (decision === "allow" || decision === "deny") {
891
+ if (!hostAccessEnablePrompt || decision !== "allow") {
892
+ return;
893
+ }
894
+ }
759
895
 
760
896
  const pendingRequestIds = this.prompter.getPendingRequestIds();
761
897
  if (pendingRequestIds.length === 0) return;
@@ -768,6 +904,28 @@ export class Conversation {
768
904
  if (interaction.conversationId !== this.conversationId) continue;
769
905
  if (interaction.kind !== "confirmation") continue;
770
906
 
907
+ if (
908
+ hostAccessEnablePrompt &&
909
+ (this.prompter.isHostAccessEnablePrompt(candidateId) ||
910
+ isConversationHostAccessEnablePrompt(interaction.confirmationDetails))
911
+ ) {
912
+ // Enabling computer access is conversation-scoped, so sibling host
913
+ // prompts should resolve immediately once the first approval lands.
914
+ pendingInteractions.resolve(candidateId);
915
+ this.handleConfirmationResponse(
916
+ candidateId,
917
+ "allow",
918
+ undefined,
919
+ undefined,
920
+ undefined,
921
+ {
922
+ source: "system",
923
+ causedByRequestId: primaryRequestId,
924
+ },
925
+ );
926
+ continue;
927
+ }
928
+
771
929
  const cascadeResult = this.shouldCascade(
772
930
  decision,
773
931
  selectedPattern,
@@ -870,9 +1028,23 @@ export class Conversation {
870
1028
  this.hostBashProxy = proxy;
871
1029
  }
872
1030
 
873
- resolveHostFile(
1031
+ resolveHostBrowser(
874
1032
  requestId: string,
875
1033
  response: { content: string; isError: boolean },
1034
+ ): void {
1035
+ this.hostBrowserProxy?.resolve(requestId, response);
1036
+ }
1037
+
1038
+ setHostBrowserProxy(proxy: HostBrowserProxy | undefined): void {
1039
+ if (this.hostBrowserProxy && this.hostBrowserProxy !== proxy) {
1040
+ this.hostBrowserProxy.dispose();
1041
+ }
1042
+ this.hostBrowserProxy = proxy;
1043
+ }
1044
+
1045
+ resolveHostFile(
1046
+ requestId: string,
1047
+ response: { content: string; isError: boolean; imageData?: string },
876
1048
  ): void {
877
1049
  this.hostFileProxy?.resolve(requestId, response);
878
1050
  }
@@ -986,6 +1158,40 @@ export class Conversation {
986
1158
  this.transportHints = hints;
987
1159
  }
988
1160
 
1161
+ /**
1162
+ * Apply client-reported host environment (home dir, username) from
1163
+ * transport metadata onto the conversation. Only interfaces whose
1164
+ * interfaceId passes `supportsHostProxy()` contribute values — all other
1165
+ * interfaces (CLI, channels, iOS, chrome-extension) clear any previously
1166
+ * stored values so a conversation reused across interfaces doesn't leak
1167
+ * stale paths into later `<workspace>` blocks.
1168
+ *
1169
+ * Gating on `supportsHostProxy` (rather than a specific interface name)
1170
+ * keeps this in lock-step with the capability set defined in
1171
+ * `HostProxyInterfaceId` — adding a new host-capable client only requires
1172
+ * extending those two, not touching this method.
1173
+ *
1174
+ * Invalidates the cached workspace top-level block when values change so
1175
+ * the next render picks up the new host env.
1176
+ */
1177
+ applyHostEnvFromTransport(transport: ConversationTransportMetadata): void {
1178
+ const prevHomeDir = this.hostHomeDir;
1179
+ const prevUsername = this.hostUsername;
1180
+ if (isHostProxyTransport(transport)) {
1181
+ this.hostHomeDir = transport.hostHomeDir;
1182
+ this.hostUsername = transport.hostUsername;
1183
+ } else {
1184
+ this.hostHomeDir = undefined;
1185
+ this.hostUsername = undefined;
1186
+ }
1187
+ if (
1188
+ prevHomeDir !== this.hostHomeDir ||
1189
+ prevUsername !== this.hostUsername
1190
+ ) {
1191
+ this.workspaceTopLevelDirty = true;
1192
+ }
1193
+ }
1194
+
989
1195
  setAssistantId(assistantId: string | null): void {
990
1196
  this.assistantId = assistantId ?? undefined;
991
1197
  }
@@ -1028,6 +1234,16 @@ export class Conversation {
1028
1234
  return this.currentTurnInterfaceContext;
1029
1235
  }
1030
1236
 
1237
+ /**
1238
+ * Implements the `transportInterface` field of `SkillProjectionContext` so
1239
+ * that `isToolActiveForContext` can gate host tools by per-capability
1240
+ * `supportsHostProxy(transport, capability)`. Derived from the live turn
1241
+ * interface context so it tracks the connected client across turns.
1242
+ */
1243
+ get transportInterface(): InterfaceId | undefined {
1244
+ return this.currentTurnInterfaceContext?.userMessageInterface;
1245
+ }
1246
+
1031
1247
  async persistUserMessage(
1032
1248
  content: string,
1033
1249
  attachments: UserMessageAttachment[],
@@ -18,14 +18,14 @@ export interface TemporalContextOptions {
18
18
  userTimeZone?: string | null;
19
19
  }
20
20
 
21
- const WEEKDAY_SHORT = [
22
- "Sun",
23
- "Mon",
24
- "Tue",
25
- "Wed",
26
- "Thu",
27
- "Fri",
28
- "Sat",
21
+ const WEEKDAY_LONG = [
22
+ "Sunday",
23
+ "Monday",
24
+ "Tuesday",
25
+ "Wednesday",
26
+ "Thursday",
27
+ "Friday",
28
+ "Saturday",
29
29
  ] as const;
30
30
  const UTC_GMT_OFFSET_TOKEN_RE = /^(?:UTC|GMT)([+-])(\d{1,2})(?::?(\d{2}))?$/i;
31
31
 
@@ -295,7 +295,7 @@ function formatLocalDate(date: Date, timeZone: string): string {
295
295
  * Uses the timezone resolution cascade:
296
296
  * explicit override → configured user tz → profile user tz → host fallback.
297
297
  *
298
- * Returns format: `2026-04-02 (Thu) 01:52:33 -05:00 (America/Chicago)`
298
+ * Returns format: `2026-04-02 (Thursday) 01:52:33 -05:00 (America/Chicago)`
299
299
  */
300
300
  export function formatTurnTimestamp(
301
301
  options: TemporalContextOptions = {},
@@ -322,7 +322,7 @@ export function formatTurnTimestamp(
322
322
 
323
323
  const dateStr = formatLocalDate(now, timeZone);
324
324
  const todayParts = localDateParts(now, timeZone);
325
- const dayName = WEEKDAY_SHORT[todayParts.weekday];
325
+ const dayName = WEEKDAY_LONG[todayParts.weekday];
326
326
 
327
327
  const fmt = new Intl.DateTimeFormat("en-US", {
328
328
  timeZone,
@@ -5,10 +5,11 @@ import { getWorkspacePromptPath } from "../util/platform.js";
5
5
  /**
6
6
  * The canned assistant response for the wake-up greeting on a fresh workspace.
7
7
  * Warm, non-presumptuous greeting that communicates "I'm new," "I improve over
8
- * time," "I'm ready to be useful," and "you're in control."
8
+ * time," and invites the user to lead with whatever they want — a task, a
9
+ * question, or getting to know each other.
9
10
  */
10
11
  export const CANNED_FIRST_GREETING =
11
- "Hey, I'm brand new. No name, no memories, nothing yet. Think of me like a new colleague on their first day: I'll get better the more we work together. First things first, let's figure out how we work best. What should I call you?";
12
+ "Hey I'm brand new. No name, no memories, no idea who you are yet. I'll get sharper the more we work together. What can I do for you?";
12
13
 
13
14
  /**
14
15
  * Returns `true` when all of the following are true:
@@ -1,10 +1,5 @@
1
1
  import { v4 as uuid } from "uuid";
2
2
 
3
- import {
4
- type InterfaceId,
5
- parseChannelId,
6
- parseInterfaceId,
7
- } from "../../channels/types.js";
8
3
  import { getConfig } from "../../config/loader.js";
9
4
  import {
10
5
  createCanonicalGuardianRequest,
@@ -13,28 +8,18 @@ import {
13
8
  import {
14
9
  batchSetDisplayOrders,
15
10
  clearAll,
16
- createConversation,
17
11
  getConversation,
18
12
  updateConversationTitle,
19
13
  } from "../../memory/conversation-crud.js";
20
14
  import { resolveConversationId } from "../../memory/conversation-key-store.js";
21
- import {
22
- GENERATING_TITLE,
23
- queueGenerateConversationTitle,
24
- UNTITLED_FALLBACK,
25
- } from "../../memory/conversation-title-service.js";
26
15
  import * as pendingInteractions from "../../runtime/pending-interactions.js";
27
16
  import { redactSecrets } from "../../security/secret-scanner.js";
28
17
  import { getSubagentManager } from "../../subagent/index.js";
29
18
  import { summarizeToolInput } from "../../tools/tool-input-summary.js";
30
19
  import { truncate } from "../../util/truncate.js";
31
20
  import type { Conversation } from "../conversation.js";
32
- import { HostBashProxy } from "../host-bash-proxy.js";
33
- import { HostCuProxy } from "../host-cu-proxy.js";
34
- import { HostFileProxy } from "../host-file-proxy.js";
35
21
  import type {
36
22
  ConfirmationResponse,
37
- ConversationCreateRequest,
38
23
  ConversationRenameRequest,
39
24
  ConversationSwitchRequest,
40
25
  DeleteQueuedMessage,
@@ -131,6 +116,12 @@ export function makeEventSender(params: {
131
116
  conversationId,
132
117
  kind: "host_bash",
133
118
  });
119
+ } else if (event.type === "host_browser_request") {
120
+ pendingInteractions.register(event.requestId, {
121
+ conversation,
122
+ conversationId,
123
+ kind: "host_browser",
124
+ });
134
125
  } else if (event.type === "host_file_request") {
135
126
  pendingInteractions.register(event.requestId, {
136
127
  conversation,
@@ -226,130 +217,6 @@ export function clearAllConversations(ctx: HandlerContext): number {
226
217
  return cleared;
227
218
  }
228
219
 
229
- export async function handleConversationCreate(
230
- msg: ConversationCreateRequest,
231
- ctx: HandlerContext,
232
- ): Promise<void> {
233
- const conversationType = normalizeConversationType(msg.conversationType);
234
- const title =
235
- msg.title ?? (msg.initialMessage ? GENERATING_TITLE : "New Conversation");
236
- const conversation = createConversation({
237
- title,
238
- conversationType,
239
- });
240
- const conversationObj = await ctx.getOrCreateConversation(conversation.id, {
241
- systemPromptOverride: msg.systemPromptOverride,
242
- maxResponseTokens: msg.maxResponseTokens,
243
- transport: msg.transport,
244
- });
245
-
246
- // Pre-activate skills before sending conversation_info so they're available
247
- // for the initial message processing.
248
- if (msg.preactivatedSkillIds?.length) {
249
- conversationObj.setPreactivatedSkillIds(msg.preactivatedSkillIds);
250
- }
251
-
252
- ctx.send({
253
- type: "conversation_info",
254
- conversationId: conversation.id,
255
- title: conversation.title ?? "New Conversation",
256
- ...(msg.correlationId ? { correlationId: msg.correlationId } : {}),
257
- conversationType: normalizeConversationType(conversation.conversationType),
258
- });
259
-
260
- // Auto-send the initial message if provided, kick-starting the skill.
261
- if (msg.initialMessage) {
262
- // Queue title generation eagerly — some processMessage paths (guardian
263
- // replies, unknown slash commands) bypass the agent loop entirely, so
264
- // we can't rely on the agent loop's early title generation alone.
265
- // The agent loop also queues title generation, but isReplaceableTitle
266
- // prevents double-writes since the first to complete sets a real title.
267
- if (title === GENERATING_TITLE) {
268
- queueGenerateConversationTitle({
269
- conversationId: conversation.id,
270
- context: { origin: "local" },
271
- userMessage: msg.initialMessage,
272
- onTitleUpdated: (newTitle) => {
273
- ctx.send({
274
- type: "conversation_title_updated",
275
- conversationId: conversation.id,
276
- title: newTitle,
277
- });
278
- },
279
- });
280
- }
281
-
282
- const requestId = uuid();
283
- const transportChannel =
284
- parseChannelId(msg.transport?.channelId) ?? "vellum";
285
- const sendEvent = makeEventSender({
286
- ctx,
287
- conversation: conversationObj,
288
- conversationId: conversation.id,
289
- sourceChannel: transportChannel,
290
- });
291
- conversationObj.setTurnChannelContext({
292
- userMessageChannel: transportChannel,
293
- assistantMessageChannel: transportChannel,
294
- });
295
- const transportInterface: InterfaceId =
296
- parseInterfaceId(msg.transport?.interfaceId) ?? "vellum";
297
- conversationObj.setTurnInterfaceContext({
298
- userMessageInterface: transportInterface,
299
- assistantMessageInterface: transportInterface,
300
- });
301
- // Only create the host bash proxy for desktop client interfaces that can
302
- // execute commands on the user's machine. Set before updateClient so
303
- // updateClient's call to hostBashProxy.updateSender targets the new proxy.
304
- if (transportInterface === "macos") {
305
- const proxy = new HostBashProxy(sendEvent, (requestId) => {
306
- pendingInteractions.resolve(requestId);
307
- });
308
- conversationObj.setHostBashProxy(proxy);
309
- const fileProxy = new HostFileProxy(sendEvent, (requestId) => {
310
- pendingInteractions.resolve(requestId);
311
- });
312
- conversationObj.setHostFileProxy(fileProxy);
313
- const cuProxy = new HostCuProxy(sendEvent, (requestId) => {
314
- pendingInteractions.resolve(requestId);
315
- });
316
- conversationObj.setHostCuProxy(cuProxy);
317
- conversationObj.addPreactivatedSkillId("computer-use");
318
- }
319
- conversationObj.updateClient(sendEvent, false);
320
- conversationObj
321
- .processMessage(msg.initialMessage, [], sendEvent, requestId)
322
- .catch((err) => {
323
- const message = err instanceof Error ? err.message : String(err);
324
- log.error(
325
- { err, conversationId: conversation.id },
326
- "Error processing initial message",
327
- );
328
- ctx.send({
329
- type: "error",
330
- message: `Failed to process initial message: ${message}`,
331
- });
332
-
333
- // Replace stuck loading placeholder with a stable fallback title
334
- // if title generation hasn't already completed or been renamed.
335
- try {
336
- const current = getConversation(conversation.id);
337
- if (current && current.title === GENERATING_TITLE) {
338
- const fallback = UNTITLED_FALLBACK;
339
- updateConversationTitle(conversation.id, fallback);
340
- ctx.send({
341
- type: "conversation_title_updated",
342
- conversationId: conversation.id,
343
- title: fallback,
344
- });
345
- }
346
- } catch {
347
- // Best-effort fallback
348
- }
349
- });
350
- }
351
- }
352
-
353
220
  /**
354
221
  * Switch to an existing conversation. Returns conversation info on success,
355
222
  * or throws/returns an error result when the conversation is not found.
@@ -361,6 +228,7 @@ export async function switchConversation(
361
228
  conversationId: string;
362
229
  title: string;
363
230
  conversationType: ReturnType<typeof normalizeConversationType>;
231
+ hostAccess: boolean;
364
232
  } | null> {
365
233
  const conversation = getConversation(conversationId);
366
234
  if (!conversation) {
@@ -383,6 +251,7 @@ export async function switchConversation(
383
251
  conversationId: conversation.id,
384
252
  title: conversation.title ?? "Untitled",
385
253
  conversationType: normalizeConversationType(conversation.conversationType),
254
+ hostAccess: conversation.hostAccess === 1,
386
255
  };
387
256
  }
388
257
 
@@ -404,6 +273,7 @@ export async function handleConversationSwitch(
404
273
  conversationId: result.conversationId,
405
274
  title: result.title,
406
275
  conversationType: result.conversationType,
276
+ hostAccess: result.hostAccess,
407
277
  });
408
278
  }
409
279