@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,291 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import type { ServerMessage } from "../daemon/message-protocol.js";
4
+ import { SubagentManager } from "../subagent/manager.js";
5
+ import type { SubagentState } from "../subagent/types.js";
6
+
7
+ /** Minimal shape matching the private ManagedSubagent interface for test injection. */
8
+ interface FakeManagedSubagent {
9
+ conversation: {
10
+ abort: () => void;
11
+ dispose: () => void;
12
+ messages: Array<{
13
+ role: string;
14
+ content: Array<{ type: string; text: string }>;
15
+ }>;
16
+ sendToClient: (msg: ServerMessage) => void;
17
+ loadFromDb?: () => Promise<void>;
18
+ persistUserMessage?: (msg: string) => string;
19
+ runAgentLoop?: () => Promise<void>;
20
+ usageStats: {
21
+ inputTokens: number;
22
+ outputTokens: number;
23
+ estimatedCost: number;
24
+ };
25
+ } | null;
26
+ state: SubagentState;
27
+ parentSendToClient: (msg: ServerMessage) => void;
28
+ }
29
+
30
+ /** Type-safe accessor for SubagentManager's private internals via bracket notation. */
31
+ interface ManagerInternals {
32
+ subagents: Map<string, FakeManagedSubagent>;
33
+ parentToChildren: Map<string, Set<string>>;
34
+ runSubagent: (subagentId: string, objective: string) => Promise<void>;
35
+ stopSweep: () => void;
36
+ }
37
+
38
+ function asInternals(manager: SubagentManager): ManagerInternals {
39
+ return manager as unknown as ManagerInternals;
40
+ }
41
+
42
+ function injectFakeSubagent(
43
+ manager: SubagentManager,
44
+ subagentId: string,
45
+ state: SubagentState,
46
+ parentSendToClient?: (msg: ServerMessage) => void,
47
+ ): void {
48
+ const fakeSession: FakeManagedSubagent["conversation"] = {
49
+ abort: () => {},
50
+ dispose: () => {},
51
+ messages: [],
52
+ sendToClient: () => {},
53
+ usageStats: { inputTokens: 100, outputTokens: 50, estimatedCost: 0.005 },
54
+ };
55
+
56
+ const internals = asInternals(manager);
57
+ const subagents = internals.subagents;
58
+ const parentToChildren = internals.parentToChildren;
59
+
60
+ subagents.set(subagentId, {
61
+ conversation: fakeSession,
62
+ state,
63
+ parentSendToClient: parentSendToClient ?? (() => {}),
64
+ });
65
+
66
+ const parentId = state.config.parentConversationId;
67
+ if (!parentToChildren.has(parentId)) {
68
+ parentToChildren.set(parentId, new Set());
69
+ }
70
+ parentToChildren.get(parentId)!.add(subagentId);
71
+ }
72
+
73
+ function makeState(
74
+ subagentId: string,
75
+ overrides: Partial<SubagentState> = {},
76
+ ): SubagentState {
77
+ return {
78
+ config: {
79
+ id: subagentId,
80
+ parentConversationId: "parent-sess-1",
81
+ label: "Test subagent",
82
+ objective: "Do something",
83
+ },
84
+ status: "running",
85
+ conversationId: "conv-sub-1",
86
+ isFork: false,
87
+ createdAt: Date.now(),
88
+ usage: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
89
+ ...overrides,
90
+ };
91
+ }
92
+
93
+ function makeForkState(
94
+ subagentId: string,
95
+ overrides: Partial<SubagentState> = {},
96
+ ): SubagentState {
97
+ return makeState(subagentId, {
98
+ isFork: true,
99
+ config: {
100
+ id: subagentId,
101
+ parentConversationId: "parent-sess-1",
102
+ label: "Analysis fork",
103
+ objective: "Analyze data",
104
+ fork: true,
105
+ sendResultToUser: false,
106
+ },
107
+ ...overrides,
108
+ });
109
+ }
110
+
111
+ describe("Fork completion notifications", () => {
112
+ test("fork completion notification includes last_n: 1 guidance", async () => {
113
+ const manager = new SubagentManager();
114
+ const subagentId = "fork-1";
115
+ const state = makeForkState(subagentId);
116
+ injectFakeSubagent(manager, subagentId, state);
117
+
118
+ const managed = asInternals(manager).subagents.get(subagentId)!;
119
+ managed.conversation!.persistUserMessage = () => "msg-1";
120
+ managed.conversation!.runAgentLoop = async () => {};
121
+
122
+ const notifications: { parentConversationId: string; message: string }[] =
123
+ [];
124
+ manager.onSubagentFinished = (parentConversationId, message) => {
125
+ notifications.push({ parentConversationId, message });
126
+ };
127
+
128
+ await asInternals(manager).runSubagent(subagentId, "Analyze data");
129
+
130
+ expect(notifications).toHaveLength(1);
131
+ expect(notifications[0].message).toContain("last_n: 1");
132
+
133
+ asInternals(manager).stopSweep();
134
+ });
135
+
136
+ test("fork completion notification includes internal-processing instruction", async () => {
137
+ const manager = new SubagentManager();
138
+ const subagentId = "fork-1";
139
+ const state = makeForkState(subagentId);
140
+ injectFakeSubagent(manager, subagentId, state);
141
+
142
+ const managed = asInternals(manager).subagents.get(subagentId)!;
143
+ managed.conversation!.persistUserMessage = () => "msg-1";
144
+ managed.conversation!.runAgentLoop = async () => {};
145
+
146
+ const notifications: { parentConversationId: string; message: string }[] =
147
+ [];
148
+ manager.onSubagentFinished = (parentConversationId, message) => {
149
+ notifications.push({ parentConversationId, message });
150
+ };
151
+
152
+ await asInternals(manager).runSubagent(subagentId, "Analyze data");
153
+
154
+ expect(notifications).toHaveLength(1);
155
+ expect(notifications[0].message).toContain(
156
+ "do NOT share raw fork output with the user",
157
+ );
158
+ expect(notifications[0].message).toContain(
159
+ '[Fork "Analysis fork" completed]',
160
+ );
161
+
162
+ asInternals(manager).stopSweep();
163
+ });
164
+
165
+ test("fork failure notification uses [Fork prefix", async () => {
166
+ const manager = new SubagentManager();
167
+ const subagentId = "fork-1";
168
+ const state = makeForkState(subagentId);
169
+ injectFakeSubagent(manager, subagentId, state);
170
+
171
+ const managed = asInternals(manager).subagents.get(subagentId)!;
172
+ managed.conversation!.persistUserMessage = () => "msg-1";
173
+ managed.conversation!.runAgentLoop = async () => {
174
+ throw new Error("Context too large");
175
+ };
176
+
177
+ const notifications: { parentConversationId: string; message: string }[] =
178
+ [];
179
+ manager.onSubagentFinished = (parentConversationId, message) => {
180
+ notifications.push({ parentConversationId, message });
181
+ };
182
+
183
+ await asInternals(manager).runSubagent(subagentId, "Analyze data");
184
+
185
+ expect(notifications).toHaveLength(1);
186
+ expect(notifications[0].message).toContain(
187
+ '[Fork "Analysis fork" failed]',
188
+ );
189
+ expect(notifications[0].message).toContain("Context too large");
190
+ expect(notifications[0].message).not.toContain("[Subagent");
191
+
192
+ asInternals(manager).stopSweep();
193
+ });
194
+ });
195
+
196
+ describe("Status response includes isFork", () => {
197
+ test("getState includes isFork for fork sub-agents", () => {
198
+ const manager = new SubagentManager();
199
+ const subagentId = "fork-1";
200
+ const state = makeForkState(subagentId);
201
+ injectFakeSubagent(manager, subagentId, state);
202
+
203
+ const retrieved = manager.getState(subagentId);
204
+ expect(retrieved).toBeDefined();
205
+ expect(retrieved!.isFork).toBe(true);
206
+ });
207
+
208
+ test("getState includes isFork: false for regular sub-agents", () => {
209
+ const manager = new SubagentManager();
210
+ const subagentId = "sub-1";
211
+ const state = makeState(subagentId);
212
+ injectFakeSubagent(manager, subagentId, state);
213
+
214
+ const retrieved = manager.getState(subagentId);
215
+ expect(retrieved).toBeDefined();
216
+ expect(retrieved!.isFork).toBe(false);
217
+ });
218
+
219
+ test("getChildrenOf includes isFork in each child state", () => {
220
+ const manager = new SubagentManager();
221
+ injectFakeSubagent(manager, "sub-1", makeState("sub-1"));
222
+ injectFakeSubagent(manager, "fork-1", makeForkState("fork-1"));
223
+
224
+ const children = manager.getChildrenOf("parent-sess-1");
225
+ expect(children).toHaveLength(2);
226
+
227
+ const regular = children.find((c) => c.config.id === "sub-1");
228
+ const fork = children.find((c) => c.config.id === "fork-1");
229
+ expect(regular!.isFork).toBe(false);
230
+ expect(fork!.isFork).toBe(true);
231
+ });
232
+ });
233
+
234
+ describe("Regular sub-agent notifications are unchanged", () => {
235
+ test("regular completed subagent uses [Subagent prefix", async () => {
236
+ const manager = new SubagentManager();
237
+ const subagentId = "sub-1";
238
+ const state = makeState(subagentId);
239
+ injectFakeSubagent(manager, subagentId, state);
240
+
241
+ const managed = asInternals(manager).subagents.get(subagentId)!;
242
+ managed.conversation!.persistUserMessage = () => "msg-1";
243
+ managed.conversation!.runAgentLoop = async () => {};
244
+
245
+ const notifications: { parentConversationId: string; message: string }[] =
246
+ [];
247
+ manager.onSubagentFinished = (parentConversationId, message) => {
248
+ notifications.push({ parentConversationId, message });
249
+ };
250
+
251
+ await asInternals(manager).runSubagent(subagentId, "Do something");
252
+
253
+ expect(notifications).toHaveLength(1);
254
+ expect(notifications[0].message).toContain(
255
+ '[Subagent "Test subagent" completed]',
256
+ );
257
+ expect(notifications[0].message).not.toContain("[Fork");
258
+ expect(notifications[0].message).not.toContain("last_n: 1");
259
+
260
+ asInternals(manager).stopSweep();
261
+ });
262
+
263
+ test("regular failed subagent uses [Subagent prefix", async () => {
264
+ const manager = new SubagentManager();
265
+ const subagentId = "sub-1";
266
+ const state = makeState(subagentId);
267
+ injectFakeSubagent(manager, subagentId, state);
268
+
269
+ const managed = asInternals(manager).subagents.get(subagentId)!;
270
+ managed.conversation!.persistUserMessage = () => "msg-1";
271
+ managed.conversation!.runAgentLoop = async () => {
272
+ throw new Error("Something went wrong");
273
+ };
274
+
275
+ const notifications: { parentConversationId: string; message: string }[] =
276
+ [];
277
+ manager.onSubagentFinished = (parentConversationId, message) => {
278
+ notifications.push({ parentConversationId, message });
279
+ };
280
+
281
+ await asInternals(manager).runSubagent(subagentId, "Do something");
282
+
283
+ expect(notifications).toHaveLength(1);
284
+ expect(notifications[0].message).toContain(
285
+ '[Subagent "Test subagent" failed]',
286
+ );
287
+ expect(notifications[0].message).not.toContain("[Fork");
288
+
289
+ asInternals(manager).stopSweep();
290
+ });
291
+ });
@@ -0,0 +1,384 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import type { ServerMessage } from "../daemon/message-protocol.js";
4
+ import type { Message } from "../providers/types.js";
5
+ import { SubagentManager } from "../subagent/manager.js";
6
+ import type { SubagentConfig, SubagentState } from "../subagent/types.js";
7
+
8
+ /** Minimal shape matching the private ManagedSubagent interface for test injection. */
9
+ interface FakeManagedSubagent {
10
+ conversation: {
11
+ abort: () => void;
12
+ dispose: () => void;
13
+ messages: Message[];
14
+ sendToClient: (msg: ServerMessage) => void;
15
+ persistUserMessage?: (msg: string) => string;
16
+ runAgentLoop?: () => Promise<void>;
17
+ enqueueMessage?: () => { rejected: boolean; queued: boolean };
18
+ injectInheritedContext?: (messages: Message[]) => void;
19
+ setSubagentAllowedTools?: (tools: Set<string>) => void;
20
+ getCurrentSystemPrompt?: () => string;
21
+ usageStats: {
22
+ inputTokens: number;
23
+ outputTokens: number;
24
+ estimatedCost: number;
25
+ };
26
+ } | null;
27
+ state: SubagentState;
28
+ parentSendToClient: (msg: ServerMessage) => void;
29
+ retainedUntil?: number;
30
+ hadEnqueuedMessages?: boolean;
31
+ }
32
+
33
+ /** Type-safe accessor for SubagentManager's private internals via bracket notation. */
34
+ interface ManagerInternals {
35
+ subagents: Map<string, FakeManagedSubagent>;
36
+ parentToChildren: Map<string, Set<string>>;
37
+ runSubagent: (subagentId: string, objective: string) => Promise<void>;
38
+ sweepTerminal: () => void;
39
+ stopSweep: () => void;
40
+ }
41
+
42
+ function asInternals(manager: SubagentManager): ManagerInternals {
43
+ return manager as unknown as ManagerInternals;
44
+ }
45
+
46
+ function makeFakeConversation(): NonNullable<
47
+ FakeManagedSubagent["conversation"]
48
+ > {
49
+ return {
50
+ abort: () => {},
51
+ dispose: () => {},
52
+ messages: [],
53
+ sendToClient: () => {},
54
+ usageStats: { inputTokens: 100, outputTokens: 50, estimatedCost: 0.005 },
55
+ };
56
+ }
57
+
58
+ function injectFakeSubagent(
59
+ manager: SubagentManager,
60
+ subagentId: string,
61
+ state: SubagentState,
62
+ parentSendToClient?: (msg: ServerMessage) => void,
63
+ conversation?: FakeManagedSubagent["conversation"],
64
+ ): void {
65
+ const internals = asInternals(manager);
66
+
67
+ internals.subagents.set(subagentId, {
68
+ conversation:
69
+ conversation === undefined ? makeFakeConversation() : conversation,
70
+ state,
71
+ parentSendToClient: parentSendToClient ?? (() => {}),
72
+ });
73
+
74
+ const parentId = state.config.parentConversationId;
75
+ if (!internals.parentToChildren.has(parentId)) {
76
+ internals.parentToChildren.set(parentId, new Set());
77
+ }
78
+ internals.parentToChildren.get(parentId)!.add(subagentId);
79
+ }
80
+
81
+ function makeConfig(
82
+ overrides: Partial<SubagentConfig> = {},
83
+ ): SubagentConfig {
84
+ return {
85
+ id: "sub-1",
86
+ parentConversationId: "parent-sess-1",
87
+ label: "Test subagent",
88
+ objective: "Do something",
89
+ ...overrides,
90
+ };
91
+ }
92
+
93
+ function makeState(
94
+ subagentId: string,
95
+ overrides: Partial<SubagentState> = {},
96
+ configOverrides: Partial<SubagentConfig> = {},
97
+ ): SubagentState {
98
+ return {
99
+ config: makeConfig({ id: subagentId, ...configOverrides }),
100
+ status: "running",
101
+ conversationId: "conv-sub-1",
102
+ isFork: false,
103
+ createdAt: Date.now(),
104
+ usage: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
105
+ ...overrides,
106
+ };
107
+ }
108
+
109
+ const FAKE_PARENT_MESSAGES: Message[] = [
110
+ {
111
+ role: "user",
112
+ content: [{ type: "text", text: "Hello from parent" }],
113
+ },
114
+ {
115
+ role: "assistant",
116
+ content: [{ type: "text", text: "Hello! How can I help?" }],
117
+ },
118
+ ];
119
+
120
+ describe("SubagentManager fork spawn", () => {
121
+ test("fork injects inherited context before persistUserMessage", async () => {
122
+ const manager = new SubagentManager();
123
+ const subagentId = "sub-fork-1";
124
+
125
+ const injectedMessages: Message[][] = [];
126
+ const fakeConversation = makeFakeConversation();
127
+ fakeConversation.persistUserMessage = () => "msg-1";
128
+ fakeConversation.runAgentLoop = async () => {};
129
+ fakeConversation.injectInheritedContext = (msgs: Message[]) => {
130
+ injectedMessages.push(msgs);
131
+ };
132
+
133
+ const state = makeState(
134
+ subagentId,
135
+ { isFork: true },
136
+ {
137
+ fork: true,
138
+ parentMessages: FAKE_PARENT_MESSAGES,
139
+ parentSystemPrompt: "You are a helpful assistant.",
140
+ },
141
+ );
142
+
143
+ injectFakeSubagent(manager, subagentId, state, undefined, fakeConversation);
144
+
145
+ await asInternals(manager).runSubagent(subagentId, "Do something");
146
+
147
+ expect(injectedMessages).toHaveLength(1);
148
+ expect(injectedMessages[0]).toEqual(FAKE_PARENT_MESSAGES);
149
+
150
+ asInternals(manager).stopSweep();
151
+ });
152
+
153
+ test("fork state has isFork: true", () => {
154
+ const state = makeState(
155
+ "sub-fork-1",
156
+ { isFork: true },
157
+ { fork: true },
158
+ );
159
+
160
+ expect(state.isFork).toBe(true);
161
+ });
162
+
163
+ test("fork defaults sendResultToUser to false", () => {
164
+ // Simulate the resolution logic from spawn():
165
+ // For forks, if sendResultToUser is undefined, it should resolve to false.
166
+ const config: SubagentConfig = makeConfig({
167
+ fork: true,
168
+ // sendResultToUser is undefined
169
+ });
170
+ const isFork = config.fork === true;
171
+ const resolvedSendResultToUser = isFork
172
+ ? config.sendResultToUser === true
173
+ ? true
174
+ : false
175
+ : config.sendResultToUser;
176
+
177
+ expect(resolvedSendResultToUser).toBe(false);
178
+ });
179
+
180
+ test("fork with explicit sendResultToUser: true preserves it", () => {
181
+ const config: SubagentConfig = makeConfig({
182
+ fork: true,
183
+ sendResultToUser: true,
184
+ });
185
+ const isFork = config.fork === true;
186
+ const resolvedSendResultToUser = isFork
187
+ ? config.sendResultToUser === true
188
+ ? true
189
+ : false
190
+ : config.sendResultToUser;
191
+
192
+ expect(resolvedSendResultToUser).toBe(true);
193
+ });
194
+
195
+ test("non-fork spawn does not inject inherited context", async () => {
196
+ const manager = new SubagentManager();
197
+ const subagentId = "sub-normal-1";
198
+
199
+ let injectCalled = false;
200
+ const fakeConversation = makeFakeConversation();
201
+ fakeConversation.persistUserMessage = () => "msg-1";
202
+ fakeConversation.runAgentLoop = async () => {};
203
+ fakeConversation.injectInheritedContext = () => {
204
+ injectCalled = true;
205
+ };
206
+
207
+ const state = makeState(subagentId, { isFork: false });
208
+
209
+ injectFakeSubagent(manager, subagentId, state, undefined, fakeConversation);
210
+
211
+ await asInternals(manager).runSubagent(subagentId, "Do something");
212
+
213
+ expect(injectCalled).toBe(false);
214
+
215
+ asInternals(manager).stopSweep();
216
+ });
217
+
218
+ test("non-fork sendResultToUser defaults are unaffected", () => {
219
+ const config: SubagentConfig = makeConfig({
220
+ fork: false,
221
+ // sendResultToUser is undefined
222
+ });
223
+ const isFork = config.fork === true;
224
+ const resolvedSendResultToUser = isFork
225
+ ? config.sendResultToUser === true
226
+ ? true
227
+ : false
228
+ : config.sendResultToUser;
229
+
230
+ // Non-fork: sendResultToUser should remain undefined (caller handles default)
231
+ expect(resolvedSendResultToUser).toBeUndefined();
232
+ });
233
+
234
+ test("fork uses default memory scope, not isolated subagent scope", () => {
235
+ // Validate the fork memory policy shape matches what spawn() produces.
236
+ const isFork = true;
237
+ const subagentId = "sub-fork-mem";
238
+
239
+ const memoryPolicy = isFork
240
+ ? {
241
+ scopeId: "default",
242
+ includeDefaultFallback: false,
243
+ strictSideEffects: false,
244
+ }
245
+ : {
246
+ scopeId: `subagent:${subagentId}`,
247
+ includeDefaultFallback: true,
248
+ strictSideEffects: false,
249
+ };
250
+
251
+ expect(memoryPolicy.scopeId).toBe("default");
252
+ expect(memoryPolicy.includeDefaultFallback).toBe(false);
253
+ });
254
+
255
+ test("non-fork uses isolated subagent memory scope", () => {
256
+ const isFork = false;
257
+ const subagentId = "sub-normal-mem";
258
+
259
+ const memoryPolicy = isFork
260
+ ? {
261
+ scopeId: "default",
262
+ includeDefaultFallback: false,
263
+ strictSideEffects: false,
264
+ }
265
+ : {
266
+ scopeId: `subagent:${subagentId}`,
267
+ includeDefaultFallback: true,
268
+ strictSideEffects: false,
269
+ };
270
+
271
+ expect(memoryPolicy.scopeId).toBe(`subagent:${subagentId}`);
272
+ expect(memoryPolicy.includeDefaultFallback).toBe(true);
273
+ });
274
+
275
+ test("fork forces general role and skips tool filtering", async () => {
276
+ const manager = new SubagentManager();
277
+ const subagentId = "sub-fork-role";
278
+
279
+ const fakeConversation = makeFakeConversation();
280
+ fakeConversation.persistUserMessage = () => "msg-1";
281
+ fakeConversation.runAgentLoop = async () => {};
282
+ fakeConversation.injectInheritedContext = () => {};
283
+ fakeConversation.setSubagentAllowedTools = () => {};
284
+
285
+ // Create a fork state — in real spawn(), the role would be forced to
286
+ // "general" regardless of what was requested, and tool filtering skipped.
287
+ const state = makeState(
288
+ subagentId,
289
+ { isFork: true },
290
+ {
291
+ fork: true,
292
+ role: "general", // forced by spawn() logic
293
+ parentMessages: FAKE_PARENT_MESSAGES,
294
+ parentSystemPrompt: "Parent system prompt.",
295
+ },
296
+ );
297
+
298
+ injectFakeSubagent(manager, subagentId, state, undefined, fakeConversation);
299
+
300
+ await asInternals(manager).runSubagent(subagentId, "Do something");
301
+
302
+ // Tool filtering is only applied in spawn(), not runSubagent(), so we
303
+ // verify the logic directly: forks skip setSubagentAllowedTools.
304
+ // For this test, we verify the fork's role is general (which has no allowedTools).
305
+ expect(state.config.role).toBe("general");
306
+
307
+ asInternals(manager).stopSweep();
308
+ });
309
+
310
+ test("fork uses parent system prompt, not subagent-built prompt", () => {
311
+ // The fork branch in spawn() uses config.parentSystemPrompt directly.
312
+ // If it's not provided, it falls back to resolveParentConversation.
313
+ const parentPrompt = "You are the parent's system prompt.";
314
+ const config: SubagentConfig = makeConfig({
315
+ fork: true,
316
+ parentSystemPrompt: parentPrompt,
317
+ });
318
+
319
+ // Simulate fork system prompt resolution from spawn():
320
+ const isFork = config.fork === true;
321
+ let systemPrompt: string;
322
+ if (isFork && config.parentSystemPrompt) {
323
+ systemPrompt = config.parentSystemPrompt;
324
+ } else {
325
+ systemPrompt = "built subagent prompt"; // would be from buildSubagentSystemPrompt
326
+ }
327
+
328
+ expect(systemPrompt).toBe(parentPrompt);
329
+ });
330
+
331
+ test("fork resolves system prompt via resolveParentConversation when parentSystemPrompt is absent", () => {
332
+ const manager = new SubagentManager();
333
+ const parentPrompt = "Resolved parent system prompt.";
334
+
335
+ // Wire the resolveParentConversation callback.
336
+ manager.resolveParentConversation = (id: string) => {
337
+ if (id === "parent-sess-1") {
338
+ return {
339
+ getCurrentSystemPrompt: () => parentPrompt,
340
+ } as any;
341
+ }
342
+ return undefined;
343
+ };
344
+
345
+ // Simulate the fallback logic from spawn():
346
+ const config: SubagentConfig = makeConfig({
347
+ fork: true,
348
+ parentConversationId: "parent-sess-1",
349
+ // parentSystemPrompt is NOT set
350
+ });
351
+
352
+ let systemPrompt: string | undefined;
353
+ if (config.fork && !config.parentSystemPrompt && manager.resolveParentConversation) {
354
+ const parentConv = manager.resolveParentConversation(config.parentConversationId);
355
+ systemPrompt = (parentConv as any)?.getCurrentSystemPrompt?.();
356
+ }
357
+
358
+ expect(systemPrompt).toBe(parentPrompt);
359
+ });
360
+
361
+ test("fork throws when no parent system prompt is available", () => {
362
+ // Simulate the error case from spawn():
363
+ const config: SubagentConfig = makeConfig({
364
+ fork: true,
365
+ parentConversationId: "parent-sess-missing",
366
+ // parentSystemPrompt is NOT set
367
+ });
368
+
369
+ const resolveParentConversation = (_id: string) => undefined;
370
+
371
+ expect(() => {
372
+ if (config.fork && !config.parentSystemPrompt) {
373
+ const parentConv = resolveParentConversation(config.parentConversationId);
374
+ const resolved = (parentConv as any)?.getCurrentSystemPrompt?.();
375
+ if (!resolved) {
376
+ throw new Error(
377
+ "Fork spawn requires a parent system prompt but neither config.parentSystemPrompt " +
378
+ "nor resolveParentConversation yielded one.",
379
+ );
380
+ }
381
+ }
382
+ }).toThrow("Fork spawn requires a parent system prompt");
383
+ });
384
+ });
@@ -87,6 +87,7 @@ function makeState(
87
87
  },
88
88
  status: "running",
89
89
  conversationId: "conv-sub-1",
90
+ isFork: false,
90
91
  createdAt: Date.now(),
91
92
  usage: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
92
93
  ...overrides,
@@ -70,6 +70,7 @@ function injectSubagent(
70
70
  },
71
71
  status,
72
72
  conversationId: `conv-${subagentId}`,
73
+ isFork: false,
73
74
  createdAt: Date.now(),
74
75
  usage: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
75
76
  ...overrides,