@vellumai/assistant 0.5.5 → 0.5.7
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 +4 -5
- package/README.md +0 -2
- package/bun.lock +0 -414
- package/docs/architecture/keychain-broker.md +45 -240
- 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/rpc.ts +119 -0
- 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 +1 -2
- package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
- package/src/__tests__/browser-skill-endstate.test.ts +6 -5
- package/src/__tests__/btw-routes.test.ts +0 -39
- package/src/__tests__/call-domain.test.ts +0 -128
- package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -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 -1
- package/src/__tests__/config-schema.test.ts +3 -3
- package/src/__tests__/context-window-manager.test.ts +78 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
- package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
- package/src/__tests__/conversation-skill-tools.test.ts +0 -54
- package/src/__tests__/conversation-title-service.test.ts +117 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
- package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
- package/src/__tests__/credential-security-e2e.test.ts +0 -66
- package/src/__tests__/credential-security-invariants.test.ts +4 -45
- package/src/__tests__/credentials-cli.test.ts +78 -0
- package/src/__tests__/db-migration-rollback.test.ts +2015 -1
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +98 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
- 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__/keychain-broker-client.test.ts +161 -22
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
- package/src/__tests__/memory-regressions.test.ts +8 -30
- 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 +0 -5
- package/src/__tests__/notification-decision-fallback.test.ts +4 -0
- package/src/__tests__/notification-decision-identity.test.ts +4 -0
- package/src/__tests__/permission-types.test.ts +1 -0
- package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
- package/src/__tests__/qdrant-manager.test.ts +28 -2
- package/src/__tests__/registry.test.ts +0 -6
- package/src/__tests__/require-fresh-approval.test.ts +4 -0
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
- package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
- package/src/__tests__/secure-keys.test.ts +83 -263
- 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-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__/suggestion-routes.test.ts +1 -32
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
- package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
- package/src/__tests__/tool-executor.test.ts +4 -0
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
- package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
- 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-scoped-grant-consumer.test.ts +0 -6
- 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 +218 -0
- package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
- package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
- package/src/calls/audio-store.test.ts +97 -0
- package/src/calls/audio-store.ts +205 -0
- package/src/calls/call-controller.ts +85 -7
- package/src/calls/call-domain.ts +3 -0
- package/src/calls/call-store.ts +10 -3
- package/src/calls/fish-audio-client.ts +117 -0
- package/src/calls/relay-server.ts +27 -0
- package/src/calls/twilio-routes.ts +2 -1
- package/src/calls/types.ts +1 -0
- package/src/calls/voice-ingress-preflight.ts +0 -42
- package/src/calls/voice-quality.ts +26 -5
- package/src/calls/voice-session-bridge.ts +6 -12
- package/src/cli/commands/config.ts +1 -4
- package/src/cli/commands/conversations.ts +0 -18
- package/src/cli/commands/credentials.ts +34 -4
- package/src/cli/commands/oauth/index.ts +7 -0
- package/src/cli/commands/oauth/platform.ts +179 -0
- package/src/cli/commands/platform.ts +3 -3
- package/src/config/assistant-feature-flags.ts +186 -5
- package/src/config/bundled-skills/messaging/SKILL.md +5 -5
- package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
- package/src/config/bundled-skills/settings/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
- package/src/config/bundled-tool-registry.ts +1 -11
- package/src/config/env-registry.ts +1 -1
- package/src/config/env.ts +16 -16
- package/src/config/feature-flag-registry.json +48 -16
- package/src/config/loader.ts +98 -31
- package/src/config/schema.ts +4 -25
- package/src/config/schemas/calls.ts +13 -0
- package/src/config/schemas/fish-audio.ts +39 -0
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/platform.ts +1 -1
- package/src/config/schemas/security.ts +4 -4
- package/src/config/types.ts +0 -1
- package/src/contacts/contact-store.ts +39 -0
- package/src/contacts/types.ts +2 -0
- package/src/context/window-manager.ts +53 -2
- 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/daemon/assistant-attachments.ts +9 -0
- package/src/daemon/config-watcher.ts +6 -4
- package/src/daemon/conversation-agent-loop.ts +0 -60
- package/src/daemon/conversation-memory.ts +0 -117
- package/src/daemon/conversation-runtime-assembly.ts +0 -2
- package/src/daemon/conversation-tool-setup.ts +0 -105
- package/src/daemon/conversation.ts +10 -1
- package/src/daemon/handlers/config-vercel.ts +92 -0
- package/src/daemon/handlers/conversations.ts +0 -11
- package/src/daemon/handlers/skills.ts +2 -15
- package/src/daemon/install-symlink.ts +195 -0
- package/src/daemon/lifecycle.ts +229 -96
- package/src/daemon/message-types/conversations.ts +3 -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 +30 -92
- package/src/events/domain-events.ts +2 -1
- package/src/followups/followup-store.ts +5 -2
- package/src/inbound/platform-callback-registration.ts +3 -3
- package/src/instrument.ts +8 -5
- package/src/memory/conversation-crud.ts +0 -236
- package/src/memory/conversation-title-service.ts +76 -11
- package/src/memory/db-init.ts +15 -11
- package/src/memory/indexer.ts +15 -106
- package/src/memory/items-extractor.ts +15 -1
- package/src/memory/job-handlers/conversation-starters.ts +4 -1
- package/src/memory/job-handlers/embedding.ts +0 -79
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +30 -13
- package/src/memory/jobs-worker.ts +31 -27
- 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/189-drop-simplified-memory.ts +42 -0
- 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/index.ts +5 -3
- package/src/memory/migrations/registry.ts +90 -0
- package/src/memory/migrations/validate-migration-state.ts +137 -11
- package/src/memory/qdrant-circuit-breaker.ts +9 -0
- package/src/memory/qdrant-client.ts +4 -6
- package/src/memory/qdrant-manager.ts +64 -7
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/conversations.ts +0 -3
- package/src/memory/schema/index.ts +0 -2
- package/src/messaging/draft-store.ts +2 -2
- package/src/notifications/decision-engine.ts +4 -1
- package/src/oauth/connection-resolver.ts +6 -4
- package/src/permissions/checker.ts +0 -38
- package/src/permissions/defaults.ts +3 -3
- package/src/permissions/shell-identity.ts +76 -22
- package/src/permissions/trust-client.ts +2 -13
- package/src/permissions/trust-store.ts +8 -3
- package/src/permissions/types.ts +4 -2
- package/src/platform/client.ts +35 -7
- package/src/prompts/persona-resolver.ts +138 -0
- package/src/prompts/system-prompt.ts +36 -4
- package/src/prompts/templates/users/default.md +1 -0
- package/src/providers/registry.ts +27 -40
- 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/external-assistant-id.ts +13 -59
- package/src/runtime/auth/route-policy.ts +29 -1
- package/src/runtime/auth/token-service.ts +53 -15
- package/src/runtime/channel-readiness-service.ts +1 -16
- package/src/runtime/http-server.ts +29 -2
- package/src/runtime/middleware/error-handler.ts +1 -9
- package/src/runtime/routes/audio-routes.ts +40 -0
- package/src/runtime/routes/btw-routes.ts +0 -17
- package/src/runtime/routes/conversation-management-routes.ts +0 -36
- package/src/runtime/routes/conversation-query-routes.ts +106 -2
- package/src/runtime/routes/conversation-routes.ts +4 -43
- package/src/runtime/routes/diagnostics-routes.ts +1 -477
- package/src/runtime/routes/identity-routes.ts +18 -29
- 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/vercel.ts +89 -0
- package/src/runtime/routes/log-export-routes.ts +5 -0
- package/src/runtime/routes/memory-item-routes.test.ts +221 -3
- package/src/runtime/routes/memory-item-routes.ts +144 -4
- 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/settings-routes.ts +41 -1
- package/src/runtime/routes/tts-routes.ts +86 -0
- package/src/runtime/routes/upgrade-broadcast-routes.ts +175 -0
- 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/schedule/schedule-store.ts +0 -21
- package/src/security/ces-credential-client.ts +59 -22
- package/src/security/ces-rpc-credential-backend.ts +85 -0
- package/src/security/credential-backend.ts +12 -88
- package/src/security/keychain-broker-client.ts +10 -2
- package/src/security/secure-keys.ts +94 -113
- package/src/skills/catalog-install.ts +13 -7
- package/src/skills/inline-command-render.ts +5 -1
- package/src/skills/inline-command-runner.ts +30 -2
- package/src/telemetry/usage-telemetry-reporter.ts +4 -2
- package/src/tools/calls/call-start.ts +1 -0
- package/src/tools/executor.ts +0 -4
- package/src/tools/memory/handlers.ts +1 -129
- package/src/tools/network/script-proxy/session-manager.ts +19 -4
- package/src/tools/network/web-fetch.ts +3 -1
- package/src/tools/permission-checker.ts +18 -0
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/skills/load.ts +9 -2
- package/src/tools/types.ts +0 -8
- package/src/util/errors.ts +0 -12
- package/src/util/platform.ts +8 -55
- package/src/util/xml.ts +8 -0
- package/src/workspace/git-service.ts +5 -2
- package/src/workspace/heartbeat-service.ts +5 -24
- 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 +95 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/workspace/migrations/runner.ts +106 -2
- package/src/workspace/migrations/types.ts +4 -0
- package/src/__tests__/archive-recall.test.ts +0 -560
- 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__/conversation-memory-dirty-tail.test.ts +0 -150
- package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
- package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
- package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
- package/src/__tests__/diagnostics-export.test.ts +0 -288
- package/src/__tests__/local-gateway-health.test.ts +0 -209
- package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
- package/src/__tests__/memory-brief-time.test.ts +0 -285
- package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
- package/src/__tests__/memory-chunk-archive.test.ts +0 -400
- package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
- package/src/__tests__/memory-episode-archive.test.ts +0 -370
- package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
- package/src/__tests__/memory-observation-archive.test.ts +0 -375
- package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
- package/src/__tests__/memory-reducer-job.test.ts +0 -538
- package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
- package/src/__tests__/memory-reducer-store.test.ts +0 -728
- package/src/__tests__/memory-reducer-types.test.ts +0 -707
- package/src/__tests__/memory-reducer.test.ts +0 -704
- package/src/__tests__/memory-simplified-config.test.ts +0 -281
- package/src/__tests__/secret-ingress-handler.test.ts +0 -120
- package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
- package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
- 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/memory-simplified.ts +0 -101
- package/src/config/schemas/swarm.ts +0 -82
- package/src/logfire.ts +0 -135
- package/src/memory/archive-recall.ts +0 -516
- package/src/memory/archive-store.ts +0 -400
- package/src/memory/brief-formatting.ts +0 -33
- package/src/memory/brief-open-loops.ts +0 -266
- package/src/memory/brief-time.ts +0 -162
- package/src/memory/brief.ts +0 -75
- package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
- package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
- package/src/memory/migrations/185-memory-brief-state.ts +0 -52
- package/src/memory/migrations/186-memory-archive.ts +0 -109
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
- package/src/memory/reducer-scheduler.ts +0 -242
- package/src/memory/reducer-store.ts +0 -271
- package/src/memory/reducer-types.ts +0 -106
- package/src/memory/reducer.ts +0 -467
- package/src/memory/schema/memory-archive.ts +0 -121
- package/src/memory/schema/memory-brief.ts +0 -55
- 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,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for resolveSigningKey() covering env var injection (Docker)
|
|
3
|
+
* and file-based load/create (local mode).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync } from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { afterAll, afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
const testDir = realpathSync(mkdtempSync(join(tmpdir(), "signing-key-test-")));
|
|
12
|
+
|
|
13
|
+
mock.module("../util/platform.js", () => ({
|
|
14
|
+
getRootDir: () => testDir,
|
|
15
|
+
getDataDir: () => testDir,
|
|
16
|
+
getDbPath: () => join(testDir, "test.db"),
|
|
17
|
+
normalizeAssistantId: (id: string) => (id === "self" ? "self" : id),
|
|
18
|
+
isMacOS: () => process.platform === "darwin",
|
|
19
|
+
isLinux: () => process.platform === "linux",
|
|
20
|
+
isWindows: () => process.platform === "win32",
|
|
21
|
+
getPidPath: () => join(testDir, "test.pid"),
|
|
22
|
+
getLogPath: () => join(testDir, "test.log"),
|
|
23
|
+
ensureDataDir: () => {},
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
mock.module("../util/logger.js", () => ({
|
|
27
|
+
getLogger: () =>
|
|
28
|
+
new Proxy({} as Record<string, unknown>, {
|
|
29
|
+
get: () => () => {},
|
|
30
|
+
}),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
const { resolveSigningKey } = await import("../runtime/auth/token-service.js");
|
|
34
|
+
|
|
35
|
+
const VALID_HEX_KEY = "ab".repeat(32); // 64 hex chars = 32 bytes
|
|
36
|
+
|
|
37
|
+
const savedEnv: Record<string, string | undefined> = {};
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
savedEnv.ACTOR_TOKEN_SIGNING_KEY = process.env.ACTOR_TOKEN_SIGNING_KEY;
|
|
41
|
+
mkdirSync(join(testDir, "protected"), { recursive: true });
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
if (savedEnv.ACTOR_TOKEN_SIGNING_KEY === undefined) {
|
|
46
|
+
delete process.env.ACTOR_TOKEN_SIGNING_KEY;
|
|
47
|
+
} else {
|
|
48
|
+
process.env.ACTOR_TOKEN_SIGNING_KEY = savedEnv.ACTOR_TOKEN_SIGNING_KEY;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
afterAll(() => {
|
|
53
|
+
try {
|
|
54
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
55
|
+
} catch {}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("resolveSigningKey", () => {
|
|
59
|
+
test("reads key from ACTOR_TOKEN_SIGNING_KEY env var", () => {
|
|
60
|
+
process.env.ACTOR_TOKEN_SIGNING_KEY = VALID_HEX_KEY;
|
|
61
|
+
|
|
62
|
+
const key = resolveSigningKey();
|
|
63
|
+
|
|
64
|
+
expect(key).toBeInstanceOf(Buffer);
|
|
65
|
+
expect(key.length).toBe(32);
|
|
66
|
+
expect(key.toString("hex")).toBe(VALID_HEX_KEY);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("rejects invalid ACTOR_TOKEN_SIGNING_KEY", () => {
|
|
70
|
+
process.env.ACTOR_TOKEN_SIGNING_KEY = "tooshort";
|
|
71
|
+
|
|
72
|
+
expect(() => resolveSigningKey()).toThrow("Invalid ACTOR_TOKEN_SIGNING_KEY");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("falls back to file-based load/create when env var is not set", () => {
|
|
76
|
+
delete process.env.ACTOR_TOKEN_SIGNING_KEY;
|
|
77
|
+
|
|
78
|
+
const key = resolveSigningKey();
|
|
79
|
+
|
|
80
|
+
expect(key).toBeInstanceOf(Buffer);
|
|
81
|
+
expect(key.length).toBe(32);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("env var takes priority over file on disk", () => {
|
|
85
|
+
process.env.ACTOR_TOKEN_SIGNING_KEY = VALID_HEX_KEY;
|
|
86
|
+
|
|
87
|
+
// First call creates a file-based key
|
|
88
|
+
delete process.env.ACTOR_TOKEN_SIGNING_KEY;
|
|
89
|
+
const fileKey = resolveSigningKey();
|
|
90
|
+
|
|
91
|
+
// Second call with env var should use the env var, not the file
|
|
92
|
+
process.env.ACTOR_TOKEN_SIGNING_KEY = "cd".repeat(32);
|
|
93
|
+
const envKey = resolveSigningKey();
|
|
94
|
+
|
|
95
|
+
expect(envKey.toString("hex")).toBe("cd".repeat(32));
|
|
96
|
+
expect(envKey.toString("hex")).not.toBe(fileKey.toString("hex"));
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -53,9 +53,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
53
53
|
|
|
54
54
|
mock.module("../config/loader.js", () => ({
|
|
55
55
|
getConfig: () => ({
|
|
56
|
-
assistantFeatureFlagValues: {
|
|
57
|
-
"feature_flags.browser.enabled": true,
|
|
58
|
-
},
|
|
59
56
|
services: {
|
|
60
57
|
inference: {
|
|
61
58
|
mode: "your-own",
|
|
@@ -77,17 +74,22 @@ mock.module("../config/loader.js", () => ({
|
|
|
77
74
|
invalidateConfigCache: () => {},
|
|
78
75
|
getNestedValue: () => undefined,
|
|
79
76
|
setNestedValue: () => {},
|
|
80
|
-
syncConfigToLockfile: () => {},
|
|
81
77
|
}));
|
|
82
78
|
|
|
83
79
|
const { buildSystemPrompt } = await import("../prompts/system-prompt.js");
|
|
80
|
+
const { _setOverridesForTesting } =
|
|
81
|
+
await import("../config/assistant-feature-flags.js");
|
|
84
82
|
|
|
85
83
|
describe("Dynamic Skill Authoring Workflow moved to tool descriptions", () => {
|
|
86
84
|
beforeEach(() => {
|
|
87
85
|
mkdirSync(TEST_DIR, { recursive: true });
|
|
86
|
+
_setOverridesForTesting({
|
|
87
|
+
"feature_flags.browser.enabled": true,
|
|
88
|
+
});
|
|
88
89
|
});
|
|
89
90
|
|
|
90
91
|
afterEach(() => {
|
|
92
|
+
_setOverridesForTesting({});
|
|
91
93
|
if (existsSync(TEST_DIR)) {
|
|
92
94
|
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
93
95
|
}
|
|
@@ -31,11 +31,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
31
31
|
}),
|
|
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
|
import { upsertContact } from "../contacts/contact-store.js";
|
|
40
35
|
import { createGuardianBinding } from "../contacts/contacts-write.js";
|
|
41
36
|
import type { TrustContext } from "../daemon/conversation-runtime-assembly.js";
|
|
@@ -846,12 +846,12 @@ describe("host_bash — proxy delegation", () => {
|
|
|
846
846
|
});
|
|
847
847
|
|
|
848
848
|
test("propagates VELLUM_UNTRUSTED_SHELL env to proxy under CES lockdown", async () => {
|
|
849
|
-
// Enable CES shell lockdown
|
|
850
|
-
const
|
|
851
|
-
.
|
|
852
|
-
(
|
|
849
|
+
// Enable CES shell lockdown via the override cache
|
|
850
|
+
const { _setOverridesForTesting } =
|
|
851
|
+
await import("../config/assistant-feature-flags.js");
|
|
852
|
+
_setOverridesForTesting({
|
|
853
853
|
"feature_flags.ces-shell-lockdown.enabled": true,
|
|
854
|
-
};
|
|
854
|
+
});
|
|
855
855
|
|
|
856
856
|
try {
|
|
857
857
|
const proxyResult: ToolExecutionResult = {
|
|
@@ -875,8 +875,7 @@ describe("host_bash — proxy delegation", () => {
|
|
|
875
875
|
expect(calls.length).toBe(1);
|
|
876
876
|
expect(calls[0].input.env).toEqual({ VELLUM_UNTRUSTED_SHELL: "1" });
|
|
877
877
|
} finally {
|
|
878
|
-
(
|
|
879
|
-
origFlags;
|
|
878
|
+
_setOverridesForTesting({});
|
|
880
879
|
}
|
|
881
880
|
});
|
|
882
881
|
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* Tests for HTTP POST /v1/messages behavior after the legacy handleUserMessage
|
|
3
3
|
* legacy entry point was retired.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* through the agent loop.
|
|
5
|
+
* Recording intent interception has been deliberately retired — the HTTP path
|
|
6
|
+
* has dedicated /v1/recording/* endpoints and the model handles
|
|
7
|
+
* recording-related messages through the agent loop.
|
|
9
8
|
*
|
|
10
9
|
* Approval reply interception has parity and is covered by
|
|
11
10
|
* conversation-routes-guardian-reply.test.ts and send-endpoint-busy.test.ts.
|
|
@@ -121,12 +120,10 @@ mock.module("../runtime/trust-context-resolver.js", () => ({
|
|
|
121
120
|
}),
|
|
122
121
|
}));
|
|
123
122
|
|
|
124
|
-
// Mock config to enable secret detection + ingress blocking
|
|
125
123
|
mock.module("../config/loader.js", () => ({
|
|
126
124
|
getConfig: () => ({
|
|
127
125
|
secretDetection: {
|
|
128
126
|
enabled: true,
|
|
129
|
-
blockIngress: true,
|
|
130
127
|
customPatterns: [],
|
|
131
128
|
entropyThreshold: 3.5,
|
|
132
129
|
},
|
|
@@ -238,103 +235,6 @@ async function sendMessage(
|
|
|
238
235
|
);
|
|
239
236
|
}
|
|
240
237
|
|
|
241
|
-
// ============================================================================
|
|
242
|
-
// SECRET INGRESS BLOCKING — now ported to HTTP path
|
|
243
|
-
// ============================================================================
|
|
244
|
-
describe("HTTP POST /v1/messages blocks secret ingress", () => {
|
|
245
|
-
beforeEach(() => {
|
|
246
|
-
routeGuardianReplyMock.mockClear();
|
|
247
|
-
listPendingByDestinationMock.mockClear();
|
|
248
|
-
listCanonicalMock.mockClear();
|
|
249
|
-
addMessageMock.mockClear();
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test("handleSendMessage rejects messages containing Telegram bot token patterns", async () => {
|
|
253
|
-
const secretContent =
|
|
254
|
-
"Set up Telegram with my bot token 123456789:ABCDefGHIJklmnopQRSTuvwxyz012345678";
|
|
255
|
-
const persistUserMessage = mock(async () => "persisted-msg-id");
|
|
256
|
-
const runAgentLoop = mock(async () => undefined);
|
|
257
|
-
const conversation = makeConversation({ persistUserMessage, runAgentLoop });
|
|
258
|
-
|
|
259
|
-
const res = await sendMessage(secretContent, conversation);
|
|
260
|
-
|
|
261
|
-
expect(res.status).toBe(422);
|
|
262
|
-
const body = (await res.json()) as {
|
|
263
|
-
accepted: boolean;
|
|
264
|
-
error: string;
|
|
265
|
-
message: string;
|
|
266
|
-
detectedTypes: string[];
|
|
267
|
-
};
|
|
268
|
-
expect(body.accepted).toBe(false);
|
|
269
|
-
expect(body.error).toBe("secret_blocked");
|
|
270
|
-
expect(body.detectedTypes.length).toBeGreaterThan(0);
|
|
271
|
-
|
|
272
|
-
// The message should NOT reach the agent loop
|
|
273
|
-
expect(persistUserMessage).toHaveBeenCalledTimes(0);
|
|
274
|
-
expect(runAgentLoop).toHaveBeenCalledTimes(0);
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
test("handleSendMessage rejects messages containing AWS credentials", async () => {
|
|
278
|
-
const secretContent =
|
|
279
|
-
"Here is my AWS key AKIAQRSTUVWXYZ123456 and secret wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY";
|
|
280
|
-
const persistUserMessage = mock(async () => "persisted-msg-id");
|
|
281
|
-
const runAgentLoop = mock(async () => undefined);
|
|
282
|
-
const conversation = makeConversation({ persistUserMessage, runAgentLoop });
|
|
283
|
-
|
|
284
|
-
const res = await sendMessage(secretContent, conversation);
|
|
285
|
-
|
|
286
|
-
expect(res.status).toBe(422);
|
|
287
|
-
const body = (await res.json()) as {
|
|
288
|
-
accepted: boolean;
|
|
289
|
-
error: string;
|
|
290
|
-
};
|
|
291
|
-
expect(body.accepted).toBe(false);
|
|
292
|
-
expect(body.error).toBe("secret_blocked");
|
|
293
|
-
|
|
294
|
-
// The message should NOT reach the agent loop
|
|
295
|
-
expect(persistUserMessage).toHaveBeenCalledTimes(0);
|
|
296
|
-
expect(runAgentLoop).toHaveBeenCalledTimes(0);
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
test("handleSendMessage rejects messages containing Stripe live API keys", async () => {
|
|
300
|
-
const secretContent = "My Stripe key is sk_live_4eC39HqLyjWDarjtT1zdp7dc";
|
|
301
|
-
const persistUserMessage = mock(async () => "persisted-msg-id");
|
|
302
|
-
const runAgentLoop = mock(async () => undefined);
|
|
303
|
-
const conversation = makeConversation({ persistUserMessage, runAgentLoop });
|
|
304
|
-
|
|
305
|
-
const res = await sendMessage(secretContent, conversation);
|
|
306
|
-
|
|
307
|
-
expect(res.status).toBe(422);
|
|
308
|
-
const body = (await res.json()) as {
|
|
309
|
-
accepted: boolean;
|
|
310
|
-
error: string;
|
|
311
|
-
};
|
|
312
|
-
expect(body.accepted).toBe(false);
|
|
313
|
-
expect(body.error).toBe("secret_blocked");
|
|
314
|
-
|
|
315
|
-
// The message should NOT reach the agent loop
|
|
316
|
-
expect(persistUserMessage).toHaveBeenCalledTimes(0);
|
|
317
|
-
expect(runAgentLoop).toHaveBeenCalledTimes(0);
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
test("handleSendMessage allows normal messages without secrets", async () => {
|
|
321
|
-
const normalContent = "What is the weather today?";
|
|
322
|
-
const persistUserMessage = mock(async () => "persisted-msg-id");
|
|
323
|
-
const runAgentLoop = mock(async () => undefined);
|
|
324
|
-
const conversation = makeConversation({ persistUserMessage, runAgentLoop });
|
|
325
|
-
|
|
326
|
-
const res = await sendMessage(normalContent, conversation);
|
|
327
|
-
|
|
328
|
-
expect(res.status).toBe(202);
|
|
329
|
-
const body = (await res.json()) as { accepted: boolean };
|
|
330
|
-
expect(body.accepted).toBe(true);
|
|
331
|
-
|
|
332
|
-
// Normal messages proceed to the agent loop
|
|
333
|
-
expect(persistUserMessage).toHaveBeenCalledTimes(1);
|
|
334
|
-
expect(runAgentLoop).toHaveBeenCalledTimes(1);
|
|
335
|
-
});
|
|
336
|
-
});
|
|
337
|
-
|
|
338
238
|
// ============================================================================
|
|
339
239
|
// RECORDING INTENT — deliberately NOT intercepted on HTTP path
|
|
340
240
|
// ============================================================================
|
|
@@ -35,10 +35,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
35
35
|
}),
|
|
36
36
|
}));
|
|
37
37
|
|
|
38
|
-
mock.module("../security/secret-ingress.js", () => ({
|
|
39
|
-
checkIngressForSecrets: () => ({ blocked: false }),
|
|
40
|
-
}));
|
|
41
|
-
|
|
42
38
|
mock.module("../config/env.js", () => ({
|
|
43
39
|
isHttpAuthDisabled: () => true,
|
|
44
40
|
getGatewayInternalBaseUrl: () => "http://127.0.0.1:7830",
|
|
@@ -18,6 +18,8 @@ import { tmpdir } from "node:os";
|
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
20
20
|
|
|
21
|
+
import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
|
|
22
|
+
|
|
21
23
|
// ── Mock setup (must be before any imports from the project) ──────────────
|
|
22
24
|
|
|
23
25
|
const testDir = mkdtempSync(join(tmpdir(), "inline-skill-perm-test-"));
|
|
@@ -46,7 +48,6 @@ interface TestConfig {
|
|
|
46
48
|
permissions: { mode: "strict" | "workspace" };
|
|
47
49
|
skills: { load: { extraDirs: string[] } };
|
|
48
50
|
sandbox: { enabled: boolean };
|
|
49
|
-
assistantFeatureFlagValues?: Record<string, boolean>;
|
|
50
51
|
[key: string]: unknown;
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -54,9 +55,6 @@ const testConfig: TestConfig = {
|
|
|
54
55
|
permissions: { mode: "workspace" },
|
|
55
56
|
skills: { load: { extraDirs: [] } },
|
|
56
57
|
sandbox: { enabled: true },
|
|
57
|
-
assistantFeatureFlagValues: {
|
|
58
|
-
"feature_flags.inline-skill-commands.enabled": true,
|
|
59
|
-
},
|
|
60
58
|
};
|
|
61
59
|
|
|
62
60
|
mock.module("../config/loader.js", () => ({
|
|
@@ -118,9 +116,9 @@ describe("inline-command skill_load permissions", () => {
|
|
|
118
116
|
clearCache();
|
|
119
117
|
testConfig.permissions = { mode: "workspace" };
|
|
120
118
|
testConfig.skills = { load: { extraDirs: [] } };
|
|
121
|
-
|
|
119
|
+
_setOverridesForTesting({
|
|
122
120
|
"feature_flags.inline-skill-commands.enabled": true,
|
|
123
|
-
};
|
|
121
|
+
});
|
|
124
122
|
try {
|
|
125
123
|
rmSync(join(testDir, "protected", "trust.json"));
|
|
126
124
|
} catch {
|
|
@@ -352,9 +350,9 @@ describe("inline-command skill_load permissions", () => {
|
|
|
352
350
|
writeDynamicSkill("dynamic-flag-off", "Dynamic Flag Off Skill");
|
|
353
351
|
|
|
354
352
|
// Disable the feature flag
|
|
355
|
-
|
|
353
|
+
_setOverridesForTesting({
|
|
356
354
|
"feature_flags.inline-skill-commands.enabled": false,
|
|
357
|
-
};
|
|
355
|
+
});
|
|
358
356
|
|
|
359
357
|
const result = await check(
|
|
360
358
|
"skill_load",
|
|
@@ -53,8 +53,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
53
53
|
mock.module("../config/loader.js", () => ({
|
|
54
54
|
getConfig: () => ({
|
|
55
55
|
ui: {},
|
|
56
|
-
|
|
57
|
-
assistantFeatureFlagValues: {},
|
|
58
56
|
services: {
|
|
59
57
|
inference: {
|
|
60
58
|
mode: "your-own",
|
|
@@ -76,7 +74,6 @@ mock.module("../config/loader.js", () => ({
|
|
|
76
74
|
invalidateConfigCache: () => {},
|
|
77
75
|
getNestedValue: () => undefined,
|
|
78
76
|
setNestedValue: () => {},
|
|
79
|
-
syncConfigToLockfile: () => {},
|
|
80
77
|
}));
|
|
81
78
|
|
|
82
79
|
// ── Import after mocks ───────────────────────────────────────────────
|
|
@@ -250,16 +247,6 @@ describe("Activation hints in skills catalog", () => {
|
|
|
250
247
|
expect(line).toContain("voice-setup");
|
|
251
248
|
});
|
|
252
249
|
|
|
253
|
-
test("orchestration skill includes hints and avoid-when in catalog line", () => {
|
|
254
|
-
const prompt = buildSystemPrompt();
|
|
255
|
-
const line = prompt
|
|
256
|
-
.split("\n")
|
|
257
|
-
.find((l) => l.includes("**orchestration**"));
|
|
258
|
-
expect(line).toBeDefined();
|
|
259
|
-
expect(line).toContain("parallel");
|
|
260
|
-
expect(line).toContain("Single-focus");
|
|
261
|
-
});
|
|
262
|
-
|
|
263
250
|
test("browser skill includes hints in catalog line", () => {
|
|
264
251
|
const prompt = buildSystemPrompt();
|
|
265
252
|
const line = prompt.split("\n").find((l) => l.includes("**browser**"));
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
afterAll,
|
|
6
|
+
beforeAll,
|
|
7
|
+
beforeEach,
|
|
8
|
+
describe,
|
|
9
|
+
expect,
|
|
10
|
+
mock,
|
|
11
|
+
test,
|
|
12
|
+
} from "bun:test";
|
|
13
|
+
|
|
14
|
+
const testDir = mkdtempSync(join(tmpdir(), "jobs-store-qdrant-breaker-"));
|
|
15
|
+
|
|
16
|
+
mock.module("../util/platform.js", () => ({
|
|
17
|
+
getDataDir: () => testDir,
|
|
18
|
+
isMacOS: () => process.platform === "darwin",
|
|
19
|
+
isLinux: () => process.platform === "linux",
|
|
20
|
+
isWindows: () => process.platform === "win32",
|
|
21
|
+
getPidPath: () => join(testDir, "test.pid"),
|
|
22
|
+
getDbPath: () => join(testDir, "test.db"),
|
|
23
|
+
getLogPath: () => join(testDir, "test.log"),
|
|
24
|
+
ensureDataDir: () => {},
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
mock.module("../util/logger.js", () => ({
|
|
28
|
+
getLogger: () =>
|
|
29
|
+
new Proxy({} as Record<string, unknown>, {
|
|
30
|
+
get: () => () => {},
|
|
31
|
+
}),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
mock.module("../config/loader.js", () => ({
|
|
35
|
+
loadConfig: () => ({}),
|
|
36
|
+
getConfig: () => ({}),
|
|
37
|
+
invalidateConfigCache: () => {},
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
import { getDb, initializeDb, resetDb } from "../memory/db.js";
|
|
41
|
+
import {
|
|
42
|
+
claimMemoryJobs,
|
|
43
|
+
enqueueMemoryJob,
|
|
44
|
+
type MemoryJobType,
|
|
45
|
+
} from "../memory/jobs-store.js";
|
|
46
|
+
import {
|
|
47
|
+
_resetQdrantBreaker,
|
|
48
|
+
withQdrantBreaker,
|
|
49
|
+
} from "../memory/qdrant-circuit-breaker.js";
|
|
50
|
+
|
|
51
|
+
describe("claimMemoryJobs with Qdrant circuit breaker", () => {
|
|
52
|
+
beforeAll(() => {
|
|
53
|
+
initializeDb();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
const db = getDb();
|
|
58
|
+
db.run("DELETE FROM memory_jobs");
|
|
59
|
+
_resetQdrantBreaker();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterAll(() => {
|
|
63
|
+
resetDb();
|
|
64
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("claims embed jobs when circuit breaker is closed (healthy)", () => {
|
|
68
|
+
enqueueMemoryJob("embed_segment", { segmentId: "seg-1" });
|
|
69
|
+
enqueueMemoryJob("embed_item", { itemId: "item-1" });
|
|
70
|
+
enqueueMemoryJob("extract_items", { conversationId: "conv-1" });
|
|
71
|
+
|
|
72
|
+
const claimed = claimMemoryJobs(10);
|
|
73
|
+
const types = claimed.map((j) => j.type);
|
|
74
|
+
|
|
75
|
+
expect(types).toContain("embed_segment");
|
|
76
|
+
expect(types).toContain("embed_item");
|
|
77
|
+
expect(types).toContain("extract_items");
|
|
78
|
+
expect(claimed).toHaveLength(3);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("skips embed jobs when circuit breaker is open", async () => {
|
|
82
|
+
// Trip the circuit breaker by recording 5 consecutive failures
|
|
83
|
+
for (let i = 0; i < 5; i++) {
|
|
84
|
+
try {
|
|
85
|
+
await withQdrantBreaker(async () => {
|
|
86
|
+
throw new Error("simulated qdrant failure");
|
|
87
|
+
});
|
|
88
|
+
} catch {
|
|
89
|
+
// expected
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
enqueueMemoryJob("embed_segment", { segmentId: "seg-1" });
|
|
94
|
+
enqueueMemoryJob("embed_item", { itemId: "item-1" });
|
|
95
|
+
enqueueMemoryJob("embed_summary", { summaryId: "sum-1" });
|
|
96
|
+
enqueueMemoryJob("extract_items", { conversationId: "conv-1" });
|
|
97
|
+
enqueueMemoryJob("build_conversation_summary", {
|
|
98
|
+
conversationId: "conv-1",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const claimed = claimMemoryJobs(10);
|
|
102
|
+
const types = claimed.map((j) => j.type);
|
|
103
|
+
|
|
104
|
+
// Only non-embed jobs should be claimed
|
|
105
|
+
expect(types).toContain("extract_items");
|
|
106
|
+
expect(types).toContain("build_conversation_summary");
|
|
107
|
+
expect(types).not.toContain("embed_segment");
|
|
108
|
+
expect(types).not.toContain("embed_item");
|
|
109
|
+
expect(types).not.toContain("embed_summary");
|
|
110
|
+
expect(claimed).toHaveLength(2);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("resumes claiming embed jobs after circuit breaker closes", async () => {
|
|
114
|
+
// Trip the circuit breaker
|
|
115
|
+
for (let i = 0; i < 5; i++) {
|
|
116
|
+
try {
|
|
117
|
+
await withQdrantBreaker(async () => {
|
|
118
|
+
throw new Error("simulated qdrant failure");
|
|
119
|
+
});
|
|
120
|
+
} catch {
|
|
121
|
+
// expected
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Verify embed jobs are skipped while open
|
|
126
|
+
enqueueMemoryJob("embed_segment", { segmentId: "seg-1" });
|
|
127
|
+
enqueueMemoryJob("extract_items", { conversationId: "conv-1" });
|
|
128
|
+
|
|
129
|
+
const claimedWhileOpen = claimMemoryJobs(10);
|
|
130
|
+
expect(claimedWhileOpen.map((j) => j.type)).not.toContain("embed_segment");
|
|
131
|
+
|
|
132
|
+
// Reset breaker (simulates successful probe closing the circuit)
|
|
133
|
+
_resetQdrantBreaker();
|
|
134
|
+
|
|
135
|
+
// Re-enqueue an embed job (the previous one is now "running")
|
|
136
|
+
enqueueMemoryJob("embed_item", { itemId: "item-2" });
|
|
137
|
+
|
|
138
|
+
const claimedAfterClose = claimMemoryJobs(10);
|
|
139
|
+
const types = claimedAfterClose.map((j) => j.type);
|
|
140
|
+
|
|
141
|
+
expect(types).toContain("embed_item");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("all embed job types are skipped when breaker is open", async () => {
|
|
145
|
+
const embedTypes: MemoryJobType[] = [
|
|
146
|
+
"embed_segment",
|
|
147
|
+
"embed_item",
|
|
148
|
+
"embed_summary",
|
|
149
|
+
"embed_media",
|
|
150
|
+
"embed_attachment",
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
// Trip the circuit breaker
|
|
154
|
+
for (let i = 0; i < 5; i++) {
|
|
155
|
+
try {
|
|
156
|
+
await withQdrantBreaker(async () => {
|
|
157
|
+
throw new Error("simulated qdrant failure");
|
|
158
|
+
});
|
|
159
|
+
} catch {
|
|
160
|
+
// expected
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Enqueue one of each embed type
|
|
165
|
+
for (const type of embedTypes) {
|
|
166
|
+
enqueueMemoryJob(type, { id: `test-${type}` });
|
|
167
|
+
}
|
|
168
|
+
// Also enqueue a non-embed job
|
|
169
|
+
enqueueMemoryJob("extract_entities", { conversationId: "conv-1" });
|
|
170
|
+
|
|
171
|
+
const claimed = claimMemoryJobs(20);
|
|
172
|
+
const types = claimed.map((j) => j.type);
|
|
173
|
+
|
|
174
|
+
// Only the non-embed job should be claimed
|
|
175
|
+
expect(claimed).toHaveLength(1);
|
|
176
|
+
expect(types).toEqual(["extract_entities"]);
|
|
177
|
+
});
|
|
178
|
+
});
|