@vellumai/assistant 0.4.56 → 0.4.57
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/ARCHITECTURE.md +10 -10
- package/Dockerfile +3 -0
- package/README.md +11 -11
- package/docs/architecture/integrations.md +2 -2
- package/docs/architecture/memory.md +3 -4
- package/docs/credential-execution-service.md +13 -20
- package/node_modules/@vellumai/ces-contracts/src/error.ts +5 -4
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +7 -7
- package/src/__tests__/anthropic-provider.test.ts +172 -0
- package/src/__tests__/app-builder-tool-scripts.test.ts +15 -1
- package/src/__tests__/approval-cascade.test.ts +2 -2
- package/src/__tests__/approval-routes-http.test.ts +3 -4
- package/src/__tests__/asset-materialize-tool.test.ts +5 -5
- package/src/__tests__/asset-search-tool.test.ts +1 -1
- package/src/__tests__/assistant-attachments.test.ts +5 -5
- package/src/__tests__/assistant-events-sse-hardening.test.ts +1 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +50 -38
- package/src/__tests__/attachments-store.test.ts +2 -2
- package/src/__tests__/avatar-e2e.test.ts +5 -3
- package/src/__tests__/browser-skill-endstate.test.ts +0 -1
- package/src/__tests__/call-routes-http.test.ts +2 -2
- package/src/__tests__/callback-handoff-copy.test.ts +1 -1
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +158 -0
- package/src/__tests__/channel-readiness-routes.test.ts +0 -1
- package/src/__tests__/channel-readiness-service.test.ts +0 -1
- package/src/__tests__/checker.test.ts +31 -32
- package/src/__tests__/chrome-cdp.test.ts +47 -18
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -2
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +9 -18
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +4 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop.test.ts +11 -4
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +2 -2
- package/src/__tests__/conversation-error.test.ts +33 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -1
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +4 -4
- package/src/__tests__/conversation-provider-retry-repair.test.ts +4 -4
- package/src/__tests__/conversation-queue.test.ts +23 -14
- package/src/__tests__/conversation-routes-slash-commands.test.ts +3 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +185 -173
- package/src/__tests__/conversation-seed-composer.test.ts +1 -1
- package/src/__tests__/conversation-slash-queue.test.ts +4 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +4 -4
- package/src/__tests__/conversation-starter-routes.test.ts +291 -0
- package/src/__tests__/conversation-wipe.test.ts +438 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -3
- package/src/__tests__/conversation-workspace-injection.test.ts +4 -5
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +4 -5
- package/src/__tests__/credential-security-e2e.test.ts +20 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/credential-vault-unit.test.ts +227 -0
- package/src/__tests__/credentials-cli.test.ts +3 -0
- package/src/__tests__/date-context.test.ts +59 -377
- package/src/__tests__/drop-capability-card-state-migration.test.ts +169 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +11 -45
- package/src/__tests__/emit-signal-routing-intent.test.ts +3 -3
- package/src/__tests__/encrypted-store.test.ts +237 -15
- package/src/__tests__/ephemeral-permissions.test.ts +4 -5
- package/src/__tests__/event-bus.test.ts +3 -3
- package/src/__tests__/gateway-only-enforcement.test.ts +2 -2
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/gemini-image-service.test.ts +4 -4
- package/src/__tests__/gemini-provider.test.ts +6 -9
- package/src/__tests__/guardian-binding-drift-heal.test.ts +128 -0
- package/src/__tests__/guardian-dispatch.test.ts +0 -1
- package/src/__tests__/host-shell-tool.test.ts +6 -6
- package/src/__tests__/http-user-message-parity.test.ts +2 -2
- package/src/__tests__/intent-routing.test.ts +51 -99
- package/src/__tests__/invite-routes-http.test.ts +5 -0
- package/src/__tests__/list-messages-attachments.test.ts +1 -1
- package/src/__tests__/managed-proxy-context.test.ts +2 -5
- package/src/__tests__/managed-skill-lifecycle.test.ts +8 -8
- package/src/__tests__/media-generate-image.test.ts +32 -15
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -1
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +1 -1
- package/src/__tests__/memory-lifecycle-e2e.test.ts +24 -18
- package/src/__tests__/memory-recall-quality.test.ts +4 -3
- package/src/__tests__/memory-regressions.test.ts +86 -90
- package/src/__tests__/migration-cross-version-compatibility.test.ts +32 -32
- package/src/__tests__/migration-export-http.test.ts +26 -27
- package/src/__tests__/migration-import-commit-http.test.ts +165 -37
- package/src/__tests__/migration-import-preflight-http.test.ts +81 -20
- package/src/__tests__/migration-validate-http.test.ts +16 -16
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/no-domain-routing-in-prompt-guard.test.ts +1 -1
- package/src/__tests__/notification-broadcaster.test.ts +1 -1
- package/src/__tests__/notification-decision-fallback.test.ts +2 -2
- package/src/__tests__/notification-decision-identity.test.ts +8 -9
- package/src/__tests__/notification-decision-strategy.test.ts +1 -1
- package/src/__tests__/notification-deep-link.test.ts +1 -1
- package/src/__tests__/notification-guardian-path.test.ts +0 -1
- package/src/__tests__/notification-schedule-dedup.test.ts +7 -7
- package/src/__tests__/oauth-store.test.ts +1 -3
- package/src/__tests__/oauth2-gateway-transport.test.ts +6 -1
- package/src/__tests__/onboarding-template-contract.test.ts +23 -59
- package/src/__tests__/provider-error-scenarios.test.ts +154 -0
- package/src/__tests__/provider-fail-open-selection.test.ts +2 -2
- package/src/__tests__/provider-managed-proxy-integration.test.ts +8 -9
- package/src/__tests__/provider-registry-ollama.test.ts +5 -2
- package/src/__tests__/qdrant-manager.test.ts +7 -7
- package/src/__tests__/ratelimit.test.ts +0 -74
- package/src/__tests__/recording-handler.test.ts +0 -1
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +1 -1
- package/src/__tests__/runtime-events-sse.test.ts +1 -1
- package/src/__tests__/scheduler-recurrence.test.ts +46 -2
- package/src/__tests__/schema-transforms.test.ts +114 -54
- package/src/__tests__/secret-onetime-send.test.ts +20 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -2
- package/src/__tests__/secret-scanner-executor.test.ts +1 -2
- package/src/__tests__/send-endpoint-busy.test.ts +63 -4
- package/src/__tests__/send-notification-tool.test.ts +2 -2
- package/src/__tests__/shell-credential-ref.test.ts +0 -1
- package/src/__tests__/shell-tool-proxy-mode.test.ts +1 -2
- package/src/__tests__/skill-memory.test.ts +547 -0
- package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -2
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +37 -0
- package/src/__tests__/slack-channel-config.test.ts +109 -94
- package/src/__tests__/swarm-conversation-integration.test.ts +2 -2
- package/src/__tests__/swarm-recursion.test.ts +2 -2
- package/src/__tests__/swarm-tool.test.ts +2 -2
- package/src/__tests__/system-prompt.test.ts +19 -66
- package/src/__tests__/telegram-config.test.ts +121 -0
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +1 -1
- package/src/__tests__/trace-emitter.test.ts +8 -1
- package/src/__tests__/trust-store.test.ts +7 -8
- package/src/__tests__/twilio-routes.test.ts +1 -18
- package/src/__tests__/user-reference.test.ts +82 -2
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +196 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +3 -3
- package/src/avatar/ascii-renderer.ts +2 -2
- package/src/avatar/png-renderer.ts +2 -2
- package/src/avatar/resvg-lazy.ts +21 -0
- package/src/calls/guardian-dispatch.ts +1 -1
- package/src/calls/relay-access-wait.ts +2 -2
- package/src/calls/twilio-rest.ts +0 -248
- package/src/cli/AGENTS.md +5 -8
- package/src/cli/__tests__/notifications.test.ts +5 -5
- package/src/cli/commands/avatar.ts +64 -2
- package/src/cli/commands/conversations.ts +131 -1
- package/src/cli/commands/credentials.ts +2 -0
- package/src/cli/commands/notifications.ts +3 -3
- package/src/cli.ts +10 -0
- package/src/config/bundled-skills/acp/SKILL.md +5 -5
- package/src/config/bundled-skills/acp/TOOLS.json +6 -6
- package/src/config/bundled-skills/app-builder/SKILL.md +42 -42
- package/src/config/bundled-skills/app-builder/TOOLS.json +10 -10
- package/src/config/bundled-skills/browser/SKILL.md +15 -15
- package/src/config/bundled-skills/browser/TOOLS.json +14 -14
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +2 -2
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +1 -1
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
- package/src/config/bundled-skills/claude-code/SKILL.md +5 -5
- package/src/config/bundled-skills/computer-use/SKILL.md +2 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +15 -15
- package/src/config/bundled-skills/contacts/SKILL.md +3 -3
- package/src/config/bundled-skills/contacts/TOOLS.json +4 -4
- package/src/config/bundled-skills/document/SKILL.md +4 -4
- package/src/config/bundled-skills/document/TOOLS.json +2 -2
- package/src/config/bundled-skills/followups/TOOLS.json +3 -3
- package/src/config/bundled-skills/gmail/SKILL.md +32 -32
- package/src/config/bundled-skills/gmail/TOOLS.json +16 -16
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +1 -1
- package/src/config/bundled-skills/google-calendar/TOOLS.json +5 -5
- package/src/config/bundled-skills/google-calendar/types.ts +1 -1
- package/src/config/bundled-skills/heartbeat/SKILL.md +43 -0
- package/src/config/bundled-skills/image-studio/SKILL.md +3 -3
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -3
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +16 -12
- package/src/config/bundled-skills/media-processing/SKILL.md +40 -40
- package/src/config/bundled-skills/media-processing/TOOLS.json +8 -8
- package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +2 -2
- package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +1 -1
- package/src/config/bundled-skills/media-processing/services/gemini-map.ts +5 -5
- package/src/config/bundled-skills/media-processing/services/gemini-video.ts +2 -2
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -2
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -2
- package/src/config/bundled-skills/media-processing/services/reduce.ts +3 -3
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
- package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +29 -25
- package/src/config/bundled-skills/messaging/TOOLS.json +11 -11
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/notifications/SKILL.md +3 -3
- package/src/config/bundled-skills/notifications/TOOLS.json +2 -2
- package/src/config/bundled-skills/notifications/tools/send-notification.ts +3 -3
- package/src/config/bundled-skills/orchestration/SKILL.md +1 -1
- package/src/config/bundled-skills/orchestration/TOOLS.json +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +18 -14
- package/src/config/bundled-skills/phone-calls/TOOLS.json +3 -3
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +1 -1
- package/src/config/bundled-skills/playbooks/TOOLS.json +4 -4
- package/src/config/bundled-skills/schedule/SKILL.md +26 -26
- package/src/config/bundled-skills/schedule/TOOLS.json +5 -5
- package/src/config/bundled-skills/screen-watch/SKILL.md +3 -3
- package/src/config/bundled-skills/screen-watch/TOOLS.json +1 -1
- package/src/config/bundled-skills/sequences/SKILL.md +2 -2
- package/src/config/bundled-skills/sequences/TOOLS.json +10 -10
- package/src/config/bundled-skills/sequences/tools/sequence-analytics.ts +2 -2
- package/src/config/bundled-skills/sequences/tools/sequence-enroll.ts +2 -2
- package/src/config/bundled-skills/sequences/tools/sequence-enrollment-list.ts +1 -1
- package/src/config/bundled-skills/sequences/tools/sequence-get.ts +1 -1
- package/src/config/bundled-skills/sequences/tools/sequence-import.ts +3 -3
- package/src/config/bundled-skills/sequences/tools/sequence-list.ts +1 -1
- package/src/config/bundled-skills/sequences/tools/sequence-update.ts +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +5 -5
- package/src/config/bundled-skills/skills-catalog/SKILL.md +84 -0
- package/src/config/bundled-skills/slack/SKILL.md +2 -2
- package/src/config/bundled-skills/slack/TOOLS.json +8 -8
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +3 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -5
- package/src/config/bundled-skills/tasks/SKILL.md +1 -1
- package/src/config/bundled-skills/tasks/TOOLS.json +9 -9
- package/src/config/bundled-skills/transcribe/SKILL.md +5 -5
- package/src/config/bundled-skills/transcribe/TOOLS.json +1 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +10 -10
- package/src/config/bundled-skills/watcher/SKILL.md +4 -4
- package/src/config/bundled-skills/watcher/TOOLS.json +5 -5
- package/src/config/feature-flag-registry.json +33 -17
- package/src/config/schemas/sandbox.ts +1 -1
- package/src/config/schemas/services.ts +13 -3
- package/src/config/schemas/timeouts.ts +0 -10
- package/src/contacts/contact-store.ts +63 -0
- package/src/contacts/contacts-write.ts +1 -1
- package/src/daemon/assistant-attachments.ts +2 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -2
- package/src/daemon/conversation-agent-loop.ts +7 -30
- package/src/daemon/conversation-error.ts +24 -0
- package/src/daemon/conversation-memory.ts +8 -7
- package/src/daemon/conversation-runtime-assembly.ts +139 -274
- package/src/daemon/conversation-slash.ts +7 -26
- package/src/daemon/conversation-surfaces.ts +14 -0
- package/src/daemon/conversation-tool-setup.ts +9 -8
- package/src/daemon/conversation.ts +2 -0
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +10 -83
- package/src/daemon/handlers/config-channels.ts +12 -2
- package/src/daemon/handlers/config-slack-channel.ts +7 -1
- package/src/daemon/handlers/config-telegram.ts +6 -1
- package/src/daemon/handlers/conversations.ts +2 -2
- package/src/daemon/handlers/skills.ts +4 -0
- package/src/daemon/lifecycle.ts +28 -4
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +1 -5
- package/src/daemon/shutdown-handlers.ts +9 -3
- package/src/daemon/tool-side-effects.ts +40 -0
- package/src/daemon/trace-emitter.ts +25 -2
- package/src/events/domain-events.ts +1 -1
- package/src/events/tool-permission-telemetry-listener.ts +46 -0
- package/src/inbound/platform-callback-registration.ts +0 -18
- package/src/media/app-icon-generator.ts +15 -8
- package/src/media/avatar-router.ts +15 -8
- package/src/media/gemini-image-service.ts +125 -21
- package/src/memory/attachments-store.ts +3 -3
- package/src/memory/channel-verification-sessions.ts +6 -6
- package/src/memory/conversation-crud.ts +196 -1
- package/src/memory/{thread-starters-cadence.ts → conversation-starters-cadence.ts} +9 -42
- package/src/memory/conversation-title-service.ts +2 -3
- package/src/memory/db-init.ts +25 -1
- package/src/memory/invite-store.ts +4 -4
- package/src/memory/items-extractor.ts +4 -4
- package/src/memory/job-handlers/{thread-starters.ts → conversation-starters.ts} +123 -38
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/jobs-worker.ts +7 -5
- package/src/memory/lifecycle-events-store.ts +63 -0
- package/src/memory/migrations/172-rename-created-by-session-id.ts +27 -0
- package/src/memory/migrations/173-rename-source-session-id.ts +16 -0
- package/src/memory/migrations/174-rename-thread-starters-table.ts +52 -0
- package/src/memory/migrations/175-create-lifecycle-events.ts +15 -0
- package/src/memory/migrations/176-drop-capability-card-state.ts +36 -0
- package/src/memory/migrations/177-create-trace-events-table.ts +40 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +223 -96
- package/src/memory/retriever.ts +115 -138
- package/src/memory/schema/calls.ts +1 -1
- package/src/memory/schema/contacts.ts +1 -1
- package/src/memory/schema/infrastructure.ts +29 -0
- package/src/memory/schema/memory-core.ts +7 -17
- package/src/memory/schema/notifications.ts +1 -1
- package/src/memory/search/formatting.ts +23 -6
- package/src/memory/search/lexical.ts +2 -0
- package/src/memory/search/semantic.ts +2 -0
- package/src/memory/search/staleness.ts +1 -0
- package/src/memory/search/types.ts +4 -0
- package/src/memory/task-memory-cleanup.ts +96 -6
- package/src/memory/trace-event-store.ts +148 -0
- package/src/notifications/README.md +1 -1
- package/src/notifications/decision-engine.ts +2 -2
- package/src/notifications/emit-signal.ts +4 -4
- package/src/notifications/events-store.ts +4 -4
- package/src/notifications/signal.ts +1 -1
- package/src/oauth/manual-token-connection.ts +49 -25
- package/src/permissions/checker.ts +6 -5
- package/src/permissions/defaults.ts +4 -4
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +9 -90
- package/src/prompts/cache-boundary.ts +8 -0
- package/src/prompts/system-prompt.ts +105 -634
- package/src/prompts/templates/BOOTSTRAP.md +166 -33
- package/src/prompts/templates/IDENTITY.md +8 -23
- package/src/prompts/templates/SOUL.md +20 -41
- package/src/prompts/templates/USER.md +3 -19
- package/src/prompts/user-reference.ts +14 -16
- package/src/providers/anthropic/client.ts +46 -2
- package/src/providers/gemini/client.ts +6 -9
- package/src/providers/managed-proxy/constants.ts +1 -7
- package/src/providers/managed-proxy/context.ts +0 -1
- package/src/providers/model-intents.ts +5 -5
- package/src/providers/openai/client.ts +10 -1
- package/src/providers/openrouter/client.ts +1 -0
- package/src/providers/ratelimit.ts +0 -35
- package/src/providers/registry.ts +3 -5
- package/src/providers/retry.ts +18 -1
- package/src/runtime/access-request-helper.ts +1 -1
- package/src/runtime/auth/route-policy.ts +7 -0
- package/src/runtime/channel-verification-service.ts +1 -1
- package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
- package/src/runtime/guardian-vellum-migration.ts +63 -1
- package/src/runtime/http-server.ts +8 -4
- package/src/runtime/migrations/vbundle-builder.ts +212 -32
- package/src/runtime/migrations/vbundle-import-analyzer.ts +74 -8
- package/src/runtime/migrations/vbundle-importer.ts +66 -1
- package/src/runtime/migrations/vbundle-validator.ts +17 -3
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +4 -4
- package/src/runtime/routes/attachment-routes.ts +2 -2
- package/src/runtime/routes/btw-routes.ts +9 -0
- package/src/runtime/routes/channel-verification-routes.ts +19 -2
- package/src/runtime/routes/conversation-management-routes.ts +55 -1
- package/src/runtime/routes/conversation-query-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +49 -5
- package/src/runtime/routes/conversation-starter-routes.ts +207 -0
- package/src/runtime/routes/guardian-bootstrap-routes.ts +13 -9
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -1
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +1 -1
- package/src/runtime/routes/migration-routes.ts +25 -13
- package/src/runtime/routes/secret-routes.ts +18 -0
- package/src/runtime/routes/settings-routes.ts +8 -8
- package/src/runtime/routes/telemetry-routes.ts +53 -0
- package/src/runtime/routes/trace-event-routes.ts +62 -0
- package/src/runtime/tool-grant-request-helper.ts +1 -1
- package/src/runtime/verification-outbound-actions.ts +47 -31
- package/src/security/encrypted-store.ts +263 -78
- package/src/skills/catalog-install.ts +10 -0
- package/src/skills/managed-store.ts +2 -0
- package/src/skills/skill-memory.ts +220 -0
- package/src/subagent/manager.ts +1 -4
- package/src/telemetry/types.ts +10 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +1 -1
- package/src/telemetry/usage-telemetry-reporter.ts +51 -4
- package/src/tools/AGENTS.md +11 -11
- package/src/tools/acp/spawn.ts +1 -1
- package/src/tools/apps/executors.ts +8 -8
- package/src/tools/apps/registry.ts +1 -1
- package/src/tools/assets/materialize.ts +6 -6
- package/src/tools/assets/search.ts +10 -10
- package/src/tools/browser/__tests__/auth-cache.test.ts +2 -2
- package/src/tools/browser/__tests__/auth-detector.test.ts +4 -4
- package/src/tools/browser/auth-detector.ts +6 -6
- package/src/tools/browser/browser-execution.ts +13 -13
- package/src/tools/browser/browser-manager.ts +3 -3
- package/src/tools/browser/chrome-cdp.ts +5 -5
- package/src/tools/browser/jit-auth.ts +2 -2
- package/src/tools/browser/network-recorder.test.ts +2 -2
- package/src/tools/browser/network-recorder.ts +3 -3
- package/src/tools/browser/runtime-check.ts +3 -3
- package/src/tools/claude-code/claude-code.ts +2 -2
- package/src/tools/computer-use/definitions.ts +18 -18
- package/src/tools/credential-execution/make-authenticated-request.ts +4 -4
- package/src/tools/credential-execution/manage-secure-command-tool.ts +3 -3
- package/src/tools/credential-execution/run-authenticated-command.ts +4 -4
- package/src/tools/credentials/broker-types.ts +5 -5
- package/src/tools/credentials/broker.ts +15 -15
- package/src/tools/credentials/metadata-store.ts +2 -2
- package/src/tools/credentials/resolve.ts +1 -1
- package/src/tools/credentials/selection.ts +1 -1
- package/src/tools/credentials/tool-policy.ts +1 -1
- package/src/tools/credentials/vault.ts +115 -25
- package/src/tools/execution-target.ts +2 -2
- package/src/tools/executor.ts +7 -7
- package/src/tools/filesystem/edit.ts +2 -2
- package/src/tools/filesystem/read.ts +1 -1
- package/src/tools/filesystem/write.ts +1 -1
- package/src/tools/host-filesystem/edit.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -1
- package/src/tools/host-filesystem/write.ts +1 -1
- package/src/tools/host-terminal/host-shell.ts +9 -8
- package/src/tools/mcp/mcp-tool-factory.ts +7 -6
- package/src/tools/memory/definitions.ts +6 -5
- package/src/tools/memory/handlers.test.ts +1 -1
- package/src/tools/network/__tests__/web-search.test.ts +3 -3
- package/src/tools/network/domain-normalize.ts +2 -2
- package/src/tools/network/script-proxy/session-manager.ts +10 -10
- package/src/tools/network/web-fetch.ts +1 -1
- package/src/tools/network/web-search.ts +3 -3
- package/src/tools/permission-checker.ts +8 -8
- package/src/tools/registry.ts +7 -7
- package/src/tools/schedule/list.ts +2 -2
- package/src/tools/schema-transforms.ts +31 -21
- package/src/tools/secret-detection-handler.ts +1 -1
- package/src/tools/sensitive-output-placeholders.ts +1 -1
- package/src/tools/shared/filesystem/edit-engine.ts +1 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +3 -3
- package/src/tools/shared/filesystem/image-read.ts +25 -5
- package/src/tools/shared/filesystem/path-policy.ts +2 -2
- package/src/tools/shared/shell-output.ts +1 -1
- package/src/tools/side-effects.ts +1 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/load.ts +3 -3
- package/src/tools/skills/sandbox-runner.ts +3 -3
- package/src/tools/subagent/read.ts +1 -1
- package/src/tools/subagent/spawn.ts +2 -2
- package/src/tools/swarm/delegate.ts +3 -3
- package/src/tools/system/request-permission.ts +5 -4
- package/src/tools/terminal/backends/native.ts +4 -4
- package/src/tools/terminal/parser.ts +6 -6
- package/src/tools/terminal/sandbox-diagnostics.ts +1 -1
- package/src/tools/terminal/shell.ts +16 -16
- package/src/tools/tool-approval-handler.ts +21 -12
- package/src/tools/tool-manifest.ts +4 -4
- package/src/tools/types.ts +3 -3
- package/src/tools/ui-surface/definitions.ts +9 -37
- package/src/tools/watcher/list.ts +1 -1
- package/src/util/logger.ts +7 -2
- package/src/util/retry.ts +29 -1
- package/src/workspace/migrations/007-web-search-provider-rename.ts +37 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/cli-help-reference-sync.test.ts +0 -26
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -190
- package/src/cli/reference.ts +0 -38
- package/src/memory/job-handlers/capability-cards.ts +0 -420
- package/src/runtime/routes/thread-starter-routes.ts +0 -294
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A .vbundle is a gzip-compressed tar archive containing:
|
|
5
5
|
* - manifest.json: metadata with schema_version, checksums, and bundle info
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
6
|
+
* - workspace/: the entire ~/.vellum/workspace/ directory tree (DB, config,
|
|
7
|
+
* skills, hooks, prompts, attachments, etc.) — excluding large/regenerable
|
|
8
|
+
* dirs (embedding-models/, data/qdrant/)
|
|
9
|
+
* - trust/trust.json: trust rules (optional, lives in protected/ outside workspace)
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import { createHash } from "node:crypto";
|
|
11
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
14
|
+
import { join, relative } from "node:path";
|
|
12
15
|
import { gzipSync } from "node:zlib";
|
|
13
16
|
|
|
14
17
|
import type {
|
|
@@ -107,12 +110,72 @@ function computeHeaderChecksum(header: Uint8Array): number {
|
|
|
107
110
|
return sum;
|
|
108
111
|
}
|
|
109
112
|
|
|
110
|
-
|
|
111
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Build a PAX extended header entry for paths that exceed the 100-byte ustar
|
|
115
|
+
* limit. The PAX entry is a special tar record (typeflag 'x') whose body
|
|
116
|
+
* contains "key=value" records. The following data entry uses a truncated name
|
|
117
|
+
* in its ustar header, but tar extractors use the PAX path attribute instead.
|
|
118
|
+
*/
|
|
119
|
+
function createPaxPathEntry(name: string): Uint8Array {
|
|
112
120
|
const encoder = new TextEncoder();
|
|
113
121
|
|
|
114
|
-
//
|
|
122
|
+
// Build PAX payload: "<length> path=<name>\n"
|
|
123
|
+
// The length field includes itself, the space, and the trailing newline.
|
|
124
|
+
const record = `path=${name}\n`;
|
|
125
|
+
// Start with a guess for the decimal length prefix
|
|
126
|
+
let prefix = `${record.length + 2} `; // +2 for prefix digit + space (min)
|
|
127
|
+
let full = `${prefix}${record}`;
|
|
128
|
+
// Iterate until the length prefix is self-consistent
|
|
129
|
+
while (new TextEncoder().encode(full).length !== Number.parseInt(prefix)) {
|
|
130
|
+
prefix = `${new TextEncoder().encode(full).length} `;
|
|
131
|
+
full = `${prefix}${record}`;
|
|
132
|
+
}
|
|
133
|
+
const paxData = encoder.encode(full);
|
|
134
|
+
|
|
135
|
+
// Build a ustar header for the PAX entry itself
|
|
136
|
+
const header = new Uint8Array(BLOCK_SIZE);
|
|
137
|
+
|
|
138
|
+
// Use a synthetic name for the PAX header entry
|
|
139
|
+
const paxName = encoder.encode("PaxHeader/entry");
|
|
140
|
+
header.set(paxName.subarray(0, 100), 0);
|
|
141
|
+
|
|
142
|
+
writeOctal(header, 100, 8, 0o644);
|
|
143
|
+
writeOctal(header, 108, 8, 0);
|
|
144
|
+
writeOctal(header, 116, 8, 0);
|
|
145
|
+
writeOctal(header, 124, 12, paxData.length);
|
|
146
|
+
writeOctal(header, 136, 12, Math.floor(Date.now() / 1000));
|
|
147
|
+
|
|
148
|
+
// Type flag 'x' = PAX extended header for the next entry
|
|
149
|
+
header[156] = "x".charCodeAt(0);
|
|
150
|
+
|
|
151
|
+
const magic = encoder.encode("ustar\0");
|
|
152
|
+
header.set(magic, 257);
|
|
153
|
+
header[263] = "0".charCodeAt(0);
|
|
154
|
+
header[264] = "0".charCodeAt(0);
|
|
155
|
+
|
|
156
|
+
const checksum = computeHeaderChecksum(header);
|
|
157
|
+
writeOctal(header, 148, 7, checksum);
|
|
158
|
+
header[155] = 0x20;
|
|
159
|
+
|
|
160
|
+
const paddedData = padToBlock(paxData);
|
|
161
|
+
const result = new Uint8Array(header.length + paddedData.length);
|
|
162
|
+
result.set(header, 0);
|
|
163
|
+
result.set(paddedData, header.length);
|
|
164
|
+
return result;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function createTarEntry(name: string, data: Uint8Array): Uint8Array {
|
|
168
|
+
const encoder = new TextEncoder();
|
|
115
169
|
const nameBytes = encoder.encode(name);
|
|
170
|
+
|
|
171
|
+
// If the name exceeds 100 bytes, emit a PAX extended header first
|
|
172
|
+
// so that the full path is preserved in the archive.
|
|
173
|
+
const needsPax = nameBytes.length > 100;
|
|
174
|
+
const paxEntry = needsPax ? createPaxPathEntry(name) : null;
|
|
175
|
+
|
|
176
|
+
const header = new Uint8Array(BLOCK_SIZE);
|
|
177
|
+
|
|
178
|
+
// File name (0-99) — truncated if >100 bytes; PAX header carries the full name
|
|
116
179
|
header.set(nameBytes.subarray(0, 100), 0);
|
|
117
180
|
|
|
118
181
|
// File mode (100-107): 0644
|
|
@@ -148,10 +211,18 @@ function createTarEntry(name: string, data: Uint8Array): Uint8Array {
|
|
|
148
211
|
|
|
149
212
|
// Combine header + padded data
|
|
150
213
|
const paddedData = padToBlock(data);
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
214
|
+
const fileEntry = new Uint8Array(header.length + paddedData.length);
|
|
215
|
+
fileEntry.set(header, 0);
|
|
216
|
+
fileEntry.set(paddedData, header.length);
|
|
217
|
+
|
|
218
|
+
if (paxEntry) {
|
|
219
|
+
const result = new Uint8Array(paxEntry.length + fileEntry.length);
|
|
220
|
+
result.set(paxEntry, 0);
|
|
221
|
+
result.set(fileEntry, paxEntry.length);
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return fileEntry;
|
|
155
226
|
}
|
|
156
227
|
|
|
157
228
|
function createTarArchive(
|
|
@@ -230,24 +301,119 @@ export function buildVBundle(options: BuildVBundleOptions): BuildVBundleResult {
|
|
|
230
301
|
return { archive, manifest };
|
|
231
302
|
}
|
|
232
303
|
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
// Directory walker — recursively collects files for archive inclusion
|
|
306
|
+
// ---------------------------------------------------------------------------
|
|
307
|
+
|
|
308
|
+
interface WalkDirectoryOptions {
|
|
309
|
+
/** Include binary files (files containing null bytes). Default: false. */
|
|
310
|
+
includeBinary?: boolean;
|
|
311
|
+
/** Directory names to skip (matched against immediate child name). */
|
|
312
|
+
skipDirs?: string[];
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Recursively walk a directory and return all non-symlink files as
|
|
317
|
+
* VBundleFileEntry objects with paths prefixed by `archivePrefix`.
|
|
318
|
+
*
|
|
319
|
+
* By default, binary files (detected via null-byte heuristic in the first
|
|
320
|
+
* 8 KB) are skipped. Pass `includeBinary: true` to include them.
|
|
321
|
+
*/
|
|
322
|
+
function walkDirectory(
|
|
323
|
+
dir: string,
|
|
324
|
+
archivePrefix: string,
|
|
325
|
+
options: WalkDirectoryOptions = {},
|
|
326
|
+
): VBundleFileEntry[] {
|
|
327
|
+
const { includeBinary = false, skipDirs = [] } = options;
|
|
328
|
+
const entries: VBundleFileEntry[] = [];
|
|
329
|
+
|
|
330
|
+
function walk(currentDir: string): void {
|
|
331
|
+
const dirEntries = readdirSync(currentDir, { withFileTypes: true });
|
|
332
|
+
for (const entry of dirEntries) {
|
|
333
|
+
const fullPath = join(currentDir, entry.name);
|
|
334
|
+
|
|
335
|
+
// Skip symlinks
|
|
336
|
+
const stat = lstatSync(fullPath);
|
|
337
|
+
if (stat.isSymbolicLink()) continue;
|
|
338
|
+
|
|
339
|
+
if (stat.isDirectory()) {
|
|
340
|
+
// Check skip list against the relative path from the walk root
|
|
341
|
+
const relDir = relative(dir, fullPath);
|
|
342
|
+
if (skipDirs.some((s) => relDir === s || relDir.startsWith(s + "/"))) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
walk(fullPath);
|
|
346
|
+
} else if (stat.isFile()) {
|
|
347
|
+
// Skip SQLite auxiliary files — these are ephemeral and race-prone
|
|
348
|
+
// with the live DB connection. The WAL is checkpointed before the
|
|
349
|
+
// walk, so the main .db file has all committed rows.
|
|
350
|
+
if (
|
|
351
|
+
entry.name.endsWith(".db-wal") ||
|
|
352
|
+
entry.name.endsWith(".db-shm") ||
|
|
353
|
+
entry.name.endsWith(".db-journal")
|
|
354
|
+
) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const data = new Uint8Array(readFileSync(fullPath));
|
|
359
|
+
|
|
360
|
+
// Skip binary files unless explicitly included
|
|
361
|
+
if (!includeBinary) {
|
|
362
|
+
const checkLength = Math.min(data.length, 8192);
|
|
363
|
+
let isBinary = false;
|
|
364
|
+
for (let i = 0; i < checkLength; i++) {
|
|
365
|
+
if (data[i] === 0) {
|
|
366
|
+
isBinary = true;
|
|
367
|
+
break;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (isBinary) continue;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const relativePath = relative(dir, fullPath);
|
|
374
|
+
entries.push({
|
|
375
|
+
path: `${archivePrefix}/${relativePath}`,
|
|
376
|
+
data,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
walk(dir);
|
|
383
|
+
return entries;
|
|
384
|
+
}
|
|
385
|
+
|
|
233
386
|
// ---------------------------------------------------------------------------
|
|
234
387
|
// Export builder — reads real data from disk
|
|
235
388
|
// ---------------------------------------------------------------------------
|
|
236
389
|
|
|
237
390
|
export interface BuildExportVBundleOptions {
|
|
238
|
-
/** Path to the SQLite database file (e.g. ~/.vellum/workspace/data/db/assistant.db). */
|
|
239
|
-
dbPath: string;
|
|
240
|
-
/** Path to the config file (e.g. ~/.vellum/workspace/config.json). */
|
|
241
|
-
configPath: string;
|
|
242
391
|
/** Source identifier. Defaults to "runtime-export". */
|
|
243
392
|
source?: string;
|
|
244
393
|
/** Human-readable description. */
|
|
245
394
|
description?: string;
|
|
395
|
+
/** Absolute path to trust.json. If provided and the file exists, it is included in the archive. */
|
|
396
|
+
trustPath?: string;
|
|
397
|
+
/**
|
|
398
|
+
* Absolute path to the hooks directory (~/.vellum/hooks/).
|
|
399
|
+
* Hooks live outside the workspace, so they must be included explicitly.
|
|
400
|
+
* Included in the archive under the "hooks/" prefix for backward compat.
|
|
401
|
+
*/
|
|
402
|
+
hooksDir?: string;
|
|
403
|
+
/**
|
|
404
|
+
* Absolute path to the workspace directory (~/.vellum/workspace/).
|
|
405
|
+
* When provided and exists, the entire directory tree is walked and
|
|
406
|
+
* included in the archive under the "workspace/" prefix, skipping
|
|
407
|
+
* large/regenerable dirs (embedding-models/, data/qdrant/).
|
|
408
|
+
* Binary files (SQLite DB, attachments) are included.
|
|
409
|
+
*/
|
|
410
|
+
workspaceDir?: string;
|
|
246
411
|
/**
|
|
247
412
|
* Optional callback to checkpoint the WAL before reading the database file.
|
|
248
413
|
* In WAL mode, committed rows may live in the -wal file and not yet be
|
|
249
414
|
* flushed to the main .db file. Callers should pass a function that runs
|
|
250
415
|
* PRAGMA wal_checkpoint(TRUNCATE) on the live database connection.
|
|
416
|
+
* Called before the workspace walk so the DB file is up to date.
|
|
251
417
|
*/
|
|
252
418
|
checkpoint?: () => void;
|
|
253
419
|
}
|
|
@@ -255,18 +421,20 @@ export interface BuildExportVBundleOptions {
|
|
|
255
421
|
/**
|
|
256
422
|
* Build a .vbundle archive populated with real assistant data.
|
|
257
423
|
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
424
|
+
* Walks the entire workspace directory (~/.vellum/workspace/) and includes
|
|
425
|
+
* all files in the archive, skipping only large/regenerable directories
|
|
426
|
+
* (embedding-models/, data/qdrant/). Binary files (SQLite DB, attachments)
|
|
427
|
+
* are included. Trust rules (in protected/, outside workspace) are handled
|
|
428
|
+
* separately.
|
|
262
429
|
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
430
|
+
* The WAL is checkpointed before the walk so the exported DB file contains
|
|
431
|
+
* all committed rows.
|
|
265
432
|
*/
|
|
266
433
|
export function buildExportVBundle(
|
|
267
434
|
options: BuildExportVBundleOptions,
|
|
268
435
|
): BuildVBundleResult {
|
|
269
|
-
const {
|
|
436
|
+
const { source, description, checkpoint, trustPath, workspaceDir, hooksDir } =
|
|
437
|
+
options;
|
|
270
438
|
|
|
271
439
|
// Flush WAL to the main database file before reading so the export
|
|
272
440
|
// captures all committed rows (SQLite WAL mode keeps recent writes
|
|
@@ -275,20 +443,32 @@ export function buildExportVBundle(
|
|
|
275
443
|
checkpoint();
|
|
276
444
|
}
|
|
277
445
|
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
446
|
+
const files: VBundleFileEntry[] = [];
|
|
447
|
+
|
|
448
|
+
// Walk the entire workspace directory, including binary files (DB,
|
|
449
|
+
// attachments) but skipping large/regenerable subdirectories.
|
|
450
|
+
if (workspaceDir && existsSync(workspaceDir) && lstatSync(workspaceDir).isDirectory()) {
|
|
451
|
+
files.push(
|
|
452
|
+
...walkDirectory(workspaceDir, "workspace", {
|
|
453
|
+
includeBinary: true,
|
|
454
|
+
skipDirs: ["embedding-models", "data/qdrant"],
|
|
455
|
+
}),
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Include hooks directory if it exists (lives at ~/.vellum/hooks/, outside workspace).
|
|
460
|
+
if (hooksDir && existsSync(hooksDir) && lstatSync(hooksDir).isDirectory()) {
|
|
461
|
+
files.push(...walkDirectory(hooksDir, "hooks"));
|
|
462
|
+
}
|
|
281
463
|
|
|
282
|
-
//
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
:
|
|
464
|
+
// Include trust rules if the file exists (lives in protected/, outside workspace).
|
|
465
|
+
if (trustPath && existsSync(trustPath)) {
|
|
466
|
+
const trustData = new Uint8Array(readFileSync(trustPath));
|
|
467
|
+
files.push({ path: "trust/trust.json", data: trustData });
|
|
468
|
+
}
|
|
286
469
|
|
|
287
470
|
return buildVBundle({
|
|
288
|
-
files
|
|
289
|
-
{ path: "data/db/assistant.db", data: dbData },
|
|
290
|
-
{ path: "config/settings.json", data: configData },
|
|
291
|
-
],
|
|
471
|
+
files,
|
|
292
472
|
source: source ?? "runtime-export",
|
|
293
473
|
description: description ?? "Runtime export bundle",
|
|
294
474
|
});
|
|
@@ -15,9 +15,18 @@
|
|
|
15
15
|
|
|
16
16
|
import { createHash } from "node:crypto";
|
|
17
17
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
18
|
+
import { join, resolve } from "node:path";
|
|
18
19
|
|
|
19
20
|
import type { ManifestType } from "./vbundle-validator.js";
|
|
20
21
|
|
|
22
|
+
/** Only these prompt filenames are accepted during import. */
|
|
23
|
+
const ALLOWED_PROMPT_FILENAMES = new Set([
|
|
24
|
+
"IDENTITY.md",
|
|
25
|
+
"SOUL.md",
|
|
26
|
+
"USER.md",
|
|
27
|
+
"UPDATES.md",
|
|
28
|
+
]);
|
|
29
|
+
|
|
21
30
|
// ---------------------------------------------------------------------------
|
|
22
31
|
// Public types
|
|
23
32
|
// ---------------------------------------------------------------------------
|
|
@@ -79,19 +88,76 @@ export interface PathResolver {
|
|
|
79
88
|
|
|
80
89
|
export class DefaultPathResolver implements PathResolver {
|
|
81
90
|
constructor(
|
|
82
|
-
private
|
|
83
|
-
private
|
|
91
|
+
private protectedDir?: string,
|
|
92
|
+
private workspaceDir?: string,
|
|
93
|
+
private hooksDir?: string,
|
|
84
94
|
) {}
|
|
85
95
|
|
|
86
96
|
resolve(archivePath: string): string | null {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
// New format: workspace/ prefix — maps directly into the workspace dir
|
|
98
|
+
if (archivePath.startsWith("workspace/") && this.workspaceDir) {
|
|
99
|
+
const relPath = archivePath.slice("workspace/".length);
|
|
100
|
+
if (!relPath) return null;
|
|
101
|
+
const resolved = resolve(this.workspaceDir, relPath);
|
|
102
|
+
const wsRoot = resolve(this.workspaceDir);
|
|
103
|
+
// Path traversal containment
|
|
104
|
+
if (resolved !== wsRoot && !resolved.startsWith(wsRoot + "/")) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
return resolved;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Backward compat: old bundle formats with specific archive paths
|
|
111
|
+
if (archivePath === "data/db/assistant.db" && this.workspaceDir) {
|
|
112
|
+
return join(this.workspaceDir, "data", "db", "assistant.db");
|
|
113
|
+
}
|
|
114
|
+
if (archivePath === "config/settings.json" && this.workspaceDir) {
|
|
115
|
+
return join(this.workspaceDir, "config.json");
|
|
116
|
+
}
|
|
117
|
+
if (archivePath === "trust/trust.json" && this.protectedDir) {
|
|
118
|
+
return join(this.protectedDir, "trust.json");
|
|
119
|
+
}
|
|
120
|
+
if (archivePath.startsWith("skills/") && this.workspaceDir) {
|
|
121
|
+
const resolved = resolve(
|
|
122
|
+
this.workspaceDir,
|
|
123
|
+
"skills",
|
|
124
|
+
archivePath.slice("skills/".length),
|
|
125
|
+
);
|
|
126
|
+
const skillsRoot = resolve(this.workspaceDir, "skills");
|
|
127
|
+
if (resolved !== skillsRoot && !resolved.startsWith(skillsRoot + "/")) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return resolved;
|
|
131
|
+
}
|
|
132
|
+
if (archivePath.startsWith("prompts/") && this.workspaceDir) {
|
|
133
|
+
// Old bundles stored prompts as prompts/IDENTITY.md etc — these map
|
|
134
|
+
// to the workspace root (e.g. workspace/IDENTITY.md).
|
|
135
|
+
// Only accepted prompt filenames resolve — unknown entries are
|
|
136
|
+
// skipped so they cannot trigger workspace clearing.
|
|
137
|
+
const filename = archivePath.slice("prompts/".length);
|
|
138
|
+
if (!ALLOWED_PROMPT_FILENAMES.has(filename)) {
|
|
93
139
|
return null;
|
|
140
|
+
}
|
|
141
|
+
const resolved = resolve(this.workspaceDir, filename);
|
|
142
|
+
const wsRoot = resolve(this.workspaceDir);
|
|
143
|
+
if (resolved !== wsRoot && !resolved.startsWith(wsRoot + "/")) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
return resolved;
|
|
94
147
|
}
|
|
148
|
+
if (archivePath.startsWith("hooks/") && this.hooksDir) {
|
|
149
|
+
const resolved = resolve(
|
|
150
|
+
this.hooksDir,
|
|
151
|
+
archivePath.slice("hooks/".length),
|
|
152
|
+
);
|
|
153
|
+
const hooksRoot = resolve(this.hooksDir);
|
|
154
|
+
if (resolved !== hooksRoot && !resolved.startsWith(hooksRoot + "/")) {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
return resolved;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return null;
|
|
95
161
|
}
|
|
96
162
|
}
|
|
97
163
|
|
|
@@ -17,10 +17,12 @@ import {
|
|
|
17
17
|
copyFileSync,
|
|
18
18
|
existsSync,
|
|
19
19
|
mkdirSync,
|
|
20
|
+
readdirSync,
|
|
20
21
|
readFileSync,
|
|
22
|
+
rmSync,
|
|
21
23
|
writeFileSync,
|
|
22
24
|
} from "node:fs";
|
|
23
|
-
import { dirname } from "node:path";
|
|
25
|
+
import { dirname, join } from "node:path";
|
|
24
26
|
|
|
25
27
|
import type { PathResolver } from "./vbundle-import-analyzer.js";
|
|
26
28
|
import type { ManifestType, VBundleTarEntry } from "./vbundle-validator.js";
|
|
@@ -113,6 +115,12 @@ export interface ImportCommitOptions {
|
|
|
113
115
|
preValidatedManifest?: ManifestType;
|
|
114
116
|
/** Pre-parsed tar entries from a prior validateVBundle call. */
|
|
115
117
|
preValidatedEntries?: Map<string, VBundleTarEntry>;
|
|
118
|
+
/**
|
|
119
|
+
* Absolute path to the workspace directory. When set and the bundle
|
|
120
|
+
* contains workspace/ entries, the workspace is cleared (except
|
|
121
|
+
* skip dirs) before writing to ensure an exact-match restore.
|
|
122
|
+
*/
|
|
123
|
+
workspaceDir?: string;
|
|
116
124
|
}
|
|
117
125
|
|
|
118
126
|
/**
|
|
@@ -128,6 +136,7 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
|
|
|
128
136
|
pathResolver,
|
|
129
137
|
preValidatedManifest,
|
|
130
138
|
preValidatedEntries,
|
|
139
|
+
workspaceDir,
|
|
131
140
|
} = options;
|
|
132
141
|
|
|
133
142
|
let manifest: ManifestType;
|
|
@@ -154,6 +163,62 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
|
|
|
154
163
|
entryMap = validation.entries;
|
|
155
164
|
}
|
|
156
165
|
|
|
166
|
+
// Directories to preserve when clearing the workspace (large/regenerable).
|
|
167
|
+
const WORKSPACE_SKIP_DIRS = new Set(["embedding-models"]);
|
|
168
|
+
// data/qdrant is nested — we skip "qdrant" inside "data/"
|
|
169
|
+
const DATA_SKIP_DIRS = new Set(["qdrant"]);
|
|
170
|
+
|
|
171
|
+
// Step 1b: Clear the workspace directory before restore if the bundle
|
|
172
|
+
// contains new-format workspace/ entries. This ensures an exact-match
|
|
173
|
+
// restore with no stale files left behind. Skips embedding-models/ and
|
|
174
|
+
// data/qdrant/ (large, regenerable).
|
|
175
|
+
//
|
|
176
|
+
// Only new-format bundles (workspace/ prefix) trigger clearing. Old-format
|
|
177
|
+
// bundles (skills/, hooks/, data/db/*, config/*) wrote specific files
|
|
178
|
+
// without clearing — preserving that behavior avoids wiping workspace
|
|
179
|
+
// data when importing legacy bundles.
|
|
180
|
+
//
|
|
181
|
+
// Gate on resolution: at least one workspace/ entry must resolve to a
|
|
182
|
+
// valid disk path. This prevents path-traversal entries (e.g.
|
|
183
|
+
// "workspace/../../etc/passwd") from triggering a workspace purge while
|
|
184
|
+
// resolving to nothing.
|
|
185
|
+
const hasWorkspaceEntries = manifest.files.some(
|
|
186
|
+
(f) => f.path.startsWith("workspace/") && !!pathResolver.resolve(f.path),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
if (hasWorkspaceEntries && workspaceDir && existsSync(workspaceDir)) {
|
|
190
|
+
try {
|
|
191
|
+
// Clear workspace contents selectively, preserving skip dirs
|
|
192
|
+
const topEntries = readdirSync(workspaceDir, { withFileTypes: true });
|
|
193
|
+
for (const entry of topEntries) {
|
|
194
|
+
if (WORKSPACE_SKIP_DIRS.has(entry.name)) continue;
|
|
195
|
+
|
|
196
|
+
const entryPath = join(workspaceDir, entry.name);
|
|
197
|
+
if (entry.name === "data" && entry.isDirectory()) {
|
|
198
|
+
// Inside data/, preserve qdrant/ but clear everything else
|
|
199
|
+
const dataEntries = readdirSync(entryPath, { withFileTypes: true });
|
|
200
|
+
for (const dataEntry of dataEntries) {
|
|
201
|
+
if (DATA_SKIP_DIRS.has(dataEntry.name)) continue;
|
|
202
|
+
rmSync(join(entryPath, dataEntry.name), {
|
|
203
|
+
recursive: true,
|
|
204
|
+
force: true,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
rmSync(entryPath, { recursive: true, force: true });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch (err) {
|
|
212
|
+
return {
|
|
213
|
+
ok: false,
|
|
214
|
+
reason: "write_failed",
|
|
215
|
+
message: `Failed to clear workspace directory "${workspaceDir}": ${
|
|
216
|
+
err instanceof Error ? err.message : String(err)
|
|
217
|
+
}`,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
157
222
|
// Step 2: Write files to disk with backups
|
|
158
223
|
const importedFiles: ImportedFileReport[] = [];
|
|
159
224
|
const warnings: string[] = [];
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A .vbundle is a gzip-compressed tar archive containing:
|
|
5
5
|
* - manifest.json: metadata with schema_version, checksums, and bundle info
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
6
|
+
* - workspace/: the entire workspace directory tree (new format), OR
|
|
7
|
+
* data/db/assistant.db + config/settings.json (old format)
|
|
8
|
+
* - trust/trust.json: trust rules (optional)
|
|
8
9
|
*
|
|
9
10
|
* Validation steps:
|
|
10
11
|
* 1. Archive structure: valid gzip tar with required entries
|
|
@@ -124,6 +125,17 @@ function parseTar(buffer: Uint8Array): TarEntry[] {
|
|
|
124
125
|
continue;
|
|
125
126
|
}
|
|
126
127
|
|
|
128
|
+
// PAX extended header (type 'x') — extract path= attribute for next entry
|
|
129
|
+
if (typeFlag === "x") {
|
|
130
|
+
const paxText = new TextDecoder().decode(data);
|
|
131
|
+
const pathMatch = paxText.match(/\d+ path=([^\n]+)\n/);
|
|
132
|
+
if (pathMatch) {
|
|
133
|
+
longName = pathMatch[1];
|
|
134
|
+
}
|
|
135
|
+
offset = dataStart + dataBlocks * BLOCK_SIZE;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
127
139
|
// Regular file or hard link
|
|
128
140
|
if (typeFlag === "0" || typeFlag === "\0" || typeFlag === "") {
|
|
129
141
|
entries.push({ name: normalizePath(name), data, size });
|
|
@@ -181,7 +193,9 @@ function canonicalizeJson(obj: unknown): string {
|
|
|
181
193
|
// Core validation
|
|
182
194
|
// ---------------------------------------------------------------------------
|
|
183
195
|
|
|
184
|
-
|
|
196
|
+
// Only manifest.json is structurally required. The DB and config live under
|
|
197
|
+
// workspace/ (new format) or data/db/ + config/ (old format) — both are valid.
|
|
198
|
+
const REQUIRED_ENTRIES = ["manifest.json"];
|
|
185
199
|
|
|
186
200
|
// 2 GB — must accommodate large but valid migrations from buildExportVBundle()
|
|
187
201
|
const MAX_DECOMPRESSED_SIZE = 2 * 1024 * 1024 * 1024;
|
|
@@ -637,7 +637,7 @@ async function handleAccessRequestApproval(
|
|
|
637
637
|
void emitNotificationSignal({
|
|
638
638
|
sourceEventName: "ingress.trusted_contact.guardian_decision",
|
|
639
639
|
sourceChannel: approval.channel as NotificationSourceChannel,
|
|
640
|
-
|
|
640
|
+
sourceContextId: approval.conversationId,
|
|
641
641
|
attentionHints: {
|
|
642
642
|
requiresAction: false,
|
|
643
643
|
urgency: "medium",
|
|
@@ -651,7 +651,7 @@ async function handleAccessRequestApproval(
|
|
|
651
651
|
void emitNotificationSignal({
|
|
652
652
|
sourceEventName: "ingress.trusted_contact.denied",
|
|
653
653
|
sourceChannel: approval.channel as NotificationSourceChannel,
|
|
654
|
-
|
|
654
|
+
sourceContextId: approval.conversationId,
|
|
655
655
|
attentionHints: {
|
|
656
656
|
requiresAction: false,
|
|
657
657
|
urgency: "low",
|
|
@@ -719,7 +719,7 @@ async function handleAccessRequestApproval(
|
|
|
719
719
|
void emitNotificationSignal({
|
|
720
720
|
sourceEventName: "ingress.trusted_contact.guardian_decision",
|
|
721
721
|
sourceChannel: approval.channel as NotificationSourceChannel,
|
|
722
|
-
|
|
722
|
+
sourceContextId: approval.conversationId,
|
|
723
723
|
attentionHints: {
|
|
724
724
|
requiresAction: false,
|
|
725
725
|
urgency: "medium",
|
|
@@ -745,7 +745,7 @@ async function handleAccessRequestApproval(
|
|
|
745
745
|
void emitNotificationSignal({
|
|
746
746
|
sourceEventName: "ingress.trusted_contact.verification_sent",
|
|
747
747
|
sourceChannel: approval.channel as NotificationSourceChannel,
|
|
748
|
-
|
|
748
|
+
sourceContextId: approval.conversationId,
|
|
749
749
|
attentionHints: {
|
|
750
750
|
requiresAction: false,
|
|
751
751
|
urgency: "low",
|
|
@@ -12,8 +12,8 @@ import {
|
|
|
12
12
|
import { httpError } from "../http-errors.js";
|
|
13
13
|
import type { RouteDefinition } from "../http-router.js";
|
|
14
14
|
|
|
15
|
-
/**
|
|
16
|
-
const MAX_UPLOAD_BODY_BYTES =
|
|
15
|
+
/** 150 MB — base64-encoded 100 MB attachment ≈ 134 MB plus JSON wrapper overhead. */
|
|
16
|
+
const MAX_UPLOAD_BODY_BYTES = 150 * 1024 * 1024;
|
|
17
17
|
|
|
18
18
|
export async function handleUploadAttachment(req: Request): Promise<Response> {
|
|
19
19
|
const rawBody = await req.arrayBuffer();
|
|
@@ -116,6 +116,7 @@ async function handleBtw(
|
|
|
116
116
|
? conversation.systemPrompt
|
|
117
117
|
: buildSystemPrompt({ excludeBootstrap: true });
|
|
118
118
|
|
|
119
|
+
let textDeltaCount = 0;
|
|
119
120
|
await conversation.provider.sendMessage(
|
|
120
121
|
messages,
|
|
121
122
|
tools,
|
|
@@ -128,6 +129,7 @@ async function handleBtw(
|
|
|
128
129
|
},
|
|
129
130
|
onEvent: (event) => {
|
|
130
131
|
if (event.type === "text_delta") {
|
|
132
|
+
textDeltaCount++;
|
|
131
133
|
controller.enqueue(
|
|
132
134
|
encoder.encode(
|
|
133
135
|
`event: btw_text_delta\ndata: ${JSON.stringify({ text: event.text })}\n\n`,
|
|
@@ -139,6 +141,13 @@ async function handleBtw(
|
|
|
139
141
|
},
|
|
140
142
|
);
|
|
141
143
|
|
|
144
|
+
if (textDeltaCount === 0) {
|
|
145
|
+
log.warn(
|
|
146
|
+
{ conversationKey, messageCount: messages.length },
|
|
147
|
+
"btw side-chain completed with no text deltas",
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
142
151
|
controller.enqueue(
|
|
143
152
|
encoder.encode(`event: btw_complete\ndata: {}\n\n`),
|
|
144
153
|
);
|