@vellumai/assistant 0.6.2 → 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.
- package/bun.lock +40 -40
- package/bunfig.toml +3 -0
- package/docs/architecture/memory.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
- package/openapi.yaml +184 -69
- package/package.json +41 -41
- package/scripts/generate-openapi.ts +1 -2
- package/src/__tests__/acp-session.test.ts +43 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +1 -0
- package/src/__tests__/app-source-watcher.test.ts +37 -11
- package/src/__tests__/approval-routes-http.test.ts +178 -1
- package/src/__tests__/browser-fill-credential.test.ts +229 -94
- package/src/__tests__/browser-manager.test.ts +40 -27
- package/src/__tests__/catalog-files.test.ts +862 -0
- package/src/__tests__/channel-approvals.test.ts +53 -0
- package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +125 -48
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
- package/src/__tests__/context-overflow-approval.test.ts +16 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +1 -1
- package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
- package/src/__tests__/conversation-fork-crud.test.ts +17 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
- package/src/__tests__/conversation-inject-context.test.ts +103 -0
- package/src/__tests__/conversation-queue.test.ts +45 -2
- package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
- package/src/__tests__/conversation-starter-routes.test.ts +126 -0
- package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
- package/src/__tests__/conversation-store.test.ts +195 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +4 -4
- package/src/__tests__/credential-vault.test.ts +152 -13
- package/src/__tests__/credentials-cli.test.ts +2 -2
- package/src/__tests__/date-context.test.ts +4 -4
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
- package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
- package/src/__tests__/gateway-only-guard.test.ts +3 -0
- package/src/__tests__/gemini-provider.test.ts +2 -2
- package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
- package/src/__tests__/headless-browser-interactions.test.ts +707 -371
- package/src/__tests__/headless-browser-navigate.test.ts +389 -47
- package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
- package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
- package/src/__tests__/host-bash-proxy.test.ts +150 -1
- package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
- package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
- package/src/__tests__/host-browser-event-routes.test.ts +350 -0
- package/src/__tests__/host-browser-proxy.test.ts +444 -0
- package/src/__tests__/host-browser-routes.test.ts +198 -0
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
- package/src/__tests__/host-cu-proxy.test.ts +171 -1
- package/src/__tests__/host-file-proxy.test.ts +185 -1
- package/src/__tests__/host-file-read-tool.test.ts +52 -0
- package/src/__tests__/host-proxy-interface.test.ts +165 -0
- package/src/__tests__/host-shell-tool.test.ts +1 -11
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/mcp-client-auth.test.ts +40 -4
- package/src/__tests__/mcp-health-check.test.ts +10 -3
- package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
- package/src/__tests__/migration-export-http.test.ts +61 -2
- package/src/__tests__/migration-export-streaming.test.ts +66 -0
- package/src/__tests__/migration-import-commit-http.test.ts +101 -1
- package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
- package/src/__tests__/oauth-apps-routes.test.ts +17 -12
- package/src/__tests__/oauth-cli.test.ts +707 -60
- package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
- package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
- package/src/__tests__/oauth-providers-routes.test.ts +50 -14
- package/src/__tests__/oauth-store.test.ts +1386 -182
- package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
- package/src/__tests__/onboarding-template-contract.test.ts +75 -57
- package/src/__tests__/openai-provider.test.ts +2 -2
- package/src/__tests__/outlook-categories.test.ts +1 -1
- package/src/__tests__/outlook-client-automation.test.ts +1 -1
- package/src/__tests__/outlook-compose-tools.test.ts +1 -1
- package/src/__tests__/outlook-email-watcher.test.ts +1 -1
- package/src/__tests__/outlook-follow-up.test.ts +1 -1
- package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
- package/src/__tests__/outlook-trash.test.ts +1 -1
- package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
- package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
- package/src/__tests__/permission-mode.test.ts +28 -56
- package/src/__tests__/platform-callback-registration.test.ts +19 -0
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
- package/src/__tests__/proxy-approval-callback.test.ts +18 -0
- package/src/__tests__/require-fresh-approval.test.ts +40 -1
- package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
- package/src/__tests__/schedule-routes.test.ts +162 -0
- package/src/__tests__/secret-detection-handler.test.ts +84 -0
- package/src/__tests__/secret-ingress-http.test.ts +1 -0
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/set-permission-mode.test.ts +13 -250
- package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
- package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
- package/src/__tests__/slack-channel-config.test.ts +12 -15
- package/src/__tests__/subagent-detail.test.ts +44 -2
- package/src/__tests__/subagent-disposal.test.ts +1 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
- package/src/__tests__/subagent-manager-notify.test.ts +1 -0
- package/src/__tests__/subagent-notify-parent.test.ts +1 -0
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/subagent-types.test.ts +1 -0
- package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
- package/src/__tests__/system-prompt.test.ts +72 -1
- package/src/__tests__/task-scheduler.test.ts +32 -6
- package/src/__tests__/telegram-config.test.ts +10 -13
- package/src/__tests__/terminal-tools.test.ts +9 -0
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
- package/src/__tests__/top-level-renderer.test.ts +73 -1
- package/src/__tests__/transport-hints-queue.test.ts +14 -29
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/loop.ts +12 -6
- package/src/approvals/guardian-request-resolvers.ts +21 -15
- package/src/browser-session/__tests__/manager.test.ts +297 -0
- package/src/browser-session/backends/cdp-inspect.ts +30 -0
- package/src/browser-session/backends/extension.ts +26 -0
- package/src/browser-session/backends/local.ts +24 -0
- package/src/browser-session/events.ts +164 -0
- package/src/browser-session/index.ts +27 -0
- package/src/browser-session/manager.ts +159 -0
- package/src/browser-session/types.ts +28 -0
- package/src/channels/__tests__/types.test.ts +134 -0
- package/src/channels/types.ts +53 -3
- package/src/cli/commands/browser-relay.ts +339 -409
- package/src/cli/commands/credentials.ts +3 -3
- package/src/cli/commands/email.ts +18 -13
- package/src/cli/commands/mcp.ts +16 -4
- package/src/cli/commands/oauth/__tests__/connect.test.ts +44 -44
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
- package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
- package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
- package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
- package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
- package/src/cli/commands/oauth/apps.ts +7 -4
- package/src/cli/commands/oauth/connect.ts +6 -3
- package/src/cli/commands/oauth/disconnect.ts +1 -1
- package/src/cli/commands/oauth/providers.ts +200 -36
- package/src/cli/commands/oauth/shared.ts +5 -5
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
- package/src/cli/commands/platform/index.ts +107 -10
- package/src/cli/commands/usage.ts +10 -9
- package/src/cli/lib/daemon-credential-client.ts +4 -0
- package/src/cli/program.ts +1 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
- package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
- package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
- package/src/config/bundled-skills/contacts/SKILL.md +3 -0
- package/src/config/bundled-skills/document/SKILL.md +4 -0
- package/src/config/bundled-skills/gmail/SKILL.md +1 -1
- package/src/config/bundled-skills/outlook/SKILL.md +7 -0
- package/src/config/bundled-skills/subagent/SKILL.md +21 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
- package/src/config/bundled-skills/tasks/SKILL.md +5 -0
- package/src/config/env-registry.ts +14 -0
- package/src/config/env.ts +21 -0
- package/src/config/feature-flag-registry.json +44 -5
- package/src/config/loader.ts +56 -1
- package/src/config/sanitize-for-transfer.ts +47 -0
- package/src/config/schema.ts +46 -5
- package/src/config/schemas/host-browser.ts +66 -0
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/schemas/memory-retrieval.ts +103 -0
- package/src/config/schemas/security.ts +0 -6
- package/src/config/schemas/services.ts +8 -0
- package/src/config/types.ts +0 -1
- package/src/context/post-turn-tool-result-truncation.ts +176 -0
- package/src/context/window-manager.ts +19 -1
- package/src/credential-execution/approval-bridge.ts +49 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/context-overflow-approval.ts +5 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
- package/src/daemon/conversation-agent-loop.ts +58 -24
- package/src/daemon/conversation-attachments.ts +40 -0
- package/src/daemon/conversation-process.ts +48 -1
- package/src/daemon/conversation-runtime-assembly.ts +118 -36
- package/src/daemon/conversation-surfaces.ts +37 -36
- package/src/daemon/conversation-tool-setup.ts +74 -8
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +226 -8
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/conversations.ts +9 -140
- package/src/daemon/handlers/shared.ts +58 -0
- package/src/daemon/handlers/skills.ts +232 -37
- package/src/daemon/host-bash-proxy.ts +48 -13
- package/src/daemon/host-browser-proxy.ts +191 -0
- package/src/daemon/host-cu-proxy.ts +36 -11
- package/src/daemon/host-file-proxy.ts +57 -9
- package/src/daemon/lifecycle.ts +65 -11
- package/src/daemon/message-protocol.ts +7 -0
- package/src/daemon/message-types/conversations.ts +55 -13
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/messages.ts +5 -5
- package/src/daemon/message-types/skills.ts +10 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/server.ts +92 -12
- package/src/daemon/tool-side-effects.ts +6 -0
- package/src/daemon/transport-hints.ts +5 -24
- package/src/inbound/platform-callback-registration.ts +18 -17
- package/src/mcp/client.ts +59 -24
- package/src/memory/app-store.ts +31 -1
- package/src/memory/conversation-crud.ts +23 -0
- package/src/memory/conversation-starters-cadence.ts +76 -0
- package/src/memory/conversation-title-service.ts +5 -2
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.test.ts +75 -0
- package/src/memory/embedding-backend.ts +131 -5
- package/src/memory/embedding-gemini.test.ts +54 -0
- package/src/memory/embedding-gemini.ts +20 -9
- package/src/memory/embedding-local.ts +176 -17
- package/src/memory/graph/consolidation.ts +10 -23
- package/src/memory/graph/extraction-job.ts +15 -0
- package/src/memory/graph/retriever.ts +40 -22
- package/src/memory/graph/store.test.ts +7 -3
- package/src/memory/graph/store.ts +47 -12
- package/src/memory/llm-usage-store.ts +45 -4
- package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
- package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
- package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
- package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
- package/src/memory/migrations/217-conversation-host-access.ts +40 -0
- package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +1 -0
- package/src/memory/schema/oauth.ts +18 -13
- package/src/oauth/AGENTS.md +76 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
- package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
- package/src/oauth/byo-connection.test.ts +8 -8
- package/src/oauth/byo-connection.ts +7 -7
- package/src/oauth/connect-orchestrator.ts +23 -21
- package/src/oauth/connect-types.ts +3 -3
- package/src/oauth/connection-resolver.test.ts +17 -4
- package/src/oauth/connection-resolver.ts +16 -16
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +13 -13
- package/src/oauth/oauth-store.ts +214 -100
- package/src/oauth/platform-connection.test.ts +3 -3
- package/src/oauth/platform-connection.ts +4 -4
- package/src/oauth/provider-serializer.ts +31 -5
- package/src/oauth/revoke.ts +76 -0
- package/src/oauth/seed-providers.ts +126 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -1
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/prompts/system-prompt.ts +18 -21
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -105
- package/src/providers/anthropic/client.ts +1 -0
- package/src/providers/types.ts +1 -1
- package/src/runtime/AGENTS.md +23 -0
- package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
- package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
- package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
- package/src/runtime/assistant-event-hub.ts +2 -2
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
- package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
- package/src/runtime/auth/middleware.ts +98 -0
- package/src/runtime/auth/route-policy.ts +6 -7
- package/src/runtime/capability-tokens.ts +414 -0
- package/src/runtime/channel-approvals.ts +18 -5
- package/src/runtime/chrome-extension-registry.ts +332 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
- package/src/runtime/guardian-decision-types.ts +7 -0
- package/src/runtime/http-server.ts +425 -70
- package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
- package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
- package/src/runtime/migrations/migration-transport.ts +6 -0
- package/src/runtime/migrations/migration-wizard.ts +22 -2
- package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
- package/src/runtime/migrations/vbundle-builder.ts +145 -38
- package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
- package/src/runtime/migrations/vbundle-importer.ts +55 -5
- package/src/runtime/pending-interactions.ts +29 -13
- package/src/runtime/routes/approval-routes.ts +90 -16
- package/src/runtime/routes/browser-cdp-routes.ts +229 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
- package/src/runtime/routes/conversation-analysis-routes.ts +2 -1
- package/src/runtime/routes/conversation-management-routes.ts +108 -0
- package/src/runtime/routes/conversation-routes.ts +301 -27
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/guardian-action-routes.ts +24 -13
- package/src/runtime/routes/host-browser-routes.ts +279 -0
- package/src/runtime/routes/host-file-routes.ts +9 -1
- package/src/runtime/routes/identity-routes.ts +259 -16
- package/src/runtime/routes/log-export-routes.ts +42 -22
- package/src/runtime/routes/memory-item-routes.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +87 -2
- package/src/runtime/routes/oauth-apps.ts +15 -17
- package/src/runtime/routes/oauth-providers.ts +4 -0
- package/src/runtime/routes/schedule-routes.ts +24 -11
- package/src/runtime/routes/settings-routes.ts +9 -97
- package/src/runtime/routes/skills-routes.ts +52 -2
- package/src/runtime/routes/subagents-routes.ts +14 -10
- package/src/runtime/routes/usage-routes.ts +8 -7
- package/src/runtime/routes/workspace-routes.test.ts +22 -0
- package/src/runtime/routes/workspace-routes.ts +8 -1
- package/src/runtime/routes/workspace-utils.ts +2 -0
- package/src/schedule/scheduler.ts +7 -5
- package/src/security/ces-credential-client.ts +20 -0
- package/src/security/ces-rpc-credential-backend.ts +17 -0
- package/src/security/credential-backend.ts +5 -0
- package/src/security/oauth2.ts +42 -25
- package/src/security/secure-keys.ts +118 -25
- package/src/security/token-manager.ts +23 -10
- package/src/skills/catalog-files.ts +492 -0
- package/src/subagent/manager.ts +131 -26
- package/src/subagent/types.ts +19 -0
- package/src/tools/apps/executors.ts +11 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
- package/src/tools/browser/auth-detector.ts +43 -12
- package/src/tools/browser/browser-execution.ts +645 -340
- package/src/tools/browser/browser-manager.ts +36 -12
- package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
- package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
- package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
- package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
- package/src/tools/browser/cdp-client/errors.ts +34 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
- package/src/tools/browser/cdp-client/factory.ts +204 -0
- package/src/tools/browser/cdp-client/index.ts +14 -0
- package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
- package/src/tools/browser/cdp-client/types.ts +52 -0
- package/src/tools/filesystem/edit.ts +1 -1
- package/src/tools/filesystem/list.ts +1 -1
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +1 -1
- package/src/tools/host-filesystem/read.ts +12 -15
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +21 -16
- package/src/tools/permission-checker.ts +77 -82
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -0
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/subagent/spawn.ts +47 -3
- package/src/tools/subagent/status.ts +2 -0
- package/src/tools/system/register.ts +2 -16
- package/src/tools/terminal/safe-env.ts +7 -0
- package/src/tools/terminal/shell.ts +21 -16
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/types.ts +2 -0
- package/src/util/platform.ts +14 -19
- package/src/workspace/top-level-renderer.ts +19 -1
- package/src/__tests__/chrome-cdp.test.ts +0 -419
- package/src/__tests__/permission-mode-sse.test.ts +0 -418
- package/src/__tests__/permission-mode-store.test.ts +0 -277
- package/src/browser-extension-relay/protocol.ts +0 -63
- package/src/browser-extension-relay/server.ts +0 -203
- package/src/config/schemas/sandbox.ts +0 -14
- package/src/permissions/permission-mode-store.ts +0 -180
- package/src/tools/browser/chrome-cdp.ts +0 -239
- 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
|
+
});
|