@vellumai/assistant 0.9.0 → 0.10.0-staging.2
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 +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +28 -22
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +976 -63
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/access-request-card-view.test.ts +98 -0
- package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
- package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
- package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-compiler.test.ts +15 -1
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/call-pointer-messages.test.ts +28 -0
- package/src/__tests__/cancel-clears-processing.test.ts +89 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -4
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
- package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
- package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
- package/src/__tests__/config-loader-backfill.test.ts +268 -27
- package/src/__tests__/config-schema.test.ts +35 -0
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +0 -6
- package/src/__tests__/contacts-tools.test.ts +29 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +58 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-lifecycle.test.ts +7 -9
- package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
- package/src/__tests__/conversation-title-service.test.ts +62 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +4 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +4 -13
- package/src/__tests__/disk-pressure-policy.test.ts +12 -0
- package/src/__tests__/disk-usage.test.ts +65 -0
- package/src/__tests__/dynamic-page-surface.test.ts +152 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-flag-listener.test.ts +110 -1
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
- package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
- package/src/__tests__/guardian-grant-minting.test.ts +3 -35
- package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
- package/src/__tests__/guardian-routing-state.test.ts +0 -1
- package/src/__tests__/headless-browser-mode.test.ts +10 -0
- package/src/__tests__/headless-browser-navigate.test.ts +8 -3
- package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
- package/src/__tests__/host-browser-proxy.test.ts +87 -0
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/injector-v3-suppression.test.ts +27 -20
- package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
- package/src/__tests__/invite-redemption-service.test.ts +4 -7
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +30 -23
- package/src/__tests__/llm-resolver.test.ts +70 -24
- package/src/__tests__/llm-schema.test.ts +1 -0
- package/src/__tests__/managed-profile-guard.test.ts +163 -4
- package/src/__tests__/mcp-health-check.test.ts +6 -7
- package/src/__tests__/media-stream-server-integration.test.ts +317 -13
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/path-policy.test.ts +34 -0
- package/src/__tests__/persona-resolver.test.ts +49 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/plugin-api-provider.test.ts +24 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
- package/src/__tests__/reaction-persistence.test.ts +150 -29
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/relay-server.test.ts +285 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
- package/src/__tests__/skill-execute-input.test.ts +51 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/skills.test.ts +51 -0
- package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
- package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
- package/src/__tests__/subagent-tools.test.ts +266 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
- package/src/__tests__/title-generate-hook.test.ts +100 -3
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
- package/src/__tests__/tool-audit-listener.test.ts +7 -7
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +8 -10
- package/src/__tests__/twilio-routes.test.ts +81 -1
- package/src/__tests__/voice-invite-redemption.test.ts +2 -3
- package/src/__tests__/weak-open-model.test.ts +30 -0
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
- package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
- package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
- package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
- package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
- package/src/agent/loop.ts +49 -29
- package/src/api/README.md +6 -6
- package/src/api/events/tool-result.ts +6 -0
- package/src/api/events/workflow-completed.ts +53 -0
- package/src/api/events/workflow-leaf-finished.ts +38 -0
- package/src/api/events/workflow-leaf-started.ts +35 -0
- package/src/api/events/workflow-progress.ts +32 -0
- package/src/api/events/workflow-started.ts +31 -0
- package/src/api/index.ts +40 -0
- package/src/api/responses/conversation-message.ts +28 -4
- package/src/api/responses/home.ts +26 -4
- package/src/api/responses/workflow-journal.ts +53 -0
- package/src/approvals/guardian-card-withdrawal.ts +145 -0
- package/src/approvals/guardian-decision-primitive.ts +26 -3
- package/src/approvals/guardian-request-resolvers.ts +183 -80
- package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
- package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
- package/src/calls/call-pointer-messages.ts +10 -4
- package/src/calls/channel-admission-reader.ts +104 -0
- package/src/calls/guardian-dispatch.ts +17 -45
- package/src/calls/media-stream-server.ts +84 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/relay-server.ts +66 -0
- package/src/calls/relay-setup-router.ts +82 -1
- package/src/calls/twilio-routes.ts +17 -8
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/clients.ts +3 -0
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
- package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
- package/src/cli/commands/memory/index.ts +30 -0
- package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
- package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
- package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
- package/src/cli/commands/oauth/status.test.ts +36 -0
- package/src/cli/commands/oauth/status.ts +23 -3
- package/src/cli/commands/plugins.ts +197 -4
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
- package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/inspect-plugin.ts +12 -1
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +328 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/plugin-surfaces.ts +104 -0
- package/src/cli/lib/upgrade-plugin.ts +298 -10
- package/src/cli/program.ts +2 -6
- package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
- package/src/config/assistant-feature-flags.ts +22 -7
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
- package/src/config/bundled-skills/messaging/SKILL.md +6 -4
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +14 -8
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +15 -2
- package/src/config/feature-flag-registry.json +46 -31
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/llm-resolver.ts +3 -0
- package/src/config/loader.ts +4 -0
- package/src/config/memory-v3-gate.ts +11 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schema.ts +8 -6
- package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
- package/src/config/schemas/call-site-catalog.ts +7 -0
- package/src/config/schemas/channels.ts +11 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/llm.ts +31 -0
- package/src/config/schemas/memory-lifecycle.ts +3 -7
- package/src/config/schemas/memory-v3.ts +6 -0
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/schemas/services.ts +18 -0
- package/src/config/seed-inference-profiles.ts +109 -44
- package/src/config/skills.ts +21 -0
- package/src/config/sync-gated-profiles.ts +220 -0
- package/src/contacts/contact-store.ts +89 -106
- package/src/contacts/contacts-write.ts +5 -22
- package/src/contacts/types.ts +0 -1
- package/src/context/compactor.ts +88 -54
- package/src/context/strip-injections.ts +58 -10
- package/src/context/token-estimator.ts +1 -1
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
- package/src/daemon/conversation-agent-loop.ts +114 -22
- package/src/daemon/conversation-history.ts +1 -1
- package/src/daemon/conversation-lifecycle.ts +3 -5
- package/src/daemon/conversation-process.ts +13 -5
- package/src/daemon/conversation-runtime-assembly.ts +13 -15
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-surfaces.ts +26 -0
- package/src/daemon/conversation-tool-setup.ts +27 -14
- package/src/daemon/conversation.ts +66 -14
- package/src/daemon/disk-pressure-policy.ts +5 -3
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
- package/src/daemon/handlers/config-a2a.ts +0 -2
- package/src/daemon/handlers/config-channels.ts +15 -16
- package/src/daemon/handlers/config-slack-channel.ts +22 -3
- package/src/daemon/handlers/conversations.ts +107 -0
- package/src/daemon/host-browser-proxy.ts +41 -0
- package/src/daemon/lifecycle.ts +55 -27
- package/src/daemon/message-provenance.ts +2 -0
- package/src/daemon/message-types/contacts.ts +0 -1
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/message-types/web-activity.ts +7 -1
- package/src/daemon/message-types/workflows.ts +83 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/daemon/tool-setup-types.ts +4 -0
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +2 -2
- package/src/home/feed-source-enrichment.test.ts +151 -0
- package/src/home/feed-source-enrichment.ts +176 -0
- package/src/home/relationship-state.ts +2 -4
- package/src/instrument.ts +18 -6
- package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
- package/src/ipc/assistant-server.ts +37 -4
- package/src/ipc/gateway-flag-listener.ts +18 -2
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
- package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
- package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
- package/src/memory/auth-fallback-events-store.ts +2 -2
- package/src/memory/auto-analysis-enqueue.ts +3 -5
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/canonical-guardian-store.ts +39 -1
- package/src/memory/conversation-crud.ts +9 -4
- package/src/memory/conversation-key-store.ts +17 -2
- package/src/memory/conversation-title-service.ts +64 -7
- package/src/memory/db-init.ts +17 -17
- package/src/memory/embedding-backend.ts +38 -1
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +54 -1
- package/src/memory/lifecycle-events-store.ts +2 -2
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-enqueue.ts +31 -6
- package/src/memory/memory-retrospective-job.ts +28 -227
- package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
- package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
- package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
- package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
- package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
- package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
- package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
- package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
- package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
- package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/onboarding-events-store.ts +3 -3
- package/src/memory/schema/contacts.ts +0 -5
- package/src/memory/skill-loaded-events-store.test.ts +7 -15
- package/src/memory/skill-loaded-events-store.ts +2 -2
- package/src/memory/tool-executed-events-store.test.ts +7 -7
- package/src/memory/turn-trace-store.test.ts +736 -0
- package/src/memory/turn-trace-store.ts +364 -0
- package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
- package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
- package/src/memory/v2/consolidation-job.ts +2 -2
- package/src/memory/v2/skill-content.ts +25 -7
- package/src/memory/v2/skill-store.ts +7 -1
- package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
- package/src/memory/v3-eval/eval-packets.ts +546 -0
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/messaging/providers/slack/api.ts +31 -0
- package/src/messaging/providers/slack/send.test.ts +114 -2
- package/src/messaging/providers/slack/send.ts +30 -7
- package/src/messaging/providers/slack/withdraw.test.ts +200 -0
- package/src/messaging/providers/slack/withdraw.ts +161 -0
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/access-request-copy.ts +72 -59
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +58 -103
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/approval-card-data.ts +333 -0
- package/src/notifications/broadcaster.ts +16 -3
- package/src/notifications/canonical-delivery-recorder.ts +139 -0
- package/src/notifications/copy-composer.ts +3 -3
- package/src/notifications/decision-engine.ts +4 -2
- package/src/notifications/destination-resolver.ts +4 -6
- package/src/notifications/guardian-question-mode.ts +10 -0
- package/src/notifications/home-feed-side-effect.ts +7 -16
- package/src/notifications/notification-utils.ts +19 -20
- package/src/notifications/signal.ts +79 -43
- package/src/notifications/types.ts +98 -121
- package/src/oauth/AGENTS.md +5 -24
- package/src/permissions/checker.test.ts +51 -0
- package/src/permissions/checker.ts +185 -26
- package/src/permissions/ipc-risk-types.ts +24 -0
- package/src/permissions/question-prompter.test.ts +27 -0
- package/src/permissions/question-prompter.ts +4 -0
- package/src/platform/client.test.ts +119 -0
- package/src/platform/client.ts +66 -0
- package/src/platform/consent-cache.test.ts +267 -0
- package/src/platform/consent-cache.ts +174 -0
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +33 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
- package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
- package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
- package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
- package/src/plugins/defaults/advisor/config.ts +21 -0
- package/src/plugins/defaults/advisor/consult.ts +93 -0
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
- package/src/plugins/defaults/advisor/package.json +14 -0
- package/src/plugins/defaults/advisor/steering.ts +67 -0
- package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
- package/src/plugins/defaults/advisor/transcript.ts +76 -0
- package/src/plugins/defaults/index.ts +60 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
- package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
- package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
- package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
- package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
- package/src/prompts/persona-resolver.ts +14 -4
- package/src/prompts/templates/system-sections.ts +7 -2
- package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +176 -0
- package/src/providers/atlascloud/client.ts +85 -0
- package/src/providers/fetch-provider-catalog.ts +85 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/model-catalog.ts +58 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
- package/src/providers/openai/chat-completions-provider.ts +7 -0
- package/src/providers/openai/responses-provider.ts +10 -0
- package/src/providers/provider-send-message.ts +11 -3
- package/src/providers/retry.ts +53 -12
- package/src/providers/search-provider-catalog.ts +10 -0
- package/src/providers/weak-open-model.ts +22 -0
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/__tests__/client-health.test.ts +44 -0
- package/src/runtime/access-request-helper.ts +21 -53
- package/src/runtime/actor-trust-resolver.ts +59 -63
- package/src/runtime/agent-wake.ts +52 -0
- package/src/runtime/assistant-event-hub.ts +18 -4
- package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
- package/src/runtime/auth/require-bound-guardian.ts +1 -4
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/capabilities.test.ts +120 -0
- package/src/runtime/capabilities.ts +197 -0
- package/src/runtime/channel-approval-types.ts +22 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/channel-verification-service.ts +3 -3
- package/src/runtime/client-health.ts +26 -0
- package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
- package/src/runtime/effective-capabilities.test.ts +128 -0
- package/src/runtime/effective-capabilities.ts +84 -0
- package/src/runtime/guardian-reply-router.ts +106 -21
- package/src/runtime/invite-redemption-service.ts +9 -25
- package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
- package/src/runtime/migrations/vbundle-builder.ts +49 -20
- package/src/runtime/pending-interactions.ts +15 -0
- package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
- package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/browser-tabs-routes.ts +9 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
- package/src/runtime/routes/client-routes.ts +10 -0
- package/src/runtime/routes/contact-routes.ts +31 -8
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +80 -1
- package/src/runtime/routes/conversation-query-routes.ts +68 -22
- package/src/runtime/routes/conversation-routes.ts +39 -14
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/events-routes.ts +1 -3
- package/src/runtime/routes/guardian-approval-interception.ts +14 -73
- package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
- package/src/runtime/routes/home-feed-routes.ts +8 -3
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-message-handler.ts +214 -228
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
- package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
- package/src/runtime/routes/integrations/slack/channel.ts +36 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
- package/src/runtime/routes/mcp-auth-routes.ts +233 -41
- package/src/runtime/routes/memory-eval-routes.ts +87 -0
- package/src/runtime/routes/notification-routes.ts +122 -133
- package/src/runtime/routes/platform-routes.ts +2 -2
- package/src/runtime/routes/plugins-routes.ts +202 -3
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/secret-routes.ts +10 -0
- package/src/runtime/routes/surface-action-routes.ts +2 -1
- package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
- package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
- package/src/runtime/routes/workflow-routes.test.ts +229 -44
- package/src/runtime/routes/workflow-routes.ts +131 -29
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/runtime/tool-grant-request-helper.ts +18 -16
- package/src/runtime/trust-context-resolver.ts +8 -5
- package/src/schedule/inference-profile.ts +2 -14
- package/src/schedule/schedule-store.ts +1 -1
- package/src/schedule/scheduler-types.ts +5 -1
- package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
- package/src/security/secret-patterns.ts +3 -0
- package/src/subagent/manager.ts +17 -4
- package/src/subagent/types.ts +6 -0
- package/src/telemetry/trace-collection-policy.test.ts +28 -0
- package/src/telemetry/trace-collection-policy.ts +30 -0
- package/src/telemetry/types.ts +89 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
- package/src/telemetry/usage-telemetry-reporter.ts +148 -41
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
- package/src/tools/browser/browser-execution.ts +30 -19
- package/src/tools/document/document-tool.ts +2 -3
- package/src/tools/executor.ts +5 -3
- package/src/tools/host-terminal/host-shell.ts +5 -4
- package/src/tools/memory/register.ts +2 -2
- package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
- package/src/tools/network/__tests__/web-search.test.ts +143 -0
- package/src/tools/network/web-fetch.ts +372 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/network/web-search.ts +213 -10
- package/src/tools/permission-checker.ts +4 -3
- package/src/tools/registry.ts +20 -0
- package/src/tools/schedule/create.ts +7 -12
- package/src/tools/schedule/update.ts +4 -11
- package/src/tools/shared/filesystem/path-policy.ts +39 -13
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +33 -0
- package/src/tools/subagent/spawn.ts +61 -12
- package/src/tools/terminal/shell.ts +10 -4
- package/src/tools/tool-approval-handler.ts +18 -13
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/types.ts +9 -0
- package/src/tools/ui-surface/definitions.ts +64 -3
- package/src/tools/verification-control-plane-policy.ts +3 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/tools/workflows/run-workflow.ts +1 -0
- package/src/util/disk-usage.ts +78 -23
- package/src/util/platform.ts +10 -3
- package/src/watcher/telemetry.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/engine.test.ts +175 -1
- package/src/workflows/engine.ts +82 -0
- package/src/workflows/journal-store.test.ts +70 -0
- package/src/workflows/journal-store.ts +18 -3
- package/src/workflows/run-manager.test.ts +171 -28
- package/src/workflows/run-manager.ts +66 -24
- package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
- package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
- package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/notifications/tool-approval-copy.ts +0 -142
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression test for descriptor hygiene in the vbundle export path.
|
|
3
|
+
*
|
|
4
|
+
* `streamExportVBundle` opens every file in the workspace twice — once to hash
|
|
5
|
+
* it (Pass 1) and once to stream it into the tar (Pass 2). This test pins the
|
|
6
|
+
* invariant that those reads release their descriptors: the number of open
|
|
7
|
+
* descriptors held by the process must not grow proportionally to the number
|
|
8
|
+
* of files exported. Without that guarantee a workspace-sized export exhausts
|
|
9
|
+
* the daemon's descriptor limit (EMFILE) and every subsequent subprocess spawn
|
|
10
|
+
* fails.
|
|
11
|
+
*
|
|
12
|
+
* Descriptor counting uses `/proc/self/fd`, which only exists on Linux. The
|
|
13
|
+
* assertion is skipped on platforms without it (e.g. macOS dev machines); CI
|
|
14
|
+
* runs on Linux, so the regression stays covered there.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
existsSync,
|
|
19
|
+
mkdirSync,
|
|
20
|
+
readdirSync,
|
|
21
|
+
rmSync,
|
|
22
|
+
writeFileSync,
|
|
23
|
+
} from "node:fs";
|
|
24
|
+
import { tmpdir } from "node:os";
|
|
25
|
+
import { join } from "node:path";
|
|
26
|
+
import { describe, expect, test } from "bun:test";
|
|
27
|
+
|
|
28
|
+
import { assertNotLiveDb } from "../../../__tests__/assert-not-live-db.js";
|
|
29
|
+
import { streamExportVBundle } from "../vbundle-builder.js";
|
|
30
|
+
import { defaultV1Options } from "./v1-test-helpers.js";
|
|
31
|
+
|
|
32
|
+
const PROC_SELF_FD = "/proc/self/fd";
|
|
33
|
+
const hasProcFd = existsSync(PROC_SELF_FD);
|
|
34
|
+
|
|
35
|
+
/** Count the descriptors currently open by this process. */
|
|
36
|
+
function openFdCount(): number {
|
|
37
|
+
return readdirSync(PROC_SELF_FD).length;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build a workspace with enough distinct files (across nested directories) that
|
|
42
|
+
* a per-file descriptor leak would be obvious against the assertion bound.
|
|
43
|
+
*/
|
|
44
|
+
function createPopulatedWorkspace(fileCount: number): {
|
|
45
|
+
dir: string;
|
|
46
|
+
cleanup: () => void;
|
|
47
|
+
} {
|
|
48
|
+
const dir = join(
|
|
49
|
+
tmpdir(),
|
|
50
|
+
`vbundle-fd-leak-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
51
|
+
);
|
|
52
|
+
mkdirSync(dir, { recursive: true });
|
|
53
|
+
writeFileSync(join(dir, "config.json"), JSON.stringify({ test: true }));
|
|
54
|
+
const dbDir = join(dir, "data", "db");
|
|
55
|
+
mkdirSync(dbDir, { recursive: true });
|
|
56
|
+
writeFileSync(join(dbDir, "assistant.db"), "fake-db-content");
|
|
57
|
+
|
|
58
|
+
// A spread of files under a few nested directories, each with non-trivial
|
|
59
|
+
// (multi-chunk) content so the read loop iterates more than once.
|
|
60
|
+
const filler = "x".repeat(200 * 1024);
|
|
61
|
+
for (let i = 0; i < fileCount; i++) {
|
|
62
|
+
const sub = join(dir, "data", "files", `dir-${i % 5}`);
|
|
63
|
+
mkdirSync(sub, { recursive: true });
|
|
64
|
+
writeFileSync(join(sub, `file-${i}.txt`), `${filler}\n${i}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
dir,
|
|
69
|
+
cleanup: () => {
|
|
70
|
+
assertNotLiveDb(dir);
|
|
71
|
+
rmSync(dir, { recursive: true, force: true });
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe("streamExportVBundle — descriptor lifecycle", () => {
|
|
77
|
+
test.skipIf(!hasProcFd)(
|
|
78
|
+
"does not leak a file descriptor per exported file",
|
|
79
|
+
async () => {
|
|
80
|
+
const fileCount = 60;
|
|
81
|
+
const workspace = createPopulatedWorkspace(fileCount);
|
|
82
|
+
|
|
83
|
+
// Warm-up export: first run can open and cache descriptors that legitimately
|
|
84
|
+
// persist (loggers, the daemon DB, etc.). Measuring the delta around a
|
|
85
|
+
// second export isolates per-file leakage from one-time setup.
|
|
86
|
+
let warmup: Awaited<ReturnType<typeof streamExportVBundle>> | undefined;
|
|
87
|
+
try {
|
|
88
|
+
warmup = await streamExportVBundle({
|
|
89
|
+
workspaceDir: workspace.dir,
|
|
90
|
+
...defaultV1Options(),
|
|
91
|
+
});
|
|
92
|
+
} finally {
|
|
93
|
+
await warmup?.cleanup();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const before = openFdCount();
|
|
97
|
+
|
|
98
|
+
let result: Awaited<ReturnType<typeof streamExportVBundle>> | undefined;
|
|
99
|
+
try {
|
|
100
|
+
result = await streamExportVBundle({
|
|
101
|
+
workspaceDir: workspace.dir,
|
|
102
|
+
...defaultV1Options(),
|
|
103
|
+
});
|
|
104
|
+
// Manifest covers every file we wrote — proves the walk actually read
|
|
105
|
+
// them (otherwise a no-op export would trivially leak nothing).
|
|
106
|
+
expect(result.manifest.contents.length).toBeGreaterThanOrEqual(
|
|
107
|
+
fileCount,
|
|
108
|
+
);
|
|
109
|
+
} finally {
|
|
110
|
+
await result?.cleanup();
|
|
111
|
+
workspace.cleanup();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const after = openFdCount();
|
|
115
|
+
const delta = after - before;
|
|
116
|
+
|
|
117
|
+
// Each export reads 2×fileCount files (hash pass + tar pass). A per-file
|
|
118
|
+
// leak would push the delta past `fileCount`. Allow a small constant for
|
|
119
|
+
// incidental descriptors (temp output file already cleaned up, jitter).
|
|
120
|
+
expect(delta).toBeLessThan(8);
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
});
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
12
12
|
import {
|
|
13
13
|
closeSync,
|
|
14
|
-
createReadStream,
|
|
15
14
|
createWriteStream,
|
|
16
15
|
existsSync,
|
|
17
16
|
lstatSync,
|
|
@@ -21,7 +20,7 @@ import {
|
|
|
21
20
|
readSync,
|
|
22
21
|
realpathSync,
|
|
23
22
|
} from "node:fs";
|
|
24
|
-
import { stat, unlink } from "node:fs/promises";
|
|
23
|
+
import { type FileHandle, open, stat, unlink } from "node:fs/promises";
|
|
25
24
|
import { tmpdir } from "node:os";
|
|
26
25
|
import { dirname, join, relative, resolve, sep } from "node:path";
|
|
27
26
|
import { Readable } from "node:stream";
|
|
@@ -886,9 +885,10 @@ export function walkDirectoryForMetadata(
|
|
|
886
885
|
}
|
|
887
886
|
|
|
888
887
|
/**
|
|
889
|
-
* Compute SHA-256 hex digest of a file by
|
|
890
|
-
* entire file in memory. When `size` is provided, only
|
|
891
|
-
* `size` bytes to match what will be archived in the tar
|
|
888
|
+
* Compute SHA-256 hex digest of a file by reading it in bounded chunks —
|
|
889
|
+
* never buffers the entire file in memory. When `size` is provided, only
|
|
890
|
+
* hashes the first `size` bytes to match what will be archived in the tar
|
|
891
|
+
* entry.
|
|
892
892
|
*/
|
|
893
893
|
async function computeFileSha256(
|
|
894
894
|
filePath: string,
|
|
@@ -896,11 +896,28 @@ async function computeFileSha256(
|
|
|
896
896
|
): Promise<string> {
|
|
897
897
|
const hash = createHash("sha256");
|
|
898
898
|
if (size === 0) return hash.digest("hex");
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
899
|
+
|
|
900
|
+
// Read through an explicitly-managed FileHandle so the descriptor is
|
|
901
|
+
// always released in `finally`. This pass opens every file in the
|
|
902
|
+
// workspace, so any per-file descriptor leak here would exhaust the
|
|
903
|
+
// daemon's file-descriptor limit (EMFILE). A bounded read loop keeps peak
|
|
904
|
+
// memory flat regardless of file size.
|
|
905
|
+
const readChunkBytes = 64 * 1024;
|
|
906
|
+
const handle = await open(filePath, "r");
|
|
907
|
+
try {
|
|
908
|
+
const chunk = Buffer.allocUnsafe(readChunkBytes);
|
|
909
|
+
let position = 0;
|
|
910
|
+
for (;;) {
|
|
911
|
+
const remaining = size !== undefined ? size - position : Infinity;
|
|
912
|
+
if (remaining <= 0) break;
|
|
913
|
+
const toRead = Math.min(chunk.length, remaining);
|
|
914
|
+
const { bytesRead } = await handle.read(chunk, 0, toRead, position);
|
|
915
|
+
if (bytesRead === 0) break;
|
|
916
|
+
hash.update(chunk.subarray(0, bytesRead));
|
|
917
|
+
position += bytesRead;
|
|
918
|
+
}
|
|
919
|
+
} finally {
|
|
920
|
+
await handle.close();
|
|
904
921
|
}
|
|
905
922
|
return hash.digest("hex");
|
|
906
923
|
}
|
|
@@ -1056,21 +1073,33 @@ async function* generateTarStream(
|
|
|
1056
1073
|
// alignment. The WAL checkpoint before export is the primary
|
|
1057
1074
|
// consistency mechanism for the database.
|
|
1058
1075
|
let bytesWritten = 0;
|
|
1059
|
-
if (
|
|
1076
|
+
if (entrySize > 0) {
|
|
1077
|
+
// Read through an explicitly-managed FileHandle so the descriptor is
|
|
1078
|
+
// released in `finally` even when the consumer abandons the generator
|
|
1079
|
+
// mid-file. Each read uses a fresh buffer so a yielded chunk is never
|
|
1080
|
+
// overwritten by the next read before the consumer drains it.
|
|
1081
|
+
let handle: FileHandle | undefined;
|
|
1060
1082
|
try {
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1083
|
+
handle = await open(file.diskPath, "r");
|
|
1084
|
+
const readChunkBytes = 64 * 1024;
|
|
1085
|
+
while (bytesWritten < entrySize) {
|
|
1086
|
+
const toRead = Math.min(readChunkBytes, entrySize - bytesWritten);
|
|
1087
|
+
const buf = Buffer.allocUnsafe(toRead);
|
|
1088
|
+
const { bytesRead } = await handle.read(
|
|
1089
|
+
buf,
|
|
1090
|
+
0,
|
|
1091
|
+
toRead,
|
|
1092
|
+
bytesWritten,
|
|
1093
|
+
);
|
|
1094
|
+
if (bytesRead === 0) break;
|
|
1095
|
+
bytesWritten += bytesRead;
|
|
1096
|
+
yield bytesRead === buf.length ? buf : buf.subarray(0, bytesRead);
|
|
1070
1097
|
}
|
|
1071
1098
|
} catch {
|
|
1072
1099
|
// File was deleted or rotated between passes — emit zeros for
|
|
1073
1100
|
// the full declared size so the tar structure stays valid
|
|
1101
|
+
} finally {
|
|
1102
|
+
if (handle) await handle.close();
|
|
1074
1103
|
}
|
|
1075
1104
|
}
|
|
1076
1105
|
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import type { InteractionResolutionState } from "../api/events/interaction-resolved.js";
|
|
22
|
+
import type { QuestionEntry } from "../api/events/question-request.js";
|
|
22
23
|
import type { UserDecision } from "../permissions/types.js";
|
|
23
24
|
import { getLogger } from "../util/logger.js";
|
|
24
25
|
import { broadcastMessage } from "./assistant-event-hub.js";
|
|
@@ -50,6 +51,18 @@ export interface ConfirmationDetails {
|
|
|
50
51
|
}>;
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Full batched question payload carried on a pending `question` interaction, so
|
|
56
|
+
* a history-load render can stamp the outstanding prompt back onto its tool
|
|
57
|
+
* call and rehydrate the question card on a cold reconnect. Mirrors the
|
|
58
|
+
* `question_request` event's `questions[]` — `metadata` only retains the
|
|
59
|
+
* `orderedIds`/`optionsById` the response route needs to validate submissions,
|
|
60
|
+
* which is insufficient to reconstruct the card.
|
|
61
|
+
*/
|
|
62
|
+
export interface QuestionDetails {
|
|
63
|
+
entries: QuestionEntry[];
|
|
64
|
+
}
|
|
65
|
+
|
|
53
66
|
export interface PendingInteraction {
|
|
54
67
|
/**
|
|
55
68
|
* Owning conversation, when the interaction was raised inside one. Absent
|
|
@@ -69,6 +82,8 @@ export interface PendingInteraction {
|
|
|
69
82
|
| "host_transfer"
|
|
70
83
|
| "acp_confirmation";
|
|
71
84
|
confirmationDetails?: ConfirmationDetails;
|
|
85
|
+
/** For a pending `question`: the full batched entries, so a history-load render can rehydrate the question card. */
|
|
86
|
+
questionDetails?: QuestionDetails;
|
|
72
87
|
/** For ACP permissions: resolves directly without a Conversation object. */
|
|
73
88
|
directResolve?: (decision: UserDecision) => void;
|
|
74
89
|
/** When set, the host_bash request should be routed to this specific client. */
|
|
@@ -51,6 +51,7 @@ type ListClientsResponse = {
|
|
|
51
51
|
machineName?: string;
|
|
52
52
|
connectedAt: string;
|
|
53
53
|
lastActiveAt: string;
|
|
54
|
+
degraded?: boolean;
|
|
54
55
|
}>;
|
|
55
56
|
};
|
|
56
57
|
|
|
@@ -152,4 +153,16 @@ describe("list_clients route — same-user filter", () => {
|
|
|
152
153
|
const ids = result.clients.map((c) => c.clientId).sort();
|
|
153
154
|
expect(ids).toEqual(["client-A1", "client-B1", "client-noprincipal"]);
|
|
154
155
|
});
|
|
156
|
+
|
|
157
|
+
test("includes a degraded flag (false for a freshly connected client)", () => {
|
|
158
|
+
registerClient({ clientId: "client-A1", actorPrincipalId: "user-A" });
|
|
159
|
+
|
|
160
|
+
const handler = findHandler("list_clients");
|
|
161
|
+
const result = handler({
|
|
162
|
+
headers: { "x-vellum-actor-principal-id": "user-A" },
|
|
163
|
+
}) as ListClientsResponse;
|
|
164
|
+
|
|
165
|
+
expect(result.clients).toHaveLength(1);
|
|
166
|
+
expect(result.clients[0].degraded).toBe(false);
|
|
167
|
+
});
|
|
155
168
|
});
|
|
@@ -26,6 +26,8 @@ mock.module("../../assistant-event-hub.js", () => ({
|
|
|
26
26
|
broadcastMessage: () => {},
|
|
27
27
|
}));
|
|
28
28
|
|
|
29
|
+
import { eq } from "drizzle-orm";
|
|
30
|
+
|
|
29
31
|
import { getDb } from "../../../memory/db-connection.js";
|
|
30
32
|
import { initializeDb } from "../../../memory/db-init.js";
|
|
31
33
|
import { conversations } from "../../../memory/schema.js";
|
|
@@ -71,6 +73,10 @@ function findHandler(routes: RouteDefinition[], operationId: string) {
|
|
|
71
73
|
return route.handler;
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
const createHandler = findHandler(
|
|
77
|
+
CONVERSATION_MANAGEMENT_ROUTES,
|
|
78
|
+
"createConversation",
|
|
79
|
+
);
|
|
74
80
|
const putHandler = findHandler(
|
|
75
81
|
CONVERSATION_MANAGEMENT_ROUTES,
|
|
76
82
|
"setConversationInferenceProfile",
|
|
@@ -112,6 +118,67 @@ function seedConversation(id: string): void {
|
|
|
112
118
|
// Tests
|
|
113
119
|
// ---------------------------------------------------------------------------
|
|
114
120
|
|
|
121
|
+
describe("POST /v1/conversations (createConversation)", () => {
|
|
122
|
+
beforeEach(() => {
|
|
123
|
+
clearConversations();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
function readConversation(id: string) {
|
|
127
|
+
return getDb()
|
|
128
|
+
.select({
|
|
129
|
+
title: conversations.title,
|
|
130
|
+
isAutoTitle: conversations.isAutoTitle,
|
|
131
|
+
})
|
|
132
|
+
.from(conversations)
|
|
133
|
+
.where(eq(conversations.id, id))
|
|
134
|
+
.get();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
test("with a title → persists it as a user-set title (isAutoTitle = 0)", async () => {
|
|
138
|
+
const result = (await createHandler({
|
|
139
|
+
body: { conversationType: "standard", title: "Setting up your check-in" },
|
|
140
|
+
})) as { id: string; created: boolean };
|
|
141
|
+
|
|
142
|
+
expect(result.created).toBe(true);
|
|
143
|
+
const row = readConversation(result.id);
|
|
144
|
+
expect(row?.title).toBe("Setting up your check-in");
|
|
145
|
+
// isAutoTitle = 0 keeps the async LLM titler from overwriting it.
|
|
146
|
+
expect(row?.isAutoTitle).toBe(0);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("blank title falls back to the replaceable 'New Conversation' placeholder", async () => {
|
|
150
|
+
const result = (await createHandler({
|
|
151
|
+
body: { conversationType: "standard", title: " " },
|
|
152
|
+
})) as { id: string; created: boolean };
|
|
153
|
+
|
|
154
|
+
expect(result.created).toBe(true);
|
|
155
|
+
const row = readConversation(result.id);
|
|
156
|
+
expect(row?.title).toBe("New Conversation");
|
|
157
|
+
// Default auto-title flag (1) leaves it replaceable by the auto-titler.
|
|
158
|
+
expect(row?.isAutoTitle).toBe(1);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("no title → 'New Conversation' placeholder", async () => {
|
|
162
|
+
const result = (await createHandler({
|
|
163
|
+
body: { conversationType: "standard" },
|
|
164
|
+
})) as { id: string; created: boolean };
|
|
165
|
+
|
|
166
|
+
expect(result.created).toBe(true);
|
|
167
|
+
const row = readConversation(result.id);
|
|
168
|
+
expect(row?.title).toBe("New Conversation");
|
|
169
|
+
expect(row?.isAutoTitle).toBe(1);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("non-string title → BadRequestError (not a 500), no row created", () => {
|
|
173
|
+
// The shared route adapter doesn't runtime-validate the body, so the
|
|
174
|
+
// handler must reject a malformed title before `.trim()` throws.
|
|
175
|
+
expect(() =>
|
|
176
|
+
createHandler({ body: { conversationType: "standard", title: 123 } }),
|
|
177
|
+
).toThrow(/title must be a string/);
|
|
178
|
+
expect(getDb().select().from(conversations).all()).toHaveLength(0);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
115
182
|
describe("PUT /v1/conversations/:id/inference-profile", () => {
|
|
116
183
|
beforeEach(() => {
|
|
117
184
|
clearConversations();
|
|
@@ -34,6 +34,12 @@
|
|
|
34
34
|
|
|
35
35
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
36
36
|
|
|
37
|
+
import {
|
|
38
|
+
type DiffPluginDeps,
|
|
39
|
+
type DiffPluginOptions,
|
|
40
|
+
type PluginDiffResult,
|
|
41
|
+
PluginDiffUnavailableError,
|
|
42
|
+
} from "../../../cli/lib/diff-plugin.js";
|
|
37
43
|
import {
|
|
38
44
|
type InspectPluginDeps,
|
|
39
45
|
type InspectPluginOptions,
|
|
@@ -66,6 +72,7 @@ import {
|
|
|
66
72
|
type UninstallPluginResult,
|
|
67
73
|
} from "../../../cli/lib/uninstall-plugin.js";
|
|
68
74
|
import {
|
|
75
|
+
PluginMergeBaselineError,
|
|
69
76
|
PluginNotUpgradableError,
|
|
70
77
|
type PluginUpgradeResult,
|
|
71
78
|
type UpgradePluginDeps,
|
|
@@ -173,10 +180,28 @@ const upgradeSpy = mock(
|
|
|
173
180
|
);
|
|
174
181
|
|
|
175
182
|
mock.module("../../../cli/lib/upgrade-plugin.js", () => ({
|
|
183
|
+
PluginMergeBaselineError,
|
|
176
184
|
PluginNotUpgradableError,
|
|
177
185
|
upgradePlugin: upgradeSpy,
|
|
178
186
|
}));
|
|
179
187
|
|
|
188
|
+
// Mock diffPlugin: the lib re-materializes the install commit and computes the
|
|
189
|
+
// per-file unified diff (covered by diff-plugin.test.ts); the route forwards
|
|
190
|
+
// the name and maps the lib's error taxonomy to HTTP status codes.
|
|
191
|
+
const diffSpy = mock(
|
|
192
|
+
async (
|
|
193
|
+
_opts: DiffPluginOptions,
|
|
194
|
+
_deps: DiffPluginDeps,
|
|
195
|
+
): Promise<PluginDiffResult> => {
|
|
196
|
+
throw new Error("diffSpy default impl not configured");
|
|
197
|
+
},
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
mock.module("../../../cli/lib/diff-plugin.js", () => ({
|
|
201
|
+
PluginDiffUnavailableError,
|
|
202
|
+
diffPlugin: diffSpy,
|
|
203
|
+
}));
|
|
204
|
+
|
|
180
205
|
import {
|
|
181
206
|
BadRequestError,
|
|
182
207
|
ConflictError,
|
|
@@ -200,6 +225,7 @@ const getHandler = findHandler("plugins_get");
|
|
|
200
225
|
const installHandler = findHandler("plugins_install");
|
|
201
226
|
const inspectHandler = findHandler("plugins_inspect");
|
|
202
227
|
const upgradeHandler = findHandler("plugins_upgrade");
|
|
228
|
+
const diffHandler = findHandler("plugins_diff");
|
|
203
229
|
|
|
204
230
|
function invoke(args: RouteHandlerArgs = {}): {
|
|
205
231
|
plugins: Array<Record<string, unknown>>;
|
|
@@ -976,6 +1002,10 @@ function inspection(
|
|
|
976
1002
|
}
|
|
977
1003
|
: overrides.remote,
|
|
978
1004
|
remoteError: overrides.remoteError ?? null,
|
|
1005
|
+
surfaces:
|
|
1006
|
+
overrides.surfaces === undefined
|
|
1007
|
+
? { skills: [], hooks: ["post-model-call"], tools: [] }
|
|
1008
|
+
: overrides.surfaces,
|
|
979
1009
|
};
|
|
980
1010
|
}
|
|
981
1011
|
|
|
@@ -1080,6 +1110,9 @@ function upgradeResult(
|
|
|
1080
1110
|
target: overrides.target ?? "/workspace/.vellum/plugins/level-up",
|
|
1081
1111
|
fileCount: overrides.fileCount === undefined ? 12 : overrides.fileCount,
|
|
1082
1112
|
dryRun: overrides.dryRun ?? false,
|
|
1113
|
+
strategy: overrides.strategy ?? "overwrite",
|
|
1114
|
+
conflicts: overrides.conflicts ?? [],
|
|
1115
|
+
binaryConflicts: overrides.binaryConflicts ?? [],
|
|
1083
1116
|
provenanceWasUnknown: overrides.provenanceWasUnknown ?? false,
|
|
1084
1117
|
};
|
|
1085
1118
|
}
|
|
@@ -1094,6 +1127,9 @@ async function invokeUpgrade(args: RouteHandlerArgs = {}): Promise<{
|
|
|
1094
1127
|
target: string;
|
|
1095
1128
|
fileCount: number | null;
|
|
1096
1129
|
dryRun: boolean;
|
|
1130
|
+
strategy: string;
|
|
1131
|
+
conflicts: readonly string[];
|
|
1132
|
+
binaryConflicts: readonly string[];
|
|
1097
1133
|
provenanceWasUnknown: boolean;
|
|
1098
1134
|
}> {
|
|
1099
1135
|
return (await upgradeHandler(args)) as {
|
|
@@ -1106,6 +1142,9 @@ async function invokeUpgrade(args: RouteHandlerArgs = {}): Promise<{
|
|
|
1106
1142
|
target: string;
|
|
1107
1143
|
fileCount: number | null;
|
|
1108
1144
|
dryRun: boolean;
|
|
1145
|
+
strategy: string;
|
|
1146
|
+
conflicts: readonly string[];
|
|
1147
|
+
binaryConflicts: readonly string[];
|
|
1109
1148
|
provenanceWasUnknown: boolean;
|
|
1110
1149
|
};
|
|
1111
1150
|
}
|
|
@@ -1136,13 +1175,83 @@ describe("POST /v1/plugins/:name/upgrade", () => {
|
|
|
1136
1175
|
target: "/workspace/.vellum/plugins/level-up",
|
|
1137
1176
|
fileCount: 12,
|
|
1138
1177
|
dryRun: false,
|
|
1178
|
+
strategy: "overwrite",
|
|
1179
|
+
conflicts: [],
|
|
1180
|
+
binaryConflicts: [],
|
|
1139
1181
|
provenanceWasUnknown: false,
|
|
1140
1182
|
});
|
|
1141
|
-
// AND the name + dryRun are forwarded to the lib
|
|
1183
|
+
// AND the name + dryRun are forwarded to the lib (strategy omitted)
|
|
1142
1184
|
expect(upgradeSpy.mock.calls[0]?.[0]).toEqual({
|
|
1143
1185
|
name: "level-up",
|
|
1144
1186
|
dryRun: false,
|
|
1187
|
+
strategy: undefined,
|
|
1188
|
+
});
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
test("forwards a requested strategy to the lib and projects it", async () => {
|
|
1192
|
+
// GIVEN upgradePlugin merges local edits forward via the `ours` strategy
|
|
1193
|
+
upgradeSpy.mockImplementation(async () =>
|
|
1194
|
+
upgradeResult({ strategy: "ours" }),
|
|
1195
|
+
);
|
|
1196
|
+
|
|
1197
|
+
// WHEN the handler runs with a strategy in the body
|
|
1198
|
+
const result = await invokeUpgrade({
|
|
1199
|
+
pathParams: { name: "level-up" },
|
|
1200
|
+
body: { strategy: "ours" },
|
|
1201
|
+
});
|
|
1202
|
+
|
|
1203
|
+
// THEN the strategy is forwarded to the lib and surfaced on the wire
|
|
1204
|
+
expect(result.strategy).toBe("ours");
|
|
1205
|
+
expect(upgradeSpy.mock.calls[0]?.[0]).toEqual({
|
|
1206
|
+
name: "level-up",
|
|
1207
|
+
dryRun: undefined,
|
|
1208
|
+
strategy: "ours",
|
|
1209
|
+
});
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
test("projects conflicts + binaryConflicts from the assistant strategy onto the wire", async () => {
|
|
1213
|
+
// GIVEN upgradePlugin merges with conflict markers under `assistant`
|
|
1214
|
+
upgradeSpy.mockImplementation(async () =>
|
|
1215
|
+
upgradeResult({
|
|
1216
|
+
strategy: "assistant",
|
|
1217
|
+
conflicts: ["hooks/post-model-call.ts"],
|
|
1218
|
+
binaryConflicts: ["assets/icon.png"],
|
|
1219
|
+
}),
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
// WHEN the handler runs with the `assistant` strategy
|
|
1223
|
+
const result = await invokeUpgrade({
|
|
1224
|
+
pathParams: { name: "level-up" },
|
|
1225
|
+
body: { strategy: "assistant" },
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
// THEN the conflicted paths are surfaced for the assistant to resolve
|
|
1229
|
+
expect(result.strategy).toBe("assistant");
|
|
1230
|
+
expect(result.conflicts).toEqual(["hooks/post-model-call.ts"]);
|
|
1231
|
+
expect(result.binaryConflicts).toEqual(["assets/icon.png"]);
|
|
1232
|
+
expect(upgradeSpy.mock.calls[0]?.[0]).toEqual({
|
|
1233
|
+
name: "level-up",
|
|
1234
|
+
dryRun: undefined,
|
|
1235
|
+
strategy: "assistant",
|
|
1236
|
+
});
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
test("PluginMergeBaselineError \u2192 ConflictError (409)", async () => {
|
|
1240
|
+
// A merge strategy whose install-time baseline can't be reconstructed: a
|
|
1241
|
+
// well-formed request that isn't actionable in the current state.
|
|
1242
|
+
upgradeSpy.mockImplementation(async () => {
|
|
1243
|
+
throw new PluginMergeBaselineError(
|
|
1244
|
+
"level-up",
|
|
1245
|
+
"the install-time baseline could not be faithfully reconstructed",
|
|
1246
|
+
);
|
|
1145
1247
|
});
|
|
1248
|
+
|
|
1249
|
+
await expect(
|
|
1250
|
+
invokeUpgrade({
|
|
1251
|
+
pathParams: { name: "level-up" },
|
|
1252
|
+
body: { strategy: "ours" },
|
|
1253
|
+
}),
|
|
1254
|
+
).rejects.toBeInstanceOf(ConflictError);
|
|
1146
1255
|
});
|
|
1147
1256
|
|
|
1148
1257
|
test("omits dryRun (passes undefined) when the body flag is absent", async () => {
|
|
@@ -1246,3 +1355,133 @@ describe("POST /v1/plugins/:name/upgrade", () => {
|
|
|
1246
1355
|
expect((caught as Error).message).toContain("ECONNRESET");
|
|
1247
1356
|
});
|
|
1248
1357
|
});
|
|
1358
|
+
|
|
1359
|
+
function diffResult(
|
|
1360
|
+
overrides: Partial<PluginDiffResult> = {},
|
|
1361
|
+
): PluginDiffResult {
|
|
1362
|
+
return {
|
|
1363
|
+
name: overrides.name ?? "level-up",
|
|
1364
|
+
target: overrides.target ?? "/workspace/.vellum/plugins/level-up",
|
|
1365
|
+
commit: overrides.commit ?? "60a392b0000000000000000000000000000000aa",
|
|
1366
|
+
committedAt: overrides.committedAt ?? "2026-06-01T12:34:56.000Z",
|
|
1367
|
+
clean: overrides.clean ?? false,
|
|
1368
|
+
files: overrides.files ?? [
|
|
1369
|
+
{
|
|
1370
|
+
path: "src/skill.ts",
|
|
1371
|
+
status: "modified",
|
|
1372
|
+
diff: "--- a/src/skill.ts\n+++ b/src/skill.ts\n@@ -1 +1 @@\n-old\n+new\n",
|
|
1373
|
+
binary: false,
|
|
1374
|
+
reconstructed: true,
|
|
1375
|
+
},
|
|
1376
|
+
],
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
async function invokeDiff(
|
|
1381
|
+
args: RouteHandlerArgs = {},
|
|
1382
|
+
): Promise<PluginDiffResult> {
|
|
1383
|
+
return (await diffHandler(args)) as PluginDiffResult;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
describe("POST /v1/plugins/:name/diff", () => {
|
|
1387
|
+
beforeEach(() => {
|
|
1388
|
+
diffSpy.mockReset();
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
test("forwards the name to diffPlugin and returns the diff verbatim", async () => {
|
|
1392
|
+
// GIVEN diffPlugin reports a single modified file against the install commit
|
|
1393
|
+
const view = diffResult();
|
|
1394
|
+
diffSpy.mockImplementation(async () => view);
|
|
1395
|
+
|
|
1396
|
+
// WHEN the route handler is invoked with the path name
|
|
1397
|
+
const result = await invokeDiff({ pathParams: { name: "level-up" } });
|
|
1398
|
+
|
|
1399
|
+
// THEN the diff is returned unchanged
|
|
1400
|
+
expect(result).toEqual(view);
|
|
1401
|
+
// AND only the name is forwarded to the lib (ref is never caller-supplied)
|
|
1402
|
+
expect(diffSpy.mock.calls[0]?.[0]).toEqual({ name: "level-up" });
|
|
1403
|
+
});
|
|
1404
|
+
|
|
1405
|
+
test("InvalidPluginNameError → BadRequestError (400)", async () => {
|
|
1406
|
+
diffSpy.mockImplementation(async () => {
|
|
1407
|
+
throw new InvalidPluginNameError("../escape");
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
await expect(
|
|
1411
|
+
invokeDiff({ pathParams: { name: "../escape" } }),
|
|
1412
|
+
).rejects.toBeInstanceOf(BadRequestError);
|
|
1413
|
+
});
|
|
1414
|
+
|
|
1415
|
+
test("PluginNotInstalledError → NotFoundError (404)", async () => {
|
|
1416
|
+
diffSpy.mockImplementation(async () => {
|
|
1417
|
+
throw new PluginNotInstalledError(
|
|
1418
|
+
"ghost",
|
|
1419
|
+
"/workspace/.vellum/plugins/ghost",
|
|
1420
|
+
);
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
await expect(
|
|
1424
|
+
invokeDiff({ pathParams: { name: "ghost" } }),
|
|
1425
|
+
).rejects.toBeInstanceOf(NotFoundError);
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
test("PluginNotFoundError → NotFoundError (404)", async () => {
|
|
1429
|
+
// The recorded commit no longer resolves to a tree (source/commit gone).
|
|
1430
|
+
diffSpy.mockImplementation(async () => {
|
|
1431
|
+
throw new PluginNotFoundError(
|
|
1432
|
+
"level-up",
|
|
1433
|
+
"deadbeef",
|
|
1434
|
+
"vellum-ai/level-up",
|
|
1435
|
+
);
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
await expect(
|
|
1439
|
+
invokeDiff({ pathParams: { name: "level-up" } }),
|
|
1440
|
+
).rejects.toBeInstanceOf(NotFoundError);
|
|
1441
|
+
});
|
|
1442
|
+
|
|
1443
|
+
test("PluginDiffUnavailableError → ConflictError (409)", async () => {
|
|
1444
|
+
// The install recorded no commit, so there is no baseline to diff against:
|
|
1445
|
+
// a well-formed request that is not actionable in the current state.
|
|
1446
|
+
diffSpy.mockImplementation(async () => {
|
|
1447
|
+
throw new PluginDiffUnavailableError(
|
|
1448
|
+
"level-up",
|
|
1449
|
+
"no install commit was recorded",
|
|
1450
|
+
);
|
|
1451
|
+
});
|
|
1452
|
+
|
|
1453
|
+
await expect(
|
|
1454
|
+
invokeDiff({ pathParams: { name: "level-up" } }),
|
|
1455
|
+
).rejects.toBeInstanceOf(ConflictError);
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
test("PluginSourceUnavailableError → ServiceUnavailableError (503)", async () => {
|
|
1459
|
+
// Re-materializing the baseline can hit a rate-limited/down GitHub; that is
|
|
1460
|
+
// retryable, so surface 503 rather than a misleading 500.
|
|
1461
|
+
diffSpy.mockImplementation(async () => {
|
|
1462
|
+
throw new PluginSourceUnavailableError(
|
|
1463
|
+
"git clone failed for vellum-ai/level-up: HTTP 403",
|
|
1464
|
+
503,
|
|
1465
|
+
);
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
await expect(
|
|
1469
|
+
invokeDiff({ pathParams: { name: "level-up" } }),
|
|
1470
|
+
).rejects.toBeInstanceOf(ServiceUnavailableError);
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
test("unknown errors → InternalError with original message preserved", async () => {
|
|
1474
|
+
diffSpy.mockImplementation(async () => {
|
|
1475
|
+
throw new Error("ECONNRESET");
|
|
1476
|
+
});
|
|
1477
|
+
|
|
1478
|
+
let caught: unknown;
|
|
1479
|
+
try {
|
|
1480
|
+
await invokeDiff({ pathParams: { name: "level-up" } });
|
|
1481
|
+
} catch (err) {
|
|
1482
|
+
caught = err;
|
|
1483
|
+
}
|
|
1484
|
+
expect(caught).toBeInstanceOf(InternalError);
|
|
1485
|
+
expect((caught as Error).message).toContain("ECONNRESET");
|
|
1486
|
+
});
|
|
1487
|
+
});
|