@vellumai/assistant 0.8.5 → 0.8.6
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/AGENTS.md +33 -1
- package/ARCHITECTURE.md +1 -1
- package/bunfig.toml +6 -1
- package/docs/credential-execution-service.md +6 -6
- package/docs/plugins.md +4 -3
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
- package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
- package/openapi.yaml +1900 -166
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +3 -2
- package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
- package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/annotate-risk-options.test.ts +1 -0
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +9 -13
- package/src/__tests__/assert-not-live-db.ts +79 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
- package/src/__tests__/audit-log-rotation.test.ts +2 -2
- package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
- package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
- package/src/__tests__/browser-skill-endstate.test.ts +3 -3
- package/src/__tests__/btw-routes.test.ts +3 -2
- package/src/__tests__/call-controller.test.ts +3 -2
- package/src/__tests__/channel-approval-routes.test.ts +3 -2
- package/src/__tests__/channel-guardian.test.ts +3 -2
- package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
- package/src/__tests__/channel-reply-delivery.test.ts +35 -0
- package/src/__tests__/channel-retry-sweep.test.ts +320 -3
- package/src/__tests__/checker.test.ts +12 -12
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/compaction-trail-store.test.ts +264 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
- package/src/__tests__/computer-use-tools.test.ts +12 -14
- package/src/__tests__/config-loader-backfill.test.ts +13 -28
- package/src/__tests__/config-loader-corrupt.test.ts +5 -5
- package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
- package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
- package/src/__tests__/config-schema.test.ts +10 -10
- package/src/__tests__/connection-model-compat.test.ts +83 -0
- package/src/__tests__/contacts-tools.test.ts +3 -2
- package/src/__tests__/context-token-estimator.test.ts +22 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
- package/src/__tests__/conversation-agent-loop.test.ts +488 -2
- package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
- package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
- package/src/__tests__/conversation-attention-store.test.ts +101 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-error.test.ts +30 -0
- package/src/__tests__/conversation-fork-crud.test.ts +69 -8
- package/src/__tests__/conversation-fork-route.test.ts +3 -2
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
- package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
- package/src/__tests__/conversation-lifecycle.test.ts +1 -0
- package/src/__tests__/conversation-list-source.test.ts +3 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
- package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
- package/src/__tests__/conversation-pairing.test.ts +53 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
- package/src/__tests__/conversation-queue.test.ts +333 -291
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
- package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
- package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
- package/src/__tests__/conversation-skill-tools.test.ts +38 -142
- package/src/__tests__/conversation-slash-queue.test.ts +84 -32
- package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
- package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
- package/src/__tests__/conversation-sync-tags.test.ts +128 -12
- package/src/__tests__/conversation-title-service.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
- package/src/__tests__/conversation-usage.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
- package/src/__tests__/credential-broker-server-use.test.ts +5 -5
- package/src/__tests__/credential-execution-client.test.ts +72 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
- package/src/__tests__/credential-health-service.test.ts +252 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -5
- package/src/__tests__/credential-vault-unit.test.ts +19 -19
- package/src/__tests__/credential-vault.test.ts +5 -5
- package/src/__tests__/cross-provider-web-search.test.ts +56 -2
- package/src/__tests__/db-connection-isolation.test.ts +7 -6
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
- package/src/__tests__/db-test-helpers.ts +58 -0
- package/src/__tests__/disk-pressure-guard.test.ts +58 -41
- package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
- package/src/__tests__/disk-pressure-routes.test.ts +0 -33
- package/src/__tests__/disk-pressure-tools.test.ts +0 -4
- package/src/__tests__/dm-persistence.test.ts +26 -40
- package/src/__tests__/document-create-dedupe.test.ts +189 -0
- package/src/__tests__/document-find-replace.test.ts +3 -2
- package/src/__tests__/document-tool-security.test.ts +81 -2
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
- package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
- package/src/__tests__/encrypted-store.test.ts +11 -9
- package/src/__tests__/feature-flag-test-helpers.ts +53 -0
- package/src/__tests__/filing-service.test.ts +1 -0
- package/src/__tests__/first-greeting.test.ts +62 -12
- package/src/__tests__/gateway-flag-listener.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +26 -0
- package/src/__tests__/guardian-action-sweep.test.ts +3 -2
- package/src/__tests__/guardian-outbound-http.test.ts +3 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/helpers/mock-logger.ts +26 -0
- package/src/__tests__/host-bash-routes.test.ts +1 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-shell-tool.test.ts +5 -4
- package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
- package/src/__tests__/http-conversation-lineage.test.ts +3 -2
- package/src/__tests__/http-user-message-parity.test.ts +29 -7
- package/src/__tests__/identity-intro-cache.test.ts +133 -22
- package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
- package/src/__tests__/inference-profile-reaper.test.ts +3 -2
- package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
- package/src/__tests__/injector-disk-pressure.test.ts +3 -17
- package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
- package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
- package/src/__tests__/llm-context-normalization.test.ts +42 -0
- package/src/__tests__/llm-resolver.test.ts +331 -0
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
- package/src/__tests__/mcp-abort-signal.test.ts +14 -0
- package/src/__tests__/mcp-client-auth.test.ts +14 -0
- package/src/__tests__/messaging-send-tool.test.ts +1 -0
- package/src/__tests__/migration-import-from-url.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +18 -2
- package/src/__tests__/model-intents.test.ts +3 -3
- package/src/__tests__/native-web-search.test.ts +30 -2
- package/src/__tests__/notification-deep-link.test.ts +62 -0
- package/src/__tests__/oauth-commands-routes.test.ts +37 -0
- package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
- package/src/__tests__/oauth-store.test.ts +3 -2
- package/src/__tests__/onboarding-template-contract.test.ts +3 -2
- package/src/__tests__/openai-provider.test.ts +8 -9
- package/src/__tests__/openai-responses-provider.test.ts +70 -10
- package/src/__tests__/openrouter-provider-only.test.ts +27 -5
- package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
- package/src/__tests__/persistence-pipeline.test.ts +139 -1
- package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
- package/src/__tests__/plugin-bootstrap.test.ts +9 -11
- package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
- package/src/__tests__/process-message-background-slack.test.ts +21 -16
- package/src/__tests__/process-message-display-content.test.ts +19 -22
- package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
- package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
- package/src/__tests__/provider-registry-ollama.test.ts +45 -22
- package/src/__tests__/recording-handler.test.ts +1 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +82 -76
- package/src/__tests__/relay-server.test.ts +10 -10
- package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
- package/src/__tests__/schedule-store.test.ts +16 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
- package/src/__tests__/secret-ingress-http.test.ts +5 -1
- package/src/__tests__/secure-keys.test.ts +3 -3
- package/src/__tests__/send-endpoint-busy.test.ts +81 -42
- package/src/__tests__/server-history-render.test.ts +4 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
- package/src/__tests__/skill-feature-flags.test.ts +14 -16
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
- package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
- package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
- package/src/__tests__/skill-tool-factory.test.ts +96 -95
- package/src/__tests__/slack-channel-config.test.ts +3 -3
- package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
- package/src/__tests__/subagent-disposal.test.ts +27 -8
- package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
- package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
- package/src/__tests__/subagent-manager-notify.test.ts +20 -8
- package/src/__tests__/subagent-notify-parent.test.ts +5 -4
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
- package/src/__tests__/subagent-tools.test.ts +2 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +38 -0
- package/src/__tests__/test-preload-verifier.ts +68 -0
- package/src/__tests__/test-preload.ts +32 -39
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
- package/src/__tests__/tool-executor.test.ts +55 -10
- package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/twilio-routes.test.ts +3 -2
- package/src/__tests__/validate-input.test.ts +381 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
- package/src/__tests__/voice-session-bridge.test.ts +37 -28
- package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
- package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
- package/src/acp/session-manager.ts +5 -6
- package/src/agent/loop.ts +80 -0
- package/src/api/README.md +124 -2
- package/src/api/constants/call-sites.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +51 -0
- package/src/api/events/assistant-text-delta.ts +32 -0
- package/src/api/events/assistant-turn-start.ts +33 -0
- package/src/api/events/document-comment-created.ts +48 -0
- package/src/api/events/document-comment-deleted.ts +24 -0
- package/src/api/events/document-comment-reopened.ts +25 -0
- package/src/api/events/document-comment-resolved.ts +27 -0
- package/src/api/events/generation-cancelled.ts +24 -0
- package/src/api/events/generation-handoff.ts +41 -0
- package/src/api/events/message-complete.ts +42 -0
- package/src/api/events/open-url.ts +30 -0
- package/src/{events → api/events}/relationship-state-updated.ts +3 -3
- package/src/api/events/tool-use-start.ts +32 -0
- package/src/api/index.ts +128 -3
- package/src/api/responses/llm-context-response.ts +39 -0
- package/src/api/responses/llm-request-log-entry.ts +93 -0
- package/src/api/responses/memory-recall-log.ts +65 -0
- package/src/api/responses/memory-v2-activation-log.ts +78 -0
- package/src/background-wake/background-wake-routes.test.ts +687 -52
- package/src/background-wake/platform-client.test.ts +308 -0
- package/src/background-wake/platform-client.ts +167 -0
- package/src/background-wake/publisher.ts +91 -0
- package/src/background-wake/runtime-registry.ts +2 -2
- package/src/background-wake/wake-intent-hooks.test.ts +282 -0
- package/src/calls/guardian-dispatch.ts +1 -0
- package/src/calls/voice-session-bridge.ts +4 -4
- package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
- package/src/cli/commands/__tests__/notifications.test.ts +184 -40
- package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
- package/src/cli/commands/channels/index.ts +229 -0
- package/src/cli/commands/memory-v3-render.ts +147 -0
- package/src/cli/commands/memory-v3.ts +255 -4
- package/src/cli/commands/notifications.ts +365 -55
- package/src/cli/lib/open-browser.ts +7 -2
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +23 -42
- package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
- package/src/config/call-site-defaults.ts +1 -1
- package/src/config/feature-flag-cache.ts +86 -0
- package/src/config/feature-flag-registry.json +17 -17
- package/src/config/llm-context-resolution.ts +10 -1
- package/src/config/llm-resolver.ts +121 -15
- package/src/config/loader.ts +4 -5
- package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +90 -1
- package/src/config/schemas/memory-v2.ts +26 -0
- package/src/config/schemas/services.ts +6 -2
- package/src/config/seed-inference-profiles.ts +36 -16
- package/src/context/token-estimator.ts +10 -5
- package/src/credential-execution/executable-discovery.ts +40 -0
- package/src/credential-execution/process-manager.ts +6 -2
- package/src/credential-health/credential-health-service.ts +125 -40
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
- package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
- package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
- package/src/daemon/conversation-agent-loop.ts +132 -28
- package/src/daemon/conversation-error.ts +33 -5
- package/src/daemon/conversation-messaging.ts +84 -43
- package/src/daemon/conversation-process.ts +74 -37
- package/src/daemon/conversation-runtime-assembly.ts +29 -9
- package/src/daemon/conversation-skill-tools.ts +14 -30
- package/src/daemon/conversation-surfaces.ts +69 -34
- package/src/daemon/conversation-tool-setup.ts +33 -48
- package/src/daemon/conversation.ts +26 -46
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/daemon-skill-host.ts +9 -2
- package/src/daemon/disk-pressure-guard.ts +27 -29
- package/src/daemon/first-greeting.ts +31 -13
- package/src/daemon/handlers/shared.ts +6 -1
- package/src/daemon/lifecycle.ts +12 -12
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/meet-manifest-loader.ts +10 -17
- package/src/daemon/message-types/conversations.ts +20 -22
- package/src/daemon/message-types/document-comments.ts +8 -44
- package/src/daemon/message-types/home.ts +2 -2
- package/src/daemon/message-types/integrations.ts +2 -7
- package/src/daemon/message-types/messages.ts +23 -38
- package/src/daemon/message-types/subagents.ts +6 -0
- package/src/daemon/process-message.ts +9 -9
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +16 -0
- package/src/daemon/switch-inference-profile-tool.ts +13 -3
- package/src/daemon/tool-setup-types.ts +0 -6
- package/src/daemon/wake-target-adapter.ts +10 -0
- package/src/documents/document-store.ts +38 -0
- package/src/export/__tests__/transcript-formatter.test.ts +1 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
- package/src/heartbeat/heartbeat-service.ts +63 -0
- package/src/home/__tests__/feed-writer.test.ts +161 -0
- package/src/home/__tests__/post-connect-feed.test.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +55 -59
- package/src/home/feed-writer.ts +146 -7
- package/src/home/suggested-prompts.ts +27 -145
- package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
- package/src/ipc/gateway-client.test.ts +4 -1
- package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
- package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
- package/src/ipc/skill-routes/memory.ts +4 -3
- package/src/ipc/skill-routes/registries.ts +28 -29
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
- package/src/memory/conversation-attention-store.ts +17 -3
- package/src/memory/conversation-crud.ts +352 -112
- package/src/memory/db-connection.ts +29 -19
- package/src/memory/db-init.ts +4 -0
- package/src/memory/db-singleton.ts +77 -0
- package/src/memory/delivery-channels.ts +82 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
- package/src/memory/graph/retriever.test.ts +3 -3
- package/src/memory/job-handlers/embedding.test.ts +3 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
- package/src/memory/jobs-worker.ts +12 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
- package/src/memory/llm-request-log-source-local.ts +24 -0
- package/src/memory/llm-request-log-source.ts +31 -0
- package/src/memory/llm-request-log-store.ts +188 -3
- package/src/memory/memory-v2-activation-log-store.ts +95 -1
- package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
- package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/schema/conversations.ts +9 -1
- package/src/memory/schema/inference.ts +0 -1
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
- package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
- package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
- package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
- package/src/memory/v2/harness/metrics.ts +5 -1
- package/src/memory/v2/harness/replay-input.ts +19 -3
- package/src/memory/v2/harness/runner.ts +6 -0
- package/src/memory/v2/harness/trace.ts +6 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
- package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
- package/src/memory/v3/__tests__/edges.test.ts +144 -1
- package/src/memory/v3/__tests__/filter.test.ts +48 -0
- package/src/memory/v3/__tests__/gate.test.ts +96 -33
- package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
- package/src/memory/v3/__tests__/loop.test.ts +250 -5
- package/src/memory/v3/__tests__/scouts.test.ts +49 -0
- package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
- package/src/memory/v3/__tests__/traversal.test.ts +39 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
- package/src/memory/v3/__tests__/validate.test.ts +32 -0
- package/src/memory/v3/coretrieval-seed.ts +240 -0
- package/src/memory/v3/edges.ts +58 -21
- package/src/memory/v3/filter.ts +27 -22
- package/src/memory/v3/gate.ts +51 -36
- package/src/memory/v3/index-composition.ts +18 -5
- package/src/memory/v3/loop.ts +65 -17
- package/src/memory/v3/scouts.ts +15 -4
- package/src/memory/v3/shadow-diff.ts +287 -0
- package/src/memory/v3/shadow-middleware.ts +44 -2
- package/src/memory/v3/traversal.ts +6 -1
- package/src/memory/v3/tree-walk.ts +6 -1
- package/src/memory/v3/validate.ts +56 -33
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
- package/src/notifications/adapters/slack.ts +45 -11
- package/src/notifications/broadcaster.ts +114 -63
- package/src/notifications/conversation-pairing.ts +23 -3
- package/src/notifications/decisions-store.ts +32 -1
- package/src/notifications/deliveries-store.ts +45 -0
- package/src/notifications/edit-notification.ts +201 -0
- package/src/notifications/emit-signal.ts +11 -1
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +37 -0
- package/src/oauth/byo-connection.test.ts +67 -3
- package/src/oauth/byo-connection.ts +32 -5
- package/src/oauth/connect-orchestrator.ts +9 -0
- package/src/oauth/connection-resolver.test.ts +76 -0
- package/src/oauth/connection-resolver.ts +49 -10
- package/src/oauth/manual-token-connection.ts +51 -3
- package/src/oauth/seed-providers.ts +3 -0
- package/src/permissions/approval-policy.test.ts +19 -5
- package/src/permissions/approval-policy.ts +14 -3
- package/src/permissions/checker.ts +21 -8
- package/src/platform/client.test.ts +24 -1
- package/src/platform/client.ts +8 -0
- package/src/platform/feature-gate.ts +15 -0
- package/src/plugins/defaults/injectors.ts +2 -8
- package/src/plugins/defaults/persistence.ts +25 -6
- package/src/plugins/types.ts +57 -13
- package/src/proactive-artifact/job.test.ts +1 -0
- package/src/prompts/__tests__/system-prompt.test.ts +4 -4
- package/src/prompts/system-prompt.ts +38 -40
- package/src/prompts/template-detection.ts +10 -4
- package/src/prompts/templates/BOOTSTRAP.md +7 -11
- package/src/prompts/templates/IDENTITY.md +0 -2
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
- package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
- package/src/providers/call-site-routing.ts +33 -9
- package/src/providers/connection-model-compat.ts +23 -0
- package/src/providers/connection-resolution.ts +39 -20
- package/src/providers/fireworks/client.ts +1 -0
- package/src/providers/gemini/client.ts +24 -3
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
- package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
- package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
- package/src/providers/inference/auth.ts +0 -8
- package/src/providers/inference/connections.ts +3 -66
- package/src/providers/inference/resolve-auth.ts +2 -3
- package/src/providers/model-catalog.ts +35 -1
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
- package/src/providers/openai/chat-completions-provider.ts +110 -12
- package/src/providers/openai/codex-models.ts +2 -0
- package/src/providers/openai/responses-provider.ts +53 -53
- package/src/providers/openrouter/client.ts +13 -8
- package/src/providers/provider-send-message.ts +18 -9
- package/src/providers/registry.ts +48 -8
- package/src/providers/retry.ts +16 -4
- package/src/providers/search-provider-catalog.ts +17 -9
- package/src/providers/types.ts +9 -0
- package/src/runtime/__tests__/agent-wake.test.ts +1 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
- package/src/runtime/access-request-helper.ts +1 -0
- package/src/runtime/auth/route-policy.ts +10 -0
- package/src/runtime/channel-readiness-service.ts +68 -0
- package/src/runtime/channel-reply-delivery.ts +23 -0
- package/src/runtime/channel-retry-sweep.ts +47 -14
- package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
- package/src/runtime/migrations/vbundle-builder.ts +3 -2
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
- package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
- package/src/runtime/routes/acp-routes-list.test.ts +3 -0
- package/src/runtime/routes/app-management-routes.ts +111 -4
- package/src/runtime/routes/background-wake-routes.ts +188 -20
- package/src/runtime/routes/btw-routes.ts +4 -4
- package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
- package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
- package/src/runtime/routes/conversation-list-routes.ts +147 -0
- package/src/runtime/routes/conversation-management-routes.ts +39 -14
- package/src/runtime/routes/conversation-query-routes.ts +60 -10
- package/src/runtime/routes/conversation-routes.ts +186 -140
- package/src/runtime/routes/conversations-import-routes.ts +19 -6
- package/src/runtime/routes/documents-routes.ts +10 -1
- package/src/runtime/routes/group-routes.ts +11 -0
- package/src/runtime/routes/home-feed-routes.ts +129 -0
- package/src/runtime/routes/identity-intro-cache.ts +61 -16
- package/src/runtime/routes/identity-routes.ts +30 -9
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
- package/src/runtime/routes/integrations/vercel.ts +15 -0
- package/src/runtime/routes/llm-context-normalization.ts +7 -2
- package/src/runtime/routes/memory-v3-routes.ts +160 -2
- package/src/runtime/routes/migration-routes.ts +20 -13
- package/src/runtime/routes/notification-routes.ts +63 -1
- package/src/runtime/routes/oauth-commands-routes.ts +6 -1
- package/src/runtime/routes/surface-action-routes.ts +1 -38
- package/src/runtime/routes/surface-content-routes.ts +12 -5
- package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
- package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
- package/src/runtime/slack-dm-text-delivery.ts +177 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -1
- package/src/runtime/tool-grant-request-helper.ts +1 -0
- package/src/schedule/schedule-store.ts +8 -1
- package/src/schedule/scheduler.ts +111 -15
- package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
- package/src/security/encrypted-store.ts +7 -16
- package/src/security/store-path-override.ts +61 -0
- package/src/signals/user-message.ts +5 -8
- package/src/skills/validate-input.ts +177 -0
- package/src/subagent/manager.ts +13 -13
- package/src/subagent/types.ts +6 -0
- package/src/tasks/tool-sanitizer.ts +2 -2
- package/src/tools/apps/definitions.ts +35 -21
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
- package/src/tools/computer-use/definitions.ts +268 -266
- package/src/tools/document/document-tool.ts +131 -8
- package/src/tools/execution-target.ts +2 -5
- package/src/tools/executor.ts +18 -55
- package/src/tools/host-filesystem/edit.test.ts +1 -0
- package/src/tools/host-filesystem/read.test.ts +1 -0
- package/src/tools/host-filesystem/transfer.test.ts +31 -6
- package/src/tools/host-filesystem/write.test.ts +1 -0
- package/src/tools/mcp/mcp-tool-factory.ts +0 -2
- package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
- package/src/tools/network/__tests__/web-search.test.ts +211 -3
- package/src/tools/network/managed-search-proxy.ts +183 -0
- package/src/tools/network/web-search.ts +199 -44
- package/src/tools/policy-context.ts +3 -1
- package/src/tools/registry.ts +146 -103
- package/src/tools/schedule/create.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +17 -36
- package/src/tools/subagent/spawn.ts +3 -0
- package/src/tools/tool-approval-handler.ts +10 -4
- package/src/tools/tool-name-aliases.ts +72 -14
- package/src/tools/types.ts +17 -15
- package/src/tools/ui-surface/definitions.ts +98 -86
- package/src/types/onboarding-context.ts +6 -0
- package/src/usage/attribution.ts +32 -1
- package/src/util/browser.ts +7 -2
- package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
- package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
- package/src/workspace/migrations/registry.ts +4 -0
|
@@ -142,6 +142,8 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
142
142
|
setLastNotifiedInferenceProfile: () => {},
|
|
143
143
|
getConversationOverrideProfileFromRow: () => undefined,
|
|
144
144
|
updateMessageMetadata: () => {},
|
|
145
|
+
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
146
|
+
updateMessageContent: mock(() => {}),
|
|
145
147
|
}));
|
|
146
148
|
|
|
147
149
|
mock.module("../memory/conversation-queries.js", () => ({
|
|
@@ -228,6 +230,9 @@ mock.module("../agent/loop.js", () => ({
|
|
|
228
230
|
messages: Message[],
|
|
229
231
|
onEvent: (event: AgentEvent) => void,
|
|
230
232
|
): Promise<Message[]> {
|
|
233
|
+
// Prime the assistant row anchor — production code emits this from
|
|
234
|
+
// `AgentLoop.run` just before `provider.sendMessage`.
|
|
235
|
+
await onEvent({ type: "llm_call_started" });
|
|
231
236
|
agentLoopScript(onEvent);
|
|
232
237
|
onEvent({
|
|
233
238
|
type: "usage",
|
|
@@ -28,8 +28,8 @@ mock.module("../util/logger.js", () => ({
|
|
|
28
28
|
// Use encrypted backend with a temp store path
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
|
|
31
|
-
import { _setStorePath } from "../security/encrypted-store.js";
|
|
32
31
|
import { _resetBackend } from "../security/secure-keys.js";
|
|
32
|
+
import { setStorePathForTesting } from "./encrypted-store-test-helpers.js";
|
|
33
33
|
|
|
34
34
|
const TEST_DIR = join(
|
|
35
35
|
tmpdir(),
|
|
@@ -74,7 +74,7 @@ describe("CredentialBroker.browserFill", () => {
|
|
|
74
74
|
for (const entry of readdirSync(TEST_DIR)) {
|
|
75
75
|
rmSync(join(TEST_DIR, entry), { recursive: true, force: true });
|
|
76
76
|
}
|
|
77
|
-
|
|
77
|
+
setStorePathForTesting(STORE_PATH);
|
|
78
78
|
_resetBackend();
|
|
79
79
|
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
80
80
|
broker = new CredentialBroker();
|
|
@@ -82,7 +82,7 @@ describe("CredentialBroker.browserFill", () => {
|
|
|
82
82
|
|
|
83
83
|
afterEach(() => {
|
|
84
84
|
_setMetadataPath(null);
|
|
85
|
-
|
|
85
|
+
setStorePathForTesting(null);
|
|
86
86
|
_resetBackend();
|
|
87
87
|
});
|
|
88
88
|
|
|
@@ -27,8 +27,8 @@ mock.module("../util/logger.js", () => ({
|
|
|
27
27
|
// Use encrypted backend with a temp store path
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
29
29
|
|
|
30
|
-
import { _setStorePath } from "../security/encrypted-store.js";
|
|
31
30
|
import { _resetBackend } from "../security/secure-keys.js";
|
|
31
|
+
import { setStorePathForTesting } from "./encrypted-store-test-helpers.js";
|
|
32
32
|
|
|
33
33
|
const TEST_DIR = join(
|
|
34
34
|
tmpdir(),
|
|
@@ -69,7 +69,7 @@ describe("CredentialBroker.serverUse", () => {
|
|
|
69
69
|
|
|
70
70
|
beforeEach(() => {
|
|
71
71
|
mkdirSync(TEST_DIR, { recursive: true });
|
|
72
|
-
|
|
72
|
+
setStorePathForTesting(STORE_PATH);
|
|
73
73
|
_resetBackend();
|
|
74
74
|
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
75
75
|
broker = new CredentialBroker();
|
|
@@ -77,7 +77,7 @@ describe("CredentialBroker.serverUse", () => {
|
|
|
77
77
|
|
|
78
78
|
afterEach(() => {
|
|
79
79
|
_setMetadataPath(null);
|
|
80
|
-
|
|
80
|
+
setStorePathForTesting(null);
|
|
81
81
|
_resetBackend();
|
|
82
82
|
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
83
83
|
});
|
|
@@ -464,7 +464,7 @@ describe("CredentialBroker.serverUseById", () => {
|
|
|
464
464
|
|
|
465
465
|
beforeEach(() => {
|
|
466
466
|
mkdirSync(TEST_DIR, { recursive: true });
|
|
467
|
-
|
|
467
|
+
setStorePathForTesting(STORE_PATH);
|
|
468
468
|
_resetBackend();
|
|
469
469
|
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
470
470
|
broker = new CredentialBroker();
|
|
@@ -472,7 +472,7 @@ describe("CredentialBroker.serverUseById", () => {
|
|
|
472
472
|
|
|
473
473
|
afterEach(() => {
|
|
474
474
|
_setMetadataPath(null);
|
|
475
|
-
|
|
475
|
+
setStorePathForTesting(null);
|
|
476
476
|
_resetBackend();
|
|
477
477
|
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
478
478
|
});
|
|
@@ -8,7 +8,15 @@
|
|
|
8
8
|
* 4. The CES RPC client correctly frames requests and validates responses.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
mkdtempSync,
|
|
13
|
+
readdirSync,
|
|
14
|
+
readFileSync,
|
|
15
|
+
rmSync,
|
|
16
|
+
statSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
} from "node:fs";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
12
20
|
import { join, resolve } from "node:path";
|
|
13
21
|
import { describe, expect, test } from "bun:test";
|
|
14
22
|
|
|
@@ -22,6 +30,7 @@ import {
|
|
|
22
30
|
createCesClient,
|
|
23
31
|
} from "../credential-execution/client.js";
|
|
24
32
|
import {
|
|
33
|
+
discoverCesWithRetry,
|
|
25
34
|
discoverLocalCes,
|
|
26
35
|
discoverManagedCes,
|
|
27
36
|
} from "../credential-execution/executable-discovery.js";
|
|
@@ -147,6 +156,68 @@ describe("managed CES discovery", () => {
|
|
|
147
156
|
});
|
|
148
157
|
});
|
|
149
158
|
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
// Managed discovery retry — absorbs the sidecar's socket re-bind window
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
describe("discoverCesWithRetry", () => {
|
|
164
|
+
function withManagedEnv(socketPath: string): () => void {
|
|
165
|
+
const savedSocket = process.env["CES_BOOTSTRAP_SOCKET"];
|
|
166
|
+
const savedSocketDir = process.env["CES_BOOTSTRAP_SOCKET_DIR"];
|
|
167
|
+
const savedContainerized = process.env["IS_CONTAINERIZED"];
|
|
168
|
+
delete process.env["CES_BOOTSTRAP_SOCKET_DIR"];
|
|
169
|
+
process.env["CES_BOOTSTRAP_SOCKET"] = socketPath;
|
|
170
|
+
process.env["IS_CONTAINERIZED"] = "true";
|
|
171
|
+
return () => {
|
|
172
|
+
const restore = (key: string, value: string | undefined) => {
|
|
173
|
+
if (value !== undefined) process.env[key] = value;
|
|
174
|
+
else delete process.env[key];
|
|
175
|
+
};
|
|
176
|
+
restore("CES_BOOTSTRAP_SOCKET", savedSocket);
|
|
177
|
+
restore("CES_BOOTSTRAP_SOCKET_DIR", savedSocketDir);
|
|
178
|
+
restore("IS_CONTAINERIZED", savedContainerized);
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
test("returns unavailable after polling when the socket never appears", async () => {
|
|
183
|
+
const socketPath = join(tmpdir(), `ces-retry-missing-${Date.now()}.sock`);
|
|
184
|
+
const restore = withManagedEnv(socketPath);
|
|
185
|
+
try {
|
|
186
|
+
const start = Date.now();
|
|
187
|
+
const result = await discoverCesWithRetry({
|
|
188
|
+
timeoutMs: 200,
|
|
189
|
+
intervalMs: 20,
|
|
190
|
+
});
|
|
191
|
+
expect(result.mode).toBe("unavailable");
|
|
192
|
+
// It must actually have waited rather than failing on the first probe.
|
|
193
|
+
expect(Date.now() - start).toBeGreaterThanOrEqual(150);
|
|
194
|
+
} finally {
|
|
195
|
+
restore();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("resolves to managed once the socket is re-bound mid-poll", async () => {
|
|
200
|
+
const dir = mkdtempSync(join(tmpdir(), "ces-retry-"));
|
|
201
|
+
const socketPath = join(dir, "ces.sock");
|
|
202
|
+
const restore = withManagedEnv(socketPath);
|
|
203
|
+
try {
|
|
204
|
+
// Simulate the sidecar re-binding its bootstrap socket shortly after
|
|
205
|
+
// the assistant begins probing.
|
|
206
|
+
setTimeout(() => writeFileSync(socketPath, ""), 80);
|
|
207
|
+
|
|
208
|
+
const result = await discoverCesWithRetry({
|
|
209
|
+
timeoutMs: 2_000,
|
|
210
|
+
intervalMs: 20,
|
|
211
|
+
});
|
|
212
|
+
expect(result.mode).toBe("managed");
|
|
213
|
+
expect((result as { socketPath: string }).socketPath).toBe(socketPath);
|
|
214
|
+
} finally {
|
|
215
|
+
restore();
|
|
216
|
+
rmSync(dir, { recursive: true, force: true });
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
150
221
|
// ---------------------------------------------------------------------------
|
|
151
222
|
// CES client — transport and framing
|
|
152
223
|
// ---------------------------------------------------------------------------
|
|
@@ -10,10 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
12
12
|
|
|
13
|
-
import {
|
|
14
|
-
_setOverridesForTesting,
|
|
15
|
-
isAssistantFeatureFlagEnabled,
|
|
16
|
-
} from "../config/assistant-feature-flags.js";
|
|
13
|
+
import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
|
|
17
14
|
import type { AssistantConfig } from "../config/schema.js";
|
|
18
15
|
import {
|
|
19
16
|
CES_GRANT_AUDIT_FLAG_KEY,
|
|
@@ -25,20 +22,21 @@ import {
|
|
|
25
22
|
isCesShellLockdownEnabled,
|
|
26
23
|
isCesToolsEnabled,
|
|
27
24
|
} from "../credential-execution/feature-gates.js";
|
|
25
|
+
import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
|
|
28
26
|
|
|
29
27
|
beforeEach(() => {
|
|
30
|
-
|
|
28
|
+
setOverridesForTesting({});
|
|
31
29
|
});
|
|
32
30
|
|
|
33
31
|
afterEach(() => {
|
|
34
|
-
|
|
32
|
+
setOverridesForTesting({});
|
|
35
33
|
});
|
|
36
34
|
|
|
37
35
|
// ---------------------------------------------------------------------------
|
|
38
36
|
// Helpers
|
|
39
37
|
// ---------------------------------------------------------------------------
|
|
40
38
|
|
|
41
|
-
/** Create a minimal AssistantConfig (flag overrides are now set via
|
|
39
|
+
/** Create a minimal AssistantConfig (flag overrides are now set via setOverridesForTesting). */
|
|
42
40
|
function makeConfig(): AssistantConfig {
|
|
43
41
|
return {} as AssistantConfig;
|
|
44
42
|
}
|
|
@@ -120,13 +118,13 @@ describe("CES flags match registry defaults", () => {
|
|
|
120
118
|
describe("CES flags can be toggled independently", () => {
|
|
121
119
|
for (const { name, fn, key } of ALL_CES_PREDICATES) {
|
|
122
120
|
test(`enabling ${key} makes ${name} return true`, () => {
|
|
123
|
-
|
|
121
|
+
setOverridesForTesting({ [key]: true });
|
|
124
122
|
const config = makeConfig();
|
|
125
123
|
expect(fn(config)).toBe(true);
|
|
126
124
|
});
|
|
127
125
|
|
|
128
126
|
test(`enabling ${key} does not change other CES flags from their defaults`, () => {
|
|
129
|
-
|
|
127
|
+
setOverridesForTesting({ [key]: true });
|
|
130
128
|
const config = makeConfig();
|
|
131
129
|
for (const {
|
|
132
130
|
fn: otherFn,
|
|
@@ -147,7 +145,7 @@ describe("CES flags can be toggled independently", () => {
|
|
|
147
145
|
describe("CES flags respect explicit false overrides", () => {
|
|
148
146
|
for (const { name, fn, key } of ALL_CES_PREDICATES) {
|
|
149
147
|
test(`${name} returns false when explicitly set to false`, () => {
|
|
150
|
-
|
|
148
|
+
setOverridesForTesting({ [key]: false });
|
|
151
149
|
const config = makeConfig();
|
|
152
150
|
expect(fn(config)).toBe(false);
|
|
153
151
|
});
|
|
@@ -164,7 +162,7 @@ describe("CES flags do not affect unrelated flags", () => {
|
|
|
164
162
|
for (const key of ALL_CES_FLAG_KEYS) {
|
|
165
163
|
overrides[key] = true;
|
|
166
164
|
}
|
|
167
|
-
|
|
165
|
+
setOverridesForTesting(overrides);
|
|
168
166
|
const config = makeConfig();
|
|
169
167
|
|
|
170
168
|
// account-deletion defaults to true in the registry and should stay true.
|
|
@@ -178,7 +176,7 @@ describe("CES flags do not affect unrelated flags", () => {
|
|
|
178
176
|
for (const key of ALL_CES_FLAG_KEYS) {
|
|
179
177
|
overrides[key] = true;
|
|
180
178
|
}
|
|
181
|
-
|
|
179
|
+
setOverridesForTesting(overrides);
|
|
182
180
|
const config = makeConfig();
|
|
183
181
|
|
|
184
182
|
// Unknown flags fail closed unless explicitly overridden.
|
|
@@ -12,6 +12,7 @@ let mockProviders: Array<{
|
|
|
12
12
|
pingMethod: string | null;
|
|
13
13
|
pingHeaders: string | null;
|
|
14
14
|
pingBody: string | null;
|
|
15
|
+
managedServiceConfigKey?: string;
|
|
15
16
|
}> = [];
|
|
16
17
|
|
|
17
18
|
let mockConnections: Map<
|
|
@@ -33,6 +34,24 @@ let mockFetchResponse: { ok: boolean; status: number } = {
|
|
|
33
34
|
};
|
|
34
35
|
let mockFetchThrows = false;
|
|
35
36
|
|
|
37
|
+
// Per-test refresh outcome — drives the withValidToken mock.
|
|
38
|
+
// "ok" → refresh succeeds (or wasn't needed); callback runs with token
|
|
39
|
+
// "refresh_failed" → refresh throws TokenExpiredError (revoked refresh token)
|
|
40
|
+
type RefreshOutcome = "ok" | "refresh_failed";
|
|
41
|
+
let mockRefreshOutcome: RefreshOutcome = "ok";
|
|
42
|
+
|
|
43
|
+
// Managed-path mock state.
|
|
44
|
+
const managedProviders = new Set<string>();
|
|
45
|
+
let managedListResponse: {
|
|
46
|
+
ok: boolean;
|
|
47
|
+
status: number;
|
|
48
|
+
body?: unknown;
|
|
49
|
+
} = { ok: true, status: 200, body: { results: [] } };
|
|
50
|
+
let managedPingOutcome:
|
|
51
|
+
| { kind: "status"; status: number }
|
|
52
|
+
| { kind: "credential_required" }
|
|
53
|
+
| { kind: "throw"; message: string } = { kind: "status", status: 200 };
|
|
54
|
+
|
|
36
55
|
// ── Module mocks ─────────────────────────────────────────────────────
|
|
37
56
|
|
|
38
57
|
mock.module("../security/secure-keys.js", () => ({
|
|
@@ -81,6 +100,88 @@ mock.module("../util/logger.js", () => ({
|
|
|
81
100
|
}),
|
|
82
101
|
}));
|
|
83
102
|
|
|
103
|
+
class MockTokenExpiredError extends Error {
|
|
104
|
+
constructor(service: string, message?: string) {
|
|
105
|
+
super(message ?? `Token expired for "${service}"`);
|
|
106
|
+
this.name = "TokenExpiredError";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
mock.module("../security/token-manager.js", () => ({
|
|
111
|
+
TokenExpiredError: MockTokenExpiredError,
|
|
112
|
+
withValidToken: async (
|
|
113
|
+
service: string,
|
|
114
|
+
callback: (token: string) => Promise<unknown>,
|
|
115
|
+
opts: { connectionId: string },
|
|
116
|
+
) => {
|
|
117
|
+
if (mockRefreshOutcome === "refresh_failed") {
|
|
118
|
+
throw new MockTokenExpiredError(service);
|
|
119
|
+
}
|
|
120
|
+
const token =
|
|
121
|
+
secureKeyValues.get(
|
|
122
|
+
`oauth_connection/${opts.connectionId}/access_token`,
|
|
123
|
+
) ?? "refreshed-token";
|
|
124
|
+
return callback(token);
|
|
125
|
+
},
|
|
126
|
+
}));
|
|
127
|
+
|
|
128
|
+
// Managed-path mocks: services schema, config loader, platform client,
|
|
129
|
+
// PlatformOAuthConnection. The managed code path uses dynamic imports for
|
|
130
|
+
// these so they only matter when a managed test scenario is exercised.
|
|
131
|
+
mock.module("../config/schemas/services.js", () => ({
|
|
132
|
+
ServicesSchema: {
|
|
133
|
+
shape: {
|
|
134
|
+
"google-oauth": {},
|
|
135
|
+
"notion-oauth": {},
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
getServiceMode: (_services: unknown, key: string) =>
|
|
139
|
+
managedProviders.has(key) ? "managed" : "your-own",
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
mock.module("../config/loader.js", () => ({
|
|
143
|
+
getConfig: () => ({ services: {} }),
|
|
144
|
+
}));
|
|
145
|
+
|
|
146
|
+
mock.module("../platform/client.js", () => ({
|
|
147
|
+
VellumPlatformClient: {
|
|
148
|
+
create: async () => ({
|
|
149
|
+
platformAssistantId: "test-assistant",
|
|
150
|
+
fetch: async (_path: string) => ({
|
|
151
|
+
ok: managedListResponse.ok,
|
|
152
|
+
status: managedListResponse.status,
|
|
153
|
+
json: async () => managedListResponse.body ?? { results: [] },
|
|
154
|
+
}),
|
|
155
|
+
}),
|
|
156
|
+
},
|
|
157
|
+
}));
|
|
158
|
+
|
|
159
|
+
class MockCredentialRequiredError extends Error {
|
|
160
|
+
constructor() {
|
|
161
|
+
super("credential required");
|
|
162
|
+
this.name = "CredentialRequiredError";
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
mock.module("../oauth/platform-connection.js", () => ({
|
|
167
|
+
PlatformOAuthConnection: class {
|
|
168
|
+
constructor(_opts: unknown) {}
|
|
169
|
+
async request(_req: unknown) {
|
|
170
|
+
if (managedPingOutcome.kind === "credential_required") {
|
|
171
|
+
throw new MockCredentialRequiredError();
|
|
172
|
+
}
|
|
173
|
+
if (managedPingOutcome.kind === "throw") {
|
|
174
|
+
throw new Error(managedPingOutcome.message);
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
status: managedPingOutcome.status,
|
|
178
|
+
headers: {},
|
|
179
|
+
body: {},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
}));
|
|
184
|
+
|
|
84
185
|
// ── Import under test ────────────────────────────────────────────────
|
|
85
186
|
|
|
86
187
|
const { checkAllCredentials, checkCredentialForProvider, _setFetchFn } =
|
|
@@ -106,6 +207,7 @@ function addProvider(
|
|
|
106
207
|
defaultScopes?: string[];
|
|
107
208
|
pingUrl?: string | null;
|
|
108
209
|
pingMethod?: string | null;
|
|
210
|
+
managedServiceConfigKey?: string;
|
|
109
211
|
},
|
|
110
212
|
) {
|
|
111
213
|
mockProviders.push({
|
|
@@ -115,6 +217,10 @@ function addProvider(
|
|
|
115
217
|
pingMethod: opts?.pingMethod ?? null,
|
|
116
218
|
pingHeaders: null,
|
|
117
219
|
pingBody: null,
|
|
220
|
+
// checkManagedProvider reads this field via OAuthProviderRow; the
|
|
221
|
+
// health-check code branches to the managed path only when set AND
|
|
222
|
+
// when managedProviders includes the corresponding config key.
|
|
223
|
+
managedServiceConfigKey: opts?.managedServiceConfigKey,
|
|
118
224
|
});
|
|
119
225
|
}
|
|
120
226
|
|
|
@@ -159,6 +265,10 @@ describe("credential-health-service", () => {
|
|
|
159
265
|
mockConnections = new Map();
|
|
160
266
|
mockFetchResponse = { ok: true, status: 200 };
|
|
161
267
|
mockFetchThrows = false;
|
|
268
|
+
mockRefreshOutcome = "ok";
|
|
269
|
+
managedProviders.clear();
|
|
270
|
+
managedListResponse = { ok: true, status: 200, body: { results: [] } };
|
|
271
|
+
managedPingOutcome = { kind: "status", status: 200 };
|
|
162
272
|
});
|
|
163
273
|
|
|
164
274
|
test("returns empty report when no providers exist", async () => {
|
|
@@ -238,8 +348,12 @@ describe("credential-health-service", () => {
|
|
|
238
348
|
expect(report.results[0]!.canAutoRecover).toBe(false);
|
|
239
349
|
});
|
|
240
350
|
|
|
241
|
-
test("returns
|
|
242
|
-
|
|
351
|
+
test("returns healthy when expired token has a refresh token, no ping URL, and refresh succeeds via the ping path", async () => {
|
|
352
|
+
// Without a pingUrl there's no opportunity to exercise the refresh —
|
|
353
|
+
// the connection is reported as healthy on the trust that the next
|
|
354
|
+
// real API call will refresh transparently. This matches the contract
|
|
355
|
+
// for connections that have a refresh token but no liveness endpoint.
|
|
356
|
+
addProvider("google"); // no pingUrl
|
|
243
357
|
addConnection("google", "conn-1", {
|
|
244
358
|
expiresAt: Date.now() - 60_000, // 1 minute ago
|
|
245
359
|
hasRefreshToken: true,
|
|
@@ -247,10 +361,61 @@ describe("credential-health-service", () => {
|
|
|
247
361
|
setToken("conn-1");
|
|
248
362
|
|
|
249
363
|
const report = await checkAllCredentials();
|
|
250
|
-
expect(report.results[0]!.status).toBe("
|
|
364
|
+
expect(report.results[0]!.status).toBe("healthy");
|
|
251
365
|
expect(report.results[0]!.canAutoRecover).toBe(true);
|
|
252
366
|
});
|
|
253
367
|
|
|
368
|
+
test("expired BYO token + successful refresh + 200 ping → healthy", async () => {
|
|
369
|
+
// The withValidToken mock returns "refreshed" by default; pingProvider
|
|
370
|
+
// sees a fresh token and the upstream returns 200.
|
|
371
|
+
mockFetchResponse = { ok: true, status: 200 };
|
|
372
|
+
addProvider("google", {
|
|
373
|
+
pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
374
|
+
});
|
|
375
|
+
addConnection("google", "conn-1", {
|
|
376
|
+
expiresAt: Date.now() - 60_000,
|
|
377
|
+
hasRefreshToken: true,
|
|
378
|
+
});
|
|
379
|
+
setToken("conn-1");
|
|
380
|
+
|
|
381
|
+
const report = await checkAllCredentials();
|
|
382
|
+
expect(report.results[0]!.status).toBe("healthy");
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test("expired BYO token + refresh fails (revoked refresh token) → revoked", async () => {
|
|
386
|
+
mockRefreshOutcome = "refresh_failed";
|
|
387
|
+
addProvider("google", {
|
|
388
|
+
pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
389
|
+
});
|
|
390
|
+
addConnection("google", "conn-1", {
|
|
391
|
+
expiresAt: Date.now() - 60_000,
|
|
392
|
+
hasRefreshToken: true,
|
|
393
|
+
});
|
|
394
|
+
setToken("conn-1");
|
|
395
|
+
|
|
396
|
+
const report = await checkAllCredentials();
|
|
397
|
+
expect(report.results[0]!.status).toBe("revoked");
|
|
398
|
+
expect(report.results[0]!.canAutoRecover).toBe(false);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("BYO ping that still returns 401 after a refresh attempt → revoked", async () => {
|
|
402
|
+
// Refresh succeeds (mockRefreshOutcome stays "ok") but the upstream
|
|
403
|
+
// still returns 401 — the token was genuinely revoked by the provider.
|
|
404
|
+
mockFetchResponse = { ok: false, status: 401 };
|
|
405
|
+
addProvider("google", {
|
|
406
|
+
pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
407
|
+
});
|
|
408
|
+
addConnection("google", "conn-1", {
|
|
409
|
+
expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000,
|
|
410
|
+
hasRefreshToken: true,
|
|
411
|
+
});
|
|
412
|
+
setToken("conn-1");
|
|
413
|
+
|
|
414
|
+
const report = await checkAllCredentials();
|
|
415
|
+
expect(report.results[0]!.status).toBe("revoked");
|
|
416
|
+
expect(report.results[0]!.details).toContain("after a refresh attempt");
|
|
417
|
+
});
|
|
418
|
+
|
|
254
419
|
test("returns expiring when token expires within 7 days without refresh token", async () => {
|
|
255
420
|
addProvider("google");
|
|
256
421
|
addConnection("google", "conn-1", {
|
|
@@ -459,6 +624,90 @@ describe("credential-health-service", () => {
|
|
|
459
624
|
});
|
|
460
625
|
});
|
|
461
626
|
|
|
627
|
+
describe("managed-provider path", () => {
|
|
628
|
+
// Distinguishing managed-path failure modes: a 424 from the platform
|
|
629
|
+
// proxy (CredentialRequiredError) means the platform tried and gave up
|
|
630
|
+
// on refresh — actionable, fire reconnect alert. A bare 401/403 from
|
|
631
|
+
// the upstream provider could be a transient platform-side miss (proxy
|
|
632
|
+
// didn't refresh-before-forward) and should NOT fire reconnect alerts
|
|
633
|
+
// without further evidence — demote to ping_failed.
|
|
634
|
+
|
|
635
|
+
function setupManagedGoogle(opts?: { hasActiveConnection?: boolean }) {
|
|
636
|
+
addProvider("google", {
|
|
637
|
+
pingUrl: "https://www.googleapis.com/oauth2/v2/userinfo",
|
|
638
|
+
managedServiceConfigKey: "google-oauth",
|
|
639
|
+
});
|
|
640
|
+
managedProviders.add("google-oauth");
|
|
641
|
+
if (opts?.hasActiveConnection !== false) {
|
|
642
|
+
managedListResponse = {
|
|
643
|
+
ok: true,
|
|
644
|
+
status: 200,
|
|
645
|
+
body: {
|
|
646
|
+
results: [
|
|
647
|
+
{
|
|
648
|
+
id: "platform-conn-1",
|
|
649
|
+
account_label: "user@example.com",
|
|
650
|
+
status: "ACTIVE",
|
|
651
|
+
},
|
|
652
|
+
],
|
|
653
|
+
},
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
test("managed 2xx ping → healthy", async () => {
|
|
659
|
+
setupManagedGoogle();
|
|
660
|
+
managedPingOutcome = { kind: "status", status: 200 };
|
|
661
|
+
|
|
662
|
+
const report = await checkAllCredentials();
|
|
663
|
+
const google = report.results.find((r) => r.provider === "google");
|
|
664
|
+
expect(google!.status).toBe("healthy");
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
test("managed 424 (CredentialRequiredError) → revoked", async () => {
|
|
668
|
+
setupManagedGoogle();
|
|
669
|
+
managedPingOutcome = { kind: "credential_required" };
|
|
670
|
+
|
|
671
|
+
const report = await checkAllCredentials();
|
|
672
|
+
const google = report.results.find((r) => r.provider === "google");
|
|
673
|
+
expect(google!.status).toBe("revoked");
|
|
674
|
+
expect(google!.canAutoRecover).toBe(false);
|
|
675
|
+
expect(google!.details).toContain("cannot be refreshed");
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
test("managed 401 from upstream → ping_failed, not revoked", async () => {
|
|
679
|
+
// This is the key change: previously this would fire a user-facing
|
|
680
|
+
// reconnect alert (because "revoked" is in hardFailureStatuses). Now
|
|
681
|
+
// we treat upstream 401 as potentially transient — only a 424 from
|
|
682
|
+
// the platform proxy is trusted enough to trigger the alert.
|
|
683
|
+
setupManagedGoogle();
|
|
684
|
+
managedPingOutcome = { kind: "status", status: 401 };
|
|
685
|
+
|
|
686
|
+
const report = await checkAllCredentials();
|
|
687
|
+
const google = report.results.find((r) => r.provider === "google");
|
|
688
|
+
expect(google!.status).toBe("ping_failed");
|
|
689
|
+
expect(google!.status).not.toBe("revoked");
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
test("managed 403 from upstream → ping_failed, not revoked", async () => {
|
|
693
|
+
setupManagedGoogle();
|
|
694
|
+
managedPingOutcome = { kind: "status", status: 403 };
|
|
695
|
+
|
|
696
|
+
const report = await checkAllCredentials();
|
|
697
|
+
const google = report.results.find((r) => r.provider === "google");
|
|
698
|
+
expect(google!.status).toBe("ping_failed");
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
test("managed 500 from upstream → ping_failed", async () => {
|
|
702
|
+
setupManagedGoogle();
|
|
703
|
+
managedPingOutcome = { kind: "status", status: 500 };
|
|
704
|
+
|
|
705
|
+
const report = await checkAllCredentials();
|
|
706
|
+
const google = report.results.find((r) => r.provider === "google");
|
|
707
|
+
expect(google!.status).toBe("ping_failed");
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
|
|
462
711
|
describe("checkCredentialForProvider", () => {
|
|
463
712
|
test("returns null when no connections exist", async () => {
|
|
464
713
|
const result = await checkCredentialForProvider("google");
|
|
@@ -41,8 +41,8 @@ afterAll(() => {
|
|
|
41
41
|
mock.restore();
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
import { _setStorePath } from "../security/encrypted-store.js";
|
|
45
44
|
import { _resetBackend } from "../security/secure-keys.js";
|
|
45
|
+
import { setStorePathForTesting } from "./encrypted-store-test-helpers.js";
|
|
46
46
|
|
|
47
47
|
const TEST_DIR = join(
|
|
48
48
|
tmpdir(),
|
|
@@ -409,7 +409,7 @@ describe("Invariant 4: credentials only used for allowed purpose", () => {
|
|
|
409
409
|
|
|
410
410
|
beforeEach(() => {
|
|
411
411
|
mkdirSync(TEST_DIR, { recursive: true });
|
|
412
|
-
|
|
412
|
+
setStorePathForTesting(STORE_PATH);
|
|
413
413
|
_resetBackend();
|
|
414
414
|
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
415
415
|
broker = new CredentialBroker();
|
|
@@ -417,7 +417,7 @@ describe("Invariant 4: credentials only used for allowed purpose", () => {
|
|
|
417
417
|
|
|
418
418
|
afterEach(() => {
|
|
419
419
|
_setMetadataPath(null);
|
|
420
|
-
|
|
420
|
+
setStorePathForTesting(null);
|
|
421
421
|
_resetBackend();
|
|
422
422
|
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
423
423
|
});
|
|
@@ -497,14 +497,14 @@ describe("Invariant 4: credentials only used for allowed purpose", () => {
|
|
|
497
497
|
describe("Invariant 6: oauth2ClientSecret not in metadata, only in secure store", () => {
|
|
498
498
|
beforeEach(() => {
|
|
499
499
|
mkdirSync(TEST_DIR, { recursive: true });
|
|
500
|
-
|
|
500
|
+
setStorePathForTesting(STORE_PATH);
|
|
501
501
|
_resetBackend();
|
|
502
502
|
_setMetadataPath(join(TEST_DIR, "metadata.json"));
|
|
503
503
|
});
|
|
504
504
|
|
|
505
505
|
afterEach(() => {
|
|
506
506
|
_setMetadataPath(null);
|
|
507
|
-
|
|
507
|
+
setStorePathForTesting(null);
|
|
508
508
|
_resetBackend();
|
|
509
509
|
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
510
510
|
});
|