@vellumai/assistant 0.5.6 → 0.5.8
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 +16 -2
- package/ARCHITECTURE.md +6 -75
- package/Dockerfile +3 -2
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docker-entrypoint.sh +9 -0
- package/docs/architecture/keychain-broker.md +45 -240
- package/docs/architecture/memory.md +13 -11
- package/docs/architecture/security.md +0 -17
- package/docs/credential-execution-service.md +2 -2
- package/node_modules/@vellumai/ces-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +120 -1
- package/node_modules/@vellumai/credential-storage/package.json +1 -0
- package/node_modules/@vellumai/egress-proxy/package.json +1 -0
- package/package.json +2 -3
- package/src/__tests__/actor-token-service.test.ts +0 -114
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
- package/src/__tests__/ces-startup-timeout.test.ts +40 -0
- package/src/__tests__/channel-approval-routes.test.ts +0 -5
- package/src/__tests__/channel-readiness-service.test.ts +1 -60
- package/src/__tests__/checker.test.ts +4 -2
- package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -2
- package/src/__tests__/config-schema.test.ts +3 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +2 -4
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/conversation-queue.test.ts +0 -1
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-slash-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-title-service.test.ts +87 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/credential-execution-client.test.ts +5 -2
- package/src/__tests__/credential-execution-feature-gates.test.ts +59 -30
- package/src/__tests__/credential-execution-managed-contract.test.ts +35 -20
- package/src/__tests__/credential-security-e2e.test.ts +1 -67
- package/src/__tests__/credential-security-invariants.test.ts +6 -50
- package/src/__tests__/credentials-cli.test.ts +82 -3
- package/src/__tests__/daemon-credential-client.test.ts +123 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
- package/src/__tests__/guardian-routing-state.test.ts +0 -5
- package/src/__tests__/host-shell-tool.test.ts +6 -7
- package/src/__tests__/http-user-message-parity.test.ts +3 -103
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
- package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
- package/src/__tests__/intent-routing.test.ts +0 -13
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
- package/src/__tests__/journal-context.test.ts +335 -0
- package/src/__tests__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
- package/src/__tests__/memory-recall-quality.test.ts +48 -17
- package/src/__tests__/memory-regressions.test.ts +408 -363
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
- package/src/__tests__/migration-export-http.test.ts +2 -2
- package/src/__tests__/migration-import-commit-http.test.ts +2 -2
- package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
- package/src/__tests__/migration-validate-http.test.ts +2 -2
- package/src/__tests__/non-member-access-request.test.ts +2 -7
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/notification-decision-strategy.test.ts +71 -0
- package/src/__tests__/oauth-cli.test.ts +5 -1
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
- package/src/__tests__/provider-error-scenarios.test.ts +0 -267
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/relay-server.test.ts +1 -2
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -1
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +95 -272
- package/src/__tests__/shell-identity.test.ts +96 -6
- package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
- package/src/__tests__/skill-feature-flags.test.ts +46 -45
- package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
- package/src/__tests__/skill-load-inline-command.test.ts +8 -12
- package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
- package/src/__tests__/skill-load-tool.test.ts +0 -2
- package/src/__tests__/skill-memory.test.ts +17 -3
- package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
- package/src/__tests__/skills.test.ts +0 -2
- package/src/__tests__/slack-inbound-verification.test.ts +0 -4
- package/src/__tests__/stale-approval-dedup.test.ts +171 -0
- package/src/__tests__/stt-hints.test.ts +437 -0
- package/src/__tests__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/task-memory-cleanup.test.ts +14 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
- package/src/__tests__/update-bulletin.test.ts +0 -2
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
- package/src/__tests__/voice-quality.test.ts +58 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -7
- package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +220 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/acp/agent-process.ts +9 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +164 -38
- package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +90 -8
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +129 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/stt-hints.ts +189 -0
- package/src/calls/tts-text-sanitizer.ts +61 -0
- package/src/calls/twilio-routes.ts +34 -5
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +38 -5
- package/src/calls/voice-session-bridge.ts +7 -12
- package/src/cli/commands/avatar.ts +2 -2
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/credentials.ts +128 -82
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/keys.ts +7 -7
- package/src/cli/commands/memory.ts +1 -1
- package/src/cli/commands/oauth/connections.ts +11 -29
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +525 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/cli/lib/daemon-credential-client.ts +284 -0
- package/src/cli.ts +1 -1
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/AGENTS.md +34 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +15 -2
- package/src/config/bundled-skills/settings/TOOLS.json +47 -2
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-skills/slack/SKILL.md +1 -1
- package/src/config/bundled-tool-registry.ts +5 -11
- package/src/config/defaults.ts +0 -2
- package/src/config/env-registry.ts +5 -5
- package/src/config/env.ts +21 -14
- package/src/config/feature-flag-registry.json +49 -9
- package/src/config/loader.ts +106 -42
- package/src/config/schema.ts +9 -29
- package/src/config/schemas/calls.ts +30 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/inference.ts +2 -2
- package/src/config/schemas/journal.ts +16 -0
- package/src/config/schemas/memory-processing.ts +2 -2
- package/src/config/schemas/security.ts +0 -4
- package/src/config/types.ts +1 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/credential-execution/approval-bridge.ts +1 -0
- package/src/credential-execution/executable-discovery.ts +28 -4
- package/src/credential-execution/feature-gates.ts +16 -0
- package/src/credential-execution/process-manager.ts +38 -0
- package/src/credential-execution/startup-timeout.ts +36 -0
- package/src/daemon/approval-generators.ts +3 -9
- package/src/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +5 -0
- package/src/daemon/conversation-error.ts +13 -1
- package/src/daemon/conversation-memory.ts +1 -2
- package/src/daemon/conversation-process.ts +18 -1
- package/src/daemon/conversation-surfaces.ts +30 -1
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +21 -1
- package/src/daemon/guardian-action-generators.ts +3 -9
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +234 -51
- package/src/daemon/message-types/conversations.ts +4 -4
- package/src/daemon/message-types/diagnostics.ts +3 -22
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/message-types/upgrades.ts +8 -0
- package/src/daemon/server.ts +32 -95
- package/src/events/domain-events.ts +2 -1
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/app-store.ts +31 -0
- package/src/memory/conversation-title-service.ts +50 -1
- package/src/memory/db-init.ts +16 -0
- package/src/memory/indexer.ts +19 -10
- package/src/memory/items-extractor.ts +328 -321
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/job-handlers/summarization.ts +26 -16
- package/src/memory/jobs-store.ts +63 -6
- package/src/memory/jobs-worker.ts +31 -7
- package/src/memory/journal-memory.ts +214 -0
- package/src/memory/migrations/001-job-deferrals.ts +19 -0
- package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
- package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
- package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
- package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
- package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
- package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
- package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
- package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
- package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
- package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
- package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
- package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
- package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
- package/src/memory/migrations/116-messages-fts.ts +106 -1
- package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
- package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
- package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
- package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
- package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
- package/src/memory/migrations/141-rename-verification-table.ts +54 -0
- package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
- package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
- package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
- package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
- package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
- package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
- package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
- package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
- package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
- package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
- package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
- package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
- package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
- package/src/memory/migrations/index.ts +5 -0
- package/src/memory/migrations/registry.ts +98 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/retriever.test.ts +37 -25
- package/src/memory/retriever.ts +24 -49
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +2 -0
- package/src/memory/search/formatting.ts +7 -44
- package/src/memory/search/staleness.ts +4 -0
- package/src/memory/search/tier-classifier.ts +10 -2
- package/src/memory/search/types.ts +2 -5
- package/src/memory/task-memory-cleanup.ts +4 -3
- package/src/notifications/adapters/slack.ts +168 -6
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +59 -2
- package/src/notifications/decision-engine.ts +4 -1
- package/src/notifications/signal.ts +2 -0
- package/src/notifications/types.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/journal-context.ts +133 -0
- package/src/prompts/persona-resolver.ts +194 -0
- package/src/prompts/system-prompt.ts +44 -4
- package/src/prompts/templates/SOUL.md +10 -0
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/provider-send-message.ts +3 -32
- package/src/providers/registry.ts +29 -179
- package/src/providers/types.ts +1 -1
- package/src/runtime/access-request-helper.ts +4 -0
- package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
- package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
- package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
- package/src/runtime/auth/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +17 -1
- package/src/runtime/auth/token-service.ts +43 -138
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/gateway-client.ts +47 -4
- package/src/runtime/guardian-decision-types.ts +45 -4
- package/src/runtime/http-server.ts +31 -3
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/access-request-decision.ts +2 -2
- package/src/runtime/routes/app-management-routes.ts +2 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/channel-readiness-routes.ts +9 -4
- package/src/runtime/routes/conversation-query-routes.ts +63 -1
- package/src/runtime/routes/conversation-routes.ts +4 -44
- package/src/runtime/routes/debug-routes.ts +12 -9
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/guardian-approval-interception.ts +168 -11
- package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
- package/src/runtime/routes/identity-routes.ts +19 -30
- package/src/runtime/routes/inbound-message-handler.ts +31 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/integrations/twilio.ts +52 -10
- package/src/runtime/routes/integrations/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -3
- package/src/runtime/routes/memory-item-routes.ts +46 -14
- package/src/runtime/routes/migration-rollback-routes.ts +209 -0
- package/src/runtime/routes/migration-routes.ts +17 -1
- package/src/runtime/routes/notification-routes.ts +58 -0
- package/src/runtime/routes/schedule-routes.ts +65 -0
- package/src/runtime/routes/secret-routes.ts +141 -10
- package/src/runtime/routes/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +96 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
- package/src/runtime/routes/workspace-commit-routes.ts +62 -0
- package/src/runtime/routes/workspace-routes.test.ts +22 -1
- package/src/runtime/routes/workspace-routes.ts +1 -1
- package/src/runtime/routes/workspace-utils.ts +86 -2
- package/src/security/ces-credential-client.ts +75 -29
- package/src/security/ces-rpc-credential-backend.ts +86 -0
- package/src/security/credential-backend.ts +22 -92
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +113 -115
- package/src/skills/catalog-install.ts +6 -32
- package/src/skills/skill-memory.ts +1 -0
- package/src/subagent/manager.ts +2 -5
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/acp/spawn.ts +78 -1
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/credentials/vault.ts +5 -3
- package/src/tools/executor.ts +0 -4
- package/src/tools/memory/definitions.ts +3 -2
- package/src/tools/memory/handlers.ts +10 -7
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/types.ts +0 -8
- package/src/util/browser.ts +15 -0
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +4 -51
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/migrations/001-avatar-rename.ts +15 -0
- package/src/workspace/migrations/003-seed-device-id.ts +17 -1
- package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
- package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
- package/src/workspace/migrations/006-services-config.ts +49 -0
- package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
- package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
- package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
- package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
- package/src/workspace/migrations/017-seed-persona-dirs.ts +96 -0
- package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +27 -5
- package/src/workspace/migrations/registry.ts +12 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/workspace/provider-commit-message-generator.ts +12 -21
- package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
- package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
- package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
- package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
- package/src/__tests__/swarm-orchestrator.test.ts +0 -463
- package/src/__tests__/swarm-plan-validator.test.ts +0 -384
- package/src/__tests__/swarm-recursion.test.ts +0 -197
- package/src/__tests__/swarm-router-planner.test.ts +0 -234
- package/src/__tests__/swarm-tool.test.ts +0 -185
- package/src/__tests__/swarm-worker-backend.test.ts +0 -144
- package/src/__tests__/swarm-worker-runner.test.ts +0 -288
- package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
- package/src/commands/cc-command-registry.ts +0 -248
- package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
- package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
- package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
- package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
- package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
- package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/memory/search/lexical.ts +0 -48
- package/src/providers/failover.ts +0 -186
- package/src/runtime/local-gateway-health.ts +0 -275
- package/src/security/secret-ingress.ts +0 -68
- package/src/swarm/backend-claude-code.ts +0 -225
- package/src/swarm/checkpoint.ts +0 -137
- package/src/swarm/graph-utils.ts +0 -53
- package/src/swarm/index.ts +0 -55
- package/src/swarm/limits.ts +0 -66
- package/src/swarm/orchestrator.ts +0 -424
- package/src/swarm/plan-validator.ts +0 -117
- package/src/swarm/router-planner.ts +0 -162
- package/src/swarm/router-prompts.ts +0 -39
- package/src/swarm/synthesizer.ts +0 -81
- package/src/swarm/types.ts +0 -72
- package/src/swarm/worker-backend.ts +0 -131
- package/src/swarm/worker-prompts.ts +0 -80
- package/src/swarm/worker-runner.ts +0 -170
- package/src/tools/claude-code/claude-code.ts +0 -610
- package/src/tools/swarm/delegate.ts +0 -205
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { CesRpcMethod } from "@vellumai/ces-contracts";
|
|
4
|
+
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Mock state
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
const callFn = mock(
|
|
10
|
+
async (_method: string, _request: unknown): Promise<unknown> => ({}),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
const isReadyFn = mock((): boolean => true);
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Mock modules — before importing module under test
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
mock.module("../util/logger.js", () => ({
|
|
20
|
+
getLogger: () =>
|
|
21
|
+
new Proxy({} as Record<string, unknown>, {
|
|
22
|
+
get: () => () => {},
|
|
23
|
+
}),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
// Import after mocking
|
|
27
|
+
import type { CesClient } from "../credential-execution/client.js";
|
|
28
|
+
import { CesRpcCredentialBackend } from "../security/ces-rpc-credential-backend.js";
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Helpers
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
function createMockClient(): CesClient {
|
|
35
|
+
return {
|
|
36
|
+
handshake: mock(async () => ({ accepted: true })),
|
|
37
|
+
call: callFn as CesClient["call"],
|
|
38
|
+
updateAssistantApiKey: mock(async () => ({ updated: true })),
|
|
39
|
+
isReady: isReadyFn,
|
|
40
|
+
close: mock(() => {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Tests
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
describe("CesRpcCredentialBackend", () => {
|
|
49
|
+
let client: CesClient;
|
|
50
|
+
let backend: CesRpcCredentialBackend;
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
callFn.mockClear();
|
|
54
|
+
isReadyFn.mockClear();
|
|
55
|
+
|
|
56
|
+
isReadyFn.mockReturnValue(true);
|
|
57
|
+
|
|
58
|
+
client = createMockClient();
|
|
59
|
+
backend = new CesRpcCredentialBackend(client);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("has name 'ces-rpc'", () => {
|
|
63
|
+
expect(backend.name).toBe("ces-rpc");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// -------------------------------------------------------------------------
|
|
67
|
+
// isAvailable
|
|
68
|
+
// -------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
describe("isAvailable", () => {
|
|
71
|
+
test("returns true when client is ready", () => {
|
|
72
|
+
isReadyFn.mockReturnValue(true);
|
|
73
|
+
expect(backend.isAvailable()).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("returns false when client is not ready", () => {
|
|
77
|
+
isReadyFn.mockReturnValue(false);
|
|
78
|
+
expect(backend.isAvailable()).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// -------------------------------------------------------------------------
|
|
83
|
+
// get
|
|
84
|
+
// -------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
describe("get", () => {
|
|
87
|
+
test("delegates to CesRpcMethod.GetCredential and returns value when found", async () => {
|
|
88
|
+
callFn.mockResolvedValue({ found: true, value: "my-secret" });
|
|
89
|
+
|
|
90
|
+
const result = await backend.get("test-account");
|
|
91
|
+
|
|
92
|
+
expect(callFn).toHaveBeenCalledWith(CesRpcMethod.GetCredential, {
|
|
93
|
+
account: "test-account",
|
|
94
|
+
});
|
|
95
|
+
expect(result).toEqual({ value: "my-secret", unreachable: false });
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("returns undefined value when credential not found", async () => {
|
|
99
|
+
callFn.mockResolvedValue({ found: false });
|
|
100
|
+
|
|
101
|
+
const result = await backend.get("missing-account");
|
|
102
|
+
|
|
103
|
+
expect(callFn).toHaveBeenCalledWith(CesRpcMethod.GetCredential, {
|
|
104
|
+
account: "missing-account",
|
|
105
|
+
});
|
|
106
|
+
expect(result).toEqual({ value: undefined, unreachable: false });
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("returns unreachable when RPC call throws", async () => {
|
|
110
|
+
callFn.mockRejectedValue(new Error("transport error"));
|
|
111
|
+
|
|
112
|
+
const result = await backend.get("broken-account");
|
|
113
|
+
|
|
114
|
+
expect(result).toEqual({ value: undefined, unreachable: true });
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// -------------------------------------------------------------------------
|
|
119
|
+
// set
|
|
120
|
+
// -------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
describe("set", () => {
|
|
123
|
+
test("delegates to CesRpcMethod.SetCredential and returns true on success", async () => {
|
|
124
|
+
callFn.mockResolvedValue({ ok: true });
|
|
125
|
+
|
|
126
|
+
const result = await backend.set("test-account", "new-secret");
|
|
127
|
+
|
|
128
|
+
expect(callFn).toHaveBeenCalledWith(CesRpcMethod.SetCredential, {
|
|
129
|
+
account: "test-account",
|
|
130
|
+
value: "new-secret",
|
|
131
|
+
});
|
|
132
|
+
expect(result).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("returns false when RPC call throws", async () => {
|
|
136
|
+
callFn.mockRejectedValue(new Error("transport error"));
|
|
137
|
+
|
|
138
|
+
const result = await backend.set("test-account", "new-secret");
|
|
139
|
+
|
|
140
|
+
expect(result).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// -------------------------------------------------------------------------
|
|
145
|
+
// delete
|
|
146
|
+
// -------------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
describe("delete", () => {
|
|
149
|
+
test("delegates to CesRpcMethod.DeleteCredential and returns the result", async () => {
|
|
150
|
+
callFn.mockResolvedValue({ result: "deleted" });
|
|
151
|
+
|
|
152
|
+
const result = await backend.delete("test-account");
|
|
153
|
+
|
|
154
|
+
expect(callFn).toHaveBeenCalledWith(CesRpcMethod.DeleteCredential, {
|
|
155
|
+
account: "test-account",
|
|
156
|
+
});
|
|
157
|
+
expect(result).toBe("deleted");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("returns not-found result from CES", async () => {
|
|
161
|
+
callFn.mockResolvedValue({ result: "not-found" });
|
|
162
|
+
|
|
163
|
+
const result = await backend.delete("nonexistent-account");
|
|
164
|
+
|
|
165
|
+
expect(result).toBe("not-found");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("returns 'error' when RPC call throws", async () => {
|
|
169
|
+
callFn.mockRejectedValue(new Error("transport error"));
|
|
170
|
+
|
|
171
|
+
const result = await backend.delete("test-account");
|
|
172
|
+
|
|
173
|
+
expect(result).toBe("error");
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// -------------------------------------------------------------------------
|
|
178
|
+
// list
|
|
179
|
+
// -------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
describe("list", () => {
|
|
182
|
+
test("delegates to CesRpcMethod.ListCredentials and returns accounts", async () => {
|
|
183
|
+
callFn.mockResolvedValue({ accounts: ["account-a", "account-b"] });
|
|
184
|
+
|
|
185
|
+
const result = await backend.list();
|
|
186
|
+
|
|
187
|
+
expect(callFn).toHaveBeenCalledWith(CesRpcMethod.ListCredentials, {});
|
|
188
|
+
expect(result).toEqual({ accounts: ["account-a", "account-b"], unreachable: false });
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("returns unreachable when RPC call throws", async () => {
|
|
192
|
+
callFn.mockRejectedValue(new Error("transport error"));
|
|
193
|
+
|
|
194
|
+
const result = await backend.list();
|
|
195
|
+
|
|
196
|
+
expect(result).toEqual({ accounts: [], unreachable: true });
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import type { CesClient } from "../credential-execution/client.js";
|
|
4
|
+
import {
|
|
5
|
+
awaitCesClientWithTimeout,
|
|
6
|
+
DEFAULT_CES_STARTUP_TIMEOUT_MS,
|
|
7
|
+
} from "../credential-execution/startup-timeout.js";
|
|
8
|
+
|
|
9
|
+
describe("awaitCesClientWithTimeout", () => {
|
|
10
|
+
test("clears the fallback timer when the CES client resolves first", async () => {
|
|
11
|
+
const onTimeout = mock(() => {});
|
|
12
|
+
const client = { isReady: () => true } as unknown as CesClient;
|
|
13
|
+
|
|
14
|
+
const result = await awaitCesClientWithTimeout(Promise.resolve(client), {
|
|
15
|
+
timeoutMs: 25,
|
|
16
|
+
onTimeout,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
expect(result).toBe(client);
|
|
20
|
+
|
|
21
|
+
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
22
|
+
expect(onTimeout).not.toHaveBeenCalled();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("returns undefined and runs the fallback handler when the timeout wins", async () => {
|
|
26
|
+
const onTimeout = mock(() => {});
|
|
27
|
+
|
|
28
|
+
const result = await awaitCesClientWithTimeout(new Promise(() => {}), {
|
|
29
|
+
timeoutMs: 10,
|
|
30
|
+
onTimeout,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(result).toBeUndefined();
|
|
34
|
+
expect(onTimeout).toHaveBeenCalledTimes(1);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("exports the daemon CES startup timeout constant", () => {
|
|
38
|
+
expect(DEFAULT_CES_STARTUP_TIMEOUT_MS).toBe(20_000);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -38,11 +38,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
38
38
|
}),
|
|
39
39
|
}));
|
|
40
40
|
|
|
41
|
-
// Mock security check to always pass
|
|
42
|
-
mock.module("../security/secret-ingress.js", () => ({
|
|
43
|
-
checkIngressForSecrets: () => ({ blocked: false }),
|
|
44
|
-
}));
|
|
45
|
-
|
|
46
41
|
// Mock render to return the raw content as text
|
|
47
42
|
mock.module("../daemon/handlers/shared.js", () => ({
|
|
48
43
|
renderHistoryContent: (content: unknown) => ({
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock,
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
let mockTwilioPhoneNumber: string | undefined;
|
|
4
4
|
let mockRawConfig: Record<string, unknown> | undefined;
|
|
5
5
|
let mockSecureKeys: Record<string, string>;
|
|
6
6
|
let mockHasTwilioCredentials: boolean;
|
|
7
|
-
let mockGatewayHealth = {
|
|
8
|
-
target: "http://127.0.0.1:7830",
|
|
9
|
-
healthy: true,
|
|
10
|
-
localDeployment: true,
|
|
11
|
-
error: undefined as string | undefined,
|
|
12
|
-
};
|
|
13
7
|
|
|
14
8
|
mock.module("../calls/twilio-rest.js", () => ({
|
|
15
9
|
getPhoneNumberSid: async () => null,
|
|
@@ -57,14 +51,12 @@ mock.module("../runtime/channel-invite-transports/whatsapp.js", () => ({
|
|
|
57
51
|
import type { ChannelId } from "../channels/types.js";
|
|
58
52
|
import {
|
|
59
53
|
ChannelReadinessService,
|
|
60
|
-
createReadinessService,
|
|
61
54
|
REMOTE_TTL_MS,
|
|
62
55
|
} from "../runtime/channel-readiness-service.js";
|
|
63
56
|
import type {
|
|
64
57
|
ChannelProbe,
|
|
65
58
|
ReadinessCheckResult,
|
|
66
59
|
} from "../runtime/channel-readiness-types.js";
|
|
67
|
-
import * as localGatewayHealth from "../runtime/local-gateway-health.js";
|
|
68
60
|
|
|
69
61
|
// ── Test helpers ────────────────────────────────────────────────────────────
|
|
70
62
|
|
|
@@ -104,12 +96,6 @@ describe("ChannelReadinessService", () => {
|
|
|
104
96
|
mockRawConfig = undefined;
|
|
105
97
|
mockSecureKeys = {};
|
|
106
98
|
mockHasTwilioCredentials = false;
|
|
107
|
-
mockGatewayHealth = {
|
|
108
|
-
target: "http://127.0.0.1:7830",
|
|
109
|
-
healthy: true,
|
|
110
|
-
localDeployment: true,
|
|
111
|
-
error: undefined,
|
|
112
|
-
};
|
|
113
99
|
});
|
|
114
100
|
|
|
115
101
|
test("local checks run on every call (no caching of local results)", async () => {
|
|
@@ -403,49 +389,4 @@ describe("ChannelReadinessService", () => {
|
|
|
403
389
|
|
|
404
390
|
expect(probe.remoteCallCount).toBe(1);
|
|
405
391
|
});
|
|
406
|
-
|
|
407
|
-
test("voice readiness includes gateway_health when ingress is configured", async () => {
|
|
408
|
-
mockHasTwilioCredentials = true;
|
|
409
|
-
mockTwilioPhoneNumber = "+15550001111";
|
|
410
|
-
mockRawConfig = {
|
|
411
|
-
ingress: {
|
|
412
|
-
enabled: true,
|
|
413
|
-
publicBaseUrl: "https://voice.example.com",
|
|
414
|
-
},
|
|
415
|
-
};
|
|
416
|
-
mockGatewayHealth = {
|
|
417
|
-
target: "http://127.0.0.1:7830",
|
|
418
|
-
healthy: false,
|
|
419
|
-
localDeployment: true,
|
|
420
|
-
error: "connect ECONNREFUSED 127.0.0.1:7830",
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
const probeLocalGatewayHealthSpy = spyOn(
|
|
424
|
-
localGatewayHealth,
|
|
425
|
-
"probeLocalGatewayHealth",
|
|
426
|
-
).mockImplementation(async () => ({
|
|
427
|
-
...mockGatewayHealth,
|
|
428
|
-
}));
|
|
429
|
-
|
|
430
|
-
let snapshot: Awaited<
|
|
431
|
-
ReturnType<ChannelReadinessService["getReadiness"]>
|
|
432
|
-
>[number];
|
|
433
|
-
try {
|
|
434
|
-
const readinessService = createReadinessService();
|
|
435
|
-
[snapshot] = await readinessService.getReadiness("phone");
|
|
436
|
-
} finally {
|
|
437
|
-
probeLocalGatewayHealthSpy.mockRestore();
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
const gatewayHealthCheck = snapshot.localChecks.find(
|
|
441
|
-
(check) => check.name === "gateway_health",
|
|
442
|
-
);
|
|
443
|
-
expect(gatewayHealthCheck).toBeDefined();
|
|
444
|
-
expect(gatewayHealthCheck?.passed).toBe(false);
|
|
445
|
-
expect(snapshot.reasons).toContainEqual({
|
|
446
|
-
code: "gateway_health",
|
|
447
|
-
text: "Local gateway is not serving requests at http://127.0.0.1:7830: connect ECONNREFUSED 127.0.0.1:7830",
|
|
448
|
-
});
|
|
449
|
-
expect(snapshot.ready).toBe(false);
|
|
450
|
-
});
|
|
451
392
|
});
|
|
@@ -1606,13 +1606,15 @@ describe("Permission Checker", () => {
|
|
|
1606
1606
|
expect(options[0].description).toContain("compound");
|
|
1607
1607
|
});
|
|
1608
1608
|
|
|
1609
|
-
test("compound command via pipeline yields exact-
|
|
1609
|
+
test("compound command via pipeline yields exact + action-key allowlist options", async () => {
|
|
1610
1610
|
const options = await generateAllowlistOptions("bash", {
|
|
1611
1611
|
command: "git log | grep fix",
|
|
1612
1612
|
});
|
|
1613
|
-
expect(options).
|
|
1613
|
+
expect(options.length).toBeGreaterThanOrEqual(2);
|
|
1614
1614
|
expect(options[0].description).toContain("compound");
|
|
1615
1615
|
expect(options[0].pattern).toBe("git log | grep fix");
|
|
1616
|
+
// Pipeline action keys should be offered as broader options
|
|
1617
|
+
expect(options.some((o) => o.pattern.startsWith("action:"))).toBe(true);
|
|
1616
1618
|
});
|
|
1617
1619
|
|
|
1618
1620
|
test("compound command via && yields exact-only allowlist option", async () => {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Guard test: assistant CLI commands must always classify as Low risk.
|
|
2
|
+
//
|
|
3
|
+
// The assistant uses its own CLI tools during normal operation. If these
|
|
4
|
+
// commands require user approval, it blocks autonomous assistant workflows.
|
|
5
|
+
// See #18982 / #18998 for the regression that motivated this guard.
|
|
6
|
+
|
|
7
|
+
import { mkdtempSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
const guardTestDir = mkdtempSync(join(tmpdir(), "cli-risk-guard-test-"));
|
|
13
|
+
|
|
14
|
+
mock.module("../util/platform.js", () => ({
|
|
15
|
+
getRootDir: () => guardTestDir,
|
|
16
|
+
getDataDir: () => join(guardTestDir, "data"),
|
|
17
|
+
getWorkspaceSkillsDir: () => join(guardTestDir, "skills"),
|
|
18
|
+
isMacOS: () => process.platform === "darwin",
|
|
19
|
+
isLinux: () => process.platform === "linux",
|
|
20
|
+
isWindows: () => process.platform === "win32",
|
|
21
|
+
getPidPath: () => join(guardTestDir, "test.pid"),
|
|
22
|
+
getDbPath: () => join(guardTestDir, "test.db"),
|
|
23
|
+
getLogPath: () => join(guardTestDir, "test.log"),
|
|
24
|
+
ensureDataDir: () => {},
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
mock.module("../util/logger.js", () => ({
|
|
28
|
+
getLogger: () =>
|
|
29
|
+
new Proxy({} as Record<string, unknown>, {
|
|
30
|
+
get: (_target: Record<string, unknown>, _prop: string) => {
|
|
31
|
+
return () => {};
|
|
32
|
+
},
|
|
33
|
+
}),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
mock.module("../config/loader.js", () => ({
|
|
37
|
+
getConfig: () => ({
|
|
38
|
+
permissions: { mode: "workspace" },
|
|
39
|
+
skills: { load: { extraDirs: [] } },
|
|
40
|
+
sandbox: { enabled: true },
|
|
41
|
+
}),
|
|
42
|
+
loadConfig: () => ({}),
|
|
43
|
+
invalidateConfigCache: () => {},
|
|
44
|
+
saveConfig: () => {},
|
|
45
|
+
loadRawConfig: () => ({}),
|
|
46
|
+
saveRawConfig: () => {},
|
|
47
|
+
getNestedValue: () => undefined,
|
|
48
|
+
setNestedValue: () => {},
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
import { buildCliProgram } from "../cli/program.js";
|
|
52
|
+
import { classifyRisk } from "../permissions/checker.js";
|
|
53
|
+
import { RiskLevel } from "../permissions/types.js";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Assert that a command classifies as Low risk, with a descriptive failure
|
|
57
|
+
* message that guides developers toward the correct fix.
|
|
58
|
+
*/
|
|
59
|
+
function expectLowRisk(command: string, actual: RiskLevel): void {
|
|
60
|
+
if (actual !== RiskLevel.Low) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`"${command}" classified as ${actual} instead of Low. ` +
|
|
63
|
+
`assistant CLI commands must always be Low risk — the assistant ` +
|
|
64
|
+
`uses its own CLI during normal operation. If you need risk ` +
|
|
65
|
+
`escalation for specific subcommands, add them to an allowlist ` +
|
|
66
|
+
`in this guard test with justification.`,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
expect(actual).toBe(RiskLevel.Low);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Dynamically extract subcommand names from the CLI program definition.
|
|
73
|
+
// This ensures new commands added to program.ts are automatically covered
|
|
74
|
+
// by this guard test without manual list maintenance.
|
|
75
|
+
const program = buildCliProgram();
|
|
76
|
+
const ASSISTANT_SUBCOMMANDS = program.commands.map((c) => c.name());
|
|
77
|
+
|
|
78
|
+
describe("CLI command risk guard: assistant commands", () => {
|
|
79
|
+
test("subcommand discovery found a reasonable number of commands", () => {
|
|
80
|
+
// Sanity check: if mocking breaks and no commands are registered,
|
|
81
|
+
// the risk guard would vacuously pass. Require a minimum count to
|
|
82
|
+
// catch that failure mode. Update this threshold when commands are
|
|
83
|
+
// removed (but it should only grow).
|
|
84
|
+
expect(ASSISTANT_SUBCOMMANDS.length).toBeGreaterThanOrEqual(20);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("all assistant CLI subcommands classify as Low risk", async () => {
|
|
88
|
+
for (const subcommand of ASSISTANT_SUBCOMMANDS) {
|
|
89
|
+
const command = `assistant ${subcommand}`;
|
|
90
|
+
const risk = await classifyRisk("bash", { command });
|
|
91
|
+
expectLowRisk(command, risk);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("bare assistant command classifies as Low risk", async () => {
|
|
96
|
+
const risk = await classifyRisk("bash", { command: "assistant" });
|
|
97
|
+
expectLowRisk("assistant", risk);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("assistant with flags classifies as Low risk", async () => {
|
|
101
|
+
const flagCommands = [
|
|
102
|
+
"assistant --version",
|
|
103
|
+
"assistant --help",
|
|
104
|
+
"assistant doctor --verbose",
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
for (const command of flagCommands) {
|
|
108
|
+
const risk = await classifyRisk("bash", { command });
|
|
109
|
+
expectLowRisk(command, risk);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -88,7 +88,6 @@ mock.module("../config/loader.js", () => ({
|
|
|
88
88
|
invalidateConfigCache: () => {},
|
|
89
89
|
getNestedValue: () => undefined,
|
|
90
90
|
setNestedValue: () => {},
|
|
91
|
-
syncConfigToLockfile: () => {},
|
|
92
91
|
}));
|
|
93
92
|
|
|
94
93
|
import { Command } from "commander";
|
|
@@ -205,7 +204,6 @@ describe("z.toJSONSchema integration", () => {
|
|
|
205
204
|
expect(properties).toBeDefined();
|
|
206
205
|
// Check that top-level keys are present
|
|
207
206
|
expect(properties.services).toBeDefined();
|
|
208
|
-
expect(properties.providerOrder).toBeDefined();
|
|
209
207
|
expect(properties.maxTokens).toBeDefined();
|
|
210
208
|
expect(properties.calls).toBeDefined();
|
|
211
209
|
expect(properties.memory).toBeDefined();
|
|
@@ -137,7 +137,6 @@ describe("AssistantConfigSchema", () => {
|
|
|
137
137
|
action: "redact",
|
|
138
138
|
entropyThreshold: 4.0,
|
|
139
139
|
allowOneTimeSend: false,
|
|
140
|
-
blockIngress: true,
|
|
141
140
|
});
|
|
142
141
|
expect(result.auditLog).toEqual({ retentionDays: 0 });
|
|
143
142
|
});
|
|
@@ -638,6 +637,9 @@ describe("AssistantConfigSchema", () => {
|
|
|
638
637
|
voice: {
|
|
639
638
|
language: "en-US",
|
|
640
639
|
transcriptionProvider: "Deepgram",
|
|
640
|
+
ttsProvider: "elevenlabs",
|
|
641
|
+
hints: [],
|
|
642
|
+
interruptSensitivity: "low",
|
|
641
643
|
},
|
|
642
644
|
callerIdentity: {
|
|
643
645
|
allowPerCallOverride: true,
|
|
@@ -168,7 +168,6 @@ mock.module("../memory/retriever.js", () => ({
|
|
|
168
168
|
injectedText: "",
|
|
169
169
|
|
|
170
170
|
semanticHits: 0,
|
|
171
|
-
recencyHits: 0,
|
|
172
171
|
injectedTokens: 0,
|
|
173
172
|
latencyMs: 0,
|
|
174
173
|
}),
|
|
@@ -199,7 +198,6 @@ mock.module("../daemon/conversation-memory.js", () => ({
|
|
|
199
198
|
injectedText: "",
|
|
200
199
|
|
|
201
200
|
semanticHits: 0,
|
|
202
|
-
recencyHits: 0,
|
|
203
201
|
injectedTokens: 0,
|
|
204
202
|
latencyMs: 0,
|
|
205
203
|
tier1Count: 0,
|
|
@@ -158,7 +158,6 @@ mock.module("../memory/retriever.js", () => ({
|
|
|
158
158
|
injectedText: "",
|
|
159
159
|
|
|
160
160
|
semanticHits: 0,
|
|
161
|
-
recencyHits: 0,
|
|
162
161
|
injectedTokens: 0,
|
|
163
162
|
latencyMs: 0,
|
|
164
163
|
}),
|
|
@@ -189,7 +188,6 @@ mock.module("../daemon/conversation-memory.js", () => ({
|
|
|
189
188
|
injectedText: "",
|
|
190
189
|
|
|
191
190
|
semanticHits: 0,
|
|
192
|
-
recencyHits: 0,
|
|
193
191
|
injectedTokens: 0,
|
|
194
192
|
latencyMs: 0,
|
|
195
193
|
tier1Count: 0,
|
|
@@ -614,7 +612,7 @@ describe("session-agent-loop", () => {
|
|
|
614
612
|
});
|
|
615
613
|
|
|
616
614
|
describe("LLM request log persistence", () => {
|
|
617
|
-
test("record request log
|
|
615
|
+
test("record request log captures the actual provider name", async () => {
|
|
618
616
|
const events: ServerMessage[] = [];
|
|
619
617
|
const rawRequest = {
|
|
620
618
|
model: "gpt-4.1",
|
|
@@ -768,7 +766,7 @@ describe("session-agent-loop", () => {
|
|
|
768
766
|
});
|
|
769
767
|
|
|
770
768
|
describe("usage accounting", () => {
|
|
771
|
-
test("records the actual provider for
|
|
769
|
+
test("records the actual provider for usage accounting", async () => {
|
|
772
770
|
const events: ServerMessage[] = [];
|
|
773
771
|
|
|
774
772
|
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
@@ -31,11 +31,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
31
31
|
truncateForLog: (value: string) => value,
|
|
32
32
|
}));
|
|
33
33
|
|
|
34
|
-
// Mock security check to always pass
|
|
35
|
-
mock.module("../security/secret-ingress.js", () => ({
|
|
36
|
-
checkIngressForSecrets: () => ({ blocked: false }),
|
|
37
|
-
}));
|
|
38
|
-
|
|
39
34
|
// Mock render to return the raw content as text
|
|
40
35
|
mock.module("../daemon/handlers/shared.js", () => ({
|
|
41
36
|
renderHistoryContent: (content: unknown) => ({
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
classifyConversationError,
|
|
7
7
|
isUserCancellation,
|
|
8
8
|
} from "../daemon/conversation-error.js";
|
|
9
|
-
import { ProviderError } from "../util/errors.js";
|
|
9
|
+
import { ProviderError, ProviderNotConfiguredError } from "../util/errors.js";
|
|
10
10
|
|
|
11
11
|
describe("isUserCancellation", () => {
|
|
12
12
|
it("returns false for non-AbortError even when abort flag is set", () => {
|
|
@@ -278,6 +278,20 @@ describe("classifyConversationError", () => {
|
|
|
278
278
|
});
|
|
279
279
|
});
|
|
280
280
|
|
|
281
|
+
describe("provider not configured errors", () => {
|
|
282
|
+
it("classifies ProviderNotConfiguredError as PROVIDER_NOT_CONFIGURED", () => {
|
|
283
|
+
const err = new ProviderNotConfiguredError("anthropic", []);
|
|
284
|
+
const result = classifyConversationError(err, baseCtx);
|
|
285
|
+
expect(result.code).toBe("PROVIDER_NOT_CONFIGURED");
|
|
286
|
+
expect(result.userMessage).toBe(
|
|
287
|
+
"No API key configured for inference. Add one in Settings to start chatting.",
|
|
288
|
+
);
|
|
289
|
+
expect(result.retryable).toBe(true);
|
|
290
|
+
expect(result.errorCategory).toBe("provider_not_configured");
|
|
291
|
+
expect(result.debugDetails).toBeDefined();
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
281
295
|
describe("streaming corruption errors", () => {
|
|
282
296
|
const cases = [
|
|
283
297
|
"Unexpected event order, got message_start before receiving message_stop",
|
|
@@ -111,10 +111,8 @@ mock.module("../util/platform.js", () => ({
|
|
|
111
111
|
getTCPPort: () => 8765,
|
|
112
112
|
isIOSPairingEnabled: () => false,
|
|
113
113
|
isTCPEnabled: () => false,
|
|
114
|
-
readLockfile: () => null,
|
|
115
114
|
readPlatformToken: () => null,
|
|
116
115
|
readSessionToken: () => null,
|
|
117
|
-
writeLockfile: () => {},
|
|
118
116
|
ensureDataDir: () => {},
|
|
119
117
|
}));
|
|
120
118
|
|
|
@@ -28,7 +28,7 @@ mock.module("../security/secure-keys.js", () => ({
|
|
|
28
28
|
setSecureKeyAsync: async (key?: string, value?: string) =>
|
|
29
29
|
setSecureKeyMock(key, value),
|
|
30
30
|
deleteSecureKeyAsync: async () => "deleted" as const,
|
|
31
|
-
listSecureKeysAsync: async () => [],
|
|
31
|
+
listSecureKeysAsync: async () => ({ accounts: [], unreachable: false }),
|
|
32
32
|
_resetBackend: () => {},
|
|
33
33
|
}));
|
|
34
34
|
|