@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
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { existsSync, readFileSync, statfsSync, statSync } from "node:fs";
|
|
6
|
-
import { cpus, totalmem } from "node:os";
|
|
6
|
+
import { availableParallelism, cpus, totalmem } from "node:os";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
|
|
12
|
+
import { getCpuLimit, getIsPlatform } from "../../config/env-registry.js";
|
|
12
13
|
import { parseIdentityFields } from "../../daemon/handlers/identity.js";
|
|
13
14
|
import { getProfilerRuntimeStatus } from "../../daemon/profiler-run-store.js";
|
|
14
15
|
import { getMaxMigrationVersion } from "../../memory/migrations/registry.js";
|
|
@@ -54,10 +55,60 @@ interface MemoryInfo {
|
|
|
54
55
|
maxMb: number;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
/**
|
|
59
|
+
* Parse a Kubernetes-style memory string (e.g. "3Gi", "512Mi", "1G") into bytes.
|
|
60
|
+
* Returns null if the value is not a recognized format.
|
|
61
|
+
*/
|
|
62
|
+
function parseK8sMemoryBytes(value: string): number | null {
|
|
63
|
+
const match = value
|
|
64
|
+
.trim()
|
|
65
|
+
.match(/^(\d+(?:\.\d+)?)\s*(Ki|Mi|Gi|Ti|Pi|Ei|k|M|G|T|P|E|m)?$/);
|
|
66
|
+
if (!match) return null;
|
|
67
|
+
const num = parseFloat(match[1]);
|
|
68
|
+
const unit = match[2] ?? "";
|
|
69
|
+
const multipliers: Record<string, number> = {
|
|
70
|
+
"": 1,
|
|
71
|
+
m: 1e-3,
|
|
72
|
+
k: 1e3,
|
|
73
|
+
M: 1e6,
|
|
74
|
+
G: 1e9,
|
|
75
|
+
T: 1e12,
|
|
76
|
+
P: 1e15,
|
|
77
|
+
E: 1e18,
|
|
78
|
+
Ki: 1024,
|
|
79
|
+
Mi: 1024 ** 2,
|
|
80
|
+
Gi: 1024 ** 3,
|
|
81
|
+
Ti: 1024 ** 4,
|
|
82
|
+
Pi: 1024 ** 5,
|
|
83
|
+
Ei: 1024 ** 6,
|
|
84
|
+
};
|
|
85
|
+
const mult = multipliers[unit];
|
|
86
|
+
if (mult === undefined) return null;
|
|
87
|
+
const bytes = Math.round(num * mult);
|
|
88
|
+
return bytes > 0 ? bytes : null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Read the memory limit from the VELLUM_MEMORY_LIMIT env var (K8s resource format),
|
|
93
|
+
* then fall back to cgroups, then to os.totalmem().
|
|
94
|
+
*
|
|
95
|
+
* In platform mode the container runs under gVisor where cgroup files may report
|
|
96
|
+
* the node's memory rather than the container limit. VELLUM_MEMORY_LIMIT is set
|
|
97
|
+
* by the StatefulSet template to the exact K8s memory limit (e.g. "3Gi").
|
|
98
|
+
*/
|
|
60
99
|
function getContainerMemoryLimitBytes(): number | null {
|
|
100
|
+
// 1. Prefer the explicit env var set by the platform StatefulSet template.
|
|
101
|
+
try {
|
|
102
|
+
const envLimit = process.env.VELLUM_MEMORY_LIMIT;
|
|
103
|
+
if (envLimit) {
|
|
104
|
+
const parsed = parseK8sMemoryBytes(envLimit);
|
|
105
|
+
if (parsed !== null) return parsed;
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
/* env var parsing failed – fall through to cgroups */
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 2. Try cgroups v2.
|
|
61
112
|
try {
|
|
62
113
|
const v2 = readFileSync("/sys/fs/cgroup/memory.max", "utf-8").trim();
|
|
63
114
|
if (v2 !== "max") {
|
|
@@ -67,6 +118,8 @@ function getContainerMemoryLimitBytes(): number | null {
|
|
|
67
118
|
} catch {
|
|
68
119
|
/* not available */
|
|
69
120
|
}
|
|
121
|
+
|
|
122
|
+
// 3. Try cgroups v1.
|
|
70
123
|
try {
|
|
71
124
|
const v1 = readFileSync(
|
|
72
125
|
"/sys/fs/cgroup/memory/memory.limit_in_bytes",
|
|
@@ -81,10 +134,56 @@ function getContainerMemoryLimitBytes(): number | null {
|
|
|
81
134
|
return null;
|
|
82
135
|
}
|
|
83
136
|
|
|
137
|
+
/**
|
|
138
|
+
* Read the container's current memory usage from cgroup files.
|
|
139
|
+
*
|
|
140
|
+
* Tries cgroups v2 (`memory.current`) first, then cgroups v1
|
|
141
|
+
* (`memory/memory.usage_in_bytes`), mirroring the v2-then-v1 fallback used by
|
|
142
|
+
* `getContainerMemoryLimitBytes`. Returns null if neither file is available
|
|
143
|
+
* or readable.
|
|
144
|
+
*
|
|
145
|
+
* Unlike the limit lookup, no env-var override is needed: the gVisor issue
|
|
146
|
+
* that motivates VELLUM_MEMORY_LIMIT is specifically about the *limit* files
|
|
147
|
+
* exposing the host node's memory instead of the sandbox limit. The *usage*
|
|
148
|
+
* files (memory.current / memory.usage_in_bytes) reflect the sandbox's own
|
|
149
|
+
* accounting and are accurate under gVisor.
|
|
150
|
+
*/
|
|
151
|
+
function getContainerMemoryUsageBytes(): number | null {
|
|
152
|
+
// 1. Try cgroups v2.
|
|
153
|
+
try {
|
|
154
|
+
const v2 = readFileSync("/sys/fs/cgroup/memory.current", "utf-8").trim();
|
|
155
|
+
const bytes = parseInt(v2, 10);
|
|
156
|
+
if (!isNaN(bytes) && bytes > 0) return bytes;
|
|
157
|
+
} catch {
|
|
158
|
+
/* not available */
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 2. Try cgroups v1.
|
|
162
|
+
try {
|
|
163
|
+
const v1 = readFileSync(
|
|
164
|
+
"/sys/fs/cgroup/memory/memory.usage_in_bytes",
|
|
165
|
+
"utf-8",
|
|
166
|
+
).trim();
|
|
167
|
+
const bytes = parseInt(v1, 10);
|
|
168
|
+
if (!isNaN(bytes) && bytes > 0) return bytes;
|
|
169
|
+
} catch {
|
|
170
|
+
/* not available */
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
84
175
|
function getMemoryInfo(): MemoryInfo {
|
|
85
176
|
const bytesToMb = (b: number) => Math.round((b / (1024 * 1024)) * 100) / 100;
|
|
177
|
+
// In platform-managed mode the daemon shares its Node process with whatever
|
|
178
|
+
// the container is doing as a whole; `process.memoryUsage().rss` only sees
|
|
179
|
+
// this process's resident set, which understates the container footprint
|
|
180
|
+
// operators care about. Read the cgroup usage file directly so /v1/health
|
|
181
|
+
// matches what the StatefulSet's memory limit is enforced against.
|
|
182
|
+
const currentBytes =
|
|
183
|
+
(getIsPlatform() ? getContainerMemoryUsageBytes() : null) ??
|
|
184
|
+
process.memoryUsage().rss;
|
|
86
185
|
return {
|
|
87
|
-
currentMb: bytesToMb(
|
|
186
|
+
currentMb: bytesToMb(currentBytes),
|
|
88
187
|
maxMb: bytesToMb(getContainerMemoryLimitBytes() ?? totalmem()),
|
|
89
188
|
};
|
|
90
189
|
}
|
|
@@ -94,36 +193,180 @@ interface CpuInfo {
|
|
|
94
193
|
maxCores: number;
|
|
95
194
|
}
|
|
96
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Parse a Kubernetes-style CPU string (e.g. "2000m", "1", "500m") into
|
|
198
|
+
* fractional cores. Returns null if the value is not a recognized format.
|
|
199
|
+
*/
|
|
200
|
+
function parseK8sCpuCores(value: string): number | null {
|
|
201
|
+
const trimmed = value.trim();
|
|
202
|
+
const milliMatch = trimmed.match(/^(\d+)m$/);
|
|
203
|
+
if (milliMatch) {
|
|
204
|
+
const millis = parseInt(milliMatch[1], 10);
|
|
205
|
+
return millis > 0 ? millis / 1000 : null;
|
|
206
|
+
}
|
|
207
|
+
if (/^\d+(\.\d+)?$/.test(trimmed)) {
|
|
208
|
+
const num = parseFloat(trimmed);
|
|
209
|
+
return !isNaN(num) && num > 0 ? num : null;
|
|
210
|
+
}
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Read the container's CPU core limit.
|
|
216
|
+
*
|
|
217
|
+
* Resolution order:
|
|
218
|
+
* 1. VELLUM_CPU_LIMIT env var (K8s resource format, e.g. "2000m" or "2").
|
|
219
|
+
* In platform mode the container runs under gVisor where cgroup files may
|
|
220
|
+
* report the node's CPU count rather than the sandbox limit.
|
|
221
|
+
* 2. cgroups v2 cpu.max (quota / period → fractional cores).
|
|
222
|
+
* 3. cgroups v1 cpu.cfs_quota_us / cpu.cfs_period_us.
|
|
223
|
+
* 4. os.cpus().length as last resort.
|
|
224
|
+
*/
|
|
225
|
+
function getContainerCpuCores(): number {
|
|
226
|
+
// 1. Prefer the explicit env var set by the platform StatefulSet template.
|
|
227
|
+
try {
|
|
228
|
+
const envLimit = getCpuLimit();
|
|
229
|
+
if (envLimit) {
|
|
230
|
+
const parsed = parseK8sCpuCores(envLimit);
|
|
231
|
+
if (parsed !== null) return parsed;
|
|
232
|
+
}
|
|
233
|
+
} catch {
|
|
234
|
+
/* env var parsing failed – fall through */
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 2. Try cgroups v2: /sys/fs/cgroup/cpu.max contains "$MAX $PERIOD".
|
|
238
|
+
try {
|
|
239
|
+
const raw = readFileSync("/sys/fs/cgroup/cpu.max", "utf-8").trim();
|
|
240
|
+
if (!raw.startsWith("max")) {
|
|
241
|
+
const parts = raw.split(/\s+/);
|
|
242
|
+
const quota = parseInt(parts[0], 10);
|
|
243
|
+
const period = parseInt(parts[1], 10);
|
|
244
|
+
if (!isNaN(quota) && !isNaN(period) && period > 0 && quota > 0) {
|
|
245
|
+
const cores = quota / period;
|
|
246
|
+
// Sanity check: if the value looks like the node's full CPU count
|
|
247
|
+
// and we're on a platform pod, it's likely gVisor leaking the host value.
|
|
248
|
+
if (cores < cpus().length * 0.9 || !getIsPlatform()) {
|
|
249
|
+
return cores;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
} catch {
|
|
254
|
+
/* not available */
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// 3. Try cgroups v1.
|
|
258
|
+
try {
|
|
259
|
+
const quota = parseInt(
|
|
260
|
+
readFileSync("/sys/fs/cgroup/cpu/cpu.cfs_quota_us", "utf-8").trim(),
|
|
261
|
+
10,
|
|
262
|
+
);
|
|
263
|
+
const period = parseInt(
|
|
264
|
+
readFileSync("/sys/fs/cgroup/cpu/cpu.cfs_period_us", "utf-8").trim(),
|
|
265
|
+
10,
|
|
266
|
+
);
|
|
267
|
+
if (!isNaN(quota) && !isNaN(period) && period > 0 && quota > 0) {
|
|
268
|
+
const cores = quota / period;
|
|
269
|
+
if (cores < cpus().length * 0.9 || !getIsPlatform()) {
|
|
270
|
+
return cores;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} catch {
|
|
274
|
+
/* not available */
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return cpus().length || availableParallelism();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Read the container's CPU usage from cgroup accounting files.
|
|
282
|
+
*
|
|
283
|
+
* Returns total CPU microseconds consumed by the container since boot.
|
|
284
|
+
* We use the delta between two samples to compute percentage.
|
|
285
|
+
*/
|
|
286
|
+
function getContainerCpuUsageUs(): number | null {
|
|
287
|
+
// cgroups v2: cpu.stat has a "usage_usec" line.
|
|
288
|
+
try {
|
|
289
|
+
const stat = readFileSync("/sys/fs/cgroup/cpu.stat", "utf-8");
|
|
290
|
+
for (const line of stat.split("\n")) {
|
|
291
|
+
if (line.startsWith("usage_usec")) {
|
|
292
|
+
const val = parseInt(line.split(/\s+/)[1], 10);
|
|
293
|
+
if (!isNaN(val) && val > 0) return val;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
} catch {
|
|
297
|
+
/* not available */
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// cgroups v1: cpuacct.usage is in nanoseconds.
|
|
301
|
+
try {
|
|
302
|
+
const ns = parseInt(
|
|
303
|
+
readFileSync("/sys/fs/cgroup/cpuacct/cpuacct.usage", "utf-8").trim(),
|
|
304
|
+
10,
|
|
305
|
+
);
|
|
306
|
+
if (!isNaN(ns) && ns > 0) return ns / 1000; // convert ns → µs
|
|
307
|
+
} catch {
|
|
308
|
+
/* not available */
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
|
|
97
314
|
// Track CPU usage over a rolling window so /v1/health reports near-real-time
|
|
98
315
|
// utilization instead of a lifetime average (total CPU time / total uptime).
|
|
99
316
|
const CPU_SAMPLE_INTERVAL_MS = 5_000;
|
|
100
|
-
let
|
|
317
|
+
let _lastProcessCpuUsage: NodeJS.CpuUsage = process.cpuUsage();
|
|
318
|
+
let _lastCgroupCpuUs: number | null = getContainerCpuUsageUs();
|
|
101
319
|
let _lastCpuTime: number = Date.now();
|
|
102
320
|
let _cachedCpuPercent = 0;
|
|
103
321
|
|
|
104
322
|
// Kick off the background sampler. unref() so it never prevents process exit.
|
|
105
323
|
setInterval(() => {
|
|
106
324
|
const now = Date.now();
|
|
107
|
-
const newUsage = process.cpuUsage();
|
|
108
325
|
const elapsedMs = now - _lastCpuTime;
|
|
109
|
-
if (elapsedMs
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
326
|
+
if (elapsedMs <= 0) return;
|
|
327
|
+
|
|
328
|
+
const numCores = getContainerCpuCores();
|
|
329
|
+
|
|
330
|
+
// Always sample process-level CPU so the baseline stays fresh. This
|
|
331
|
+
// prevents a spike if the platform cgroup path later falls back to
|
|
332
|
+
// process.cpuUsage() after cgroup stats were previously available.
|
|
333
|
+
const newProcessUsage = process.cpuUsage();
|
|
334
|
+
const processDeltaUs =
|
|
335
|
+
newProcessUsage.user -
|
|
336
|
+
_lastProcessCpuUsage.user +
|
|
337
|
+
(newProcessUsage.system - _lastProcessCpuUsage.system);
|
|
338
|
+
_lastProcessCpuUsage = newProcessUsage;
|
|
339
|
+
|
|
340
|
+
if (getIsPlatform()) {
|
|
341
|
+
// In platform mode, prefer cgroup-level CPU usage so we see the full
|
|
342
|
+
// container footprint, not just this process.
|
|
343
|
+
const cgroupUs = getContainerCpuUsageUs();
|
|
344
|
+
if (cgroupUs !== null && _lastCgroupCpuUs !== null) {
|
|
345
|
+
const deltaCpuUs = cgroupUs - _lastCgroupCpuUs;
|
|
346
|
+
const deltaCpuMs = deltaCpuUs / 1000;
|
|
347
|
+
_cachedCpuPercent =
|
|
348
|
+
Math.round((deltaCpuMs / (elapsedMs * numCores)) * 10000) / 100;
|
|
349
|
+
} else {
|
|
350
|
+
// cgroup CPU stats unavailable (e.g. gVisor) – fall back to process-level.
|
|
351
|
+
const deltaCpuMs = processDeltaUs / 1000;
|
|
352
|
+
_cachedCpuPercent =
|
|
353
|
+
Math.round((deltaCpuMs / (elapsedMs * numCores)) * 10000) / 100;
|
|
354
|
+
}
|
|
355
|
+
_lastCgroupCpuUs = cgroupUs;
|
|
356
|
+
} else {
|
|
357
|
+
// Non-platform: use process.cpuUsage() (accurate for single-process mode).
|
|
358
|
+
const deltaCpuMs = processDeltaUs / 1000;
|
|
116
359
|
_cachedCpuPercent =
|
|
117
360
|
Math.round((deltaCpuMs / (elapsedMs * numCores)) * 10000) / 100;
|
|
118
361
|
}
|
|
119
|
-
|
|
362
|
+
|
|
120
363
|
_lastCpuTime = now;
|
|
121
364
|
}, CPU_SAMPLE_INTERVAL_MS).unref();
|
|
122
365
|
|
|
123
366
|
function getCpuInfo(): CpuInfo {
|
|
124
367
|
return {
|
|
125
368
|
currentPercent: _cachedCpuPercent,
|
|
126
|
-
maxCores:
|
|
369
|
+
maxCores: Math.ceil(getContainerCpuCores()),
|
|
127
370
|
};
|
|
128
371
|
}
|
|
129
372
|
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Log Export — Workspace Allowlist Rules
|
|
2
|
+
|
|
3
|
+
`POST /v1/export` (handled by `log-export-routes.ts`) builds a tar.gz archive
|
|
4
|
+
from audit DB rows, daemon logs under `<workspace>/data/logs/`, and a
|
|
5
|
+
sanitized `config.json` snapshot. This directory
|
|
6
|
+
(`assistant/src/runtime/routes/log-export/`) houses the allowlist module
|
|
7
|
+
that governs which subpaths of the user's workspace directory
|
|
8
|
+
(`~/.vellum/workspace/`) are permitted to flow into that archive.
|
|
9
|
+
|
|
10
|
+
Workspace contents are **opt-in (allowlist), not opt-out**. The workspace
|
|
11
|
+
contains arbitrary user files — skills, hooks, routes, conversations,
|
|
12
|
+
credentials scaffolding, and other material the user has authored or
|
|
13
|
+
installed locally. Accidentally bundling any of that into a support
|
|
14
|
+
archive would exfiltrate data the user never intended to share. The
|
|
15
|
+
default must therefore be "nothing from the workspace ships" and each
|
|
16
|
+
individual entry that _does_ ship must be justified against the rules
|
|
17
|
+
below.
|
|
18
|
+
|
|
19
|
+
## Rule 1 — Prefer time-filterable data
|
|
20
|
+
|
|
21
|
+
Only allowlist a workspace subpath if its contents can be narrowed to the
|
|
22
|
+
`[startTime, endTime]` window carried on the export request.
|
|
23
|
+
|
|
24
|
+
- When the data is organized as per-record files or per-record
|
|
25
|
+
directories whose **names encode a timestamp**, filter by parsing the
|
|
26
|
+
name. The canonical example is the per-conversation directory layout
|
|
27
|
+
where each directory is named `<ISO-with-dashes>_<conversationId>`
|
|
28
|
+
(the ISO date comes first so ordinary lexicographic comparison yields
|
|
29
|
+
chronological order, and colons in the ISO string are replaced with
|
|
30
|
+
`-` so the name is filesystem-safe). A time filter can be implemented
|
|
31
|
+
by parsing the prefix and comparing it to `startTime` / `endTime`
|
|
32
|
+
without reading file contents.
|
|
33
|
+
- When the relevant time information lives **only inside files**, the
|
|
34
|
+
allowlist entry should err on the side of **not** being included —
|
|
35
|
+
unless the file is small, rarely changes, and its full contents are
|
|
36
|
+
acceptable to ship regardless of the requested window.
|
|
37
|
+
|
|
38
|
+
## Rule 2 — Prefer conversation-filterable data
|
|
39
|
+
|
|
40
|
+
When the export request carries a `conversationId`, every allowlisted
|
|
41
|
+
subpath should narrow itself to that conversation **if at all possible**.
|
|
42
|
+
|
|
43
|
+
- Data that is intrinsically global (i.e. not associated with a single
|
|
44
|
+
conversation) is acceptable to include **only** when Rule 1 alone is
|
|
45
|
+
sufficient and the request has no `conversationId` filter.
|
|
46
|
+
- When a `conversationId` _is_ set and an entry cannot be scoped to it,
|
|
47
|
+
prefer omitting the entry for that particular export rather than
|
|
48
|
+
shipping unrelated conversation data.
|
|
49
|
+
|
|
50
|
+
The `<ISO-with-dashes>_<conversationId>` directory naming is again the
|
|
51
|
+
motivating example: the suffix lets us select exactly one
|
|
52
|
+
per-conversation directory without scanning file contents.
|
|
53
|
+
|
|
54
|
+
## Rule 3 — Default deny
|
|
55
|
+
|
|
56
|
+
Anything in the workspace that is not explicitly added to the allowlist
|
|
57
|
+
module must remain excluded from the export archive. Adding a new entry
|
|
58
|
+
requires, in the same PR:
|
|
59
|
+
|
|
60
|
+
1. Updating the allowlist module in this directory to teach it about the
|
|
61
|
+
new subpath (including its time filter, conversation filter, and
|
|
62
|
+
size cap).
|
|
63
|
+
2. Updating this `AGENTS.md` to record the entry name, which filters it
|
|
64
|
+
honors, and its size cap under `## Allowlisted entries`.
|
|
65
|
+
|
|
66
|
+
Review must confirm both updates landed together. A workspace subpath
|
|
67
|
+
that is not mentioned in the registry below is, by definition, not
|
|
68
|
+
allowed in the export archive.
|
|
69
|
+
|
|
70
|
+
## Rule 4 — Bounded size
|
|
71
|
+
|
|
72
|
+
Every allowlisted entry must enforce a byte cap so that a misbehaving
|
|
73
|
+
workspace (e.g. a runaway log, a giant attachment, a pathological skill)
|
|
74
|
+
cannot blow up the archive and defeat the export endpoint.
|
|
75
|
+
|
|
76
|
+
The current convention is **10 MB** across the workspace allowlist,
|
|
77
|
+
mirroring `MAX_LOG_PAYLOAD_BYTES` in `log-export-routes.ts`. Entries
|
|
78
|
+
should track the number of bytes already consumed and stop adding files
|
|
79
|
+
once the cap would be exceeded, preferring to include the newest /
|
|
80
|
+
most-relevant records first.
|
|
81
|
+
|
|
82
|
+
## Allowlisted entries
|
|
83
|
+
|
|
84
|
+
- **`conversations/`**
|
|
85
|
+
- **Path**: `<workspace>/conversations/<ISO-with-dashes>_<conversationId>/`
|
|
86
|
+
- **Honors filters**: time (union of parsed `createdAt` prefix _and_
|
|
87
|
+
per-message `ts` inside `messages.jsonl`) and `conversationId`
|
|
88
|
+
(exact match on the directory-name suffix — no substring matching).
|
|
89
|
+
- **Time semantics**: A conversation directory is included if EITHER
|
|
90
|
+
its `createdAt` (parsed from the directory name) falls in the
|
|
91
|
+
requested window OR `messages.jsonl` contains at least one message
|
|
92
|
+
whose `ts` falls in the window. The cheap directory-name check runs
|
|
93
|
+
first; the per-message scan only runs as a fallback when the cheap
|
|
94
|
+
check failed, so the common in-window case stays IO-free. This is
|
|
95
|
+
the "support bundle" union — false positives are cheaper than false
|
|
96
|
+
negatives because the user almost always wants the conversations
|
|
97
|
+
they were _active in_ during the window, not just the ones they
|
|
98
|
+
_started_ during it.
|
|
99
|
+
- **Cap**: shares the 10 MB workspace cap defined by
|
|
100
|
+
`MAX_WORKSPACE_PAYLOAD_BYTES` in `workspace-allowlist.ts`.
|
|
101
|
+
- **Notes**: Directory names that don't match the canonical
|
|
102
|
+
`<ISO-with-dashes>_<conversationId>` format are silently skipped
|
|
103
|
+
(Rule 3 — default deny). Legacy `<id>_<ISO>` directories are
|
|
104
|
+
intentionally excluded until they migrate to the canonical format.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for the `result.entries` contract on `collectWorkspaceData`.
|
|
3
|
+
*
|
|
4
|
+
* Consumers and telemetry rely on `collectWorkspaceData` always returning at
|
|
5
|
+
* least one entry summary for the `conversations` allowlist entry — even when
|
|
6
|
+
* something throws partway through the candidate loop. This file pins that
|
|
7
|
+
* contract by mocking `parseConversationDirName` to return a malicious object
|
|
8
|
+
* whose `createdAtMs` getter throws. That throw escapes the inner per-iteration
|
|
9
|
+
* try/catch (it happens during sort + filter expression evaluation, not inside
|
|
10
|
+
* the wrapped parser call), bubbles up to the outer try/catch in
|
|
11
|
+
* `collectWorkspaceData`, and verifies that `result.entries` still contains
|
|
12
|
+
* exactly one `conversations` entry summary.
|
|
13
|
+
*
|
|
14
|
+
* Lives in its own file because `mock.module` is a global module override and
|
|
15
|
+
* we don't want it bleeding into the rest of the workspace-allowlist tests.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
21
|
+
|
|
22
|
+
mock.module("../../../../memory/conversation-directories.js", () => ({
|
|
23
|
+
parseConversationDirName: (_name: string) => {
|
|
24
|
+
// Return an object whose `createdAtMs` accessor throws. This bypasses the
|
|
25
|
+
// inner try/catch wrapping `parseConversationDirName(name)` (which only
|
|
26
|
+
// catches synchronous throws from the call itself, not from later property
|
|
27
|
+
// accesses) and triggers the unwrapped sort/filter comparisons further
|
|
28
|
+
// down in `collectConversations`.
|
|
29
|
+
return {
|
|
30
|
+
conversationId: "evil",
|
|
31
|
+
get createdAtMs(): number {
|
|
32
|
+
throw new Error("simulated parser corruption");
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
|
|
38
|
+
import { getConversationsDir } from "../../../../util/platform.js";
|
|
39
|
+
import { collectWorkspaceData } from "../workspace-allowlist.js";
|
|
40
|
+
|
|
41
|
+
let staging: string;
|
|
42
|
+
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
// Fresh staging directory for each test.
|
|
45
|
+
const conversationsDir = getConversationsDir();
|
|
46
|
+
rmSync(conversationsDir, { recursive: true, force: true });
|
|
47
|
+
mkdirSync(conversationsDir, { recursive: true });
|
|
48
|
+
|
|
49
|
+
staging = join(
|
|
50
|
+
process.env.VELLUM_WORKSPACE_DIR ?? "/tmp",
|
|
51
|
+
"ws-allowlist-error-staging",
|
|
52
|
+
);
|
|
53
|
+
rmSync(staging, { recursive: true, force: true });
|
|
54
|
+
mkdirSync(staging, { recursive: true });
|
|
55
|
+
|
|
56
|
+
// Seed a single canonical-looking dir so the loop has something to chew on.
|
|
57
|
+
const dirName = "2025-01-15T00-00-00.000Z_conv-jan15";
|
|
58
|
+
const dir = join(conversationsDir, dirName);
|
|
59
|
+
mkdirSync(dir, { recursive: true });
|
|
60
|
+
writeFileSync(
|
|
61
|
+
join(dir, "meta.json"),
|
|
62
|
+
JSON.stringify({ name: dirName }),
|
|
63
|
+
"utf-8",
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
try {
|
|
69
|
+
rmSync(staging, { recursive: true, force: true });
|
|
70
|
+
} catch {
|
|
71
|
+
/* best-effort cleanup */
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
rmSync(getConversationsDir(), { recursive: true, force: true });
|
|
75
|
+
} catch {
|
|
76
|
+
/* best-effort cleanup */
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("collectWorkspaceData — entry contract on unexpected error", () => {
|
|
81
|
+
test("synthesizes a conversations entry summary even when the loop throws", () => {
|
|
82
|
+
// The mocked parser returns a poisoned object whose `createdAtMs` accessor
|
|
83
|
+
// throws. The first read happens inside the time-filter checks in the
|
|
84
|
+
// candidate-collection loop, which is NOT wrapped in a per-iteration
|
|
85
|
+
// try/catch. The throw should propagate up to the outer try/catch in
|
|
86
|
+
// `collectWorkspaceData`, where it must be swallowed without dropping
|
|
87
|
+
// the entry summary.
|
|
88
|
+
const result = collectWorkspaceData({
|
|
89
|
+
staging,
|
|
90
|
+
// Force the time-filter branch to read `createdAtMs`.
|
|
91
|
+
startTime: 0,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Contract: exactly one entry, named "conversations", regardless of error.
|
|
95
|
+
expect(result.entries).toHaveLength(1);
|
|
96
|
+
const [entry] = result.entries;
|
|
97
|
+
expect(entry.entry).toBe("conversations");
|
|
98
|
+
expect(entry.itemCount).toBe(0);
|
|
99
|
+
expect(entry.bytes).toBe(0);
|
|
100
|
+
expect(entry.skippedDueToCap).toBe(0);
|
|
101
|
+
expect(result.totalBytes).toBe(0);
|
|
102
|
+
});
|
|
103
|
+
});
|