@vellumai/assistant 0.6.1 → 0.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +40 -40
- package/bunfig.toml +3 -0
- package/docker-entrypoint.sh +12 -2
- package/docs/architecture/memory.md +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- 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__/assistant-event-hub.test.ts +30 -0
- 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__/checker.test.ts +104 -170
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- 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 +21 -6
- 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 +169 -0
- package/src/__tests__/conversation-attachments.test.ts +80 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -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 -3
- 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__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- package/src/__tests__/integration-status.test.ts +6 -7
- package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
- package/src/__tests__/log-export-workspace.test.ts +190 -0
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- 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__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -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 +74 -55
- 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__/pkb-autoinject.test.ts +96 -0
- 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 -3
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- 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-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +11 -5
- package/src/__tests__/test-preload.ts +14 -0
- package/src/__tests__/tool-approval-handler.test.ts +73 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +0 -1
- 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 +62 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
- package/src/__tests__/v2-consent-policy.test.ts +103 -0
- package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/acp/client-handler.ts +30 -4
- package/src/agent/loop.ts +12 -35
- 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 +55 -0
- package/src/cli/__tests__/run-assistant-command.ts +34 -7
- package/src/cli/__tests__/unknown-command.test.ts +33 -0
- package/src/cli/commands/browser-relay.ts +339 -409
- package/src/cli/commands/credentials.ts +3 -3
- package/src/cli/commands/default-action.ts +68 -1
- 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 +68 -41
- 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 +16 -2
- 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/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- 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 +10 -3
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +33 -173
- 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 +12 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- package/src/config/bundled-skills/outlook/SKILL.md +7 -0
- package/src/config/bundled-skills/settings/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
- 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 +46 -7
- 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 +16 -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 -16
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
- package/src/daemon/app-source-watcher.ts +35 -0
- package/src/daemon/config-watcher.ts +6 -2
- package/src/daemon/context-overflow-approval.ts +5 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
- package/src/daemon/conversation-agent-loop.ts +74 -19
- package/src/daemon/conversation-attachments.ts +40 -1
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +66 -3
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +159 -20
- package/src/daemon/conversation-surfaces.ts +78 -12
- package/src/daemon/conversation-tool-setup.ts +74 -11
- package/src/daemon/conversation-workspace.ts +12 -0
- package/src/daemon/conversation.ts +227 -11
- package/src/daemon/date-context.ts +10 -10
- package/src/daemon/first-greeting.ts +3 -2
- package/src/daemon/handlers/conversations.ts +9 -139
- package/src/daemon/handlers/shared.ts +65 -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 +86 -12
- package/src/daemon/message-protocol.ts +7 -0
- package/src/daemon/message-types/conversations.ts +59 -13
- package/src/daemon/message-types/host-browser.ts +100 -0
- package/src/daemon/message-types/messages.ts +5 -6
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/settings.ts +12 -0
- package/src/daemon/message-types/skills.ts +10 -0
- package/src/daemon/message-types/subagents.ts +2 -0
- package/src/daemon/server.ts +112 -35
- package/src/daemon/tool-side-effects.ts +6 -0
- package/src/daemon/transport-hints.ts +14 -0
- package/src/inbound/platform-callback-registration.ts +18 -17
- package/src/index.ts +1 -1
- package/src/mcp/client.ts +59 -24
- package/src/memory/app-store.ts +31 -1
- package/src/memory/conversation-crud.ts +38 -10
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +65 -5
- 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 +177 -18
- package/src/memory/graph/capability-seed.ts +3 -5
- 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/group-crud.ts +25 -9
- 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/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- 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 +5 -5
- 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 +127 -87
- package/src/oauth/token-persistence.ts +1 -1
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode.ts +4 -11
- package/src/permissions/prompter.ts +13 -3
- package/src/permissions/v2-consent-policy.ts +87 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -21
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
- package/src/prompts/templates/BOOTSTRAP.md +59 -96
- package/src/prompts/templates/SOUL.md +11 -11
- 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 +24 -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/auth/token-service.ts +8 -0
- 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 +18 -5
- package/src/runtime/routes/conversation-management-routes.ts +108 -0
- package/src/runtime/routes/conversation-routes.ts +308 -28
- package/src/runtime/routes/conversation-starter-routes.ts +78 -16
- package/src/runtime/routes/group-routes.ts +22 -8
- 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/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +60 -25
- 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/skills/inline-command-runner.ts +12 -14
- 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 -100
- package/src/tools/registry.ts +0 -2
- package/src/tools/secret-detection-handler.ts +34 -1
- package/src/tools/shared/filesystem/image-read.ts +61 -40
- package/src/tools/skills/sandbox-runner.ts +3 -6
- 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/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +24 -21
- package/src/tools/tool-approval-handler.ts +48 -2
- package/src/tools/types.ts +2 -3
- package/src/util/platform.ts +14 -19
- package/src/watcher/provider-types.ts +1 -1
- package/src/workspace/migrations/029-seed-pkb.ts +1 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +2 -0
- 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,492 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* catalog-files — preview file listings and single-file content for catalog
|
|
3
|
+
* skills, including ones that are NOT installed locally.
|
|
4
|
+
*
|
|
5
|
+
* This module is pure library code: it does NOT wire itself into any handler
|
|
6
|
+
* or route. Higher-level daemon handlers consume it via the exported
|
|
7
|
+
* `readCatalogSkillFiles` / `readCatalogSkillFileContent` functions.
|
|
8
|
+
*
|
|
9
|
+
* Data sources:
|
|
10
|
+
* - In `VELLUM_DEV` mode, when `<repo>/skills/<id>/` exists on disk, we
|
|
11
|
+
* read the skill files directly from the repo checkout (matching the
|
|
12
|
+
* behavior of `getCatalog()` in dev).
|
|
13
|
+
* - Otherwise, we call the platform preview endpoints:
|
|
14
|
+
* GET /v1/skills/{skill_id}/files/
|
|
15
|
+
* GET /v1/skills/{skill_id}/files/content/?path=...
|
|
16
|
+
*
|
|
17
|
+
* Crucially, this code does NOT extract any tar/gzip archives. Previewing a
|
|
18
|
+
* skill's files never installs it or touches the install flow.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
existsSync,
|
|
23
|
+
lstatSync,
|
|
24
|
+
readdirSync,
|
|
25
|
+
readFileSync,
|
|
26
|
+
realpathSync,
|
|
27
|
+
statSync,
|
|
28
|
+
} from "node:fs";
|
|
29
|
+
import { basename, join, posix, sep } from "node:path";
|
|
30
|
+
|
|
31
|
+
import { getPlatformBaseUrl } from "../config/env.js";
|
|
32
|
+
import {
|
|
33
|
+
isTextMimeType as isTextMime,
|
|
34
|
+
MAX_INLINE_TEXT_SIZE,
|
|
35
|
+
} from "../runtime/routes/workspace-utils.js";
|
|
36
|
+
import { getLogger } from "../util/logger.js";
|
|
37
|
+
import { readPlatformToken } from "../util/platform.js";
|
|
38
|
+
import { getCatalog } from "./catalog-cache.js";
|
|
39
|
+
import { getRepoSkillsDir } from "./catalog-install.js";
|
|
40
|
+
|
|
41
|
+
const log = getLogger("catalog-files");
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Classify a file as text/binary from its name alone. Used by the preview
|
|
45
|
+
* listings where we do not have the file's bytes on hand (platform mode) or
|
|
46
|
+
* where we want to defer reading content until explicitly requested (dev
|
|
47
|
+
* mode listings). Bun derives the mime type from the file extension, so
|
|
48
|
+
* this works for non-existent paths too.
|
|
49
|
+
*/
|
|
50
|
+
function classifyByName(name: string): boolean {
|
|
51
|
+
const mime = Bun.file(name).type;
|
|
52
|
+
return !isTextMime(mime, name);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── Shared types ────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* A single file entry in a skill directory or preview listing.
|
|
59
|
+
*
|
|
60
|
+
* This module owns the canonical shape; `daemon/handlers/skills.ts`
|
|
61
|
+
* re-exports it so handler consumers can import it from either location.
|
|
62
|
+
* Keeping the definition here avoids a circular import — catalog-files
|
|
63
|
+
* depends on `catalog-cache.ts`, which would otherwise be reachable via
|
|
64
|
+
* the handler module.
|
|
65
|
+
*/
|
|
66
|
+
export interface SkillFileEntry {
|
|
67
|
+
path: string; // relative to skill directory root (e.g. "SKILL.md", "tools/foo.ts")
|
|
68
|
+
name: string; // basename
|
|
69
|
+
size: number;
|
|
70
|
+
mimeType: string;
|
|
71
|
+
isBinary: boolean;
|
|
72
|
+
content: string | null; // inline text if ≤ 2 MB and text MIME, else null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ─── Platform response contracts ─────────────────────────────────────────────
|
|
76
|
+
//
|
|
77
|
+
// The platform preview API uses snake_case on the wire. We map to the
|
|
78
|
+
// daemon's camelCase shape inside this module so nothing downstream needs to
|
|
79
|
+
// know about the platform contract.
|
|
80
|
+
|
|
81
|
+
interface PlatformFileListResponse {
|
|
82
|
+
skill_id: string;
|
|
83
|
+
files: Array<{ path: string; name: string; size: number; sha: string }>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface PlatformFileContentResponse {
|
|
87
|
+
path: string;
|
|
88
|
+
name: string;
|
|
89
|
+
size: number;
|
|
90
|
+
mime_type: string;
|
|
91
|
+
is_binary: boolean;
|
|
92
|
+
content: string | null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── Path sanitization ───────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Normalize and validate a relative path coming from untrusted input. Returns
|
|
99
|
+
* the normalized posix path on success, or `null` if the input is unsafe.
|
|
100
|
+
*
|
|
101
|
+
* Rules:
|
|
102
|
+
* - Reject empty strings and strings containing null bytes.
|
|
103
|
+
* - Reject absolute paths (unix or windows drive-prefixed).
|
|
104
|
+
* - Normalize backslashes to forward slashes, strip leading `./`, and
|
|
105
|
+
* run `posix.normalize` to collapse redundant segments.
|
|
106
|
+
* - Reject paths that escape the root (normalized result equals `..` or
|
|
107
|
+
* begins with `../`).
|
|
108
|
+
*
|
|
109
|
+
* The platform also validates these server-side; client-side sanitization is
|
|
110
|
+
* defense in depth and short-circuits obvious bad requests before a network
|
|
111
|
+
* round trip.
|
|
112
|
+
*/
|
|
113
|
+
export function sanitizeRelativePath(rawPath: string): string | null {
|
|
114
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) return null;
|
|
115
|
+
if (rawPath.includes("\0")) return null;
|
|
116
|
+
if (rawPath.startsWith("/")) return null;
|
|
117
|
+
if (/^[a-zA-Z]:[/\\]/.test(rawPath)) return null;
|
|
118
|
+
|
|
119
|
+
// Normalize separators and strip any leading "./" before posix.normalize
|
|
120
|
+
// (which would otherwise preserve it in some cases).
|
|
121
|
+
let candidate = rawPath.replace(/\\/g, "/");
|
|
122
|
+
while (candidate.startsWith("./")) {
|
|
123
|
+
candidate = candidate.slice(2);
|
|
124
|
+
}
|
|
125
|
+
if (candidate.length === 0) return null;
|
|
126
|
+
|
|
127
|
+
const normalized = posix.normalize(candidate);
|
|
128
|
+
if (normalized === "..") return null;
|
|
129
|
+
if (normalized.startsWith("../")) return null;
|
|
130
|
+
// posix.normalize can still return "." for purely no-op paths.
|
|
131
|
+
if (normalized === ".") return null;
|
|
132
|
+
// Reject if normalization produced an absolute or Windows-drive path.
|
|
133
|
+
// Covers bypasses like `.//etc/passwd` where the leading `./` strip loop
|
|
134
|
+
// leaves `/etc/passwd`, which `posix.normalize` then passes through as an
|
|
135
|
+
// absolute path. The pre-normalization absolute-path check above only
|
|
136
|
+
// catches inputs that were absolute to begin with.
|
|
137
|
+
if (normalized.startsWith("/")) return null;
|
|
138
|
+
if (/^[a-zA-Z]:[/\\]/.test(normalized)) return null;
|
|
139
|
+
|
|
140
|
+
return normalized;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── Source resolution ───────────────────────────────────────────────────────
|
|
144
|
+
|
|
145
|
+
type CatalogSource =
|
|
146
|
+
| { kind: "dir"; dirPath: string }
|
|
147
|
+
| { kind: "platform"; skillId: string };
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Resolve where to read files for a given catalog skill id. Performs NO
|
|
151
|
+
* network calls — network requests happen only inside `readCatalogSkillFiles`
|
|
152
|
+
* and `readCatalogSkillFileContent` on the platform path.
|
|
153
|
+
*
|
|
154
|
+
* Dev-mode safety: when a `<repoSkillsDir>/<skillId>` entry exists on disk,
|
|
155
|
+
* we verify that the skill root is a real directory physically located
|
|
156
|
+
* inside `repoSkillsDir` — rejecting symlinks and anything whose realpath
|
|
157
|
+
* escapes the repo skills dir. This prevents a symlinked skill root from
|
|
158
|
+
* pointing at an external directory and bypassing the later realpath
|
|
159
|
+
* containment check in `readCatalogSkillFileContent` (the check there
|
|
160
|
+
* derives `realRoot` from the already-resolved skill dir, so if the skill
|
|
161
|
+
* root itself is a symlink, `realRoot` resolves through the symlink target
|
|
162
|
+
* and the containment check becomes a no-op). On any violation we silently
|
|
163
|
+
* fall through to platform mode, which is the safe default — the dev-mode
|
|
164
|
+
* shortcut is an optimization, not a required code path.
|
|
165
|
+
*/
|
|
166
|
+
async function resolveCatalogSource(
|
|
167
|
+
skillId: string,
|
|
168
|
+
): Promise<CatalogSource | null> {
|
|
169
|
+
const catalog = await getCatalog();
|
|
170
|
+
const inCatalog = catalog.some((skill) => skill.id === skillId);
|
|
171
|
+
if (!inCatalog) return null;
|
|
172
|
+
|
|
173
|
+
const repoSkillsDir = getRepoSkillsDir();
|
|
174
|
+
if (repoSkillsDir) {
|
|
175
|
+
const candidate = join(repoSkillsDir, skillId);
|
|
176
|
+
if (existsSync(candidate) && isSafeDevSkillRoot(candidate, repoSkillsDir)) {
|
|
177
|
+
return { kind: "dir", dirPath: candidate };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { kind: "platform", skillId };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Verify that a dev-mode skill root candidate is a real directory physically
|
|
185
|
+
* located inside `repoSkillsDir`. Returns `false` for any of:
|
|
186
|
+
*
|
|
187
|
+
* - `candidate` is itself a symbolic link (even if the target is still
|
|
188
|
+
* "nearby" — following it would break the realpath containment check
|
|
189
|
+
* in `readCatalogSkillFileContent`).
|
|
190
|
+
* - `candidate` is not a directory.
|
|
191
|
+
* - `realpath(candidate)` escapes `realpath(repoSkillsDir)`.
|
|
192
|
+
* - Any fs call throws (EACCES, ENOENT race, etc.).
|
|
193
|
+
*
|
|
194
|
+
* Callers should fall through to platform mode on `false`.
|
|
195
|
+
*/
|
|
196
|
+
function isSafeDevSkillRoot(candidate: string, repoSkillsDir: string): boolean {
|
|
197
|
+
let lstat;
|
|
198
|
+
try {
|
|
199
|
+
lstat = lstatSync(candidate);
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
if (lstat.isSymbolicLink()) return false;
|
|
204
|
+
if (!lstat.isDirectory()) return false;
|
|
205
|
+
|
|
206
|
+
let realCandidate: string;
|
|
207
|
+
let realRepoSkillsDir: string;
|
|
208
|
+
try {
|
|
209
|
+
realCandidate = realpathSync(candidate);
|
|
210
|
+
realRepoSkillsDir = realpathSync(repoSkillsDir);
|
|
211
|
+
} catch {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
if (
|
|
215
|
+
!(
|
|
216
|
+
realCandidate === realRepoSkillsDir ||
|
|
217
|
+
realCandidate.startsWith(realRepoSkillsDir + sep)
|
|
218
|
+
)
|
|
219
|
+
) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ─── Platform fetch helper ───────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Fetch JSON from the platform preview API. Returns `null` on any failure
|
|
229
|
+
* (non-2xx, network error, abort). The query string is stripped from log
|
|
230
|
+
* messages to avoid leaking user-supplied file paths.
|
|
231
|
+
*/
|
|
232
|
+
async function fetchPlatformJson<T>(
|
|
233
|
+
path: string,
|
|
234
|
+
query?: Record<string, string>,
|
|
235
|
+
): Promise<T | null> {
|
|
236
|
+
const base = getPlatformBaseUrl();
|
|
237
|
+
const url = new URL(`${base}${path}`);
|
|
238
|
+
if (query) {
|
|
239
|
+
const params = new URLSearchParams(query);
|
|
240
|
+
url.search = params.toString();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const headers: Record<string, string> = { Accept: "application/json" };
|
|
244
|
+
const token = readPlatformToken();
|
|
245
|
+
if (token) {
|
|
246
|
+
headers["X-Conversation-Token"] = token;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
const response = await fetch(url.toString(), {
|
|
251
|
+
headers,
|
|
252
|
+
signal: AbortSignal.timeout(15000),
|
|
253
|
+
});
|
|
254
|
+
if (!response.ok) {
|
|
255
|
+
log.warn(
|
|
256
|
+
{ status: response.status, path },
|
|
257
|
+
"Platform preview API returned non-2xx",
|
|
258
|
+
);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
return (await response.json()) as T;
|
|
262
|
+
} catch (err) {
|
|
263
|
+
log.warn({ err, path }, "Platform preview API request failed");
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ─── Dev-mode directory walker ───────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
// Directory names that are always skipped when walking a catalog skill dir in
|
|
271
|
+
// dev mode. Also used by `daemon/handlers/skills.ts` — both for the
|
|
272
|
+
// installed-skill walker and for the single-file content endpoint's
|
|
273
|
+
// hidden/skipped path rejection. Exported so the daemon handler can
|
|
274
|
+
// import this single source of truth and stay in sync.
|
|
275
|
+
export const SKIP_DIRS = new Set(["node_modules", "__pycache__", ".git"]);
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Returns true if the given sanitized posix path contains any segment that
|
|
279
|
+
* is hidden (starts with `.`) or present in `SKIP_DIRS`. Used to reject
|
|
280
|
+
* file-content reads for paths the listing APIs intentionally hide, so
|
|
281
|
+
* callers cannot fetch `.env`, `.git/config`, `node_modules/...`, etc. via
|
|
282
|
+
* the content endpoint even though the listing never surfaces them.
|
|
283
|
+
*
|
|
284
|
+
* The input MUST already be a normalized posix path (i.e. the return value
|
|
285
|
+
* of `sanitizeRelativePath`). This helper does not re-normalize — it splits
|
|
286
|
+
* on `/` and inspects each segment directly.
|
|
287
|
+
*/
|
|
288
|
+
export function hasHiddenOrSkippedSegment(sanitized: string): boolean {
|
|
289
|
+
const segments = sanitized.split("/");
|
|
290
|
+
for (const segment of segments) {
|
|
291
|
+
if (segment.length === 0) continue;
|
|
292
|
+
if (segment.startsWith(".")) return true;
|
|
293
|
+
if (SKIP_DIRS.has(segment)) return true;
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function walkSkillDir(dir: string, rootDir: string): SkillFileEntry[] {
|
|
299
|
+
const out: SkillFileEntry[] = [];
|
|
300
|
+
let dirents;
|
|
301
|
+
try {
|
|
302
|
+
dirents = readdirSync(dir, { withFileTypes: true });
|
|
303
|
+
} catch {
|
|
304
|
+
return out;
|
|
305
|
+
}
|
|
306
|
+
for (const dirent of dirents) {
|
|
307
|
+
// Skip dot-prefixed entries (hidden files like `.DS_Store` and dot-dirs
|
|
308
|
+
// like `.git`, `.venv`). Matches the behavior of the installed-skill
|
|
309
|
+
// walker in `daemon/handlers/skills.ts`.
|
|
310
|
+
if (dirent.name.startsWith(".")) continue;
|
|
311
|
+
const abs = join(dir, dirent.name);
|
|
312
|
+
// Silently skip symlinks, sockets, devices, etc.
|
|
313
|
+
if (dirent.isSymbolicLink()) continue;
|
|
314
|
+
if (dirent.isDirectory()) {
|
|
315
|
+
// Skip well-known heavyweight directories (node_modules, __pycache__,
|
|
316
|
+
// ...) so a dev working on a catalog skill locally doesn't see
|
|
317
|
+
// thousands of spurious entries in the preview listing.
|
|
318
|
+
if (SKIP_DIRS.has(dirent.name)) continue;
|
|
319
|
+
out.push(...walkSkillDir(abs, rootDir));
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (!dirent.isFile()) continue;
|
|
323
|
+
try {
|
|
324
|
+
const stat = statSync(abs);
|
|
325
|
+
// Convert absolute → relative with manual separator normalization so
|
|
326
|
+
// the result is always posix-style regardless of the host platform.
|
|
327
|
+
const relFromRoot = abs.slice(rootDir.length);
|
|
328
|
+
const cleaned = relFromRoot.startsWith(sep)
|
|
329
|
+
? relFromRoot.slice(sep.length)
|
|
330
|
+
: relFromRoot;
|
|
331
|
+
const posixPath = cleaned.split(sep).join("/");
|
|
332
|
+
out.push({
|
|
333
|
+
path: posixPath,
|
|
334
|
+
name: basename(posixPath),
|
|
335
|
+
size: stat.size,
|
|
336
|
+
mimeType: "",
|
|
337
|
+
isBinary: classifyByName(dirent.name),
|
|
338
|
+
content: null,
|
|
339
|
+
});
|
|
340
|
+
} catch {
|
|
341
|
+
// Skip unreadable files silently.
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return out;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ─── Public API ──────────────────────────────────────────────────────────────
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* List files for a catalog skill (installed or not).
|
|
351
|
+
*
|
|
352
|
+
* Returns `null` if the skill id is not in the catalog at all. Otherwise
|
|
353
|
+
* returns an array of `SkillFileEntry` with `content === null` for every
|
|
354
|
+
* entry (single-file content is fetched on demand via
|
|
355
|
+
* `readCatalogSkillFileContent`).
|
|
356
|
+
*/
|
|
357
|
+
export async function readCatalogSkillFiles(
|
|
358
|
+
skillId: string,
|
|
359
|
+
): Promise<SkillFileEntry[] | null> {
|
|
360
|
+
const source = await resolveCatalogSource(skillId);
|
|
361
|
+
if (!source) return null;
|
|
362
|
+
|
|
363
|
+
if (source.kind === "dir") {
|
|
364
|
+
const entries = walkSkillDir(source.dirPath, source.dirPath);
|
|
365
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
366
|
+
return entries;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const response = await fetchPlatformJson<PlatformFileListResponse>(
|
|
370
|
+
`/v1/skills/${encodeURIComponent(skillId)}/files/`,
|
|
371
|
+
);
|
|
372
|
+
if (!response) return null;
|
|
373
|
+
|
|
374
|
+
const entries: SkillFileEntry[] = response.files.map((file) => ({
|
|
375
|
+
path: file.path,
|
|
376
|
+
name: file.name,
|
|
377
|
+
size: file.size,
|
|
378
|
+
mimeType: "",
|
|
379
|
+
isBinary: classifyByName(file.name),
|
|
380
|
+
content: null,
|
|
381
|
+
}));
|
|
382
|
+
entries.sort((a, b) => a.path.localeCompare(b.path));
|
|
383
|
+
return entries;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Read a single file's content from a catalog skill (installed or not).
|
|
388
|
+
*
|
|
389
|
+
* Returns `null` if the skill is missing from the catalog, the path fails
|
|
390
|
+
* sanitization, the underlying source rejects the request, or the file does
|
|
391
|
+
* not exist. In dev mode, text files up to `MAX_INLINE_TEXT_SIZE` are returned
|
|
392
|
+
* with their UTF-8 content inline; anything larger or flagged as binary
|
|
393
|
+
* returns with `content === null`. In platform mode, the server enforces
|
|
394
|
+
* the same contract and we pass its response through unchanged.
|
|
395
|
+
*/
|
|
396
|
+
export async function readCatalogSkillFileContent(
|
|
397
|
+
skillId: string,
|
|
398
|
+
relativePath: string,
|
|
399
|
+
): Promise<SkillFileEntry | null> {
|
|
400
|
+
const sanitized = sanitizeRelativePath(relativePath);
|
|
401
|
+
if (!sanitized) return null;
|
|
402
|
+
|
|
403
|
+
// Defense in depth: reject any path that references a hidden or
|
|
404
|
+
// SKIP_DIRS segment. The daemon handler performs the same check before
|
|
405
|
+
// calling us, but we repeat it here so direct callers of this module
|
|
406
|
+
// short-circuit without a network round trip and without touching disk.
|
|
407
|
+
if (hasHiddenOrSkippedSegment(sanitized)) return null;
|
|
408
|
+
|
|
409
|
+
const source = await resolveCatalogSource(skillId);
|
|
410
|
+
if (!source) return null;
|
|
411
|
+
|
|
412
|
+
if (source.kind === "dir") {
|
|
413
|
+
const abs = join(source.dirPath, sanitized);
|
|
414
|
+
// Defense in depth: make absolutely sure the resolved absolute path is
|
|
415
|
+
// still inside the skill root, even after `join` normalization. This is
|
|
416
|
+
// a cheap lexical short-circuit that runs before any fs stat calls.
|
|
417
|
+
if (!(abs === source.dirPath || abs.startsWith(source.dirPath + sep))) {
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
if (!existsSync(abs)) return null;
|
|
421
|
+
|
|
422
|
+
// Reject symlinks at the target path directly: we do NOT want to follow
|
|
423
|
+
// a symlinked file inside a catalog skill dir out of the skill root.
|
|
424
|
+
let lstat;
|
|
425
|
+
try {
|
|
426
|
+
lstat = lstatSync(abs);
|
|
427
|
+
} catch {
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
if (lstat.isSymbolicLink()) return null;
|
|
431
|
+
if (!lstat.isFile()) return null;
|
|
432
|
+
|
|
433
|
+
// Also resolve any intermediate symlinks in the parent path via
|
|
434
|
+
// realpath and verify the result is still contained within the skill
|
|
435
|
+
// root's own realpath. This catches symlinked parent directories that
|
|
436
|
+
// the lexical check above can't see through.
|
|
437
|
+
let realAbs: string;
|
|
438
|
+
let realRoot: string;
|
|
439
|
+
try {
|
|
440
|
+
realAbs = realpathSync(abs);
|
|
441
|
+
realRoot = realpathSync(source.dirPath);
|
|
442
|
+
} catch {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
if (!(realAbs === realRoot || realAbs.startsWith(realRoot + sep))) {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
let stat;
|
|
450
|
+
try {
|
|
451
|
+
stat = statSync(abs);
|
|
452
|
+
} catch {
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
if (!stat.isFile()) return null;
|
|
456
|
+
|
|
457
|
+
const name = basename(abs);
|
|
458
|
+
const mimeType = Bun.file(abs).type;
|
|
459
|
+
const isBinary = !isTextMime(mimeType, name);
|
|
460
|
+
let content: string | null = null;
|
|
461
|
+
if (!isBinary && stat.size <= MAX_INLINE_TEXT_SIZE) {
|
|
462
|
+
try {
|
|
463
|
+
content = readFileSync(abs, "utf-8");
|
|
464
|
+
} catch {
|
|
465
|
+
content = null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
path: sanitized,
|
|
470
|
+
name,
|
|
471
|
+
size: stat.size,
|
|
472
|
+
mimeType,
|
|
473
|
+
isBinary,
|
|
474
|
+
content,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const response = await fetchPlatformJson<PlatformFileContentResponse>(
|
|
479
|
+
`/v1/skills/${encodeURIComponent(skillId)}/files/content/`,
|
|
480
|
+
{ path: sanitized },
|
|
481
|
+
);
|
|
482
|
+
if (!response) return null;
|
|
483
|
+
|
|
484
|
+
return {
|
|
485
|
+
path: response.path,
|
|
486
|
+
name: response.name,
|
|
487
|
+
size: response.size,
|
|
488
|
+
mimeType: response.mime_type,
|
|
489
|
+
isBinary: response.is_binary,
|
|
490
|
+
content: response.content,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Runner for inline command expansions (`!\`command\``).
|
|
3
3
|
*
|
|
4
|
-
* Executes the literal command string
|
|
5
|
-
*
|
|
4
|
+
* Executes the literal command string without going through the general `bash`
|
|
5
|
+
* tool's permission path. Security constraints:
|
|
6
6
|
*
|
|
7
|
-
* - Network mode forced off (no outbound connections)
|
|
8
7
|
* - Sanitized environment variables only (no API keys, tokens, credentials)
|
|
9
8
|
* - No credential proxy, no CES client, no host fallback
|
|
9
|
+
* - Runs in Docker/platform-managed environments (network/filesystem isolation
|
|
10
|
+
* is provided by the container, not OS-level sandboxing)
|
|
10
11
|
* - Uses the conversation working directory as `cwd` so repo-local commands
|
|
11
12
|
* remain interoperable with externally authored skills that expect project
|
|
12
13
|
* context.
|
|
@@ -22,7 +23,6 @@
|
|
|
22
23
|
|
|
23
24
|
import { spawn } from "node:child_process";
|
|
24
25
|
|
|
25
|
-
import { getConfig } from "../config/loader.js";
|
|
26
26
|
import { buildSanitizedEnv } from "../tools/terminal/safe-env.js";
|
|
27
27
|
import { wrapCommand } from "../tools/terminal/sandbox.js";
|
|
28
28
|
import { getLogger } from "../util/logger.js";
|
|
@@ -91,7 +91,7 @@ export interface InlineCommandRunnerOptions {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
|
-
* Run an inline command expansion
|
|
94
|
+
* Run an inline command expansion.
|
|
95
95
|
*
|
|
96
96
|
* @param command The literal command string from the `!\`...\`` token.
|
|
97
97
|
* @param workingDir The conversation's working directory (repo root).
|
|
@@ -105,14 +105,12 @@ export async function runInlineCommand(
|
|
|
105
105
|
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
106
106
|
const maxChars = options?.maxOutputChars ?? MAX_OUTPUT_CHARS;
|
|
107
107
|
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const wrapped = wrapCommand(command, workingDir, sandboxConfig
|
|
114
|
-
networkMode: "off",
|
|
115
|
-
});
|
|
108
|
+
// Sandbox is disabled — the assistant runs in Docker or platform-managed
|
|
109
|
+
// environments where the container provides isolation. The networkMode
|
|
110
|
+
// option is a no-op when sandbox is disabled (wrapCommand returns a plain
|
|
111
|
+
// bash invocation); network isolation is handled at the container level.
|
|
112
|
+
const sandboxConfig = { enabled: false } as const;
|
|
113
|
+
const wrapped = wrapCommand(command, workingDir, sandboxConfig);
|
|
116
114
|
|
|
117
115
|
// Build a minimal, sanitized environment. Explicitly exclude gateway URL,
|
|
118
116
|
// workspace dir, and data dir since inline commands have no business calling
|