@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
|
@@ -8,7 +8,7 @@ import type { Tool, ToolContext, ToolExecutionResult } from "../types.js";
|
|
|
8
8
|
class HostFileWriteTool implements Tool {
|
|
9
9
|
name = "host_file_write";
|
|
10
10
|
description =
|
|
11
|
-
"Write content to a file on
|
|
11
|
+
"Write content to a file on your guardian's device, creating it if it does not exist. For files on your own machine, use file_write instead.";
|
|
12
12
|
category = "host-filesystem";
|
|
13
13
|
defaultRiskLevel = RiskLevel.Medium;
|
|
14
14
|
|
|
@@ -207,30 +207,35 @@ class HostShellTool implements Tool {
|
|
|
207
207
|
detached: true,
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
-
|
|
211
|
-
|
|
210
|
+
// Kill the entire process tree. Tries the process group first
|
|
211
|
+
// (negative PID), then falls back to killing the direct child if the
|
|
212
|
+
// PID is unavailable or the group kill fails.
|
|
213
|
+
const killTree = () => {
|
|
214
|
+
if (child.pid != null) {
|
|
215
|
+
try {
|
|
216
|
+
process.kill(-child.pid, "SIGKILL");
|
|
217
|
+
return;
|
|
218
|
+
} catch {
|
|
219
|
+
// Process group may have already exited — fall through.
|
|
220
|
+
}
|
|
221
|
+
}
|
|
212
222
|
try {
|
|
213
|
-
|
|
223
|
+
child.kill("SIGKILL");
|
|
214
224
|
} catch {
|
|
215
|
-
//
|
|
225
|
+
// Child may have already exited.
|
|
216
226
|
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const timer = setTimeout(() => {
|
|
230
|
+
timedOut = true;
|
|
231
|
+
killTree();
|
|
217
232
|
}, timeoutMs);
|
|
218
233
|
|
|
219
234
|
// Cooperative cancellation via AbortSignal
|
|
220
|
-
const onAbort = () =>
|
|
221
|
-
try {
|
|
222
|
-
process.kill(-child.pid!, "SIGKILL");
|
|
223
|
-
} catch {
|
|
224
|
-
// Process group may have already exited.
|
|
225
|
-
}
|
|
226
|
-
};
|
|
235
|
+
const onAbort = () => killTree();
|
|
227
236
|
if (context.signal) {
|
|
228
237
|
if (context.signal.aborted) {
|
|
229
|
-
|
|
230
|
-
process.kill(-child.pid!, "SIGKILL");
|
|
231
|
-
} catch {
|
|
232
|
-
// Process group may have already exited.
|
|
233
|
-
}
|
|
238
|
+
killTree();
|
|
234
239
|
} else {
|
|
235
240
|
context.signal.addEventListener("abort", onAbort, { once: true });
|
|
236
241
|
}
|
|
@@ -7,11 +7,14 @@ import {
|
|
|
7
7
|
generateAllowlistOptions,
|
|
8
8
|
generateScopeOptions,
|
|
9
9
|
} from "../permissions/checker.js";
|
|
10
|
-
import { getMode } from "../permissions/permission-mode-store.js";
|
|
11
10
|
import type { PermissionPrompter } from "../permissions/prompter.js";
|
|
12
11
|
import { addRule } from "../permissions/trust-store.js";
|
|
13
12
|
import { RiskLevel } from "../permissions/types.js";
|
|
14
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
CONVERSATION_HOST_ACCESS_PROMPT,
|
|
15
|
+
evaluateV2ConsentDisposition,
|
|
16
|
+
isConversationHostAccessDecision,
|
|
17
|
+
} from "../permissions/v2-consent-policy.js";
|
|
15
18
|
import {
|
|
16
19
|
getEffectiveMode,
|
|
17
20
|
setConversationMode,
|
|
@@ -67,50 +70,29 @@ export class PermissionChecker {
|
|
|
67
70
|
}
|
|
68
71
|
| undefined,
|
|
69
72
|
): Promise<PermissionDecision> {
|
|
70
|
-
// ── permission-controls-v2 early gate ──────────────────────────────
|
|
71
|
-
// When the v2 flag is enabled, replace the entire risk-classification
|
|
72
|
-
// path with a simple binary check: is it a host tool + is host access
|
|
73
|
-
// enabled? Certain security gates (requireFreshApproval,
|
|
74
|
-
// forcePromptSideEffects, hostAccess=false) fall through to the v1
|
|
75
|
-
// prompt flow so the interactive prompter is engaged.
|
|
76
|
-
const cfg = getConfig();
|
|
77
73
|
let v2ForcePrompt = false;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// interactive prompter is engaged (returning allowed:false here
|
|
100
|
-
// would surface an error string instead of a permission dialog).
|
|
101
|
-
// The v2ForcePrompt flag ensures check()'s allow decision is
|
|
102
|
-
// promoted to prompt so the user sees a permission dialog.
|
|
103
|
-
v2ForcePrompt = true;
|
|
104
|
-
} else {
|
|
105
|
-
// Non-host tools are auto-allowed when v2 is on
|
|
106
|
-
return {
|
|
107
|
-
allowed: true,
|
|
108
|
-
decision: "allow",
|
|
109
|
-
riskLevel: RiskLevel.Low,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
74
|
+
const cfg = getConfig();
|
|
75
|
+
const v2Enabled = isAssistantFeatureFlagEnabled(
|
|
76
|
+
"permission-controls-v2",
|
|
77
|
+
cfg,
|
|
78
|
+
);
|
|
79
|
+
if (v2Enabled) {
|
|
80
|
+
const v2Disposition = evaluateV2ConsentDisposition(name, input, context);
|
|
81
|
+
if (v2Disposition === "auto_allow") {
|
|
82
|
+
return {
|
|
83
|
+
allowed: true,
|
|
84
|
+
decision: "allow",
|
|
85
|
+
riskLevel: RiskLevel.Low,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (v2Disposition === "prompt_host_access") {
|
|
89
|
+
// Host tool with hostAccess disabled — fall through to v1 so the
|
|
90
|
+
// interactive prompter is engaged (returning allowed:false here
|
|
91
|
+
// would surface an error string instead of a permission dialog).
|
|
92
|
+
// The v2ForcePrompt flag ensures check()'s allow decision is
|
|
93
|
+
// promoted to prompt so the user sees a permission dialog.
|
|
94
|
+
v2ForcePrompt = true;
|
|
112
95
|
}
|
|
113
|
-
// Falls through to the v1 risk-classification + prompter path
|
|
114
96
|
}
|
|
115
97
|
|
|
116
98
|
const risk = await classifyRisk(
|
|
@@ -301,25 +283,34 @@ export class PermissionChecker {
|
|
|
301
283
|
return { allowed: true, decision: "temporary_override", riskLevel };
|
|
302
284
|
}
|
|
303
285
|
|
|
304
|
-
const allowlistOptions = await generateAllowlistOptions(
|
|
305
|
-
name,
|
|
306
|
-
input,
|
|
307
|
-
context.signal,
|
|
308
|
-
);
|
|
309
|
-
const scopeOptions = generateScopeOptions(context.workingDir, name);
|
|
310
286
|
const previewDiff = computePreviewDiff(name, input, context.workingDir);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
:
|
|
287
|
+
const promptOptions = v2ForcePrompt
|
|
288
|
+
? CONVERSATION_HOST_ACCESS_PROMPT
|
|
289
|
+
: v2Enabled
|
|
290
|
+
? {
|
|
291
|
+
allowlistOptions: [] as Awaited<
|
|
292
|
+
ReturnType<typeof generateAllowlistOptions>
|
|
293
|
+
>,
|
|
294
|
+
scopeOptions: [] as ReturnType<typeof generateScopeOptions>,
|
|
295
|
+
persistentDecisionsAllowed: false,
|
|
296
|
+
temporaryOptionsAvailable: undefined,
|
|
297
|
+
}
|
|
298
|
+
: {
|
|
299
|
+
allowlistOptions: await generateAllowlistOptions(
|
|
300
|
+
name,
|
|
301
|
+
input,
|
|
302
|
+
context.signal,
|
|
303
|
+
),
|
|
304
|
+
scopeOptions: generateScopeOptions(context.workingDir, name),
|
|
305
|
+
persistentDecisionsAllowed: !context.requireFreshApproval,
|
|
306
|
+
temporaryOptionsAvailable:
|
|
307
|
+
context.trustClass === "guardian" &&
|
|
308
|
+
!context.requireFreshApproval
|
|
309
|
+
? (["allow_10m", "allow_conversation"] as Array<
|
|
310
|
+
"allow_10m" | "allow_conversation"
|
|
311
|
+
>)
|
|
312
|
+
: undefined,
|
|
313
|
+
};
|
|
323
314
|
|
|
324
315
|
emitLifecycleEvent({
|
|
325
316
|
type: "permission_prompt",
|
|
@@ -331,10 +322,10 @@ export class PermissionChecker {
|
|
|
331
322
|
requestId: context.requestId,
|
|
332
323
|
riskLevel,
|
|
333
324
|
reason: result.reason,
|
|
334
|
-
allowlistOptions,
|
|
335
|
-
scopeOptions,
|
|
325
|
+
allowlistOptions: promptOptions.allowlistOptions,
|
|
326
|
+
scopeOptions: promptOptions.scopeOptions,
|
|
336
327
|
diff: previewDiff,
|
|
337
|
-
persistentDecisionsAllowed,
|
|
328
|
+
persistentDecisionsAllowed: promptOptions.persistentDecisionsAllowed,
|
|
338
329
|
});
|
|
339
330
|
|
|
340
331
|
await getHookManager().trigger("permission-request", {
|
|
@@ -348,27 +339,31 @@ export class PermissionChecker {
|
|
|
348
339
|
name,
|
|
349
340
|
input,
|
|
350
341
|
riskLevel,
|
|
351
|
-
allowlistOptions,
|
|
352
|
-
scopeOptions,
|
|
342
|
+
promptOptions.allowlistOptions,
|
|
343
|
+
promptOptions.scopeOptions,
|
|
353
344
|
previewDiff,
|
|
354
345
|
context.conversationId,
|
|
355
346
|
executionTarget,
|
|
356
|
-
persistentDecisionsAllowed,
|
|
347
|
+
promptOptions.persistentDecisionsAllowed,
|
|
357
348
|
context.signal,
|
|
358
|
-
temporaryOptionsAvailable,
|
|
349
|
+
promptOptions.temporaryOptionsAvailable,
|
|
359
350
|
context.toolUseId,
|
|
351
|
+
v2ForcePrompt,
|
|
360
352
|
);
|
|
361
353
|
|
|
362
|
-
const decision =
|
|
354
|
+
const decision =
|
|
355
|
+
v2ForcePrompt && !isConversationHostAccessDecision(response.decision)
|
|
356
|
+
? "deny"
|
|
357
|
+
: response.decision;
|
|
363
358
|
|
|
364
359
|
await getHookManager().trigger("permission-resolve", {
|
|
365
360
|
toolName: name,
|
|
366
|
-
decision
|
|
361
|
+
decision,
|
|
367
362
|
riskLevel,
|
|
368
363
|
conversationId: context.conversationId,
|
|
369
364
|
});
|
|
370
365
|
|
|
371
|
-
if (
|
|
366
|
+
if (decision === "deny") {
|
|
372
367
|
const contextualDenial =
|
|
373
368
|
typeof response.decisionContext === "string"
|
|
374
369
|
? response.decisionContext.trim()
|
|
@@ -403,15 +398,15 @@ export class PermissionChecker {
|
|
|
403
398
|
};
|
|
404
399
|
}
|
|
405
400
|
|
|
406
|
-
if (
|
|
401
|
+
if (decision === "always_deny") {
|
|
407
402
|
// For non-scoped tools (empty scopeOptions), default to 'everywhere' since
|
|
408
403
|
// the client has no scope picker and will send undefined.
|
|
409
404
|
const effectiveDenyScope =
|
|
410
|
-
scopeOptions.length === 0
|
|
405
|
+
promptOptions.scopeOptions.length === 0
|
|
411
406
|
? (response.selectedScope ?? "everywhere")
|
|
412
407
|
: response.selectedScope;
|
|
413
408
|
const ruleSaved = !!(
|
|
414
|
-
persistentDecisionsAllowed &&
|
|
409
|
+
promptOptions.persistentDecisionsAllowed &&
|
|
415
410
|
response.selectedPattern &&
|
|
416
411
|
effectiveDenyScope
|
|
417
412
|
);
|
|
@@ -452,9 +447,9 @@ export class PermissionChecker {
|
|
|
452
447
|
}
|
|
453
448
|
|
|
454
449
|
if (
|
|
455
|
-
persistentDecisionsAllowed &&
|
|
456
|
-
(
|
|
457
|
-
|
|
450
|
+
promptOptions.persistentDecisionsAllowed &&
|
|
451
|
+
(decision === "always_allow" ||
|
|
452
|
+
decision === "always_allow_high_risk") &&
|
|
458
453
|
response.selectedPattern
|
|
459
454
|
) {
|
|
460
455
|
const ruleOptions: {
|
|
@@ -462,7 +457,7 @@ export class PermissionChecker {
|
|
|
462
457
|
executionTarget?: string;
|
|
463
458
|
} = {};
|
|
464
459
|
|
|
465
|
-
if (
|
|
460
|
+
if (decision === "always_allow_high_risk") {
|
|
466
461
|
ruleOptions.allowHighRisk = true;
|
|
467
462
|
}
|
|
468
463
|
|
|
@@ -474,7 +469,7 @@ export class PermissionChecker {
|
|
|
474
469
|
// Only default to 'everywhere' for non-scoped tools (empty scopeOptions).
|
|
475
470
|
// For scoped tools, require an explicit scope to prevent silent permission widening.
|
|
476
471
|
const effectiveScope =
|
|
477
|
-
scopeOptions.length === 0
|
|
472
|
+
promptOptions.scopeOptions.length === 0
|
|
478
473
|
? (response.selectedScope ?? "everywhere")
|
|
479
474
|
: response.selectedScope;
|
|
480
475
|
if (effectiveScope) {
|
|
@@ -493,13 +488,13 @@ export class PermissionChecker {
|
|
|
493
488
|
// time-limited or conversation-scoped override. Subsequent tool
|
|
494
489
|
// invocations in this conversation will auto-approve without
|
|
495
490
|
// prompting (checked above in the temporary override block).
|
|
496
|
-
if (
|
|
491
|
+
if (decision === "allow_10m") {
|
|
497
492
|
setTimedMode(context.conversationId);
|
|
498
493
|
log.info(
|
|
499
494
|
{ toolName: name, conversationId: context.conversationId },
|
|
500
495
|
"Activated timed (10m) temporary approval mode",
|
|
501
496
|
);
|
|
502
|
-
} else if (
|
|
497
|
+
} else if (decision === "allow_conversation") {
|
|
503
498
|
setConversationMode(context.conversationId);
|
|
504
499
|
log.info(
|
|
505
500
|
{ toolName: name, conversationId: context.conversationId },
|
package/src/tools/registry.ts
CHANGED
|
@@ -8,7 +8,6 @@ import { hostFileReadTool } from "./host-filesystem/read.js";
|
|
|
8
8
|
import { hostFileWriteTool } from "./host-filesystem/write.js";
|
|
9
9
|
import { hostShellTool } from "./host-terminal/host-shell.js";
|
|
10
10
|
import { registerSystemTools } from "./system/register.js";
|
|
11
|
-
import { setPermissionModeTool } from "./system/set-permission-mode.js";
|
|
12
11
|
import type { Tool } from "./types.js";
|
|
13
12
|
import { allUiSurfaceTools } from "./ui-surface/definitions.js";
|
|
14
13
|
import { registerUiSurfaceTools } from "./ui-surface/registry.js";
|
|
@@ -285,7 +284,6 @@ export async function initializeTools(): Promise<void> {
|
|
|
285
284
|
...allComputerUseTools.map((t: Tool) => t.name),
|
|
286
285
|
...allUiSurfaceTools.map((t: Tool) => t.name),
|
|
287
286
|
...coreAppProxyTools.map((t: Tool) => t.name),
|
|
288
|
-
setPermissionModeTool.name,
|
|
289
287
|
]);
|
|
290
288
|
|
|
291
289
|
coreToolsSnapshot = new Map<string, Tool>();
|
|
@@ -2,6 +2,7 @@ import { getConfig } from "../config/loader.js";
|
|
|
2
2
|
import { getHookManager } from "../hooks/manager.js";
|
|
3
3
|
import { PermissionPrompter } from "../permissions/prompter.js";
|
|
4
4
|
import { RiskLevel } from "../permissions/types.js";
|
|
5
|
+
import { isPermissionControlsV2Enabled } from "../permissions/v2-consent-policy.js";
|
|
5
6
|
import type { SecretPattern } from "../security/secret-scanner.js";
|
|
6
7
|
import {
|
|
7
8
|
compileCustomPatterns,
|
|
@@ -269,6 +270,39 @@ export class SecretDetectionHandler {
|
|
|
269
270
|
): Promise<{ result: ToolExecutionResult; earlyReturn: boolean }> {
|
|
270
271
|
const types = [...new Set(allMatches.map((m) => m.type))].join(", ");
|
|
271
272
|
|
|
273
|
+
if (isPermissionControlsV2Enabled()) {
|
|
274
|
+
const blockedContent = `Tool output blocked: detected ${allMatches.length} potential secret(s) (${types}). Secret-output approval cards are disabled under v2. Ask the user for confirmation conversationally before retrying.`;
|
|
275
|
+
const durationMs = Date.now() - startTime;
|
|
276
|
+
|
|
277
|
+
emitLifecycleEvent(context, {
|
|
278
|
+
type: "permission_denied",
|
|
279
|
+
toolName: name,
|
|
280
|
+
executionTarget,
|
|
281
|
+
input,
|
|
282
|
+
workingDir: context.workingDir,
|
|
283
|
+
conversationId: context.conversationId,
|
|
284
|
+
requestId: context.requestId,
|
|
285
|
+
riskLevel: RiskLevel.High,
|
|
286
|
+
decision: "deny",
|
|
287
|
+
reason: "Secret output blocked without deterministic prompt under v2",
|
|
288
|
+
durationMs,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
void getHookManager().trigger("post-tool-execute", {
|
|
292
|
+
toolName: name,
|
|
293
|
+
input: sanitizeToolInput(name, input),
|
|
294
|
+
riskLevel,
|
|
295
|
+
isError: true,
|
|
296
|
+
durationMs,
|
|
297
|
+
conversationId: context.conversationId,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
result: { content: blockedContent, isError: true },
|
|
302
|
+
earlyReturn: true,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
272
306
|
// Non-interactive sessions: auto-block secret output instead of waiting for prompt
|
|
273
307
|
if (context.isInteractive === false) {
|
|
274
308
|
const blockedContent = `Tool output blocked: detected ${allMatches.length} potential secret(s) (${types}). No interactive client available to approve.`;
|
|
@@ -61,63 +61,37 @@ function detectMediaType(buf: Buffer): string | null {
|
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*/
|
|
71
|
-
export function readImageFile(resolvedPath: string): ToolExecutionResult {
|
|
72
|
-
let stat;
|
|
73
|
-
try {
|
|
74
|
-
stat = statSync(resolvedPath);
|
|
75
|
-
} catch {
|
|
76
|
-
return {
|
|
77
|
-
content: `Error: file not found: ${resolvedPath}`,
|
|
78
|
-
isError: true,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (!stat.isFile()) {
|
|
83
|
-
return { content: `Error: ${resolvedPath} is not a file`, isError: true };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (stat.size > MAX_SOURCE_SIZE_BYTES) {
|
|
87
|
-
const sizeMB = (stat.size / (1024 * 1024)).toFixed(1);
|
|
64
|
+
function buildImageToolResult(
|
|
65
|
+
buffer: Buffer,
|
|
66
|
+
sourceLabel: string,
|
|
67
|
+
): ToolExecutionResult {
|
|
68
|
+
if (buffer.length > MAX_SOURCE_SIZE_BYTES) {
|
|
69
|
+
const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
|
|
88
70
|
return {
|
|
89
71
|
content: `Error: image too large (${sizeMB} MB). Maximum source file size is 100 MB.`,
|
|
90
72
|
isError: true,
|
|
91
73
|
};
|
|
92
74
|
}
|
|
93
75
|
|
|
94
|
-
let buffer: Buffer;
|
|
95
|
-
try {
|
|
96
|
-
buffer = readFileSync(resolvedPath) as Buffer;
|
|
97
|
-
} catch (err) {
|
|
98
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
99
|
-
return { content: `Error reading file: ${msg}`, isError: true };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
76
|
// Detect actual format from magic bytes - never trust the file extension
|
|
103
77
|
// alone, since sips converts to JPEG and files can be misnamed.
|
|
104
78
|
const detectedType = detectMediaType(buffer);
|
|
105
79
|
if (!detectedType) {
|
|
106
80
|
return {
|
|
107
|
-
content: `Error: could not detect image format for ${
|
|
81
|
+
content: `Error: could not detect image format for ${sourceLabel}. The file may be corrupt.`,
|
|
108
82
|
isError: true,
|
|
109
83
|
};
|
|
110
84
|
}
|
|
111
85
|
|
|
112
86
|
// Optimize before size-checking — oversized images may compress under the limit.
|
|
113
87
|
const rawBase64 = buffer.toString("base64");
|
|
114
|
-
const { data: base64Data, mediaType: finalType } =
|
|
115
|
-
|
|
88
|
+
const { data: base64Data, mediaType: finalType } = optimizeImageForTransport(
|
|
89
|
+
rawBase64,
|
|
90
|
+
detectedType,
|
|
91
|
+
);
|
|
116
92
|
const optimized = base64Data !== rawBase64;
|
|
117
93
|
|
|
118
|
-
const optimizedBytes =
|
|
119
|
-
? Math.ceil((base64Data.length * 3) / 4)
|
|
120
|
-
: buffer.length;
|
|
94
|
+
const optimizedBytes = Buffer.from(base64Data, "base64").length;
|
|
121
95
|
if (optimizedBytes > MAX_SIZE_BYTES) {
|
|
122
96
|
const sizeMB = (optimizedBytes / (1024 * 1024)).toFixed(1);
|
|
123
97
|
return {
|
|
@@ -136,14 +110,61 @@ export function readImageFile(resolvedPath: string): ToolExecutionResult {
|
|
|
136
110
|
};
|
|
137
111
|
|
|
138
112
|
const sizeSuffix = optimized
|
|
139
|
-
? ` (optimized from ${(
|
|
113
|
+
? ` (optimized from ${(buffer.length / 1024).toFixed(0)} KB to ${(
|
|
140
114
|
optimizedBytes / 1024
|
|
141
115
|
).toFixed(0)} KB)`
|
|
142
116
|
: "";
|
|
143
117
|
|
|
144
118
|
return {
|
|
145
|
-
content: `Image loaded: ${
|
|
119
|
+
content: `Image loaded: ${sourceLabel} (${optimizedBytes} bytes, ${finalType})${sizeSuffix}`,
|
|
146
120
|
isError: false,
|
|
147
121
|
contentBlocks: [imageBlock],
|
|
148
122
|
};
|
|
149
123
|
}
|
|
124
|
+
|
|
125
|
+
export function readImageBase64(
|
|
126
|
+
base64Data: string,
|
|
127
|
+
sourceLabel: string,
|
|
128
|
+
): ToolExecutionResult {
|
|
129
|
+
return buildImageToolResult(Buffer.from(base64Data, "base64"), sourceLabel);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Read an image file from disk, optionally optimize it, and return a
|
|
134
|
+
* ToolExecutionResult with base64-encoded image content blocks.
|
|
135
|
+
*
|
|
136
|
+
* The caller is responsible for path resolution and sandbox enforcement -
|
|
137
|
+
* `resolvedPath` must be an already-validated absolute path.
|
|
138
|
+
*/
|
|
139
|
+
export function readImageFile(resolvedPath: string): ToolExecutionResult {
|
|
140
|
+
let stat;
|
|
141
|
+
try {
|
|
142
|
+
stat = statSync(resolvedPath);
|
|
143
|
+
} catch {
|
|
144
|
+
return {
|
|
145
|
+
content: `Error: file not found: ${resolvedPath}`,
|
|
146
|
+
isError: true,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!stat.isFile()) {
|
|
151
|
+
return { content: `Error: ${resolvedPath} is not a file`, isError: true };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (stat.size > MAX_SOURCE_SIZE_BYTES) {
|
|
155
|
+
const sizeMB = (stat.size / (1024 * 1024)).toFixed(1);
|
|
156
|
+
return {
|
|
157
|
+
content: `Error: image too large (${sizeMB} MB). Maximum source file size is 100 MB.`,
|
|
158
|
+
isError: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let buffer: Buffer;
|
|
163
|
+
try {
|
|
164
|
+
buffer = readFileSync(resolvedPath) as Buffer;
|
|
165
|
+
} catch (err) {
|
|
166
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
167
|
+
return { content: `Error reading file: ${msg}`, isError: true };
|
|
168
|
+
}
|
|
169
|
+
return buildImageToolResult(buffer, resolvedPath);
|
|
170
|
+
}
|
|
@@ -8,9 +8,15 @@ export async function executeSubagentSpawn(
|
|
|
8
8
|
const label = input.label as string;
|
|
9
9
|
const objective = input.objective as string;
|
|
10
10
|
const extraContext = input.context as string | undefined;
|
|
11
|
-
const
|
|
11
|
+
const fork = input.fork === true;
|
|
12
12
|
const role = (input.role as string | undefined) ?? undefined;
|
|
13
13
|
|
|
14
|
+
// For fork mode, sendResultToUser defaults to false unless explicitly set to true.
|
|
15
|
+
// For regular mode, sendResultToUser defaults to true (existing behavior).
|
|
16
|
+
const sendResultToUser = fork
|
|
17
|
+
? input.send_result_to_user === true
|
|
18
|
+
: input.send_result_to_user !== false;
|
|
19
|
+
|
|
14
20
|
if (!label || !objective) {
|
|
15
21
|
return {
|
|
16
22
|
content: 'Both "label" and "objective" are required.',
|
|
@@ -29,6 +35,36 @@ export async function executeSubagentSpawn(
|
|
|
29
35
|
};
|
|
30
36
|
}
|
|
31
37
|
|
|
38
|
+
// ── Fork mode: resolve parent context ────────────────────────────
|
|
39
|
+
let forkFields: {
|
|
40
|
+
fork: true;
|
|
41
|
+
parentMessages: import("../../providers/types.js").Message[];
|
|
42
|
+
parentSystemPrompt: string;
|
|
43
|
+
} | undefined;
|
|
44
|
+
|
|
45
|
+
if (fork) {
|
|
46
|
+
const parentConversation = manager.resolveParentConversation?.(
|
|
47
|
+
context.conversationId,
|
|
48
|
+
);
|
|
49
|
+
if (!parentConversation) {
|
|
50
|
+
return {
|
|
51
|
+
content:
|
|
52
|
+
"Cannot fork: parent conversation could not be resolved. " +
|
|
53
|
+
"This may happen if the conversation was evicted or the resolveParentConversation callback is not wired.",
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const parentMessages = [...parentConversation.messages];
|
|
59
|
+
const parentSystemPrompt = parentConversation.getCurrentSystemPrompt();
|
|
60
|
+
|
|
61
|
+
forkFields = {
|
|
62
|
+
fork: true,
|
|
63
|
+
parentMessages,
|
|
64
|
+
parentSystemPrompt,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
32
68
|
try {
|
|
33
69
|
const subagentId = await manager.spawn(
|
|
34
70
|
{
|
|
@@ -37,7 +73,12 @@ export async function executeSubagentSpawn(
|
|
|
37
73
|
objective,
|
|
38
74
|
context: extraContext,
|
|
39
75
|
sendResultToUser,
|
|
40
|
-
|
|
76
|
+
// For fork mode, role is ignored by the manager (forced to general),
|
|
77
|
+
// but we still omit it from the config to signal intent.
|
|
78
|
+
...(!fork && role
|
|
79
|
+
? { role: role as import("../../subagent/types.js").SubagentRole }
|
|
80
|
+
: {}),
|
|
81
|
+
...forkFields,
|
|
41
82
|
},
|
|
42
83
|
sendToClient as (msg: unknown) => void,
|
|
43
84
|
);
|
|
@@ -47,7 +88,10 @@ export async function executeSubagentSpawn(
|
|
|
47
88
|
subagentId,
|
|
48
89
|
label,
|
|
49
90
|
status: "pending",
|
|
50
|
-
|
|
91
|
+
...(fork ? { isFork: true } : {}),
|
|
92
|
+
message: fork
|
|
93
|
+
? `Forked subagent "${label}" spawned with full parent context. You will be notified automatically when it completes or fails - do NOT poll subagent_status. Continue the conversation normally.`
|
|
94
|
+
: `Subagent "${label}" spawned. You will be notified automatically when it completes or fails - do NOT poll subagent_status. Continue the conversation normally.`,
|
|
51
95
|
}),
|
|
52
96
|
isError: false,
|
|
53
97
|
};
|
|
@@ -34,6 +34,7 @@ export async function executeSubagentStatus(
|
|
|
34
34
|
subagentId: state.config.id,
|
|
35
35
|
label: state.config.label,
|
|
36
36
|
status: state.status,
|
|
37
|
+
isFork: state.isFork,
|
|
37
38
|
error: state.error,
|
|
38
39
|
createdAt: state.createdAt,
|
|
39
40
|
startedAt: state.startedAt,
|
|
@@ -57,6 +58,7 @@ export async function executeSubagentStatus(
|
|
|
57
58
|
subagentId: s.config.id,
|
|
58
59
|
label: s.config.label,
|
|
59
60
|
status: s.status,
|
|
61
|
+
isFork: s.isFork,
|
|
60
62
|
error: s.error,
|
|
61
63
|
}));
|
|
62
64
|
|
|
@@ -1,23 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Registers feature-flag-gated system tools with the daemon's tool registry.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* registered (e.g. request_system_permission) are handled via the tool
|
|
6
|
-
* manifest's explicit tools list; this module handles conditional registration.
|
|
4
|
+
* No conditional tools are currently registered.
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
|
-
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
10
|
-
import { getConfig } from "../../config/loader.js";
|
|
11
|
-
import { registerTool } from "../registry.js";
|
|
12
|
-
import { setPermissionModeTool } from "./set-permission-mode.js";
|
|
13
|
-
|
|
14
7
|
export function registerSystemTools(): void {
|
|
15
|
-
|
|
16
|
-
const config = getConfig();
|
|
17
|
-
if (isAssistantFeatureFlagEnabled("permission-controls-v2", config)) {
|
|
18
|
-
registerTool(setPermissionModeTool);
|
|
19
|
-
}
|
|
20
|
-
} catch {
|
|
21
|
-
// Config not yet loaded (e.g. during test setup) — permission mode tool stays off.
|
|
22
|
-
}
|
|
8
|
+
// No-op.
|
|
23
9
|
}
|