@vellumai/assistant 0.5.13 → 0.5.15
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/.env.example +1 -6
- package/AGENTS.md +4 -0
- package/ARCHITECTURE.md +0 -1
- package/bunfig.toml +1 -0
- package/docs/architecture/memory.md +3 -3
- package/openapi.yaml +127 -22
- package/package.json +1 -1
- package/src/__tests__/access-request-decision.test.ts +2 -32
- package/src/__tests__/actor-token-service.test.ts +1 -31
- package/src/__tests__/anthropic-provider.test.ts +53 -40
- package/src/__tests__/app-git-history.test.ts +9 -17
- package/src/__tests__/app-git-service.test.ts +14 -20
- package/src/__tests__/app-store-dir-names.test.ts +10 -20
- package/src/__tests__/approval-cascade.test.ts +2 -19
- package/src/__tests__/approval-primitive.test.ts +2 -27
- package/src/__tests__/approval-routes-http.test.ts +2 -30
- package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -28
- package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -45
- package/src/__tests__/attachments-store.test.ts +5 -32
- package/src/__tests__/audit-log-rotation.test.ts +5 -36
- package/src/__tests__/avatar-e2e.test.ts +1 -9
- package/src/__tests__/avatar-generator.test.ts +1 -7
- package/src/__tests__/browser-fill-credential.test.ts +0 -4
- package/src/__tests__/browser-manager.test.ts +0 -6
- package/src/__tests__/call-controller.test.ts +1 -22
- package/src/__tests__/call-conversation-messages.test.ts +0 -21
- package/src/__tests__/call-domain.test.ts +0 -25
- package/src/__tests__/call-pointer-messages.test.ts +0 -21
- package/src/__tests__/call-recovery.test.ts +0 -22
- package/src/__tests__/call-routes-http.test.ts +0 -24
- package/src/__tests__/call-store.test.ts +0 -21
- package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -24
- package/src/__tests__/canonical-guardian-store.test.ts +48 -21
- package/src/__tests__/channel-approval-routes.test.ts +6 -26
- package/src/__tests__/channel-approvals.test.ts +1 -38
- package/src/__tests__/channel-delivery-store.test.ts +0 -21
- package/src/__tests__/channel-guardian.test.ts +0 -26
- package/src/__tests__/channel-reply-delivery.test.ts +5 -0
- package/src/__tests__/channel-retry-sweep.test.ts +0 -21
- package/src/__tests__/checker.test.ts +26 -61
- package/src/__tests__/clawhub.test.ts +9 -25
- package/src/__tests__/cli-command-risk-guard.test.ts +0 -18
- package/src/__tests__/config-loader-backfill.test.ts +9 -28
- package/src/__tests__/config-schema-cmd.test.ts +5 -25
- package/src/__tests__/config-schema.test.ts +21 -40
- package/src/__tests__/config-watcher.test.ts +4 -91
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -21
- package/src/__tests__/contacts-tools.test.ts +0 -21
- package/src/__tests__/context-memory-e2e.test.ts +0 -21
- package/src/__tests__/context-window-manager.test.ts +130 -3
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -4
- package/src/__tests__/conversation-agent-loop.test.ts +0 -4
- package/src/__tests__/conversation-attachments.test.ts +1 -24
- package/src/__tests__/conversation-attention-store.test.ts +0 -21
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -22
- package/src/__tests__/conversation-clear-safety.test.ts +0 -22
- package/src/__tests__/conversation-confirmation-signals.test.ts +2 -21
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -24
- package/src/__tests__/conversation-disk-view-integration.test.ts +1 -23
- package/src/__tests__/conversation-disk-view.test.ts +5 -27
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +1 -33
- package/src/__tests__/conversation-fork-route.test.ts +0 -27
- package/src/__tests__/conversation-history-web-search.test.ts +23 -16
- package/src/__tests__/conversation-init.benchmark.test.ts +22 -43
- package/src/__tests__/conversation-key-store-disk-view.test.ts +8 -34
- package/src/__tests__/conversation-load-history-repair.test.ts +0 -4
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -4
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -4
- package/src/__tests__/conversation-queue.test.ts +8 -8
- package/src/__tests__/conversation-routes-disk-view.test.ts +13 -51
- package/src/__tests__/conversation-runtime-assembly.test.ts +64 -38
- package/src/__tests__/conversation-slash-commands.test.ts +5 -0
- package/src/__tests__/conversation-slash-queue.test.ts +0 -4
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -4
- package/src/__tests__/conversation-speed-override.test.ts +326 -0
- package/src/__tests__/conversation-starter-routes.test.ts +0 -23
- package/src/__tests__/conversation-store.test.ts +0 -21
- package/src/__tests__/conversation-unread-route.test.ts +0 -24
- package/src/__tests__/conversation-usage.test.ts +56 -21
- package/src/__tests__/conversation-wipe.test.ts +0 -21
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -4
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -4
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -4
- package/src/__tests__/credential-execution-shell-lockdown.test.ts +8 -5
- package/src/__tests__/credential-vault-unit.test.ts +9 -428
- package/src/__tests__/credentials-cli.test.ts +10 -10
- package/src/__tests__/daemon-assistant-events.test.ts +0 -19
- package/src/__tests__/date-context.test.ts +77 -97
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +7 -24
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +29 -42
- package/src/__tests__/delete-managed-skill-tool.test.ts +2 -10
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -26
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +61 -15
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -36
- package/src/__tests__/email-cli.test.ts +6 -6
- package/src/__tests__/ephemeral-permissions.test.ts +5 -17
- package/src/__tests__/first-greeting.test.ts +4 -32
- package/src/__tests__/followup-tools.test.ts +0 -21
- package/src/__tests__/gateway-only-enforcement.test.ts +0 -20
- package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -23
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -23
- package/src/__tests__/guardian-action-followup-store.test.ts +0 -21
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -21
- package/src/__tests__/guardian-action-late-reply.test.ts +0 -21
- package/src/__tests__/guardian-action-store.test.ts +0 -21
- package/src/__tests__/guardian-action-sweep.test.ts +0 -21
- package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -23
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -22
- package/src/__tests__/guardian-dispatch.test.ts +0 -21
- package/src/__tests__/guardian-grant-minting.test.ts +0 -22
- package/src/__tests__/guardian-outbound-http.test.ts +0 -22
- package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -23
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -22
- package/src/__tests__/guardian-routing-state.test.ts +0 -22
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -24
- package/src/__tests__/headless-browser-interactions.test.ts +0 -4
- package/src/__tests__/headless-browser-navigate.test.ts +0 -4
- package/src/__tests__/headless-browser-read-tools.test.ts +0 -4
- package/src/__tests__/headless-browser-snapshot.test.ts +0 -4
- package/src/__tests__/heartbeat-service.test.ts +99 -26
- package/src/__tests__/hooks-blocking.test.ts +3 -3
- package/src/__tests__/hooks-config.test.ts +7 -7
- package/src/__tests__/hooks-discovery.test.ts +3 -3
- package/src/__tests__/hooks-integration.test.ts +5 -5
- package/src/__tests__/hooks-manager.test.ts +3 -3
- package/src/__tests__/hooks-runner.test.ts +5 -23
- package/src/__tests__/hooks-settings.test.ts +3 -3
- package/src/__tests__/hooks-templates.test.ts +3 -3
- package/src/__tests__/http-conversation-lineage.test.ts +0 -27
- package/src/__tests__/identity-intro-cache.test.ts +0 -4
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -22
- package/src/__tests__/inline-skill-load-permissions.test.ts +5 -16
- package/src/__tests__/intent-routing.test.ts +2 -55
- package/src/__tests__/invite-redemption-service.test.ts +0 -21
- package/src/__tests__/invite-routes-http.test.ts +0 -21
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +0 -17
- package/src/__tests__/journal-context.test.ts +8 -75
- package/src/__tests__/list-messages-attachments.test.ts +0 -22
- package/src/__tests__/llm-context-route-provider.test.ts +0 -21
- package/src/__tests__/llm-request-log-turn-query.test.ts +46 -28
- package/src/__tests__/llm-usage-store.test.ts +0 -21
- package/src/__tests__/log-export-workspace.test.ts +1 -1
- package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
- package/src/__tests__/managed-store.test.ts +1 -1
- package/src/__tests__/mcp-cli.test.ts +7 -10
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -21
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +0 -11
- package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -21
- package/src/__tests__/memory-recall-log-store.test.ts +0 -27
- package/src/__tests__/memory-recall-quality.test.ts +0 -21
- package/src/__tests__/memory-regressions.experimental.test.ts +31 -30
- package/src/__tests__/memory-regressions.test.ts +282 -70
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -21
- package/src/__tests__/memory-upsert-concurrency.test.ts +0 -21
- package/src/__tests__/messaging-send-tool.test.ts +201 -0
- package/src/__tests__/migration-cross-version-compatibility.test.ts +18 -13
- package/src/__tests__/migration-export-http.test.ts +7 -1
- package/src/__tests__/migration-import-commit-http.test.ts +16 -14
- package/src/__tests__/migration-import-preflight-http.test.ts +27 -44
- package/src/__tests__/migration-validate-http.test.ts +1 -28
- package/src/__tests__/native-web-search.test.ts +25 -22
- package/src/__tests__/non-member-access-request.test.ts +0 -22
- package/src/__tests__/notification-guardian-path.test.ts +0 -21
- package/src/__tests__/notification-schedule-dedup.test.ts +1 -25
- package/src/__tests__/oauth-apps-routes.test.ts +103 -2
- package/src/__tests__/oauth-cli.test.ts +52 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +0 -16
- package/src/__tests__/oauth-provider-serializer.test.ts +232 -0
- package/src/__tests__/oauth-providers-routes.test.ts +257 -0
- package/src/__tests__/oauth-store.test.ts +0 -21
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/openai-provider.test.ts +261 -0
- package/src/__tests__/pairing-concurrent.test.ts +6 -6
- package/src/__tests__/pairing-routes.test.ts +7 -1
- package/src/__tests__/path-policy.test.ts +1 -1
- package/src/__tests__/platform.test.ts +64 -88
- package/src/__tests__/playbook-execution.test.ts +0 -21
- package/src/__tests__/playbook-tools.test.ts +0 -21
- package/src/__tests__/pricing.test.ts +100 -0
- package/src/__tests__/relay-server.test.ts +1 -25
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -24
- package/src/__tests__/runtime-events-sse-parity.test.ts +2 -24
- package/src/__tests__/runtime-events-sse.test.ts +0 -24
- package/src/__tests__/sandbox-diagnostics.test.ts +2 -1
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
- package/src/__tests__/schedule-store.test.ts +0 -21
- package/src/__tests__/schedule-tools.test.ts +0 -21
- package/src/__tests__/scheduler-recurrence.test.ts +0 -21
- package/src/__tests__/scoped-approval-grants.test.ts +0 -21
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -21
- package/src/__tests__/secret-allowlist.test.ts +1 -1
- package/src/__tests__/secret-ingress-channel.test.ts +0 -5
- package/src/__tests__/secret-ingress-cli.test.ts +0 -6
- package/src/__tests__/secret-ingress-http.test.ts +0 -5
- package/src/__tests__/secret-ingress.test.ts +0 -5
- package/src/__tests__/send-endpoint-busy.test.ts +0 -24
- package/src/__tests__/sequence-store.test.ts +0 -21
- package/src/__tests__/server-history-render.test.ts +0 -24
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -4
- package/src/__tests__/skill-load-inline-command.test.ts +9 -0
- package/src/__tests__/skill-load-inline-includes.test.ts +9 -0
- package/src/__tests__/skill-load-tool.test.ts +11 -0
- package/src/__tests__/skills-uninstall.test.ts +10 -8
- package/src/__tests__/skills.test.ts +1 -1
- package/src/__tests__/slack-channel-config.test.ts +1 -1
- package/src/__tests__/slack-inbound-verification.test.ts +0 -22
- package/src/__tests__/starter-bundle.test.ts +4 -1
- package/src/__tests__/suggestion-routes.test.ts +2 -0
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +1 -1
- package/src/__tests__/test-preload.ts +31 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor.test.ts +0 -20
- package/src/__tests__/tool-input-summary.test.ts +124 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -1
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -1
- package/src/__tests__/trusted-contact-multichannel.test.ts +1 -1
- package/src/__tests__/trusted-contact-verification.test.ts +1 -1
- package/src/__tests__/turn-boundary-resolution.test.ts +1 -1
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/update-bulletin.test.ts +1 -1
- package/src/__tests__/vbundle-pax-and-symlink.test.ts +1 -1
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
- package/src/__tests__/voice-session-bridge.test.ts +1 -1
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -4
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
- package/src/__tests__/workspace-migration-down-functions.test.ts +15 -3
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +40 -4
- package/src/agent/loop.ts +6 -9
- package/src/approvals/guardian-decision-primitive.ts +46 -18
- package/src/approvals/guardian-request-resolvers.ts +19 -2
- package/src/calls/active-call-lease.ts +2 -2
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/commands/doctor.ts +9 -9
- package/src/cli/commands/memory.ts +142 -0
- package/src/cli/commands/oauth/__tests__/connect.test.ts +13 -11
- package/src/cli/commands/oauth/__tests__/ping.test.ts +1 -1
- package/src/cli/commands/oauth/connect.ts +13 -12
- package/src/cli/commands/oauth/index.ts +1 -1
- package/src/cli/commands/oauth/providers.ts +47 -62
- package/src/cli/commands/platform/__tests__/connect.test.ts +72 -46
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +54 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +36 -0
- package/src/cli/commands/platform/connect.ts +17 -7
- package/src/cli/commands/platform/disconnect.ts +28 -3
- package/src/cli/commands/platform/index.ts +3 -3
- package/src/cli.ts +1 -299
- package/src/config/assistant-feature-flags.ts +23 -15
- package/src/config/bundled-skills/app-builder/TOOLS.json +16 -0
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +4 -0
- package/src/config/bundled-skills/app-builder/tools/app-delete.ts +5 -1
- package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +9 -1
- package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +5 -1
- package/src/config/bundled-skills/contacts/TOOLS.json +8 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +10 -1
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +16 -2
- package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +1 -0
- package/src/config/bundled-skills/messaging/SKILL.md +7 -7
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +37 -0
- package/src/config/bundled-skills/slack/SKILL.md +18 -0
- package/src/config/env-registry.ts +15 -11
- package/src/config/env.ts +1 -11
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/schema.ts +4 -0
- package/src/config/schemas/heartbeat.ts +6 -1
- package/src/config/schemas/inference.ts +14 -3
- package/src/config/schemas/memory-processing.ts +16 -8
- package/src/config/schemas/memory-retrieval.ts +3 -3
- package/src/config/skills.ts +1 -1
- package/src/context/window-manager.ts +174 -51
- package/src/credential-execution/executable-discovery.ts +2 -2
- package/src/daemon/approved-devices-store.ts +2 -2
- package/src/daemon/assistant-attachments.ts +2 -0
- package/src/daemon/config-watcher.ts +4 -50
- package/src/daemon/conversation-agent-loop-handlers.ts +9 -1
- package/src/daemon/conversation-agent-loop.ts +12 -0
- package/src/daemon/conversation-error.ts +3 -5
- package/src/daemon/conversation-history.ts +7 -3
- package/src/daemon/conversation-lifecycle.ts +16 -0
- package/src/daemon/conversation-messaging.ts +1 -0
- package/src/daemon/conversation-notifiers.ts +67 -30
- package/src/daemon/conversation-process.ts +161 -2
- package/src/daemon/conversation-queue-manager.ts +2 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -11
- package/src/daemon/conversation-slash.ts +14 -3
- package/src/daemon/conversation-tool-setup.ts +2 -0
- package/src/daemon/conversation-usage.ts +32 -4
- package/src/daemon/conversation.ts +33 -1
- package/src/daemon/daemon-control.ts +32 -16
- package/src/daemon/date-context.ts +47 -45
- package/src/daemon/dictation-profile-store.ts +2 -2
- package/src/daemon/handlers/conversations.ts +19 -0
- package/src/daemon/handlers/shared.ts +14 -21
- package/src/daemon/lifecycle.ts +5 -7
- package/src/daemon/message-types/conversations.ts +2 -0
- package/src/daemon/message-types/guardian-actions.ts +3 -17
- package/src/daemon/message-types/integrations.ts +11 -1
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/pairing-store.ts +2 -79
- package/src/daemon/server.ts +154 -8
- package/src/daemon/watch-handler.ts +65 -21
- package/src/email/guardrails.ts +3 -3
- package/src/heartbeat/heartbeat-service.ts +14 -7
- package/src/hooks/cli.ts +2 -2
- package/src/hooks/config.ts +2 -2
- package/src/hooks/discovery.ts +2 -2
- package/src/hooks/manager.ts +2 -2
- package/src/hooks/runner.ts +5 -2
- package/src/hooks/templates.ts +2 -2
- package/src/memory/admin.ts +181 -2
- package/src/memory/app-git-service.ts +61 -4
- package/src/memory/attachments-store.ts +2 -0
- package/src/memory/canonical-guardian-store.ts +16 -0
- package/src/memory/db-init.ts +8 -0
- package/src/memory/embedding-local.ts +5 -2
- package/src/memory/indexer.ts +44 -26
- package/src/memory/items-extractor.ts +34 -82
- package/src/memory/job-handlers/batch-extraction.ts +741 -0
- package/src/memory/job-handlers/journal-carry-forward.test.ts +383 -0
- package/src/memory/job-handlers/journal-carry-forward.ts +255 -0
- package/src/memory/jobs-store.ts +28 -0
- package/src/memory/jobs-worker.ts +56 -9
- package/src/memory/lifecycle-events-store.ts +4 -2
- package/src/memory/llm-request-log-store.ts +40 -2
- package/src/memory/llm-usage-store.ts +4 -3
- package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +71 -0
- package/src/memory/migrations/200-usage-llm-call-count.ts +20 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/query-expansion.ts +83 -0
- package/src/memory/retriever.test.ts +119 -0
- package/src/memory/retriever.ts +513 -105
- package/src/memory/schema/guardian.ts +4 -0
- package/src/memory/schema/infrastructure.ts +1 -0
- package/src/memory/search/formatting.test.ts +140 -0
- package/src/memory/search/formatting.ts +143 -198
- package/src/memory/search/mmr.ts +136 -0
- package/src/memory/search/staleness.ts +0 -15
- package/src/memory/search/tier-classifier.ts +10 -21
- package/src/memory/search/types.ts +17 -0
- package/src/messaging/providers/slack/adapter.ts +51 -5
- package/src/notifications/broadcaster.ts +13 -0
- package/src/notifications/copy-composer.ts +8 -0
- package/src/oauth/connect-orchestrator.ts +1 -1
- package/src/oauth/connection-resolver.ts +2 -2
- package/src/oauth/provider-serializer.ts +116 -0
- package/src/permissions/trust-store.ts +24 -7
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +5 -0
- package/src/prompts/journal-context.ts +50 -35
- package/src/prompts/persona-resolver.ts +1 -1
- package/src/prompts/system-prompt.ts +27 -28
- package/src/prompts/templates/BOOTSTRAP.md +14 -1
- package/src/prompts/templates/HEARTBEAT.md +10 -0
- package/src/prompts/templates/NOW.md +19 -25
- package/src/prompts/templates/SOUL.md +13 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/update-bulletin.ts +1 -1
- package/src/providers/anthropic/client.ts +89 -18
- package/src/providers/model-catalog.ts +22 -2
- package/src/providers/model-intents.ts +2 -2
- package/src/providers/openai/client.ts +40 -1
- package/src/providers/retry.ts +23 -4
- package/src/providers/types.ts +2 -0
- package/src/runtime/assistant-scope.ts +1 -1
- package/src/runtime/auth/__tests__/credential-service.test.ts +1 -0
- package/src/runtime/auth/route-policy.ts +1 -0
- package/src/runtime/auth/token-service.ts +51 -29
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -1
- package/src/runtime/guardian-decision-types.ts +16 -10
- package/src/runtime/http-server.ts +3 -14
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +7 -4
- package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -4
- package/src/runtime/migrations/vbundle-importer.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +40 -8
- package/src/runtime/routes/conversation-routes.ts +125 -3
- package/src/runtime/routes/guardian-action-routes.ts +9 -3
- package/src/runtime/routes/identity-routes.ts +25 -4
- package/src/runtime/routes/llm-context-normalization.ts +1 -0
- package/src/runtime/routes/log-export-routes.ts +34 -12
- package/src/runtime/routes/migration-routes.ts +6 -10
- package/src/runtime/routes/oauth-apps.ts +2 -9
- package/src/runtime/routes/oauth-providers.ts +60 -0
- package/src/runtime/routes/pairing-routes.ts +0 -8
- package/src/runtime/routes/settings-routes.ts +0 -1
- package/src/runtime/routes/telemetry-routes.ts +16 -4
- package/src/security/encrypted-store.ts +2 -2
- package/src/security/secret-allowlist.ts +3 -3
- package/src/signals/emit-event.ts +42 -0
- package/src/signals/user-message.ts +37 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +83 -19
- package/src/telemetry/usage-telemetry-reporter.ts +23 -17
- package/src/tools/browser/runtime-check.ts +2 -2
- package/src/tools/credentials/vault.ts +2 -249
- package/src/tools/memory/definitions.ts +1 -1
- package/src/tools/memory/handlers.test.ts +50 -8
- package/src/tools/memory/handlers.ts +3 -1
- package/src/tools/side-effects.ts +1 -6
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +11 -14
- package/src/tools/tool-approval-handler.ts +20 -1
- package/src/tools/tool-input-summary.ts +66 -0
- package/src/tools/types.ts +4 -0
- package/src/usage/types.ts +4 -0
- package/src/util/device-id.ts +10 -10
- package/src/util/platform.ts +71 -33
- package/src/util/pricing.ts +19 -6
- package/src/util/strip-comment-lines.ts +28 -0
- package/src/workspace/git-service.ts +8 -18
- package/src/workspace/migrations/003-seed-device-id.ts +6 -4
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +7 -1
- package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -4
- package/src/workspace/migrations/021-move-signals-to-workspace.ts +84 -0
- package/src/workspace/migrations/022-move-hooks-to-workspace.ts +94 -0
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +86 -0
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +126 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +3 -6
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/signals/confirm.ts +0 -82
- package/src/signals/trust-rule.ts +0 -174
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { summarizeToolInput } from "../tools/tool-input-summary.js";
|
|
4
|
+
|
|
5
|
+
describe("summarizeToolInput", () => {
|
|
6
|
+
test("bash with short command returns full command", () => {
|
|
7
|
+
expect(summarizeToolInput("bash", { command: "ls -la" })).toBe("ls -la");
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("bash with long command truncates with ellipsis", () => {
|
|
11
|
+
const longCmd = "a".repeat(200);
|
|
12
|
+
const result = summarizeToolInput("bash", { command: longCmd });
|
|
13
|
+
expect(result.length).toBe(121); // 120 chars + ellipsis
|
|
14
|
+
expect(result.endsWith("…")).toBe(true);
|
|
15
|
+
expect(result.startsWith("a".repeat(120))).toBe(true);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("terminal tool behaves like bash", () => {
|
|
19
|
+
expect(summarizeToolInput("terminal", { command: "echo hello" })).toBe(
|
|
20
|
+
"echo hello",
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("file_read returns file path", () => {
|
|
25
|
+
expect(
|
|
26
|
+
summarizeToolInput("file_read", { file_path: "/src/index.ts" }),
|
|
27
|
+
).toBe("/src/index.ts");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("file_write returns file path from path key", () => {
|
|
31
|
+
expect(summarizeToolInput("file_write", { path: "/src/main.ts" })).toBe(
|
|
32
|
+
"/src/main.ts",
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("file_edit prefers file_path over path", () => {
|
|
37
|
+
expect(
|
|
38
|
+
summarizeToolInput("file_edit", {
|
|
39
|
+
file_path: "/preferred.ts",
|
|
40
|
+
path: "/fallback.ts",
|
|
41
|
+
}),
|
|
42
|
+
).toBe("/preferred.ts");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("web_fetch returns URL truncated to 100 chars", () => {
|
|
46
|
+
const longUrl = `https://example.com/${"x".repeat(200)}`;
|
|
47
|
+
const result = summarizeToolInput("web_fetch", { url: longUrl });
|
|
48
|
+
expect(result.length).toBe(101); // 100 chars + ellipsis
|
|
49
|
+
expect(result.endsWith("…")).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("web_fetch with short URL returns full URL", () => {
|
|
53
|
+
expect(
|
|
54
|
+
summarizeToolInput("web_fetch", { url: "https://example.com" }),
|
|
55
|
+
).toBe("https://example.com");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("network_request behaves like web_fetch", () => {
|
|
59
|
+
expect(
|
|
60
|
+
summarizeToolInput("network_request", { url: "https://api.test.com" }),
|
|
61
|
+
).toBe("https://api.test.com");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("empty input returns empty string", () => {
|
|
65
|
+
expect(summarizeToolInput("bash", {})).toBe("");
|
|
66
|
+
expect(summarizeToolInput("file_read", {})).toBe("");
|
|
67
|
+
expect(summarizeToolInput("web_fetch", {})).toBe("");
|
|
68
|
+
expect(summarizeToolInput("unknown_tool", {})).toBe("");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("unknown tool with string input returns first string value", () => {
|
|
72
|
+
expect(
|
|
73
|
+
summarizeToolInput("custom_tool", {
|
|
74
|
+
query: "search for something",
|
|
75
|
+
count: 10,
|
|
76
|
+
}),
|
|
77
|
+
).toBe("search for something");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("unknown tool with long string truncates to 80 chars", () => {
|
|
81
|
+
const longVal = "b".repeat(150);
|
|
82
|
+
const result = summarizeToolInput("custom_tool", { data: longVal });
|
|
83
|
+
expect(result.length).toBe(81); // 80 chars + ellipsis
|
|
84
|
+
expect(result.endsWith("…")).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("input with no string values returns empty string", () => {
|
|
88
|
+
expect(
|
|
89
|
+
summarizeToolInput("custom_tool", { count: 42, flag: true, obj: {} }),
|
|
90
|
+
).toBe("");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("whitespace-only string values are treated as empty", () => {
|
|
94
|
+
expect(summarizeToolInput("bash", { command: " " })).toBe("");
|
|
95
|
+
expect(summarizeToolInput("custom_tool", { data: " \n\t " })).toBe("");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("host_bash behaves like bash", () => {
|
|
99
|
+
expect(summarizeToolInput("host_bash", { command: "git status" })).toBe(
|
|
100
|
+
"git status",
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("host_file_read behaves like file_read", () => {
|
|
105
|
+
expect(
|
|
106
|
+
summarizeToolInput("host_file_read", { file_path: "/src/index.ts" }),
|
|
107
|
+
).toBe("/src/index.ts");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("host_file_write behaves like file_write", () => {
|
|
111
|
+
expect(
|
|
112
|
+
summarizeToolInput("host_file_write", { path: "/src/main.ts" }),
|
|
113
|
+
).toBe("/src/main.ts");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("host_file_edit behaves like file_edit", () => {
|
|
117
|
+
expect(
|
|
118
|
+
summarizeToolInput("host_file_edit", {
|
|
119
|
+
file_path: "/preferred.ts",
|
|
120
|
+
path: "/fallback.ts",
|
|
121
|
+
}),
|
|
122
|
+
).toBe("/preferred.ts");
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
* - handleToolResult includes toolUseId in emitted tool_result
|
|
9
9
|
* - Event ordering: tool_use_preview_start → input_json_delta → tool_use
|
|
10
10
|
*/
|
|
11
|
+
import { join } from "node:path";
|
|
11
12
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
12
13
|
|
|
13
14
|
// ── Mock platform (must precede imports that read it) ─────────────────────────
|
|
14
15
|
mock.module("../util/platform.js", () => ({
|
|
15
16
|
getSessionTokenPath: () => "/tmp/test-token",
|
|
16
|
-
|
|
17
|
+
getProtectedDir: () => join("/tmp/test", "protected"),
|
|
17
18
|
getDataDir: () => "/tmp/test",
|
|
18
19
|
getWorkspaceDir: () => "/tmp/test/workspace",
|
|
19
20
|
getWorkspaceSkillsDir: () => "/tmp/test/skills",
|
|
@@ -12,9 +12,15 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
12
12
|
// Create a temp directory for the trust file
|
|
13
13
|
const testDir = mkdtempSync(join(tmpdir(), "trust-store-test-"));
|
|
14
14
|
|
|
15
|
+
// Point the file-based trust backend at the test temp dir so
|
|
16
|
+
// getGatewaySecurityDir() (which checks this env var first) writes
|
|
17
|
+
// trust.json under the test directory instead of ~/.vellum/protected.
|
|
18
|
+
process.env.GATEWAY_SECURITY_DIR = join(testDir, "protected");
|
|
19
|
+
|
|
15
20
|
// Mock platform module so trust-store writes to temp dir instead of ~/.vellum
|
|
16
21
|
mock.module("../util/platform.js", () => ({
|
|
17
|
-
|
|
22
|
+
getProtectedDir: () => join(testDir, "protected"),
|
|
23
|
+
getWorkspaceDir: () => join(testDir, "workspace"),
|
|
18
24
|
getDataDir: () => testDir,
|
|
19
25
|
isMacOS: () => process.platform === "darwin",
|
|
20
26
|
isLinux: () => process.platform === "linux",
|
|
@@ -30,7 +30,7 @@ const testDir = mkdtempSync(join(tmpdir(), "tc-inline-approval-integration-"));
|
|
|
30
30
|
|
|
31
31
|
mock.module("../util/platform.js", () => ({
|
|
32
32
|
getDataDir: () => testDir,
|
|
33
|
-
|
|
33
|
+
getProtectedDir: () => join(testDir, "protected"),
|
|
34
34
|
isMacOS: () => process.platform === "darwin",
|
|
35
35
|
isLinux: () => process.platform === "linux",
|
|
36
36
|
isWindows: () => process.platform === "win32",
|
|
@@ -23,7 +23,7 @@ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
23
23
|
const testDir = mkdtempSync(join(tmpdir(), "trusted-contact-lifecycle-notif-"));
|
|
24
24
|
|
|
25
25
|
mock.module("../util/platform.js", () => ({
|
|
26
|
-
|
|
26
|
+
getProtectedDir: () => join(testDir, "protected"),
|
|
27
27
|
getDataDir: () => testDir,
|
|
28
28
|
isMacOS: () => process.platform === "darwin",
|
|
29
29
|
isLinux: () => process.platform === "linux",
|
|
@@ -18,7 +18,7 @@ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
18
18
|
const testDir = mkdtempSync(join(tmpdir(), "trusted-contact-multichannel-"));
|
|
19
19
|
|
|
20
20
|
mock.module("../util/platform.js", () => ({
|
|
21
|
-
|
|
21
|
+
getProtectedDir: () => join(testDir, "protected"),
|
|
22
22
|
getDataDir: () => testDir,
|
|
23
23
|
isMacOS: () => process.platform === "darwin",
|
|
24
24
|
isLinux: () => process.platform === "linux",
|
|
@@ -21,7 +21,7 @@ import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
|
21
21
|
const testDir = mkdtempSync(join(tmpdir(), "trusted-contact-verify-test-"));
|
|
22
22
|
|
|
23
23
|
mock.module("../util/platform.js", () => ({
|
|
24
|
-
|
|
24
|
+
getProtectedDir: () => join(testDir, "protected"),
|
|
25
25
|
getDataDir: () => testDir,
|
|
26
26
|
isMacOS: () => process.platform === "darwin",
|
|
27
27
|
isLinux: () => process.platform === "linux",
|
|
@@ -10,7 +10,7 @@ const workspaceDir = join(testDir, ".vellum", "workspace");
|
|
|
10
10
|
const conversationsDir = join(workspaceDir, "conversations");
|
|
11
11
|
|
|
12
12
|
mock.module("../util/platform.js", () => ({
|
|
13
|
-
|
|
13
|
+
getProtectedDir: () => join(join(testDir, ".vellum"), "protected"),
|
|
14
14
|
getDataDir: () => join(workspaceDir, "data"),
|
|
15
15
|
getWorkspaceDir: () => workspaceDir,
|
|
16
16
|
getConversationsDir: () => conversationsDir,
|
|
@@ -89,7 +89,7 @@ const testDir = realpathSync(
|
|
|
89
89
|
);
|
|
90
90
|
|
|
91
91
|
mock.module("../util/platform.js", () => ({
|
|
92
|
-
|
|
92
|
+
getProtectedDir: () => join(testDir, "protected"),
|
|
93
93
|
getDataDir: () => testDir,
|
|
94
94
|
isMacOS: () => process.platform === "darwin",
|
|
95
95
|
isLinux: () => process.platform === "linux",
|
|
@@ -44,7 +44,7 @@ let tempTemplateDir: string;
|
|
|
44
44
|
mock.module("../util/platform.js", () => ({
|
|
45
45
|
getWorkspacePromptPath: mock((file: string) => join(tempDir, file)),
|
|
46
46
|
getWorkspaceDir: () => tempDir,
|
|
47
|
-
|
|
47
|
+
getProtectedDir: () => join(tempDir, "protected"),
|
|
48
48
|
getDataDir: () => join(tempDir, "data"),
|
|
49
49
|
getPlatformName: () => "darwin",
|
|
50
50
|
isMacOS: () => false,
|
|
@@ -23,7 +23,7 @@ const testDir = realpathSync(
|
|
|
23
23
|
);
|
|
24
24
|
|
|
25
25
|
mock.module("../util/platform.js", () => ({
|
|
26
|
-
|
|
26
|
+
getProtectedDir: () => join(testDir, "protected"),
|
|
27
27
|
getDataDir: () => testDir,
|
|
28
28
|
isMacOS: () => process.platform === "darwin",
|
|
29
29
|
isLinux: () => process.platform === "linux",
|
|
@@ -42,6 +42,7 @@ const SKILL_SRC_DIR = join(
|
|
|
42
42
|
|
|
43
43
|
const platformOverrides: Record<string, (...args: unknown[]) => unknown> = {
|
|
44
44
|
getRootDir: () => TEST_DIR,
|
|
45
|
+
getProtectedDir: () => join(TEST_DIR, "protected"),
|
|
45
46
|
getDataDir: () => join(TEST_DIR, "data"),
|
|
46
47
|
ensureDataDir: () => {},
|
|
47
48
|
getPidPath: () => join(TEST_DIR, "vellum.pid"),
|
|
@@ -28,7 +28,7 @@ const testDir = mkdtempSync(
|
|
|
28
28
|
// ── Platform + logger mocks (must come before any source imports) ────
|
|
29
29
|
|
|
30
30
|
mock.module("../util/platform.js", () => ({
|
|
31
|
-
|
|
31
|
+
getProtectedDir: () => join(testDir, "protected"),
|
|
32
32
|
getDataDir: () => testDir,
|
|
33
33
|
isMacOS: () => process.platform === "darwin",
|
|
34
34
|
isLinux: () => process.platform === "linux",
|
|
@@ -21,7 +21,7 @@ let mockedConfig: {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
mock.module("../util/platform.js", () => ({
|
|
24
|
-
|
|
24
|
+
getProtectedDir: () => join(testDir, "protected"),
|
|
25
25
|
getDataDir: () => testDir,
|
|
26
26
|
isMacOS: () => process.platform === "darwin",
|
|
27
27
|
isLinux: () => process.platform === "linux",
|
|
@@ -31,7 +31,7 @@ mock.module("../util/platform.js", () => ({
|
|
|
31
31
|
getDbPath: () => join(testDir, "test.db"),
|
|
32
32
|
getLogPath: () => join(testDir, "test.log"),
|
|
33
33
|
ensureDataDir: () => {},
|
|
34
|
-
|
|
34
|
+
getProtectedDir: () => join(testDir, "protected"),
|
|
35
35
|
}));
|
|
36
36
|
|
|
37
37
|
mock.module("../util/logger.js", () => ({
|
|
@@ -252,9 +252,9 @@ describe("009-backfill-conversation-disk-view migration", () => {
|
|
|
252
252
|
|
|
253
253
|
expect(existsSync(expectedNewDirPath)).toBe(false);
|
|
254
254
|
expect(existsSync(legacyDirPath)).toBe(true);
|
|
255
|
-
expect(
|
|
256
|
-
|
|
257
|
-
)
|
|
255
|
+
expect(JSON.parse(readFileSync(metaPath, "utf-8")).updatedAt).toBe(
|
|
256
|
+
new Date(conversationUpdatedAt).toISOString(),
|
|
257
|
+
);
|
|
258
258
|
expect(readFileSync(messagesPath, "utf-8").trim().split("\n")).toHaveLength(
|
|
259
259
|
1,
|
|
260
260
|
);
|
|
@@ -31,7 +31,7 @@ mock.module("../util/platform.js", () => ({
|
|
|
31
31
|
getDbPath: () => join(testDir, "test.db"),
|
|
32
32
|
getLogPath: () => join(testDir, "test.log"),
|
|
33
33
|
ensureDataDir: () => {},
|
|
34
|
-
|
|
34
|
+
getProtectedDir: () => join(testDir, "protected"),
|
|
35
35
|
}));
|
|
36
36
|
|
|
37
37
|
mock.module("../util/logger.js", () => ({
|
|
@@ -35,7 +35,7 @@ mock.module("../security/credential-key.js", () => ({
|
|
|
35
35
|
// Mock getRootDir for 016-extract-feature-flags-to-protected
|
|
36
36
|
let mockRootDir: string = "/tmp/mock-root";
|
|
37
37
|
mock.module("../util/platform.js", () => ({
|
|
38
|
-
|
|
38
|
+
getProtectedDir: () => join(mockRootDir, "protected"),
|
|
39
39
|
getDataDir: () => join(mockRootDir, "workspace", "data"),
|
|
40
40
|
getWorkspaceDir: () => join(mockRootDir, "workspace"),
|
|
41
41
|
}));
|
|
@@ -769,9 +769,21 @@ describe("014-migrate-to-workspace-volume down()", () => {
|
|
|
769
769
|
// ---------------------------------------------------------------------------
|
|
770
770
|
|
|
771
771
|
describe("016-extract-feature-flags-to-protected down()", () => {
|
|
772
|
+
let savedBaseDataDir: string | undefined;
|
|
773
|
+
|
|
772
774
|
beforeEach(() => {
|
|
773
|
-
//
|
|
774
|
-
|
|
775
|
+
// The migration has an inlined platform helpers that reads BASE_DATA_DIR,
|
|
776
|
+
// so we set that env var so the inlined function resolves to mockRootDir.
|
|
777
|
+
const baseDir = freshWorkspace();
|
|
778
|
+
mockRootDir = join(baseDir, ".vellum");
|
|
779
|
+
mkdirSync(mockRootDir, { recursive: true });
|
|
780
|
+
savedBaseDataDir = process.env.BASE_DATA_DIR;
|
|
781
|
+
process.env.BASE_DATA_DIR = baseDir;
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
afterEach(() => {
|
|
785
|
+
if (savedBaseDataDir === undefined) delete process.env.BASE_DATA_DIR;
|
|
786
|
+
else process.env.BASE_DATA_DIR = savedBaseDataDir;
|
|
775
787
|
});
|
|
776
788
|
|
|
777
789
|
test("moves feature flags from protected dir back to config.json", () => {
|
|
@@ -10,6 +10,7 @@ const writeFileSyncFn = mock(
|
|
|
10
10
|
(_path: string, _data: string, _opts?: object) => {},
|
|
11
11
|
);
|
|
12
12
|
const mkdirSyncFn = mock((_path: string, _opts?: object) => {});
|
|
13
|
+
const homedirFn = mock((): string => "/mock-home");
|
|
13
14
|
const getDeviceIdBaseDirFn = mock((): string => "/mock-home");
|
|
14
15
|
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
@@ -23,6 +24,10 @@ mock.module("node:fs", () => ({
|
|
|
23
24
|
mkdirSync: mkdirSyncFn,
|
|
24
25
|
}));
|
|
25
26
|
|
|
27
|
+
mock.module("node:os", () => ({
|
|
28
|
+
homedir: homedirFn,
|
|
29
|
+
}));
|
|
30
|
+
|
|
26
31
|
mock.module("../util/device-id.js", () => ({
|
|
27
32
|
getDeviceIdBaseDir: getDeviceIdBaseDirFn,
|
|
28
33
|
}));
|
|
@@ -63,6 +68,7 @@ describe("003-seed-device-id migration", () => {
|
|
|
63
68
|
readFileSyncFn.mockClear();
|
|
64
69
|
writeFileSyncFn.mockClear();
|
|
65
70
|
mkdirSyncFn.mockClear();
|
|
71
|
+
homedirFn.mockReturnValue("/mock-home");
|
|
66
72
|
getDeviceIdBaseDirFn.mockReturnValue("/mock-home");
|
|
67
73
|
});
|
|
68
74
|
|
|
@@ -265,16 +271,17 @@ describe("003-seed-device-id migration", () => {
|
|
|
265
271
|
expect(parsed.deviceId).toBe("install-legacy");
|
|
266
272
|
});
|
|
267
273
|
|
|
268
|
-
test("
|
|
274
|
+
test("reads lockfile from homedir even when getDeviceIdBaseDir differs", () => {
|
|
269
275
|
const customBase = "/custom-base";
|
|
270
276
|
getDeviceIdBaseDirFn.mockReturnValue(customBase);
|
|
277
|
+
homedirFn.mockReturnValue("/mock-home");
|
|
271
278
|
|
|
272
|
-
const customLockPath = `${customBase}/.vellum.lock.json`;
|
|
273
279
|
const customDevicePath = `${customBase}/.vellum/device.json`;
|
|
274
280
|
|
|
275
|
-
|
|
281
|
+
// Lockfile is at homedir, NOT at customBase
|
|
282
|
+
existsSyncFn.mockImplementation((path: string) => path === LOCK_PATH);
|
|
276
283
|
readFileSyncFn.mockImplementation((path: string, _enc: string) => {
|
|
277
|
-
if (path ===
|
|
284
|
+
if (path === LOCK_PATH) {
|
|
278
285
|
return makeLockfile([
|
|
279
286
|
{
|
|
280
287
|
name: "custom",
|
|
@@ -294,11 +301,40 @@ describe("003-seed-device-id migration", () => {
|
|
|
294
301
|
string,
|
|
295
302
|
object,
|
|
296
303
|
];
|
|
304
|
+
// device.json is written under getDeviceIdBaseDir, not homedir
|
|
297
305
|
expect(path).toBe(customDevicePath);
|
|
298
306
|
const parsed = JSON.parse(data);
|
|
299
307
|
expect(parsed.deviceId).toBe("install-custom");
|
|
300
308
|
});
|
|
301
309
|
|
|
310
|
+
test("ignores lockfile under getDeviceIdBaseDir when it differs from homedir", () => {
|
|
311
|
+
const customBase = "/custom-base";
|
|
312
|
+
getDeviceIdBaseDirFn.mockReturnValue(customBase);
|
|
313
|
+
homedirFn.mockReturnValue("/mock-home");
|
|
314
|
+
|
|
315
|
+
// Only a lockfile under customBase exists — should be ignored since
|
|
316
|
+
// the migration always reads the lockfile from homedir().
|
|
317
|
+
const customLockPath = `${customBase}/.vellum.lock.json`;
|
|
318
|
+
existsSyncFn.mockImplementation((path: string) => path === customLockPath);
|
|
319
|
+
readFileSyncFn.mockImplementation((path: string, _enc: string) => {
|
|
320
|
+
if (path === customLockPath) {
|
|
321
|
+
return makeLockfile([
|
|
322
|
+
{
|
|
323
|
+
name: "custom",
|
|
324
|
+
installationId: "install-custom",
|
|
325
|
+
hatchedAt: "2025-01-01T00:00:00Z",
|
|
326
|
+
},
|
|
327
|
+
]);
|
|
328
|
+
}
|
|
329
|
+
throw new Error(`ENOENT: ${path}`);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
seedDeviceIdMigration.run(WORKSPACE_DIR);
|
|
333
|
+
|
|
334
|
+
// No lockfile found at homedir, so no device.json is written
|
|
335
|
+
expect(writeFileSyncFn).not.toHaveBeenCalled();
|
|
336
|
+
});
|
|
337
|
+
|
|
302
338
|
test("entries without hatchedAt are treated as oldest", () => {
|
|
303
339
|
setupFs({
|
|
304
340
|
[LOCK_PATH]: makeLockfile([
|
package/src/agent/loop.ts
CHANGED
|
@@ -24,6 +24,7 @@ export interface AgentLoopConfig {
|
|
|
24
24
|
maxInputTokens?: number; // context window size for tool result truncation
|
|
25
25
|
thinking?: { enabled: boolean };
|
|
26
26
|
effort: "low" | "medium" | "high" | "max";
|
|
27
|
+
speed?: "standard" | "fast";
|
|
27
28
|
toolChoice?:
|
|
28
29
|
| { type: "auto" }
|
|
29
30
|
| { type: "any" }
|
|
@@ -246,6 +247,10 @@ export class AgentLoop {
|
|
|
246
247
|
providerConfig.effort = this.config.effort;
|
|
247
248
|
}
|
|
248
249
|
|
|
250
|
+
if (this.config.speed && this.config.speed !== "standard") {
|
|
251
|
+
providerConfig.speed = this.config.speed;
|
|
252
|
+
}
|
|
253
|
+
|
|
249
254
|
if (this.config.toolChoice) {
|
|
250
255
|
providerConfig.tool_choice = this.config.toolChoice;
|
|
251
256
|
}
|
|
@@ -396,7 +401,7 @@ export class AgentLoop {
|
|
|
396
401
|
);
|
|
397
402
|
|
|
398
403
|
// Check if the assistant turn contained any visible text (used for
|
|
399
|
-
//
|
|
404
|
+
// the empty-response nudge).
|
|
400
405
|
const hasTextBlock = response.content.some(
|
|
401
406
|
(block) => block.type === "text" && block.text.trim().length > 0,
|
|
402
407
|
);
|
|
@@ -601,14 +606,6 @@ export class AgentLoop {
|
|
|
601
606
|
});
|
|
602
607
|
}
|
|
603
608
|
|
|
604
|
-
// Remind the LLM not to repeat text it already streamed
|
|
605
|
-
if (hasTextBlock) {
|
|
606
|
-
resultBlocks.push({
|
|
607
|
-
type: "text",
|
|
608
|
-
text: '<system_notice>Your previous text was already shown to the user in real time. Do not repeat or rephrase it. Do not narrate retries or internal process chatter ("let me try", "that didn\'t work"). Keep working with tools silently unless you need user input, and only send user-facing text when you have concrete progress or final results.</system_notice>',
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
|
|
612
609
|
// Add tool results as a user message and continue the loop
|
|
613
610
|
history.push({ role: "user", content: resultBlocks });
|
|
614
611
|
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* legacy parser, requester self-cancel) call through this module instead of
|
|
6
6
|
* inlining the decision-application logic. This centralizes:
|
|
7
7
|
*
|
|
8
|
-
* 1. `approve_always` downgrade for guardian-on-behalf requests
|
|
8
|
+
* 1. `approve_always` downgrade for guardian-on-behalf requests (time-bounded
|
|
9
|
+
* modes like `approve_10m` and `approve_conversation` are preserved)
|
|
9
10
|
* 2. Identity validation (actor must match assigned guardian)
|
|
10
11
|
* 3. Approval-info capture before the pending interaction is consumed
|
|
11
12
|
* 4. Atomic decision application via `handleChannelDecision`
|
|
@@ -21,7 +22,9 @@
|
|
|
21
22
|
* - Decision authorization is purely principal-based:
|
|
22
23
|
* actor.guardianPrincipalId === request.guardianPrincipalId (strict equality)
|
|
23
24
|
* - Decisions are first-response-wins (CAS-like stale protection)
|
|
24
|
-
* - `approve_always` is
|
|
25
|
+
* - `approve_always` is downgraded to `approve_once` for guardian-on-behalf
|
|
26
|
+
* requests; time-bounded modes (`approve_10m`, `approve_conversation`) are
|
|
27
|
+
* preserved since grants are scoped via `scopeMode: "tool_signature"`
|
|
25
28
|
* - Scoped grant minting only on explicit approve for requests with tool metadata
|
|
26
29
|
*/
|
|
27
30
|
|
|
@@ -61,6 +64,28 @@ const log = getLogger("guardian-decision-primitive");
|
|
|
61
64
|
/** TTL for scoped approval grants minted on guardian approve_once decisions. */
|
|
62
65
|
export const GRANT_TTL_MS = 5 * 60 * 1000;
|
|
63
66
|
|
|
67
|
+
/** TTL for scoped approval grants minted on guardian approve_10m decisions. */
|
|
68
|
+
export const GRANT_TTL_10M_MS = 10 * 60 * 1000;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Compute the grant `expiresAt` timestamp for a given approval action.
|
|
72
|
+
*
|
|
73
|
+
* - `approve_10m` → 10 minutes from now
|
|
74
|
+
* - `approve_conversation` → no wall-clock expiry (far-future sentinel;
|
|
75
|
+
* conversation lifecycle manages cleanup)
|
|
76
|
+
* - All others (`approve_once`, etc.) → 5 minutes from now (default)
|
|
77
|
+
*/
|
|
78
|
+
export function computeGrantExpiresAt(action: ApprovalAction): number {
|
|
79
|
+
switch (action) {
|
|
80
|
+
case "approve_10m":
|
|
81
|
+
return Date.now() + GRANT_TTL_10M_MS;
|
|
82
|
+
case "approve_conversation":
|
|
83
|
+
return Number.MAX_SAFE_INTEGER;
|
|
84
|
+
default:
|
|
85
|
+
return Date.now() + GRANT_TTL_MS;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
64
89
|
// ---------------------------------------------------------------------------
|
|
65
90
|
// Scoped grant minting
|
|
66
91
|
// ---------------------------------------------------------------------------
|
|
@@ -79,8 +104,9 @@ export function tryMintToolApprovalGrant(params: {
|
|
|
79
104
|
approval: GuardianApprovalRequest;
|
|
80
105
|
decisionChannel: ChannelId;
|
|
81
106
|
guardianExternalUserId: string;
|
|
107
|
+
effectiveAction: ApprovalAction;
|
|
82
108
|
}): void {
|
|
83
|
-
const { approvalInfo, approval, decisionChannel, guardianExternalUserId } =
|
|
109
|
+
const { approvalInfo, approval, decisionChannel, guardianExternalUserId, effectiveAction } =
|
|
84
110
|
params;
|
|
85
111
|
|
|
86
112
|
if (!approvalInfo.toolName) {
|
|
@@ -116,7 +142,7 @@ export function tryMintToolApprovalGrant(params: {
|
|
|
116
142
|
callSessionId: null,
|
|
117
143
|
guardianExternalUserId,
|
|
118
144
|
requesterExternalUserId: approval.requesterExternalUserId,
|
|
119
|
-
expiresAt:
|
|
145
|
+
expiresAt: computeGrantExpiresAt(effectiveAction),
|
|
120
146
|
});
|
|
121
147
|
|
|
122
148
|
if (result.ok) {
|
|
@@ -166,7 +192,8 @@ export interface ApplyGuardianDecisionParams {
|
|
|
166
192
|
* self-cancel paths:
|
|
167
193
|
*
|
|
168
194
|
* 1. Downgrade `approve_always` to `approve_once` (guardians cannot
|
|
169
|
-
* permanently allowlist tools on behalf of requesters)
|
|
195
|
+
* permanently allowlist tools on behalf of requesters). Time-bounded
|
|
196
|
+
* modes (`approve_10m`, `approve_conversation`) are preserved.
|
|
170
197
|
* 2. Capture pending approval info before resolution
|
|
171
198
|
* 3. Apply the decision atomically via `handleChannelDecision`
|
|
172
199
|
* 4. Update the guardian approval record
|
|
@@ -186,11 +213,11 @@ export function applyGuardianDecision(
|
|
|
186
213
|
decisionContext,
|
|
187
214
|
} = params;
|
|
188
215
|
|
|
189
|
-
// Guardians cannot grant
|
|
216
|
+
// Guardians cannot grant permanent allow modes on behalf of requesters.
|
|
217
|
+
// Time-bounded modes (approve_10m, approve_conversation) are safe because
|
|
218
|
+
// grants are scoped to the tool+input signature via scopeMode: "tool_signature".
|
|
190
219
|
const effectiveDecision: ApprovalDecisionResult =
|
|
191
|
-
decision.action === "approve_always"
|
|
192
|
-
decision.action === "approve_10m" ||
|
|
193
|
-
decision.action === "approve_conversation"
|
|
220
|
+
decision.action === "approve_always"
|
|
194
221
|
? { ...decision, action: "approve_once" }
|
|
195
222
|
: decision;
|
|
196
223
|
|
|
@@ -240,6 +267,7 @@ export function applyGuardianDecision(
|
|
|
240
267
|
approval,
|
|
241
268
|
decisionChannel: actorChannel,
|
|
242
269
|
guardianExternalUserId: effectiveGuardianId,
|
|
270
|
+
effectiveAction: effectiveDecision.action,
|
|
243
271
|
});
|
|
244
272
|
}
|
|
245
273
|
|
|
@@ -267,8 +295,9 @@ export function mintCanonicalRequestGrant(params: {
|
|
|
267
295
|
request: CanonicalGuardianRequest;
|
|
268
296
|
actorChannel: string;
|
|
269
297
|
guardianExternalUserId?: string;
|
|
298
|
+
effectiveAction: ApprovalAction;
|
|
270
299
|
}): { minted: boolean } {
|
|
271
|
-
const { request, actorChannel, guardianExternalUserId } = params;
|
|
300
|
+
const { request, actorChannel, guardianExternalUserId, effectiveAction } = params;
|
|
272
301
|
|
|
273
302
|
if (!request.toolName || !request.inputDigest) {
|
|
274
303
|
return { minted: false };
|
|
@@ -285,7 +314,7 @@ export function mintCanonicalRequestGrant(params: {
|
|
|
285
314
|
callSessionId: request.callSessionId ?? null,
|
|
286
315
|
guardianExternalUserId: guardianExternalUserId ?? null,
|
|
287
316
|
requesterExternalUserId: request.requesterExternalUserId ?? null,
|
|
288
|
-
expiresAt:
|
|
317
|
+
expiresAt: computeGrantExpiresAt(effectiveAction),
|
|
289
318
|
});
|
|
290
319
|
|
|
291
320
|
if (result.ok) {
|
|
@@ -371,7 +400,8 @@ export type CanonicalDecisionResult =
|
|
|
371
400
|
* Steps:
|
|
372
401
|
* 1. Look up the canonical request by ID
|
|
373
402
|
* 2. Validate: exists, pending status, identity match, valid action
|
|
374
|
-
* 3. Downgrade approve_always to approve_once (guardian-on-behalf invariant
|
|
403
|
+
* 3. Downgrade `approve_always` to `approve_once` (guardian-on-behalf invariant;
|
|
404
|
+
* time-bounded modes like `approve_10m`/`approve_conversation` are preserved)
|
|
375
405
|
* 4. CAS resolve the canonical request atomically
|
|
376
406
|
* 5. Dispatch to kind-specific resolver
|
|
377
407
|
* 6. Mint grant if applicable
|
|
@@ -491,13 +521,10 @@ export async function applyCanonicalGuardianDecision(
|
|
|
491
521
|
return { applied: false, reason: "expired" };
|
|
492
522
|
}
|
|
493
523
|
|
|
494
|
-
// 3. Downgrade approve_always
|
|
495
|
-
//
|
|
496
|
-
// (permanent, timed, or conversation-scoped) on behalf of requesters.
|
|
524
|
+
// 3. Downgrade approve_always to approve_once for guardian-on-behalf requests.
|
|
525
|
+
// Time-bounded modes (approve_10m, approve_conversation) are permitted.
|
|
497
526
|
const effectiveAction: ApprovalAction =
|
|
498
|
-
action === "approve_always"
|
|
499
|
-
action === "approve_10m" ||
|
|
500
|
-
action === "approve_conversation"
|
|
527
|
+
action === "approve_always"
|
|
501
528
|
? "approve_once"
|
|
502
529
|
: action;
|
|
503
530
|
|
|
@@ -578,6 +605,7 @@ export async function applyCanonicalGuardianDecision(
|
|
|
578
605
|
actorContext.actorExternalUserId ??
|
|
579
606
|
resolved.guardianExternalUserId ??
|
|
580
607
|
undefined,
|
|
608
|
+
effectiveAction,
|
|
581
609
|
});
|
|
582
610
|
grantMinted = grantResult.minted;
|
|
583
611
|
}
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
type NotificationSourceChannel,
|
|
25
25
|
} from "../notifications/signal.js";
|
|
26
26
|
import { addRule } from "../permissions/trust-store.js";
|
|
27
|
+
import type { UserDecision } from "../permissions/types.js";
|
|
27
28
|
import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
|
|
28
29
|
import { mintDaemonDeliveryToken } from "../runtime/auth/token-service.js";
|
|
29
30
|
import type { ApprovalAction } from "../runtime/channel-approval-types.js";
|
|
@@ -231,8 +232,24 @@ const pendingInteractionResolver: GuardianRequestResolver = {
|
|
|
231
232
|
}
|
|
232
233
|
|
|
233
234
|
// Map action to the permission system's UserDecision type and notify session.
|
|
234
|
-
|
|
235
|
-
|
|
235
|
+
// Mirrors mapApprovalActionToUserDecision from channel-approvals.ts so
|
|
236
|
+
// temporal modes (approve_10m, approve_conversation) reach the session with
|
|
237
|
+
// the correct UserDecision instead of collapsing to plain "allow".
|
|
238
|
+
let userDecision: UserDecision;
|
|
239
|
+
switch (decision.action) {
|
|
240
|
+
case "reject":
|
|
241
|
+
userDecision = "deny";
|
|
242
|
+
break;
|
|
243
|
+
case "approve_10m":
|
|
244
|
+
userDecision = "allow_10m";
|
|
245
|
+
break;
|
|
246
|
+
case "approve_conversation":
|
|
247
|
+
userDecision = "allow_conversation";
|
|
248
|
+
break;
|
|
249
|
+
default:
|
|
250
|
+
userDecision = "allow";
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
236
253
|
resolved.conversation!.handleConfirmationResponse(
|
|
237
254
|
request.id,
|
|
238
255
|
userDecision,
|