@vellumai/assistant 0.10.2-dev.202606250318.5e7cfb0 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bun.lock +0 -20
- package/docs/workspace-tools.md +33 -42
- package/eslint-rules/cli-no-daemon-internals.js +0 -6
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +0 -31
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +0 -44
- package/node_modules/@vellumai/gateway-client/src/index.ts +0 -14
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +0 -17
- package/node_modules/@vellumai/service-contracts/package.json +0 -1
- package/node_modules/@vellumai/service-contracts/src/index.ts +0 -1
- package/openapi.yaml +0 -155
- package/package.json +1 -4
- package/scripts/test.sh +15 -36
- package/src/__tests__/actor-token-service.test.ts +14 -36
- package/src/__tests__/agent-loop-override-profile.test.ts +0 -1
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -2
- package/src/__tests__/agent-wake-override-profile.test.ts +0 -2
- package/src/__tests__/annotate-activity-metadata.test.ts +0 -2
- package/src/__tests__/annotate-risk-options.test.ts +0 -2
- package/src/__tests__/approval-cascade.test.ts +0 -2
- package/src/__tests__/assistant-attachments.test.ts +0 -42
- package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
- package/src/__tests__/btw-routes.test.ts +0 -2
- package/src/__tests__/build-persisted-content.test.ts +0 -2
- package/src/__tests__/call-controller.test.ts +0 -19
- package/src/__tests__/channel-guardian.test.ts +58 -94
- package/src/__tests__/channel-reply-delivery.test.ts +0 -2
- package/src/__tests__/compaction-events.test.ts +0 -2
- package/src/__tests__/compaction.benchmark.test.ts +0 -2
- package/src/__tests__/compactor-call-site-logging.test.ts +0 -2
- package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -2
- package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -2
- package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -2
- package/src/__tests__/compactor-web-search-strip.test.ts +0 -2
- package/src/__tests__/config-loader-backfill.test.ts +10 -123
- package/src/__tests__/config-schema.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -31
- package/src/__tests__/contacts-relay-reads.test.ts +15 -13
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +0 -134
- package/src/__tests__/conversation-analysis-routes.test.ts +0 -2
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -2
- package/src/__tests__/conversation-history-web-search.test.ts +0 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +0 -2
- package/src/__tests__/conversation-load-history-stripped.test.ts +0 -2
- package/src/__tests__/conversation-pairing.test.ts +0 -2
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -2
- package/src/__tests__/conversation-process-callsite.test.ts +0 -2
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -2
- package/src/__tests__/conversation-queue.test.ts +0 -91
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -14
- package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -14
- package/src/__tests__/conversation-slash-queue.test.ts +0 -2
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -2
- package/src/__tests__/conversation-speed-override.test.ts +0 -2
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -29
- package/src/__tests__/conversation-title-service.test.ts +0 -2
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -47
- package/src/__tests__/conversation-usage.test.ts +0 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -2
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -2
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/db-migration-rollback.test.ts +171 -205
- package/src/__tests__/db-test-helpers.ts +4 -5
- package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -4
- package/src/__tests__/disk-pressure-guard.test.ts +0 -41
- package/src/__tests__/dm-persistence.test.ts +0 -2
- package/src/__tests__/emit-signal-routing-intent.test.ts +5 -10
- package/src/__tests__/events-dev-bypass-actor.test.ts +1 -7
- package/src/__tests__/exploration-drift-hook.test.ts +2 -3
- package/src/__tests__/filing-service.test.ts +0 -2
- package/src/__tests__/guardian-binding-drift-heal.test.ts +10 -75
- package/src/__tests__/guardian-dispatch.test.ts +1 -95
- package/src/__tests__/guardian-outbound-http.test.ts +0 -13
- package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -2
- package/src/__tests__/heartbeat-service.test.ts +0 -2
- package/src/__tests__/helpers/channel-test-adapter.ts +7 -1
- package/src/__tests__/host-app-control-routes.test.ts +30 -24
- package/src/__tests__/host-bash-routes.test.ts +41 -31
- package/src/__tests__/host-browser-routes.test.ts +32 -26
- package/src/__tests__/host-cu-routes-targeted.test.ts +33 -25
- package/src/__tests__/host-file-routes-targeted.test.ts +52 -40
- package/src/__tests__/host-transfer-routes-targeted.test.ts +43 -31
- package/src/__tests__/http-user-message-parity.test.ts +8 -290
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -28
- package/src/__tests__/inbound-slack-persistence.test.ts +0 -2
- package/src/__tests__/invite-redemption-service.test.ts +0 -198
- package/src/__tests__/llm-context-normalization.test.ts +0 -105
- package/src/__tests__/llm-request-log-error-payload.test.ts +9 -71
- package/src/__tests__/llm-usage-store.test.ts +0 -25
- package/src/__tests__/mcp-health-check.test.ts +1 -2
- package/src/__tests__/media-stream-server-integration.test.ts +0 -127
- package/src/__tests__/memory-retrieval-hook.test.ts +0 -2
- package/src/__tests__/messaging-send-tool.test.ts +0 -2
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/mtime-cache.test.ts +5 -146
- package/src/__tests__/native-web-search.test.ts +0 -2
- package/src/__tests__/non-member-access-request.test.ts +17 -189
- package/src/__tests__/notification-broadcaster.test.ts +0 -4
- package/src/__tests__/notification-decision-recipient-context.test.ts +32 -33
- package/src/__tests__/notification-deep-link.test.ts +0 -6
- package/src/__tests__/notification-guardian-path.test.ts +0 -19
- package/src/__tests__/openai-provider.test.ts +12 -22
- package/src/__tests__/openai-responses-provider.test.ts +2 -12
- package/src/__tests__/outbound-slack-persistence.test.ts +0 -2
- package/src/__tests__/pending-interactions-resolved-event.test.ts +4 -7
- package/src/__tests__/persistence-secret-redaction.test.ts +0 -2
- package/src/__tests__/plugin-bootstrap.test.ts +73 -3
- package/src/__tests__/plugin-route-contribution.test.ts +17 -4
- package/src/__tests__/plugin-tool-contribution.test.ts +18 -3
- package/src/__tests__/plugin-types.test.ts +2 -0
- package/src/__tests__/process-message-background-slack.test.ts +0 -2
- package/src/__tests__/process-message-display-content.test.ts +0 -2
- package/src/__tests__/provider-error-scenarios.test.ts +4 -5
- package/src/__tests__/provider-usage-tracking.test.ts +0 -39
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -2
- package/src/__tests__/registry.test.ts +1 -4
- package/src/__tests__/relay-server.test.ts +25 -694
- package/src/__tests__/runtime-attachment-metadata.test.ts +1 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -14
- package/src/__tests__/send-endpoint-busy.test.ts +8 -30
- package/src/__tests__/skills.test.ts +0 -44
- package/src/__tests__/slack-inbound-verification.test.ts +2 -47
- package/src/__tests__/stt-hints.test.ts +13 -44
- package/src/__tests__/subagent-detail.test.ts +0 -27
- package/src/__tests__/subagent-disposal.test.ts +0 -65
- package/src/__tests__/subagent-notify-parent.test.ts +0 -2
- package/src/__tests__/subagent-role-registry.test.ts +2 -7
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +0 -2
- package/src/__tests__/subagent-tools.test.ts +0 -2
- package/src/__tests__/suggestion-routes.test.ts +0 -2
- package/src/__tests__/title-generate-hook.test.ts +0 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -2
- package/src/__tests__/tool-executor.test.ts +11 -16
- package/src/__tests__/tool-preview-lifecycle.test.ts +0 -2
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -2
- package/src/__tests__/tool-start-timestamp.test.ts +0 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
- package/src/__tests__/twilio-routes.test.ts +0 -96
- package/src/__tests__/ui-file-upload-surface.test.ts +0 -86
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-invite-redemption.test.ts +0 -33
- package/src/__tests__/web-search-backend-failure.test.ts +0 -2
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +35 -14
- package/src/__tests__/workspace-tool-loader.test.ts +2 -195
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
- package/src/agent/loop.ts +0 -56
- package/src/api/index.ts +1 -19
- package/src/api/responses/llm-request-log-entry.ts +0 -29
- package/src/api/responses/subagent-detail.ts +0 -17
- package/src/api/surfaces.ts +3 -39
- package/src/approvals/guardian-request-resolvers.ts +11 -1
- package/src/calls/__tests__/relay-setup-router.test.ts +4 -262
- package/src/calls/call-domain.ts +3 -3
- package/src/calls/guardian-dispatch.ts +8 -10
- package/src/calls/inbound-trust-reader.ts +1 -17
- package/src/calls/media-stream-server.ts +0 -21
- package/src/calls/relay-server.ts +50 -167
- package/src/calls/relay-setup-router.ts +7 -37
- package/src/calls/relay-verification.ts +4 -4
- package/src/calls/stt-hints.ts +12 -9
- package/src/calls/twilio-routes.ts +4 -14
- package/src/channels/types.ts +20 -10
- package/src/cli/commands/__tests__/cache.test.ts +1 -8
- package/src/cli/commands/cache.ts +181 -194
- package/src/cli/commands/db/__tests__/repair.test.ts +5 -6
- package/src/cli/commands/db/status.ts +1 -37
- package/src/cli/commands/mcp.ts +218 -252
- package/src/cli/commands/memory/index.ts +0 -2
- package/src/cli/commands/plugins.ts +3 -75
- package/src/cli/lib/__tests__/install-from-github.test.ts +0 -102
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +1 -160
- package/src/cli/lib/list-installed-plugins.ts +1 -179
- package/src/config/__tests__/sync-gated-profiles.test.ts +3 -11
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +17 -27
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +3 -13
- package/src/config/bundled-skills/subagent/SKILL.md +1 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
- package/src/config/feature-flag-registry.json +13 -5
- package/src/config/loader.ts +5 -38
- package/src/config/schemas/__tests__/memory-v3.test.ts +0 -1
- package/src/config/schemas/memory-lifecycle.ts +0 -12
- package/src/config/schemas/memory-v3.ts +0 -7
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/timeouts.ts +0 -8
- package/src/config/seed-inference-profiles.ts +11 -21
- package/src/config/skills.ts +5 -27
- package/src/config/sync-gated-profiles.ts +13 -12
- package/src/contacts/contacts-write.ts +0 -3
- package/src/daemon/assistant-attachments.ts +4 -27
- package/src/daemon/conversation-agent-loop.ts +0 -28
- package/src/daemon/conversation-process.ts +16 -35
- package/src/daemon/conversation-surfaces.ts +38 -111
- package/src/daemon/conversation-tool-setup.ts +16 -50
- package/src/daemon/conversation.ts +1 -13
- package/src/daemon/disk-pressure-guard.ts +2 -12
- package/src/daemon/event-loop-watchdog.ts +1 -28
- package/src/daemon/external-plugins-bootstrap.ts +34 -4
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -25
- package/src/daemon/handlers/config-a2a.ts +14 -6
- package/src/daemon/handlers/config-channels.ts +22 -78
- package/src/daemon/handlers/conversations.ts +0 -77
- package/src/daemon/lifecycle.ts +0 -4
- package/src/daemon/mcp-reload-service.ts +0 -10
- package/src/daemon/memory-v2-startup.test.ts +0 -72
- package/src/daemon/memory-v2-startup.ts +19 -87
- package/src/daemon/message-types/conversations.ts +0 -2
- package/src/daemon/message-types/surfaces.ts +12 -12
- package/src/daemon/server.ts +4 -0
- package/src/daemon/shutdown-handlers.ts +0 -20
- package/src/daemon/tool-setup-types.ts +0 -9
- package/src/daemon/workspace-tools-watcher.ts +328 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
- package/src/ipc/assistant-server.ts +2 -2
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/client.ts +1 -15
- package/src/mcp/mcp-auth-orchestrator.ts +1 -6
- package/src/mcp/mcp-oauth-provider.ts +8 -19
- package/src/memory/__tests__/memory-retrospective-job.test.ts +0 -8
- package/src/memory/conversation-crud.ts +0 -38
- package/src/memory/db-connection.ts +3 -22
- package/src/memory/db-init.ts +502 -36
- package/src/memory/db-singleton.ts +4 -6
- package/src/memory/jobs-worker.ts +0 -58
- package/src/memory/llm-request-log-store.ts +1 -26
- package/src/memory/llm-usage-store.ts +20 -48
- package/src/memory/memory-retrospective-job.ts +8 -9
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +56 -130
- package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
- package/src/memory/migrations/registry.ts +573 -0
- package/src/memory/migrations/run-migrations.ts +6 -90
- package/src/memory/migrations/validate-migration-state.ts +66 -101
- package/src/memory/schema/conversations.ts +0 -9
- package/src/memory/schema/infrastructure.ts +0 -20
- package/src/memory/v2/__tests__/cli-command-store.test.ts +0 -25
- package/src/memory/v2/__tests__/skill-store.test.ts +0 -80
- package/src/memory/v2/cli-command-store.ts +38 -75
- package/src/memory/v2/prompts/consolidation.ts +82 -13
- package/src/memory/v2/prompts/router.ts +93 -21
- package/src/memory/v2/skill-store.ts +31 -68
- package/src/notifications/__tests__/broadcaster.test.ts +8 -16
- package/src/notifications/__tests__/decision-engine.test.ts +9 -78
- package/src/notifications/broadcaster.ts +1 -8
- package/src/notifications/decision-engine.ts +7 -15
- package/src/notifications/destination-resolver.ts +24 -68
- package/src/notifications/emit-signal.ts +14 -39
- package/src/permissions/question-prompter.test.ts +1 -1
- package/src/permissions/question-prompter.ts +4 -7
- package/src/plugin-api/index.ts +6 -6
- package/src/plugin-api/types.ts +5 -3
- package/src/plugin-api/vision-support.test.ts +4 -28
- package/src/plugin-api/vision-support.ts +31 -66
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -161
- package/src/plugins/defaults/advisor/consult.ts +6 -110
- package/src/plugins/defaults/advisor/steering.ts +2 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +5 -32
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +1 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +7 -47
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +11 -10
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +20 -12
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +11 -42
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +3 -33
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +4 -48
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +8 -4
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +15 -43
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +2 -11
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -77
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +11 -12
- package/src/plugins/mtime-cache.ts +291 -76
- package/src/plugins/pipeline.ts +13 -111
- package/src/plugins/types.ts +2 -0
- package/src/providers/anthropic/client.ts +0 -5
- package/src/providers/call-site-routing.ts +0 -4
- package/src/providers/model-catalog.ts +0 -16
- package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
- package/src/providers/openai/chat-completions-provider.ts +83 -37
- package/src/providers/openai/responses-provider.ts +46 -50
- package/src/providers/openrouter/client.ts +0 -5
- package/src/providers/provider-send-message.ts +0 -4
- package/src/providers/ratelimit.ts +0 -4
- package/src/providers/retry.ts +0 -4
- package/src/providers/types.ts +0 -9
- package/src/providers/usage-tracking.ts +0 -4
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +3 -335
- package/src/runtime/access-request-helper.ts +39 -19
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/assistant-stream-state.ts +2 -9
- package/src/runtime/auth/require-bound-guardian.ts +11 -21
- package/src/runtime/channel-verification-service.ts +31 -56
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
- package/src/runtime/guardian-vellum-migration.ts +7 -66
- package/src/runtime/invite-redemption-service.ts +187 -198
- package/src/runtime/local-actor-identity.ts +11 -76
- package/src/runtime/pending-interactions.ts +1 -11
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +5 -56
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +0 -187
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -13
- package/src/runtime/routes/channel-verification-routes.ts +3 -3
- package/src/runtime/routes/contact-routes.ts +32 -8
- package/src/runtime/routes/conversation-cli-routes.ts +5 -4
- package/src/runtime/routes/conversation-list-routes.ts +7 -4
- package/src/runtime/routes/conversation-query-routes.ts +0 -72
- package/src/runtime/routes/conversation-routes.ts +85 -84
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/global-search-routes.ts +1 -3
- package/src/runtime/routes/guardian-action-routes.ts +5 -4
- package/src/runtime/routes/host-app-control-routes.ts +4 -5
- package/src/runtime/routes/host-bash-routes.ts +4 -5
- package/src/runtime/routes/host-browser-routes.ts +11 -9
- package/src/runtime/routes/host-cu-routes.ts +4 -5
- package/src/runtime/routes/host-file-routes.ts +4 -5
- package/src/runtime/routes/host-transfer-routes.ts +6 -6
- package/src/runtime/routes/http-adapter.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +2 -3
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +5 -97
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +49 -61
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -16
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +8 -21
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +3 -14
- package/src/runtime/routes/index.ts +0 -2
- package/src/runtime/routes/llm-context-normalization.ts +0 -83
- package/src/runtime/routes/mcp-auth-routes.ts +19 -171
- package/src/runtime/routes/migration-rollback-routes.ts +3 -4
- package/src/runtime/routes/migration-routes.ts +1 -4
- package/src/runtime/routes/subagents-routes.ts +0 -5
- package/src/runtime/routes/surface-action-routes.ts +56 -42
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -1
- package/src/runtime/services/conversation-serializer.ts +9 -7
- package/src/runtime/tool-grant-request-helper.ts +3 -3
- package/src/runtime/trust-verdict-consumer.ts +9 -85
- package/src/runtime/verification-outbound-actions.ts +18 -18
- package/src/signals/user-message.ts +0 -16
- package/src/subagent/manager.ts +0 -9
- package/src/subagent/types.ts +3 -3
- package/src/telemetry/types.ts +1 -34
- package/src/telemetry/usage-telemetry-reporter.test.ts +2 -3
- package/src/telemetry/usage-telemetry-reporter.ts +3 -87
- package/src/tools/ask-question/ask-question-tool.test.ts +0 -29
- package/src/tools/ask-question/ask-question-tool.ts +0 -13
- package/src/tools/executor.ts +4 -4
- package/src/tools/registry.ts +0 -18
- package/src/tools/shared/filesystem/path-policy.ts +5 -12
- package/src/tools/tool-approval-handler.ts +1 -1
- package/src/tools/tool-defaults.ts +2 -9
- package/src/tools/tool-manifest.ts +0 -3
- package/src/tools/types.ts +2 -17
- package/src/tools/workspace-tools/loader.ts +244 -348
- package/src/util/errors.ts +1 -26
- package/src/util/platform.ts +0 -5
- package/src/workflows/library.test.ts +0 -140
- package/src/workflows/library.ts +28 -82
- package/src/workspace/migrations/017-seed-persona-dirs.ts +34 -3
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +24 -3
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +66 -14
- package/src/workspace/migrations/registry.ts +0 -2
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +0 -91
- package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +0 -48
- package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +0 -28
- package/node_modules/@vellumai/service-contracts/src/channels.ts +0 -41
- package/src/__tests__/code-search-tool.test.ts +0 -585
- package/src/__tests__/guardian-expiry-notifier.test.ts +0 -282
- package/src/__tests__/mcp-config-secret-boundary.test.ts +0 -390
- package/src/__tests__/plugin-pipeline.test.ts +0 -96
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +0 -102
- package/src/__tests__/steer-on-enqueue-question.test.ts +0 -181
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +0 -208
- package/src/agent/loop-exclusive-tool.test.ts +0 -150
- package/src/api/constants/sse-replay.ts +0 -41
- package/src/api/events/conversation-notice.ts +0 -26
- package/src/approvals/guardian-channel-delivery.ts +0 -30
- package/src/approvals/guardian-expiry-notifier.ts +0 -148
- package/src/cli/commands/memory/__tests__/worker.test.ts +0 -302
- package/src/cli/commands/memory/worker.ts +0 -175
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -143
- package/src/config/prune-seeded-callsite-defaults.ts +0 -110
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -129
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +0 -312
- package/src/contacts/__tests__/member-write-relay.test.ts +0 -202
- package/src/contacts/guardian-delivery-reader.ts +0 -223
- package/src/contacts/member-write-relay.ts +0 -189
- package/src/daemon/conversation-notices.ts +0 -60
- package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -225
- package/src/hooks/hook-loader.ts +0 -341
- package/src/mcp/mcp-header-store.ts +0 -134
- package/src/memory/__tests__/301-create-watchdog-events.test.ts +0 -110
- package/src/memory/__tests__/prompt-override.test.ts +0 -192
- package/src/memory/__tests__/watchdog-events-store.test.ts +0 -161
- package/src/memory/migrations/300-add-processing-started-at.ts +0 -30
- package/src/memory/migrations/301-create-watchdog-events.ts +0 -45
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +0 -224
- package/src/memory/prompt-override.ts +0 -129
- package/src/memory/steps.ts +0 -573
- package/src/memory/watchdog-events-store.ts +0 -87
- package/src/memory/worker-control.ts +0 -118
- package/src/memory/worker-process.ts +0 -72
- package/src/notifications/__tests__/connected-channels.test.ts +0 -114
- package/src/notifications/__tests__/destination-resolver.test.ts +0 -256
- package/src/onboarding/checkin-event.test.ts +0 -222
- package/src/onboarding/checkin-event.ts +0 -321
- package/src/onboarding/schedule-checkin.ts +0 -190
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
- package/src/plugins/defaults/advisor/context-pack.ts +0 -288
- package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +0 -146
- package/src/plugins/surface-import.ts +0 -121
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +0 -321
- package/src/providers/openai/api-error-normalization.ts +0 -270
- package/src/runtime/__tests__/channel-verification-service.test.ts +0 -133
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -181
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +0 -66
- package/src/runtime/__tests__/local-principal-trust.test.ts +0 -164
- package/src/runtime/anchored-guardian.test.ts +0 -156
- package/src/runtime/anchored-guardian.ts +0 -135
- package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +0 -99
- package/src/runtime/local-principal-trust.ts +0 -52
- package/src/runtime/routes/__tests__/contact-routes.test.ts +0 -212
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -93
- package/src/runtime/routes/onboarding-checkin-routes.ts +0 -86
- package/src/tools/filesystem/search.ts +0 -543
- package/src/util/telemetry-db-path.ts +0 -24
- package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +0 -134
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import type OpenAI from "openai";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Normalized view of an OpenAI-compatible `APIError`. The SDK reads
|
|
5
|
-
* `error.message` and renders bodies it can't parse as "(no body)", so
|
|
6
|
-
* Django `{"detail": "..."}` payloads from the managed runtime proxy and
|
|
7
|
-
* OpenRouter's nested `error.metadata.raw` get dropped. We capture the raw
|
|
8
|
-
* non-2xx body in a `fetch` wrapper and reconstruct the useful fields here.
|
|
9
|
-
*/
|
|
10
|
-
export interface NormalizedOpenAIAPIError {
|
|
11
|
-
message: string;
|
|
12
|
-
detail?: string;
|
|
13
|
-
requestId?: string;
|
|
14
|
-
apiErrorCode?: string;
|
|
15
|
-
apiErrorType?: string;
|
|
16
|
-
apiErrorParam?: string;
|
|
17
|
-
/**
|
|
18
|
-
* The captured raw upstream non-2xx body, verbatim (possibly truncated to
|
|
19
|
-
* MAX_CAPTURED_BODY_CHARS). Carried so callers can persist the actual
|
|
20
|
-
* provider payload for the inspector's Raw tab instead of only the
|
|
21
|
-
* extracted fields. Absent for retryable (429/5xx) errors, whose bodies
|
|
22
|
-
* `captureRawErrorBodyFetch` intentionally doesn't drain.
|
|
23
|
-
*/
|
|
24
|
-
rawBody?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const MAX_DETAIL_CHARS = 2000;
|
|
28
|
-
const REQUEST_ID_HEADERS = [
|
|
29
|
-
"x-request-id",
|
|
30
|
-
"x-openrouter-request-id",
|
|
31
|
-
"openai-request-id",
|
|
32
|
-
"x-amzn-requestid",
|
|
33
|
-
] as const;
|
|
34
|
-
|
|
35
|
-
// The OpenAI SDK keeps only the `.error` key of a parsed JSON error body and
|
|
36
|
-
// discards the rest, so a Django-proxy `{ "detail": … }` payload never reaches
|
|
37
|
-
// the thrown APIError. captureRawErrorBodyFetch stashes the raw body in a
|
|
38
|
-
// WeakMap keyed by the response's headers object — which the SDK passes through
|
|
39
|
-
// to `APIError.headers` by reference — so it stays correlated to its own
|
|
40
|
-
// request, with no shared provider state for concurrent calls to clobber. A
|
|
41
|
-
// WeakMap (rather than a synthetic header) keeps the body out of the SDK's
|
|
42
|
-
// debug header logging and is reclaimed automatically with the response.
|
|
43
|
-
const capturedErrorBodies = new WeakMap<object, string>();
|
|
44
|
-
// Bound the stored body so a huge HTML error page can't balloon memory; the
|
|
45
|
-
// message is re-truncated to MAX_DETAIL_CHARS downstream anyway.
|
|
46
|
-
const MAX_CAPTURED_BODY_CHARS = 16_384;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* SDK `fetch` option that captures non-2xx response bodies. Install via the
|
|
50
|
-
* OpenAI client `fetch` option; safe to share across requests and instances
|
|
51
|
-
* because the captured body is keyed per-response in a WeakMap, not stored on
|
|
52
|
-
* any shared field.
|
|
53
|
-
*/
|
|
54
|
-
export const captureRawErrorBodyFetch = async (
|
|
55
|
-
url: RequestInfo | URL,
|
|
56
|
-
init?: RequestInit,
|
|
57
|
-
): Promise<Response> => {
|
|
58
|
-
const res = await globalThis.fetch(url, init);
|
|
59
|
-
if (res.ok) return res;
|
|
60
|
-
// Don't drain bodies the SDK will retry: reading a large or slow upstream
|
|
61
|
-
// error page on every attempt would delay those retries and buffer the whole
|
|
62
|
-
// body. We still capture terminal (non-retryable) errors — that's where the
|
|
63
|
-
// actionable upstream detail lives (unsupported model, invalid key, malformed
|
|
64
|
-
// request, etc.).
|
|
65
|
-
if (sdkWillRetry(res)) return res;
|
|
66
|
-
// clone() so reading the body leaves the SDK's own read of `res` intact.
|
|
67
|
-
const body = await res
|
|
68
|
-
.clone()
|
|
69
|
-
.text()
|
|
70
|
-
.catch(() => undefined);
|
|
71
|
-
if (!body) return res;
|
|
72
|
-
capturedErrorBodies.set(
|
|
73
|
-
res.headers,
|
|
74
|
-
body.length > MAX_CAPTURED_BODY_CHARS
|
|
75
|
-
? body.slice(0, MAX_CAPTURED_BODY_CHARS)
|
|
76
|
-
: body,
|
|
77
|
-
);
|
|
78
|
-
return res;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Mirror the OpenAI SDK's `shouldRetry` predicate so this wrapper never drains
|
|
83
|
-
* a body the SDK is about to retry. The SDK retries more than 429/5xx: an
|
|
84
|
-
* explicit `x-should-retry` header (which also overrides 429/5xx to *not*
|
|
85
|
-
* retry), plus 408 (request timeout) and 409 (lock timeout). Keep in sync with
|
|
86
|
-
* `openai/client.js` `shouldRetry`.
|
|
87
|
-
*/
|
|
88
|
-
function sdkWillRetry(res: Response): boolean {
|
|
89
|
-
const shouldRetryHeader = res.headers.get("x-should-retry");
|
|
90
|
-
if (shouldRetryHeader === "true") return true;
|
|
91
|
-
if (shouldRetryHeader === "false") return false;
|
|
92
|
-
return (
|
|
93
|
-
res.status === 408 ||
|
|
94
|
-
res.status === 409 ||
|
|
95
|
-
res.status === 429 ||
|
|
96
|
-
res.status >= 500
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function normalizeOpenAIAPIError(
|
|
101
|
-
error: InstanceType<typeof OpenAI.APIError>,
|
|
102
|
-
rawBody: string | undefined = readCapturedErrorBody(error.headers),
|
|
103
|
-
): NormalizedOpenAIAPIError {
|
|
104
|
-
// Prefer the captured raw body (intact upstream payload) over the SDK's
|
|
105
|
-
// already-parsed `error.error`, which may have collapsed the detail. But if
|
|
106
|
-
// the captured body didn't parse as a JSON object — e.g. it was truncated
|
|
107
|
-
// past MAX_CAPTURED_BODY_CHARS into an invalid prefix — fall back to the
|
|
108
|
-
// SDK's parsed object so we don't drop code/type/param it already extracted.
|
|
109
|
-
const parsedRaw = parseBody(rawBody);
|
|
110
|
-
const sdkError = (error as { error?: unknown }).error;
|
|
111
|
-
const parsed = asRecord(parsedRaw) ?? sdkError ?? parsedRaw;
|
|
112
|
-
const body = extractBody(parsed);
|
|
113
|
-
|
|
114
|
-
const message =
|
|
115
|
-
body.message ||
|
|
116
|
-
stripLeadingStatus(error.message ?? "", error.status) ||
|
|
117
|
-
"Request failed";
|
|
118
|
-
|
|
119
|
-
const out: NormalizedOpenAIAPIError = { message };
|
|
120
|
-
if (body.detail && body.detail !== message) out.detail = body.detail;
|
|
121
|
-
const code = body.apiErrorCode ?? scalar((error as { code?: unknown }).code);
|
|
122
|
-
const type = body.apiErrorType ?? scalar((error as { type?: unknown }).type);
|
|
123
|
-
const param =
|
|
124
|
-
body.apiErrorParam ?? scalar((error as { param?: unknown }).param);
|
|
125
|
-
if (code) out.apiErrorCode = code;
|
|
126
|
-
if (type) out.apiErrorType = type;
|
|
127
|
-
if (param) out.apiErrorParam = param;
|
|
128
|
-
const requestId = readHeader(error.headers);
|
|
129
|
-
if (requestId) out.requestId = requestId;
|
|
130
|
-
if (rawBody) out.rawBody = rawBody;
|
|
131
|
-
return out;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export function formatNormalizedOpenAIAPIError(
|
|
135
|
-
providerLabel: string,
|
|
136
|
-
status: number | undefined,
|
|
137
|
-
n: NormalizedOpenAIAPIError,
|
|
138
|
-
): string {
|
|
139
|
-
const statusLabel =
|
|
140
|
-
typeof status === "number" ? String(status) : "unknown status";
|
|
141
|
-
const extras = [
|
|
142
|
-
n.detail,
|
|
143
|
-
n.apiErrorCode && `code=${n.apiErrorCode}`,
|
|
144
|
-
n.apiErrorType && `type=${n.apiErrorType}`,
|
|
145
|
-
n.apiErrorParam && `param=${n.apiErrorParam}`,
|
|
146
|
-
n.requestId && `request_id=${n.requestId}`,
|
|
147
|
-
].filter((v): v is string => Boolean(v));
|
|
148
|
-
const suffix = extras.length > 0 ? ` [${extras.join("; ")}]` : "";
|
|
149
|
-
return `${providerLabel} API error (${statusLabel}): ${n.message}${suffix}`;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
interface BodyDetails {
|
|
153
|
-
message?: string;
|
|
154
|
-
detail?: string;
|
|
155
|
-
apiErrorCode?: string;
|
|
156
|
-
apiErrorType?: string;
|
|
157
|
-
apiErrorParam?: string;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function extractBody(body: unknown): BodyDetails {
|
|
161
|
-
if (typeof body === "string") return { message: trunc(body.trim()) };
|
|
162
|
-
const rec = asRecord(body);
|
|
163
|
-
if (!rec) return {};
|
|
164
|
-
|
|
165
|
-
// OpenAI/OpenRouter nest under `error`; Django puts `detail` at the top.
|
|
166
|
-
// A plain `error: "string"` is the whole message but may still carry
|
|
167
|
-
// sibling code/type/param, so fall through to the metadata extraction below.
|
|
168
|
-
const err = asRecord(rec.error) ?? rec;
|
|
169
|
-
|
|
170
|
-
let message =
|
|
171
|
-
str(rec.error) ?? str(err.message) ?? str(err.detail) ?? str(rec.detail);
|
|
172
|
-
let detail: string | undefined;
|
|
173
|
-
|
|
174
|
-
// OpenRouter: the real downstream error lives in metadata.raw while the
|
|
175
|
-
// top-level message is a generic "Provider returned error".
|
|
176
|
-
const meta = asRecord(err.metadata);
|
|
177
|
-
const raw = str(meta?.raw);
|
|
178
|
-
const provider = str(meta?.provider_name);
|
|
179
|
-
if (raw && message && /^provider returned error$/i.test(message)) {
|
|
180
|
-
message = raw;
|
|
181
|
-
} else if (raw && raw !== message) {
|
|
182
|
-
detail = raw;
|
|
183
|
-
}
|
|
184
|
-
if (provider) {
|
|
185
|
-
detail = detail
|
|
186
|
-
? `${detail}; provider=${provider}`
|
|
187
|
-
: `provider=${provider}`;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return {
|
|
191
|
-
...(message ? { message: trunc(message) } : {}),
|
|
192
|
-
...(detail ? { detail: trunc(detail) } : {}),
|
|
193
|
-
...withScalar("apiErrorCode", err.code ?? rec.code),
|
|
194
|
-
...withScalar("apiErrorType", err.type ?? rec.type),
|
|
195
|
-
...withScalar("apiErrorParam", err.param ?? rec.param),
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function parseBody(raw: string | undefined): unknown {
|
|
200
|
-
const trimmed = raw?.trim();
|
|
201
|
-
if (!trimmed) return undefined;
|
|
202
|
-
try {
|
|
203
|
-
return JSON.parse(trimmed);
|
|
204
|
-
} catch {
|
|
205
|
-
return trimmed; // non-JSON body (HTML error page, plain text)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function stripLeadingStatus(
|
|
210
|
-
message: string,
|
|
211
|
-
status: number | undefined,
|
|
212
|
-
): string {
|
|
213
|
-
const trimmed = message.trim();
|
|
214
|
-
// SDK sentinel for an unparseable/empty body — carries no signal, so let the
|
|
215
|
-
// caller fall back to "Request failed" rather than surface SDK phrasing.
|
|
216
|
-
if (/^\d*\s*status code \(no body\)$/i.test(trimmed)) return "";
|
|
217
|
-
if (typeof status !== "number") return trimmed;
|
|
218
|
-
return trimmed.replace(new RegExp(`^${status}\\s+`), "").trim() || trimmed;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function trunc(s: string): string {
|
|
222
|
-
return s.length > MAX_DETAIL_CHARS ? `${s.slice(0, MAX_DETAIL_CHARS)}…` : s;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function withScalar(key: keyof BodyDetails, value: unknown): BodyDetails {
|
|
226
|
-
const s = scalar(value);
|
|
227
|
-
return s ? { [key]: s } : {};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function scalar(value: unknown): string | undefined {
|
|
231
|
-
if (typeof value === "string" && value.length > 0) return value;
|
|
232
|
-
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
233
|
-
return undefined;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function str(value: unknown): string | undefined {
|
|
237
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
241
|
-
return value && typeof value === "object"
|
|
242
|
-
? (value as Record<string, unknown>)
|
|
243
|
-
: undefined;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function readHeader(headers: unknown): string | undefined {
|
|
247
|
-
return readHeaderValue(headers, REQUEST_ID_HEADERS);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function readHeaderValue(
|
|
251
|
-
headers: unknown,
|
|
252
|
-
names: readonly string[],
|
|
253
|
-
): string | undefined {
|
|
254
|
-
if (!headers) return undefined;
|
|
255
|
-
const get = (headers as { get?: unknown }).get;
|
|
256
|
-
const getter = typeof get === "function" ? get.bind(headers) : undefined;
|
|
257
|
-
for (const name of names) {
|
|
258
|
-
const raw = getter
|
|
259
|
-
? (getter(name) as string | null)
|
|
260
|
-
: (asRecord(headers)?.[name] ?? asRecord(headers)?.[name.toLowerCase()]);
|
|
261
|
-
if (typeof raw === "string" && raw.length > 0) return raw;
|
|
262
|
-
}
|
|
263
|
-
return undefined;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function readCapturedErrorBody(headers: unknown): string | undefined {
|
|
267
|
-
return headers && typeof headers === "object"
|
|
268
|
-
? capturedErrorBodies.get(headers)
|
|
269
|
-
: undefined;
|
|
270
|
-
}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
// Gateway guardian-delivery list drives both getGuardianBinding and isGuardian:
|
|
4
|
-
// null = couldn't determine, [] = authoritative unbound, one active entry =
|
|
5
|
-
// bound. Tests set this to mirror the gateway-owned ACL state.
|
|
6
|
-
let mockGuardianList: Array<Record<string, unknown>> | null = [];
|
|
7
|
-
const cachedCalls: Array<{ channelTypes?: string[] } | undefined> = [];
|
|
8
|
-
|
|
9
|
-
const resolveList = (input?: { channelTypes?: string[] }) => {
|
|
10
|
-
cachedCalls.push(input);
|
|
11
|
-
return Promise.resolve(mockGuardianList);
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
mock.module("../../contacts/guardian-delivery-reader.js", () => ({
|
|
15
|
-
getGuardianDelivery: resolveList,
|
|
16
|
-
getGuardianDeliveryFresh: resolveList,
|
|
17
|
-
guardianForChannel: (
|
|
18
|
-
list: Array<{ channelType: string; status: string }>,
|
|
19
|
-
channelType: string,
|
|
20
|
-
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
const { getGuardianBinding, isGuardian } = await import(
|
|
24
|
-
"../channel-verification-service.js"
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
const TELEGRAM_DELIVERY = {
|
|
28
|
-
channelType: "telegram",
|
|
29
|
-
contactId: "contact-1",
|
|
30
|
-
principalId: "principal-1",
|
|
31
|
-
displayName: "Guardian",
|
|
32
|
-
address: "guardian-handle",
|
|
33
|
-
externalChatId: "chat-1",
|
|
34
|
-
status: "active",
|
|
35
|
-
verifiedAt: 1700,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
describe("getGuardianBinding", () => {
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
mockGuardianList = [];
|
|
41
|
-
cachedCalls.length = 0;
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("filters delivery by the requested channel type", async () => {
|
|
45
|
-
await getGuardianBinding("asst-1", "telegram");
|
|
46
|
-
expect(cachedCalls).toEqual([{ channelTypes: ["telegram"] }]);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("returns null when no guardian is bound", async () => {
|
|
50
|
-
mockGuardianList = [];
|
|
51
|
-
expect(await getGuardianBinding("asst-1", "telegram")).toBeNull();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("returns null when the gateway is unreachable", async () => {
|
|
55
|
-
mockGuardianList = null;
|
|
56
|
-
expect(await getGuardianBinding("asst-1", "telegram")).toBeNull();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("synthesizes the binding from the gateway delivery", async () => {
|
|
60
|
-
mockGuardianList = [TELEGRAM_DELIVERY];
|
|
61
|
-
|
|
62
|
-
const binding = await getGuardianBinding("asst-1", "telegram");
|
|
63
|
-
|
|
64
|
-
expect(binding).not.toBeNull();
|
|
65
|
-
expect(binding?.assistantId).toBe("asst-1");
|
|
66
|
-
expect(binding?.channel).toBe("telegram");
|
|
67
|
-
expect(binding?.id).toBe("contact-1");
|
|
68
|
-
expect(binding?.guardianPrincipalId).toBe("principal-1");
|
|
69
|
-
expect(binding?.guardianExternalUserId).toBe("guardian-handle");
|
|
70
|
-
expect(binding?.guardianDeliveryChatId).toBe("chat-1");
|
|
71
|
-
expect(binding?.verifiedAt).toBe(1700);
|
|
72
|
-
expect(binding?.status).toBe("active");
|
|
73
|
-
expect(binding?.verifiedVia).toBe("verified");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("falls back to empty strings for absent optional delivery fields", async () => {
|
|
77
|
-
mockGuardianList = [
|
|
78
|
-
{
|
|
79
|
-
channelType: "telegram",
|
|
80
|
-
contactId: "contact-2",
|
|
81
|
-
address: "addr",
|
|
82
|
-
status: "active",
|
|
83
|
-
},
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
const binding = await getGuardianBinding("asst-1", "telegram");
|
|
87
|
-
|
|
88
|
-
expect(binding?.guardianPrincipalId).toBe("");
|
|
89
|
-
expect(binding?.guardianDeliveryChatId).toBe("");
|
|
90
|
-
expect(binding?.verifiedAt).toBe(0);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test("ignores deliveries for a different channel", async () => {
|
|
94
|
-
mockGuardianList = [TELEGRAM_DELIVERY];
|
|
95
|
-
expect(await getGuardianBinding("asst-1", "phone")).toBeNull();
|
|
96
|
-
});
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
describe("isGuardian", () => {
|
|
100
|
-
beforeEach(() => {
|
|
101
|
-
mockGuardianList = [];
|
|
102
|
-
cachedCalls.length = 0;
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("returns true when the address matches the gateway guardian", async () => {
|
|
106
|
-
mockGuardianList = [TELEGRAM_DELIVERY];
|
|
107
|
-
expect(await isGuardian("asst-1", "telegram", "guardian-handle")).toBe(true);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("compares case-insensitively", async () => {
|
|
111
|
-
mockGuardianList = [TELEGRAM_DELIVERY];
|
|
112
|
-
expect(await isGuardian("asst-1", "telegram", "GUARDIAN-HANDLE")).toBe(true);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("returns false for a non-matching address", async () => {
|
|
116
|
-
mockGuardianList = [TELEGRAM_DELIVERY];
|
|
117
|
-
expect(await isGuardian("asst-1", "telegram", "someone-else")).toBe(false);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test("returns false when no guardian is bound", async () => {
|
|
121
|
-
mockGuardianList = [];
|
|
122
|
-
expect(await isGuardian("asst-1", "telegram", "guardian-handle")).toBe(
|
|
123
|
-
false,
|
|
124
|
-
);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("returns false when the gateway is unreachable", async () => {
|
|
128
|
-
mockGuardianList = null;
|
|
129
|
-
expect(await isGuardian("asst-1", "telegram", "guardian-handle")).toBe(
|
|
130
|
-
false,
|
|
131
|
-
);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the narrow reset-drift trust-recovery helper
|
|
3
|
-
* `reResolveTrustOnResetDrift`.
|
|
4
|
-
*
|
|
5
|
-
* The real helper runs against mocked leaf deps: the gateway guardian read
|
|
6
|
-
* (`getGuardianDelivery`/`guardianForChannel`), the local-mirror heal
|
|
7
|
-
* (`findGuardianForChannel`/`updateContactPrincipalAndChannel`, which the real
|
|
8
|
-
* `healGuardianBindingDrift` drives), and the local trust resolver
|
|
9
|
-
* (`resolveTrustContext`). Heal invocations are observed via the contact-store
|
|
10
|
-
* write mock.
|
|
11
|
-
*/
|
|
12
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
13
|
-
|
|
14
|
-
mock.module("../../util/logger.js", () => ({
|
|
15
|
-
getLogger: () =>
|
|
16
|
-
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
let mockGuardianList: Array<Record<string, unknown>> | null = [];
|
|
20
|
-
|
|
21
|
-
mock.module("../../contacts/guardian-delivery-reader.js", () => ({
|
|
22
|
-
getGuardianDelivery: async () => mockGuardianList,
|
|
23
|
-
guardianForChannel: (
|
|
24
|
-
list: Array<Record<string, unknown>>,
|
|
25
|
-
channelType: string,
|
|
26
|
-
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
27
|
-
}));
|
|
28
|
-
|
|
29
|
-
// Local mirror the real heal reads/writes. `findGuardianForChannel` returns the
|
|
30
|
-
// stored guardian; `updateContactPrincipalAndChannel` records heal writes.
|
|
31
|
-
let mockLocalGuardian: {
|
|
32
|
-
contact: { id: string; principalId: string };
|
|
33
|
-
channel: { id: string };
|
|
34
|
-
} | null = null;
|
|
35
|
-
const healWrites: Array<{ principalId: string }> = [];
|
|
36
|
-
|
|
37
|
-
mock.module("../../contacts/contact-store.js", () => ({
|
|
38
|
-
findGuardianForChannel: () => mockLocalGuardian,
|
|
39
|
-
updateContactPrincipalAndChannel: (
|
|
40
|
-
_contactId: string,
|
|
41
|
-
_channelId: string,
|
|
42
|
-
principalId: string,
|
|
43
|
-
) => {
|
|
44
|
-
healWrites.push({ principalId });
|
|
45
|
-
return true;
|
|
46
|
-
},
|
|
47
|
-
}));
|
|
48
|
-
|
|
49
|
-
// The local trust resolver returns guardian for the actor; the gate threads
|
|
50
|
-
// sourceChannel via the real withSourceChannel wrapper.
|
|
51
|
-
mock.module("../../runtime/trust-context-resolver.js", () => ({
|
|
52
|
-
resolveTrustContext: (input: { actorExternalId?: string }) => ({
|
|
53
|
-
trustClass: "guardian",
|
|
54
|
-
sourceChannel: "vellum",
|
|
55
|
-
resolvedActor: input.actorExternalId,
|
|
56
|
-
}),
|
|
57
|
-
withSourceChannel: (sourceChannel: unknown, ctx: Record<string, unknown>) => ({
|
|
58
|
-
...ctx,
|
|
59
|
-
sourceChannel,
|
|
60
|
-
}),
|
|
61
|
-
}));
|
|
62
|
-
|
|
63
|
-
const { reResolveTrustOnResetDrift } = await import(
|
|
64
|
-
"../guardian-vellum-migration.js"
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
function gatewayGuardian(principalId: string): Record<string, unknown> {
|
|
68
|
-
return {
|
|
69
|
-
channelType: "vellum",
|
|
70
|
-
contactId: "guardian-contact",
|
|
71
|
-
principalId,
|
|
72
|
-
address: principalId,
|
|
73
|
-
status: "active",
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function localGuardian(principalId: string) {
|
|
78
|
-
return {
|
|
79
|
-
contact: { id: "contact-1", principalId },
|
|
80
|
-
channel: { id: "channel-1" },
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
describe("reResolveTrustOnResetDrift", () => {
|
|
85
|
-
beforeEach(() => {
|
|
86
|
-
mockGuardianList = [];
|
|
87
|
-
mockLocalGuardian = null;
|
|
88
|
-
healWrites.length = 0;
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("reset drift: heals and returns the re-resolved guardian ctx", async () => {
|
|
92
|
-
// Stale local mirror still holds the pre-reset principal; the incoming JWT
|
|
93
|
-
// carries the old one. Heal repairs the mirror toward the incoming actor.
|
|
94
|
-
mockGuardianList = [gatewayGuardian("vellum-principal-new")];
|
|
95
|
-
mockLocalGuardian = localGuardian("vellum-principal-stale");
|
|
96
|
-
|
|
97
|
-
const ctx = await reResolveTrustOnResetDrift(
|
|
98
|
-
"vellum-principal-old",
|
|
99
|
-
"vellum",
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
expect(ctx?.trustClass).toBe("guardian");
|
|
103
|
-
expect(healWrites).toEqual([{ principalId: "vellum-principal-old" }]);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("repeat drift where heal no-ops still returns the guardian ctx", async () => {
|
|
107
|
-
// Local mirror already matches the incoming principal, so heal's write is
|
|
108
|
-
// skipped, but the gate still passes and the re-resolve yields guardian.
|
|
109
|
-
mockGuardianList = [gatewayGuardian("vellum-principal-new")];
|
|
110
|
-
mockLocalGuardian = localGuardian("vellum-principal-old");
|
|
111
|
-
|
|
112
|
-
const ctx = await reResolveTrustOnResetDrift(
|
|
113
|
-
"vellum-principal-old",
|
|
114
|
-
"vellum",
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
expect(ctx?.trustClass).toBe("guardian");
|
|
118
|
-
expect(healWrites).toEqual([]);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("gateway unreachable (null): returns null, heal not called", async () => {
|
|
122
|
-
mockGuardianList = null;
|
|
123
|
-
mockLocalGuardian = localGuardian("vellum-principal-old");
|
|
124
|
-
|
|
125
|
-
const ctx = await reResolveTrustOnResetDrift(
|
|
126
|
-
"vellum-principal-old",
|
|
127
|
-
"vellum",
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
expect(ctx).toBeNull();
|
|
131
|
-
expect(healWrites).toEqual([]);
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test("empty/revoked gateway (no active guardian): returns null, heal not called", async () => {
|
|
135
|
-
mockGuardianList = [];
|
|
136
|
-
mockLocalGuardian = localGuardian("vellum-principal-old");
|
|
137
|
-
|
|
138
|
-
const ctx = await reResolveTrustOnResetDrift(
|
|
139
|
-
"vellum-principal-old",
|
|
140
|
-
"vellum",
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
expect(ctx).toBeNull();
|
|
144
|
-
expect(healWrites).toEqual([]);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
test("gateway guardian is a real (non vellum-principal-*) id: returns null", async () => {
|
|
148
|
-
mockGuardianList = [gatewayGuardian("user@example.com")];
|
|
149
|
-
mockLocalGuardian = localGuardian("vellum-principal-old");
|
|
150
|
-
|
|
151
|
-
const ctx = await reResolveTrustOnResetDrift(
|
|
152
|
-
"vellum-principal-old",
|
|
153
|
-
"vellum",
|
|
154
|
-
);
|
|
155
|
-
|
|
156
|
-
expect(ctx).toBeNull();
|
|
157
|
-
expect(healWrites).toEqual([]);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
test("incoming principal is not vellum-principal-*: returns null", async () => {
|
|
161
|
-
mockGuardianList = [gatewayGuardian("vellum-principal-new")];
|
|
162
|
-
mockLocalGuardian = localGuardian("vellum-principal-old");
|
|
163
|
-
|
|
164
|
-
const ctx = await reResolveTrustOnResetDrift("user@example.com", "vellum");
|
|
165
|
-
|
|
166
|
-
expect(ctx).toBeNull();
|
|
167
|
-
expect(healWrites).toEqual([]);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("threads sourceChannel into the returned ctx", async () => {
|
|
171
|
-
mockGuardianList = [gatewayGuardian("vellum-principal-new")];
|
|
172
|
-
mockLocalGuardian = localGuardian("vellum-principal-old");
|
|
173
|
-
|
|
174
|
-
const ctx = await reResolveTrustOnResetDrift(
|
|
175
|
-
"vellum-principal-old",
|
|
176
|
-
"telegram",
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
expect(ctx?.sourceChannel).toBe("telegram");
|
|
180
|
-
});
|
|
181
|
-
});
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
// Gateway guardian-delivery list: null = couldn't determine (transport failure
|
|
4
|
-
// OR gateway-side resolver/DB error), [] = authoritative unbound, one active
|
|
5
|
-
// entry = bound.
|
|
6
|
-
let mockGuardianList: Array<Record<string, unknown>> | null = [];
|
|
7
|
-
const freshCalls: Array<{ channelTypes?: string[] } | undefined> = [];
|
|
8
|
-
|
|
9
|
-
mock.module("../../contacts/guardian-delivery-reader.js", () => ({
|
|
10
|
-
// Existence guard reads fresh (uncached); the binding/identity reads use the
|
|
11
|
-
// cached variant. The service imports both, so both must be stubbed.
|
|
12
|
-
getGuardianDeliveryFresh: (input?: { channelTypes?: string[] }) => {
|
|
13
|
-
freshCalls.push(input);
|
|
14
|
-
return Promise.resolve(mockGuardianList);
|
|
15
|
-
},
|
|
16
|
-
getGuardianDelivery: () => Promise.resolve(mockGuardianList),
|
|
17
|
-
guardianForChannel: (
|
|
18
|
-
list: Array<{ channelType: string; status: string }>,
|
|
19
|
-
channelType: string,
|
|
20
|
-
) => list.find((g) => g.channelType === channelType && g.status === "active"),
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
const { isGuardianBoundForChannel } = await import(
|
|
24
|
-
"../channel-verification-service.js"
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
describe("isGuardianBoundForChannel", () => {
|
|
28
|
-
beforeEach(() => {
|
|
29
|
-
mockGuardianList = [];
|
|
30
|
-
freshCalls.length = 0;
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test("reads fresh so a stale cached empty list can't mask a present guardian", async () => {
|
|
34
|
-
await isGuardianBoundForChannel("telegram");
|
|
35
|
-
expect(freshCalls).toEqual([{ channelTypes: ["telegram"] }]);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("returns false when no guardian is bound", async () => {
|
|
39
|
-
mockGuardianList = [];
|
|
40
|
-
expect(await isGuardianBoundForChannel("telegram")).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("returns true when a guardian is bound", async () => {
|
|
44
|
-
mockGuardianList = [{ channelType: "telegram", status: "active" }];
|
|
45
|
-
expect(await isGuardianBoundForChannel("telegram")).toBe(true);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("null list (gateway unreachable) is treated as bound", async () => {
|
|
49
|
-
mockGuardianList = null;
|
|
50
|
-
expect(await isGuardianBoundForChannel("telegram")).toBe(true);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
test("gateway resolver error (null, not []) is treated as bound — no duplicate", async () => {
|
|
54
|
-
// A gateway-side DB/resolver error now reaches the reader as null (the
|
|
55
|
-
// handler no longer swallows it into an empty list), so the guard's
|
|
56
|
-
// null fail-safe applies and reports bound instead of mis-reading the
|
|
57
|
-
// error as "no guardian" and allowing a duplicate binding.
|
|
58
|
-
mockGuardianList = null;
|
|
59
|
-
expect(await isGuardianBoundForChannel("telegram")).toBe(true);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test("genuine empty ([], not null) reports unbound so first-bind is allowed", async () => {
|
|
63
|
-
mockGuardianList = [];
|
|
64
|
-
expect(await isGuardianBoundForChannel("telegram")).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
});
|