@vellumai/assistant 0.4.49 → 0.4.51
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/ARCHITECTURE.md +24 -33
- package/README.md +3 -3
- package/docs/architecture/integrations.md +2 -2
- package/docs/architecture/keychain-broker.md +6 -6
- package/docs/architecture/memory.md +180 -119
- package/knip.json +32 -0
- package/package.json +3 -2
- package/src/__tests__/agent-loop.test.ts +3 -1
- package/src/__tests__/anthropic-provider.test.ts +114 -23
- package/src/__tests__/approval-cascade.test.ts +1 -15
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
- package/src/__tests__/btw-routes.test.ts +61 -5
- package/src/__tests__/canonical-guardian-store.test.ts +95 -0
- package/src/__tests__/checker.test.ts +13 -0
- package/src/__tests__/config-schema.test.ts +1 -68
- package/src/__tests__/config-watcher.test.ts +8 -0
- package/src/__tests__/context-memory-e2e.test.ts +11 -100
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-security-e2e.test.ts +1 -0
- package/src/__tests__/credential-security-invariants.test.ts +8 -7
- package/src/__tests__/credential-vault-unit.test.ts +23 -18
- package/src/__tests__/credential-vault.test.ts +30 -18
- package/src/__tests__/credentials-cli.test.ts +257 -82
- package/src/__tests__/cu-unified-flow.test.ts +532 -0
- package/src/__tests__/date-context.test.ts +93 -77
- package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
- package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
- package/src/__tests__/history-repair.test.ts +245 -0
- package/src/__tests__/host-cu-proxy.test.ts +165 -3
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
- package/src/__tests__/integration-status.test.ts +31 -30
- package/src/__tests__/invite-redemption-service.test.ts +166 -13
- package/src/__tests__/invite-routes-http.test.ts +166 -5
- package/src/__tests__/keychain-broker-client.test.ts +4 -4
- package/src/__tests__/list-messages-attachments.test.ts +193 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
- package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
- package/src/__tests__/memory-recall-quality.test.ts +244 -407
- package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
- package/src/__tests__/memory-regressions.test.ts +477 -2841
- package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
- package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
- package/src/__tests__/mime-builder.test.ts +28 -0
- package/src/__tests__/native-web-search.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +824 -31
- package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
- package/src/__tests__/oauth-store.test.ts +363 -17
- package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
- package/src/__tests__/registry.test.ts +0 -1
- package/src/__tests__/relay-server.test.ts +55 -1
- package/src/__tests__/schedule-tools.test.ts +32 -0
- package/src/__tests__/script-proxy-certs.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
- package/src/__tests__/secure-keys.test.ts +78 -18
- package/src/__tests__/send-endpoint-busy.test.ts +3 -0
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/session-abort-tool-results.test.ts +1 -14
- package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
- package/src/__tests__/session-agent-loop.test.ts +19 -15
- package/src/__tests__/session-confirmation-signals.test.ts +1 -15
- package/src/__tests__/session-error.test.ts +124 -2
- package/src/__tests__/session-history-web-search.test.ts +918 -0
- package/src/__tests__/session-pre-run-repair.test.ts +1 -14
- package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
- package/src/__tests__/session-queue.test.ts +37 -27
- package/src/__tests__/session-runtime-assembly.test.ts +54 -0
- package/src/__tests__/session-slash-known.test.ts +1 -15
- package/src/__tests__/session-slash-queue.test.ts +1 -15
- package/src/__tests__/session-slash-unknown.test.ts +1 -15
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
- package/src/__tests__/session-workspace-injection.test.ts +3 -37
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
- package/src/__tests__/skills-install-extract.test.ts +93 -0
- package/src/__tests__/skills.test.ts +2 -2
- package/src/__tests__/skillssh-registry.test.ts +451 -0
- package/src/__tests__/slack-channel-config.test.ts +10 -8
- package/src/__tests__/trust-store.test.ts +15 -0
- package/src/__tests__/twilio-config.test.ts +11 -10
- package/src/__tests__/twilio-provider.test.ts +9 -4
- package/src/__tests__/voice-invite-redemption.test.ts +85 -5
- package/src/agent/ax-tree-compaction.test.ts +51 -0
- package/src/agent/loop.ts +39 -12
- package/src/approvals/AGENTS.md +1 -1
- package/src/approvals/guardian-request-resolvers.ts +14 -2
- package/src/bundler/compiler-tools.ts +66 -2
- package/src/calls/call-domain.ts +134 -3
- package/src/calls/call-store.ts +6 -0
- package/src/calls/relay-server.ts +44 -6
- package/src/calls/relay-setup-router.ts +17 -1
- package/src/calls/twilio-config.ts +5 -4
- package/src/calls/twilio-provider.ts +14 -9
- package/src/calls/twilio-rest.ts +10 -7
- package/src/calls/types.ts +3 -1
- package/src/cli/commands/config.ts +14 -9
- package/src/cli/commands/contacts.ts +3 -0
- package/src/cli/commands/credentials.ts +170 -174
- package/src/cli/commands/doctor.ts +11 -8
- package/src/cli/commands/keys.ts +9 -9
- package/src/cli/commands/mcp.ts +46 -59
- package/src/cli/commands/memory.ts +16 -165
- package/src/cli/commands/oauth/apps.ts +68 -10
- package/src/cli/commands/oauth/connections.ts +475 -105
- package/src/cli/commands/oauth/index.ts +3 -3
- package/src/cli/commands/oauth/providers.ts +18 -4
- package/src/cli/commands/sessions.ts +5 -2
- package/src/cli/commands/skills.ts +173 -1
- package/src/cli/http-client.ts +0 -20
- package/src/cli/main-screen.tsx +2 -2
- package/src/cli/program.ts +5 -6
- package/src/cli.ts +20 -22
- package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
- package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
- package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
- package/src/config/bundled-skills/contacts/SKILL.md +35 -11
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/gmail/SKILL.md +1 -1
- package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
- package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
- package/src/config/bundled-skills/messaging/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
- package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
- package/src/config/bundled-tool-registry.ts +2 -5
- package/src/config/loader.ts +6 -42
- package/src/config/schema.ts +1 -12
- package/src/config/schemas/memory-lifecycle.ts +0 -9
- package/src/config/schemas/memory-processing.ts +0 -180
- package/src/config/schemas/memory-retrieval.ts +32 -104
- package/src/config/schemas/memory.ts +0 -10
- package/src/config/types.ts +0 -4
- package/src/contacts/contact-store.ts +39 -2
- package/src/contacts/contacts-write.ts +9 -0
- package/src/context/window-manager.ts +4 -1
- package/src/daemon/config-watcher.ts +55 -2
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/date-context.ts +114 -31
- package/src/daemon/handlers/config-ingress.ts +2 -2
- package/src/daemon/handlers/config-slack-channel.ts +59 -39
- package/src/daemon/handlers/config-telegram.ts +23 -14
- package/src/daemon/handlers/session-history.ts +1 -358
- package/src/daemon/handlers/sessions.ts +18 -13
- package/src/daemon/handlers/shared.ts +3 -17
- package/src/daemon/handlers/skills.ts +20 -1
- package/src/daemon/history-repair.ts +72 -8
- package/src/daemon/host-cu-proxy.ts +55 -26
- package/src/daemon/lifecycle.ts +39 -4
- package/src/daemon/mcp-reload-service.ts +2 -2
- package/src/daemon/message-types/computer-use.ts +1 -12
- package/src/daemon/message-types/memory.ts +4 -16
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/sessions.ts +4 -42
- package/src/daemon/server.ts +6 -1
- package/src/daemon/session-agent-loop-handlers.ts +38 -0
- package/src/daemon/session-agent-loop.ts +334 -48
- package/src/daemon/session-error.ts +89 -6
- package/src/daemon/session-history.ts +17 -7
- package/src/daemon/session-media-retry.ts +6 -2
- package/src/daemon/session-memory.ts +69 -149
- package/src/daemon/session-process.ts +10 -1
- package/src/daemon/session-runtime-assembly.ts +49 -19
- package/src/daemon/session-slash.ts +3 -5
- package/src/daemon/session-surfaces.ts +4 -1
- package/src/daemon/session-tool-setup.ts +7 -1
- package/src/daemon/session.ts +12 -2
- package/src/email/providers/index.ts +2 -2
- package/src/instrument.ts +61 -1
- package/src/media/avatar-router.ts +1 -1
- package/src/memory/admin.ts +2 -191
- package/src/memory/canonical-guardian-store.ts +38 -2
- package/src/memory/conversation-crud.ts +0 -33
- package/src/memory/conversation-queries.ts +25 -83
- package/src/memory/db-init.ts +32 -0
- package/src/memory/embedding-backend.ts +84 -8
- package/src/memory/embedding-types.ts +9 -1
- package/src/memory/indexer.ts +7 -46
- package/src/memory/invite-store.ts +19 -0
- package/src/memory/items-extractor.ts +274 -76
- package/src/memory/job-handlers/backfill.ts +2 -127
- package/src/memory/job-handlers/cleanup.ts +2 -16
- package/src/memory/job-handlers/extraction.ts +2 -138
- package/src/memory/job-handlers/index-maintenance.ts +1 -6
- package/src/memory/job-handlers/summarization.ts +3 -148
- package/src/memory/job-utils.ts +21 -59
- package/src/memory/jobs-store.ts +1 -159
- package/src/memory/jobs-worker.ts +9 -52
- package/src/memory/migrations/104-core-indexes.ts +3 -3
- package/src/memory/migrations/149-oauth-tables.ts +2 -0
- package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
- package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
- package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
- package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
- package/src/memory/migrations/154-drop-fts.ts +20 -0
- package/src/memory/migrations/155-drop-conflicts.ts +7 -0
- package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
- package/src/memory/migrations/157-invite-contact-id.ts +104 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +6 -0
- package/src/memory/qdrant-client.ts +148 -51
- package/src/memory/raw-query.ts +1 -1
- package/src/memory/retriever.test.ts +294 -273
- package/src/memory/retriever.ts +421 -645
- package/src/memory/schema/calls.ts +2 -0
- package/src/memory/schema/contacts.ts +1 -0
- package/src/memory/schema/memory-core.ts +3 -48
- package/src/memory/schema/oauth.ts +2 -0
- package/src/memory/search/formatting.ts +263 -176
- package/src/memory/search/lexical.ts +1 -254
- package/src/memory/search/ranking.ts +0 -455
- package/src/memory/search/semantic.ts +100 -14
- package/src/memory/search/staleness.ts +47 -0
- package/src/memory/search/tier-classifier.ts +21 -0
- package/src/memory/search/types.ts +15 -77
- package/src/memory/task-memory-cleanup.ts +4 -6
- package/src/messaging/provider.ts +1 -1
- package/src/messaging/providers/gmail/adapter.ts +1 -1
- package/src/messaging/providers/gmail/mime-builder.ts +17 -7
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
- package/src/messaging/providers/whatsapp/adapter.ts +13 -9
- package/src/messaging/registry.ts +9 -5
- package/src/oauth/byo-connection.test.ts +40 -25
- package/src/oauth/connect-orchestrator.ts +4 -10
- package/src/oauth/connection-resolver.ts +20 -6
- package/src/oauth/manual-token-connection.ts +5 -5
- package/src/oauth/oauth-store.ts +183 -31
- package/src/oauth/platform-connection.test.ts +1 -1
- package/src/oauth/provider-behaviors.ts +503 -4
- package/src/oauth/seed-providers.ts +214 -8
- package/src/oauth/token-persistence.ts +31 -16
- package/src/permissions/defaults.ts +1 -0
- package/src/permissions/trust-store.ts +23 -1
- package/src/playbooks/playbook-compiler.ts +1 -1
- package/src/prompts/system-prompt.ts +18 -2
- package/src/providers/anthropic/client.ts +56 -126
- package/src/providers/types.ts +7 -1
- package/src/runtime/AGENTS.md +9 -0
- package/src/runtime/auth/route-policy.ts +6 -3
- package/src/runtime/channel-readiness-service.ts +48 -40
- package/src/runtime/guardian-reply-router.ts +24 -22
- package/src/runtime/http-server.ts +2 -2
- package/src/runtime/http-types.ts +2 -0
- package/src/runtime/invite-redemption-service.ts +72 -12
- package/src/runtime/invite-service.ts +43 -0
- package/src/runtime/middleware/twilio-validation.ts +1 -1
- package/src/runtime/pending-interactions.ts +2 -2
- package/src/runtime/routes/brain-graph-routes.ts +10 -90
- package/src/runtime/routes/btw-routes.ts +10 -5
- package/src/runtime/routes/conversation-routes.ts +56 -11
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
- package/src/runtime/routes/integrations/slack/channel.ts +2 -2
- package/src/runtime/routes/integrations/telegram.ts +2 -2
- package/src/runtime/routes/integrations/twilio.ts +17 -17
- package/src/runtime/routes/invite-routes.ts +29 -4
- package/src/runtime/routes/memory-item-routes.test.ts +754 -0
- package/src/runtime/routes/memory-item-routes.ts +503 -0
- package/src/runtime/routes/secret-routes.ts +17 -0
- package/src/runtime/routes/session-management-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +3 -3
- package/src/runtime/routes/trust-rules-routes.ts +14 -0
- package/src/runtime/routes/workspace-routes.ts +9 -4
- package/src/runtime/routes/workspace-utils.ts +8 -2
- package/src/schedule/integration-status.ts +26 -19
- package/src/security/keychain-broker-client.ts +17 -4
- package/src/security/oauth2.ts +6 -7
- package/src/security/secure-keys.ts +44 -19
- package/src/security/token-manager.ts +46 -39
- package/src/services/vercel-deploy.ts +0 -24
- package/src/signals/confirm.ts +78 -0
- package/src/signals/mcp-reload.ts +18 -0
- package/src/skills/catalog-install.ts +74 -18
- package/src/skills/skillssh-registry.ts +503 -0
- package/src/tools/assets/search.ts +5 -1
- package/src/tools/computer-use/definitions.ts +0 -10
- package/src/tools/computer-use/registry.ts +1 -1
- package/src/tools/credentials/vault.ts +22 -7
- package/src/tools/memory/definitions.ts +4 -13
- package/src/tools/memory/handlers.test.ts +83 -103
- package/src/tools/memory/handlers.ts +50 -85
- package/src/tools/network/script-proxy/session-manager.ts +8 -8
- package/src/tools/schedule/create.ts +10 -3
- package/src/tools/schedule/update.ts +8 -1
- package/src/tools/skills/load.ts +25 -2
- package/src/watcher/provider-types.ts +1 -1
- package/src/watcher/providers/github.ts +1 -1
- package/src/watcher/providers/gmail.ts +3 -3
- package/src/watcher/providers/google-calendar.ts +3 -3
- package/src/watcher/providers/linear.ts +1 -1
- package/src/__tests__/clarification-resolver.test.ts +0 -193
- package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
- package/src/__tests__/conflict-policy.test.ts +0 -269
- package/src/__tests__/conflict-store.test.ts +0 -372
- package/src/__tests__/contradiction-checker.test.ts +0 -361
- package/src/__tests__/entity-extractor.test.ts +0 -211
- package/src/__tests__/entity-search.test.ts +0 -1117
- package/src/__tests__/profile-compiler.test.ts +0 -392
- package/src/__tests__/session-conflict-gate.test.ts +0 -1228
- package/src/__tests__/session-profile-injection.test.ts +0 -557
- package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
- package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
- package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
- package/src/daemon/session-conflict-gate.ts +0 -167
- package/src/daemon/session-dynamic-profile.ts +0 -77
- package/src/memory/clarification-resolver.ts +0 -417
- package/src/memory/conflict-intent.ts +0 -205
- package/src/memory/conflict-policy.ts +0 -127
- package/src/memory/conflict-store.ts +0 -410
- package/src/memory/contradiction-checker.ts +0 -508
- package/src/memory/entity-extractor.ts +0 -535
- package/src/memory/format-recall.ts +0 -47
- package/src/memory/fts-reconciler.ts +0 -165
- package/src/memory/job-handlers/conflict.ts +0 -200
- package/src/memory/profile-compiler.ts +0 -195
- package/src/memory/recall-cache.ts +0 -117
- package/src/memory/search/entity.ts +0 -535
- package/src/memory/search/query-expansion.test.ts +0 -70
- package/src/memory/search/query-expansion.ts +0 -118
- package/src/runtime/routes/mcp-routes.ts +0 -20
|
@@ -20,9 +20,6 @@ import { computeRecallBudget } from "../memory/retrieval-budget.js";
|
|
|
20
20
|
import { buildMemoryRecall } from "../memory/retriever.js";
|
|
21
21
|
import {
|
|
22
22
|
conversations,
|
|
23
|
-
memoryEntities,
|
|
24
|
-
memoryEntityRelations,
|
|
25
|
-
memoryItemEntities,
|
|
26
23
|
memoryItems,
|
|
27
24
|
memoryItemSources,
|
|
28
25
|
messages,
|
|
@@ -52,6 +49,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
52
49
|
mock.module("../memory/qdrant-client.js", () => ({
|
|
53
50
|
getQdrantClient: () => ({
|
|
54
51
|
searchWithFilter: async () => [],
|
|
52
|
+
hybridSearch: async () => [],
|
|
55
53
|
upsertPoints: async () => {},
|
|
56
54
|
deletePoints: async () => {},
|
|
57
55
|
}),
|
|
@@ -168,13 +166,9 @@ describe("Context + Memory E2E regression", () => {
|
|
|
168
166
|
beforeEach(() => {
|
|
169
167
|
const db = getDb();
|
|
170
168
|
db.run("DELETE FROM memory_item_sources");
|
|
171
|
-
db.run("DELETE FROM memory_item_entities");
|
|
172
|
-
db.run("DELETE FROM memory_entity_relations");
|
|
173
|
-
db.run("DELETE FROM memory_entities");
|
|
174
169
|
db.run("DELETE FROM memory_embeddings");
|
|
175
|
-
db.run("DELETE FROM memory_summaries");
|
|
176
170
|
db.run("DELETE FROM memory_items");
|
|
177
|
-
|
|
171
|
+
|
|
178
172
|
db.run("DELETE FROM memory_segments");
|
|
179
173
|
db.run("DELETE FROM messages");
|
|
180
174
|
db.run("DELETE FROM conversations");
|
|
@@ -279,43 +273,6 @@ describe("Context + Memory E2E regression", () => {
|
|
|
279
273
|
now + 100_000,
|
|
280
274
|
);
|
|
281
275
|
|
|
282
|
-
db.insert(memoryEntities)
|
|
283
|
-
.values([
|
|
284
|
-
{
|
|
285
|
-
id: "entity-apollo",
|
|
286
|
-
name: "Apollo",
|
|
287
|
-
type: "project",
|
|
288
|
-
aliases: JSON.stringify(["project-apollo"]),
|
|
289
|
-
description: null,
|
|
290
|
-
firstSeenAt: now,
|
|
291
|
-
lastSeenAt: now + 100_000,
|
|
292
|
-
mentionCount: 6,
|
|
293
|
-
},
|
|
294
|
-
{
|
|
295
|
-
id: "entity-hermes",
|
|
296
|
-
name: "HermesGate",
|
|
297
|
-
type: "strategy",
|
|
298
|
-
aliases: JSON.stringify(["hermes-gate"]),
|
|
299
|
-
description: null,
|
|
300
|
-
firstSeenAt: now,
|
|
301
|
-
lastSeenAt: now + 100_000,
|
|
302
|
-
mentionCount: 4,
|
|
303
|
-
},
|
|
304
|
-
])
|
|
305
|
-
.run();
|
|
306
|
-
|
|
307
|
-
db.insert(memoryEntityRelations)
|
|
308
|
-
.values({
|
|
309
|
-
id: "rel-apollo-hermes",
|
|
310
|
-
sourceEntityId: "entity-apollo",
|
|
311
|
-
targetEntityId: "entity-hermes",
|
|
312
|
-
relation: "uses",
|
|
313
|
-
evidence: "Apollo uses HermesGate for risky changes",
|
|
314
|
-
firstSeenAt: now,
|
|
315
|
-
lastSeenAt: now + 50_000,
|
|
316
|
-
})
|
|
317
|
-
.run();
|
|
318
|
-
|
|
319
276
|
insertMemoryItem({
|
|
320
277
|
id: "item-apollo-direct",
|
|
321
278
|
kind: "preference",
|
|
@@ -335,16 +292,10 @@ describe("Context + Memory E2E regression", () => {
|
|
|
335
292
|
createdAt: now + 10_000,
|
|
336
293
|
})
|
|
337
294
|
.run();
|
|
338
|
-
db.insert(memoryItemEntities)
|
|
339
|
-
.values({
|
|
340
|
-
memoryItemId: "item-apollo-direct",
|
|
341
|
-
entityId: "entity-apollo",
|
|
342
|
-
})
|
|
343
|
-
.run();
|
|
344
295
|
|
|
345
296
|
insertMemoryItem({
|
|
346
297
|
id: "item-hermes-relation",
|
|
347
|
-
kind: "
|
|
298
|
+
kind: "identity",
|
|
348
299
|
subject: "hermes rollout execution",
|
|
349
300
|
statement:
|
|
350
301
|
"HermesGate rollout guidance: start at 5% traffic and promote only after error budget checks pass.",
|
|
@@ -361,12 +312,6 @@ describe("Context + Memory E2E regression", () => {
|
|
|
361
312
|
createdAt: now + 12_000,
|
|
362
313
|
})
|
|
363
314
|
.run();
|
|
364
|
-
db.insert(memoryItemEntities)
|
|
365
|
-
.values({
|
|
366
|
-
memoryItemId: "item-hermes-relation",
|
|
367
|
-
entityId: "entity-hermes",
|
|
368
|
-
})
|
|
369
|
-
.run();
|
|
370
315
|
|
|
371
316
|
insertMemoryItem({
|
|
372
317
|
id: "item-apollo-stale",
|
|
@@ -387,16 +332,10 @@ describe("Context + Memory E2E regression", () => {
|
|
|
387
332
|
createdAt: now - 390 * 24 * 60 * 60 * 1000,
|
|
388
333
|
})
|
|
389
334
|
.run();
|
|
390
|
-
db.insert(memoryItemEntities)
|
|
391
|
-
.values({
|
|
392
|
-
memoryItemId: "item-apollo-stale",
|
|
393
|
-
entityId: "entity-apollo",
|
|
394
|
-
})
|
|
395
|
-
.run();
|
|
396
335
|
|
|
397
336
|
insertMemoryItem({
|
|
398
337
|
id: "item-apollo-secret",
|
|
399
|
-
kind: "
|
|
338
|
+
kind: "identity",
|
|
400
339
|
subject: "apollo secret",
|
|
401
340
|
statement: "Current-turn secret: Apollo code is 123XYZ.",
|
|
402
341
|
confidence: 0.99,
|
|
@@ -412,12 +351,6 @@ describe("Context + Memory E2E regression", () => {
|
|
|
412
351
|
createdAt: now + 100_000,
|
|
413
352
|
})
|
|
414
353
|
.run();
|
|
415
|
-
db.insert(memoryItemEntities)
|
|
416
|
-
.values({
|
|
417
|
-
memoryItemId: "item-apollo-secret",
|
|
418
|
-
entityId: "entity-apollo",
|
|
419
|
-
})
|
|
420
|
-
.run();
|
|
421
354
|
|
|
422
355
|
const summaryCounter = { calls: 0 };
|
|
423
356
|
const provider = makeSummaryProvider(summaryCounter);
|
|
@@ -454,13 +387,7 @@ describe("Context + Memory E2E regression", () => {
|
|
|
454
387
|
},
|
|
455
388
|
retrieval: {
|
|
456
389
|
...DEFAULT_CONFIG.memory.retrieval,
|
|
457
|
-
lexicalTopK: 50,
|
|
458
|
-
semanticTopK: 16,
|
|
459
390
|
maxInjectTokens: 900,
|
|
460
|
-
reranking: {
|
|
461
|
-
...DEFAULT_CONFIG.memory.retrieval.reranking,
|
|
462
|
-
enabled: false,
|
|
463
|
-
},
|
|
464
391
|
dynamicBudget: {
|
|
465
392
|
enabled: true,
|
|
466
393
|
minInjectTokens: 180,
|
|
@@ -468,17 +395,6 @@ describe("Context + Memory E2E regression", () => {
|
|
|
468
395
|
targetHeadroomTokens: 700,
|
|
469
396
|
},
|
|
470
397
|
},
|
|
471
|
-
entity: {
|
|
472
|
-
...DEFAULT_CONFIG.memory.entity,
|
|
473
|
-
relationRetrieval: {
|
|
474
|
-
...DEFAULT_CONFIG.memory.entity.relationRetrieval,
|
|
475
|
-
enabled: true,
|
|
476
|
-
maxSeedEntities: 4,
|
|
477
|
-
maxNeighborEntities: 6,
|
|
478
|
-
maxEdges: 8,
|
|
479
|
-
neighborScoreMultiplier: 0.65,
|
|
480
|
-
},
|
|
481
|
-
},
|
|
482
398
|
},
|
|
483
399
|
};
|
|
484
400
|
|
|
@@ -511,19 +427,14 @@ describe("Context + Memory E2E regression", () => {
|
|
|
511
427
|
);
|
|
512
428
|
|
|
513
429
|
expect(recall.injectedTokens).toBeLessThanOrEqual(recallBudget);
|
|
514
|
-
expect(recall.relationSeedEntityCount).toBeGreaterThan(0);
|
|
515
|
-
expect(recall.relationTraversedEdgeCount).toBeGreaterThan(0);
|
|
516
|
-
expect(recall.relationNeighborEntityCount).toBeGreaterThan(0);
|
|
517
|
-
expect(recall.relationExpandedItemCount).toBeGreaterThan(0);
|
|
518
430
|
|
|
519
|
-
|
|
520
|
-
|
|
431
|
+
// With Qdrant mocked empty the only retrieval path is recency search,
|
|
432
|
+
// but recency-only candidates score below the tier-2 threshold (0.6)
|
|
433
|
+
// since finalScore = semantic*0.7 + recency*0.2 + confidence*0.1 and
|
|
434
|
+
// semantic=0 for recency hits. This means no candidates pass tier
|
|
435
|
+
// classification and injectedText is empty — which is correct behavior:
|
|
436
|
+
// the pipeline requires at least tier-2 quality to inject memory context.
|
|
437
|
+
// Verify current-turn secrets never leak regardless.
|
|
521
438
|
expect(recall.injectedText).not.toContain("123XYZ");
|
|
522
|
-
|
|
523
|
-
const directIndex = recall.injectedText.indexOf("staged canary releases");
|
|
524
|
-
const relationIndex = recall.injectedText.indexOf("start at 5% traffic");
|
|
525
|
-
expect(directIndex).toBeGreaterThanOrEqual(0);
|
|
526
|
-
expect(relationIndex).toBeGreaterThanOrEqual(0);
|
|
527
|
-
expect(directIndex).toBeLessThan(relationIndex);
|
|
528
439
|
});
|
|
529
440
|
});
|
|
@@ -172,6 +172,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
172
172
|
setHostBashProxy: () => {},
|
|
173
173
|
setHostFileProxy: () => {},
|
|
174
174
|
setHostCuProxy: () => {},
|
|
175
|
+
addPreactivatedSkillId: () => {},
|
|
175
176
|
} as unknown as import("../daemon/session.js").Session;
|
|
176
177
|
|
|
177
178
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -248,6 +249,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
248
249
|
setHostBashProxy: () => {},
|
|
249
250
|
setHostFileProxy: () => {},
|
|
250
251
|
setHostCuProxy: () => {},
|
|
252
|
+
addPreactivatedSkillId: () => {},
|
|
251
253
|
} as unknown as import("../daemon/session.js").Session;
|
|
252
254
|
|
|
253
255
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -320,6 +322,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
320
322
|
setHostBashProxy: () => {},
|
|
321
323
|
setHostFileProxy: () => {},
|
|
322
324
|
setHostCuProxy: () => {},
|
|
325
|
+
addPreactivatedSkillId: () => {},
|
|
323
326
|
} as unknown as import("../daemon/session.js").Session;
|
|
324
327
|
|
|
325
328
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -396,6 +399,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
396
399
|
setHostBashProxy: () => {},
|
|
397
400
|
setHostFileProxy: () => {},
|
|
398
401
|
setHostCuProxy: () => {},
|
|
402
|
+
addPreactivatedSkillId: () => {},
|
|
399
403
|
} as unknown as import("../daemon/session.js").Session;
|
|
400
404
|
|
|
401
405
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -468,6 +472,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
468
472
|
setHostBashProxy: () => {},
|
|
469
473
|
setHostFileProxy: () => {},
|
|
470
474
|
setHostCuProxy: () => {},
|
|
475
|
+
addPreactivatedSkillId: () => {},
|
|
471
476
|
} as unknown as import("../daemon/session.js").Session;
|
|
472
477
|
|
|
473
478
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -534,6 +539,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
534
539
|
setHostBashProxy: () => {},
|
|
535
540
|
setHostFileProxy: () => {},
|
|
536
541
|
setHostCuProxy: () => {},
|
|
542
|
+
addPreactivatedSkillId: () => {},
|
|
537
543
|
} as unknown as import("../daemon/session.js").Session;
|
|
538
544
|
|
|
539
545
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -602,6 +608,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
602
608
|
setHostBashProxy: () => {},
|
|
603
609
|
setHostFileProxy: () => {},
|
|
604
610
|
setHostCuProxy: () => {},
|
|
611
|
+
addPreactivatedSkillId: () => {},
|
|
605
612
|
} as unknown as import("../daemon/session.js").Session;
|
|
606
613
|
|
|
607
614
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -671,6 +678,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
|
|
|
671
678
|
setHostBashProxy: () => {},
|
|
672
679
|
setHostFileProxy: () => {},
|
|
673
680
|
setHostCuProxy: () => {},
|
|
681
|
+
addPreactivatedSkillId: () => {},
|
|
674
682
|
} as unknown as import("../daemon/session.js").Session;
|
|
675
683
|
|
|
676
684
|
const req = new Request("http://localhost/v1/messages", {
|
|
@@ -44,6 +44,7 @@ mock.module("../security/secure-keys.js", () => {
|
|
|
44
44
|
};
|
|
45
45
|
return {
|
|
46
46
|
getSecureKey: (key: string) => storedKeys.get(key) ?? null,
|
|
47
|
+
getSecureKeyAsync: async (key: string) => storedKeys.get(key) ?? undefined,
|
|
47
48
|
setSecureKey: syncSet,
|
|
48
49
|
setSecureKeyAsync: async (key: string, value: string) =>
|
|
49
50
|
syncSet(key, value),
|
|
@@ -236,6 +236,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
|
|
|
236
236
|
"oauth/oauth-store.ts", // OAuth provider disconnect (delete stored tokens)
|
|
237
237
|
"cli/commands/oauth/connections.ts", // CLI OAuth connection delete (legacy credential cleanup)
|
|
238
238
|
"oauth/manual-token-connection.ts", // manual-token provider backfill (keychain credential existence check)
|
|
239
|
+
"cli/commands/doctor.ts", // CLI diagnostic API key verification via secure storage
|
|
239
240
|
]);
|
|
240
241
|
|
|
241
242
|
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -504,7 +505,7 @@ describe("Invariant 6: oauth2ClientSecret not in metadata, only in secure store"
|
|
|
504
505
|
|
|
505
506
|
test("upsertCredentialMetadata does not accept oauth2ClientSecret or other OAuth fields", () => {
|
|
506
507
|
const record = upsertCredentialMetadata(
|
|
507
|
-
"integration:
|
|
508
|
+
"integration:google",
|
|
508
509
|
"access_token",
|
|
509
510
|
{
|
|
510
511
|
allowedTools: ["api_request"],
|
|
@@ -517,14 +518,14 @@ describe("Invariant 6: oauth2ClientSecret not in metadata, only in secure store"
|
|
|
517
518
|
|
|
518
519
|
test("client secret is read from secure store, not metadata", () => {
|
|
519
520
|
setSecureKey(
|
|
520
|
-
credentialKey("integration:
|
|
521
|
+
credentialKey("integration:google", "client_secret"),
|
|
521
522
|
"my-secret",
|
|
522
523
|
);
|
|
523
|
-
upsertCredentialMetadata("integration:
|
|
524
|
+
upsertCredentialMetadata("integration:google", "access_token", {
|
|
524
525
|
allowedTools: ["api_request"],
|
|
525
526
|
});
|
|
526
527
|
|
|
527
|
-
const meta = getCredentialMetadata("integration:
|
|
528
|
+
const meta = getCredentialMetadata("integration:google", "access_token");
|
|
528
529
|
expect(meta).toBeDefined();
|
|
529
530
|
expect("oauth2ClientSecret" in meta!).toBe(false);
|
|
530
531
|
// OAuth-specific fields are no longer in metadata (v5)
|
|
@@ -533,7 +534,7 @@ describe("Invariant 6: oauth2ClientSecret not in metadata, only in secure store"
|
|
|
533
534
|
|
|
534
535
|
// Secret is in secure store
|
|
535
536
|
expect(
|
|
536
|
-
getSecureKey(credentialKey("integration:
|
|
537
|
+
getSecureKey(credentialKey("integration:google", "client_secret")),
|
|
537
538
|
).toBe("my-secret");
|
|
538
539
|
});
|
|
539
540
|
|
|
@@ -543,7 +544,7 @@ describe("Invariant 6: oauth2ClientSecret not in metadata, only in secure store"
|
|
|
543
544
|
credentials: [
|
|
544
545
|
{
|
|
545
546
|
credentialId: "cred-v2-secret",
|
|
546
|
-
service: "integration:
|
|
547
|
+
service: "integration:google",
|
|
547
548
|
field: "access_token",
|
|
548
549
|
allowedTools: [],
|
|
549
550
|
allowedDomains: [],
|
|
@@ -561,7 +562,7 @@ describe("Invariant 6: oauth2ClientSecret not in metadata, only in secure store"
|
|
|
561
562
|
"utf-8",
|
|
562
563
|
);
|
|
563
564
|
|
|
564
|
-
const meta = getCredentialMetadata("integration:
|
|
565
|
+
const meta = getCredentialMetadata("integration:google", "access_token");
|
|
565
566
|
expect(meta).toBeDefined();
|
|
566
567
|
expect("oauth2ClientSecret" in meta!).toBe(false);
|
|
567
568
|
|
|
@@ -531,8 +531,8 @@ describe("credential_store tool — prompt action", () => {
|
|
|
531
531
|
describe("credential_store tool — oauth2_connect error paths", () => {
|
|
532
532
|
/** Well-known provider rows returned by the mocked getProvider */
|
|
533
533
|
const wellKnownProviders: Record<string, object> = {
|
|
534
|
-
"integration:
|
|
535
|
-
key: "integration:
|
|
534
|
+
"integration:google": {
|
|
535
|
+
key: "integration:google",
|
|
536
536
|
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
537
537
|
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
538
538
|
defaultScopes: JSON.stringify(["https://mail.google.com/"]),
|
|
@@ -624,9 +624,10 @@ describe("credential_store tool — oauth2_connect error paths", () => {
|
|
|
624
624
|
expect(result.content).toContain("client_id is required");
|
|
625
625
|
});
|
|
626
626
|
|
|
627
|
-
test("
|
|
628
|
-
//
|
|
629
|
-
//
|
|
627
|
+
test("non-interactive loopback oauth2_connect returns deferred auth URL", async () => {
|
|
628
|
+
// After the blanket non-interactive guard was removed (#16337),
|
|
629
|
+
// loopback-transport flows succeed with a deferred auth URL so the
|
|
630
|
+
// agent can present it to the user.
|
|
630
631
|
mockGetProvider.mockImplementation((key: string) => {
|
|
631
632
|
if (key === "custom-svc") {
|
|
632
633
|
return {
|
|
@@ -651,11 +652,11 @@ describe("credential_store tool — oauth2_connect error paths", () => {
|
|
|
651
652
|
},
|
|
652
653
|
{ ..._ctx, isInteractive: false },
|
|
653
654
|
);
|
|
654
|
-
expect(result.isError).toBe(
|
|
655
|
-
expect(result.content).toContain("
|
|
655
|
+
expect(result.isError).toBe(false);
|
|
656
|
+
expect(result.content).toContain("mock-auth-url.example.com");
|
|
656
657
|
});
|
|
657
658
|
|
|
658
|
-
test("resolves gmail alias to integration:
|
|
659
|
+
test("resolves gmail alias to integration:google", async () => {
|
|
659
660
|
// Even with alias resolution, missing client_id should still fail
|
|
660
661
|
const result = await credentialStoreTool.execute(
|
|
661
662
|
{
|
|
@@ -687,12 +688,13 @@ describe("credential_store tool — oauth2_connect error paths", () => {
|
|
|
687
688
|
// and store client_secret in the secure store.
|
|
688
689
|
mockGetMostRecentAppByProvider.mockImplementation(() => ({
|
|
689
690
|
id: "test-app-id",
|
|
690
|
-
providerKey: "integration:
|
|
691
|
+
providerKey: "integration:google",
|
|
691
692
|
clientId: "stored-client-id-123",
|
|
693
|
+
clientSecretCredentialPath: "oauth_app/test-app-id/client_secret",
|
|
692
694
|
createdAt: Date.now(),
|
|
693
695
|
}));
|
|
694
696
|
mockGetProvider.mockImplementation(() => ({
|
|
695
|
-
key: "integration:
|
|
697
|
+
key: "integration:google",
|
|
696
698
|
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
697
699
|
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
698
700
|
defaultScopes: JSON.stringify(["https://mail.google.com/"]),
|
|
@@ -730,13 +732,15 @@ describe("credential_store tool — oauth2_connect error paths", () => {
|
|
|
730
732
|
mockGetAppByProviderAndClientId.mockImplementation(
|
|
731
733
|
(providerKey: string, cId: string) => {
|
|
732
734
|
if (
|
|
733
|
-
providerKey === "integration:
|
|
735
|
+
providerKey === "integration:google" &&
|
|
734
736
|
cId === "caller-supplied-client-id"
|
|
735
737
|
) {
|
|
736
738
|
return {
|
|
737
739
|
id: "matched-app-id",
|
|
738
|
-
providerKey: "integration:
|
|
740
|
+
providerKey: "integration:google",
|
|
739
741
|
clientId: "caller-supplied-client-id",
|
|
742
|
+
clientSecretCredentialPath:
|
|
743
|
+
"oauth_app/matched-app-id/client_secret",
|
|
740
744
|
createdAt: Date.now(),
|
|
741
745
|
};
|
|
742
746
|
}
|
|
@@ -744,7 +748,7 @@ describe("credential_store tool — oauth2_connect error paths", () => {
|
|
|
744
748
|
},
|
|
745
749
|
);
|
|
746
750
|
mockGetProvider.mockImplementation(() => ({
|
|
747
|
-
key: "integration:
|
|
751
|
+
key: "integration:google",
|
|
748
752
|
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
749
753
|
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
750
754
|
defaultScopes: JSON.stringify(["https://mail.google.com/"]),
|
|
@@ -779,12 +783,13 @@ describe("credential_store tool — oauth2_connect error paths", () => {
|
|
|
779
783
|
// use getMostRecentAppByProvider (the fallback heuristic).
|
|
780
784
|
mockGetMostRecentAppByProvider.mockImplementation(() => ({
|
|
781
785
|
id: "recent-app-id",
|
|
782
|
-
providerKey: "integration:
|
|
786
|
+
providerKey: "integration:google",
|
|
783
787
|
clientId: "recent-client-id",
|
|
788
|
+
clientSecretCredentialPath: "oauth_app/recent-app-id/client_secret",
|
|
784
789
|
createdAt: Date.now(),
|
|
785
790
|
}));
|
|
786
791
|
mockGetProvider.mockImplementation(() => ({
|
|
787
|
-
key: "integration:
|
|
792
|
+
key: "integration:google",
|
|
788
793
|
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
789
794
|
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
790
795
|
defaultScopes: JSON.stringify(["https://mail.google.com/"]),
|
|
@@ -818,7 +823,7 @@ describe("credential_store tool — oauth2_connect error paths", () => {
|
|
|
818
823
|
// report the missing secret error.
|
|
819
824
|
mockGetAppByProviderAndClientId.mockImplementation(() => undefined);
|
|
820
825
|
mockGetProvider.mockImplementation(() => ({
|
|
821
|
-
key: "integration:
|
|
826
|
+
key: "integration:google",
|
|
822
827
|
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
823
828
|
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
824
829
|
defaultScopes: JSON.stringify(["https://mail.google.com/"]),
|
|
@@ -849,12 +854,12 @@ describe("credential_store tool — oauth2_connect error paths", () => {
|
|
|
849
854
|
// guardrail.
|
|
850
855
|
mockGetMostRecentAppByProvider.mockImplementation(() => ({
|
|
851
856
|
id: "test-app-id-no-secret",
|
|
852
|
-
providerKey: "integration:
|
|
857
|
+
providerKey: "integration:google",
|
|
853
858
|
clientId: "stored-client-id-456",
|
|
854
859
|
createdAt: Date.now(),
|
|
855
860
|
}));
|
|
856
861
|
mockGetProvider.mockImplementation(() => ({
|
|
857
|
-
key: "integration:
|
|
862
|
+
key: "integration:google",
|
|
858
863
|
authUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
859
864
|
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
860
865
|
defaultScopes: JSON.stringify(["https://mail.google.com/"]),
|
|
@@ -81,7 +81,12 @@ const mockConnections = new Map<
|
|
|
81
81
|
>();
|
|
82
82
|
const mockApps = new Map<
|
|
83
83
|
string,
|
|
84
|
-
{
|
|
84
|
+
{
|
|
85
|
+
id: string;
|
|
86
|
+
providerKey: string;
|
|
87
|
+
clientId: string;
|
|
88
|
+
clientSecretCredentialPath: string;
|
|
89
|
+
}
|
|
85
90
|
>();
|
|
86
91
|
const mockProviders = new Map<
|
|
87
92
|
string,
|
|
@@ -109,6 +114,12 @@ mock.module("../oauth/oauth-store.js", () => {
|
|
|
109
114
|
return {
|
|
110
115
|
disconnectOAuthProvider: mockDisconnectOAuthProvider,
|
|
111
116
|
getConnectionByProvider: (service: string) => mockConnections.get(service),
|
|
117
|
+
getConnection: (id: string) => {
|
|
118
|
+
for (const conn of mockConnections.values()) {
|
|
119
|
+
if (conn.id === id) return conn;
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
},
|
|
112
123
|
getApp: (id: string) => mockApps.get(id),
|
|
113
124
|
getProvider: (key: string) => mockProviders.get(key),
|
|
114
125
|
updateConnection: () => {},
|
|
@@ -724,7 +735,7 @@ describe("credential_store tool", () => {
|
|
|
724
735
|
await credentialStoreTool.execute(
|
|
725
736
|
{
|
|
726
737
|
action: "store",
|
|
727
|
-
service: "integration:
|
|
738
|
+
service: "integration:google",
|
|
728
739
|
field: "api_key",
|
|
729
740
|
value: "test-value",
|
|
730
741
|
},
|
|
@@ -732,9 +743,9 @@ describe("credential_store tool", () => {
|
|
|
732
743
|
);
|
|
733
744
|
|
|
734
745
|
// Simulate an active OAuth connection for this service
|
|
735
|
-
mockConnections.set("integration:
|
|
746
|
+
mockConnections.set("integration:google", {
|
|
736
747
|
id: "conn-gmail",
|
|
737
|
-
providerKey: "integration:
|
|
748
|
+
providerKey: "integration:google",
|
|
738
749
|
oauthAppId: "app-gmail",
|
|
739
750
|
expiresAt: Date.now() + 3600_000,
|
|
740
751
|
});
|
|
@@ -742,7 +753,7 @@ describe("credential_store tool", () => {
|
|
|
742
753
|
const result = await credentialStoreTool.execute(
|
|
743
754
|
{
|
|
744
755
|
action: "delete",
|
|
745
|
-
service: "integration:
|
|
756
|
+
service: "integration:google",
|
|
746
757
|
field: "api_key",
|
|
747
758
|
},
|
|
748
759
|
_ctx,
|
|
@@ -753,7 +764,7 @@ describe("credential_store tool", () => {
|
|
|
753
764
|
// Verify disconnectOAuthProvider was called with the service name
|
|
754
765
|
expect(mockDisconnectOAuthProvider).toHaveBeenCalledTimes(1);
|
|
755
766
|
expect(mockDisconnectOAuthProvider).toHaveBeenCalledWith(
|
|
756
|
-
"integration:
|
|
767
|
+
"integration:google",
|
|
757
768
|
);
|
|
758
769
|
});
|
|
759
770
|
});
|
|
@@ -1313,6 +1324,7 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1313
1324
|
id: appId,
|
|
1314
1325
|
providerKey: service,
|
|
1315
1326
|
clientId: "test-client-id",
|
|
1327
|
+
clientSecretCredentialPath: `oauth_app/${appId}/client_secret`,
|
|
1316
1328
|
});
|
|
1317
1329
|
mockConnections.set(service, {
|
|
1318
1330
|
id: connId,
|
|
@@ -1331,7 +1343,7 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1331
1343
|
}
|
|
1332
1344
|
|
|
1333
1345
|
test("3 concurrent 401 refreshes for the same service call doRefresh exactly once", async () => {
|
|
1334
|
-
setupService("integration:
|
|
1346
|
+
setupService("integration:google");
|
|
1335
1347
|
|
|
1336
1348
|
let resolveRefresh!: (value: {
|
|
1337
1349
|
accessToken: string;
|
|
@@ -1355,9 +1367,9 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1355
1367
|
|
|
1356
1368
|
// Launch 3 concurrent withValidToken calls — all will get a non-expired
|
|
1357
1369
|
// token first, call the callback, get a 401, and then try to refresh.
|
|
1358
|
-
const p1 = withValidToken("integration:
|
|
1359
|
-
const p2 = withValidToken("integration:
|
|
1360
|
-
const p3 = withValidToken("integration:
|
|
1370
|
+
const p1 = withValidToken("integration:google", callback);
|
|
1371
|
+
const p2 = withValidToken("integration:google", callback);
|
|
1372
|
+
const p3 = withValidToken("integration:google", callback);
|
|
1361
1373
|
|
|
1362
1374
|
// Let the event loop tick so all 3 calls enter the 401 retry path
|
|
1363
1375
|
await new Promise((r) => setTimeout(r, 10));
|
|
@@ -1379,7 +1391,7 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1379
1391
|
});
|
|
1380
1392
|
|
|
1381
1393
|
test("concurrent refreshes for different services proceed independently", async () => {
|
|
1382
|
-
setupService("integration:
|
|
1394
|
+
setupService("integration:google");
|
|
1383
1395
|
setupService("integration:slack");
|
|
1384
1396
|
|
|
1385
1397
|
let resolveGmail!: (value: {
|
|
@@ -1424,7 +1436,7 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1424
1436
|
return `slack-${token}`;
|
|
1425
1437
|
};
|
|
1426
1438
|
|
|
1427
|
-
const p1 = withValidToken("integration:
|
|
1439
|
+
const p1 = withValidToken("integration:google", gmailCallback);
|
|
1428
1440
|
const p2 = withValidToken("integration:slack", slackCallback);
|
|
1429
1441
|
|
|
1430
1442
|
await new Promise((r) => setTimeout(r, 10));
|
|
@@ -1443,7 +1455,7 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1443
1455
|
});
|
|
1444
1456
|
|
|
1445
1457
|
test("deduplication cleans up after refresh completes, allowing subsequent refreshes", async () => {
|
|
1446
|
-
setupService("integration:
|
|
1458
|
+
setupService("integration:google");
|
|
1447
1459
|
|
|
1448
1460
|
let refreshCount = 0;
|
|
1449
1461
|
mockRefreshOAuth2Token.mockImplementation(() => {
|
|
@@ -1458,7 +1470,7 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1458
1470
|
|
|
1459
1471
|
// First call triggers a refresh (old token → 401 → refresh → token-1)
|
|
1460
1472
|
const r1 = await withValidToken(
|
|
1461
|
-
"integration:
|
|
1473
|
+
"integration:google",
|
|
1462
1474
|
async (token: string) => {
|
|
1463
1475
|
if (token !== "token-1") throw err401;
|
|
1464
1476
|
return token;
|
|
@@ -1470,7 +1482,7 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1470
1482
|
// Second call also triggers a 401 to verify dedup state was cleaned up
|
|
1471
1483
|
// and a new refresh is allowed (not deduplicated with the first).
|
|
1472
1484
|
const r2 = await withValidToken(
|
|
1473
|
-
"integration:
|
|
1485
|
+
"integration:google",
|
|
1474
1486
|
async (token: string) => {
|
|
1475
1487
|
if (token !== "token-2") throw err401;
|
|
1476
1488
|
return token;
|
|
@@ -1483,7 +1495,7 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1483
1495
|
});
|
|
1484
1496
|
|
|
1485
1497
|
test("deduplication propagates refresh errors to all waiting callers", async () => {
|
|
1486
|
-
setupService("integration:
|
|
1498
|
+
setupService("integration:google");
|
|
1487
1499
|
|
|
1488
1500
|
mockRefreshOAuth2Token.mockImplementation(() =>
|
|
1489
1501
|
Promise.reject(
|
|
@@ -1501,8 +1513,8 @@ describe("withValidToken refresh deduplication", () => {
|
|
|
1501
1513
|
};
|
|
1502
1514
|
|
|
1503
1515
|
// Launch 2 concurrent calls — both should fail with the same error
|
|
1504
|
-
const p1 = withValidToken("integration:
|
|
1505
|
-
const p2 = withValidToken("integration:
|
|
1516
|
+
const p1 = withValidToken("integration:google", callback);
|
|
1517
|
+
const p2 = withValidToken("integration:google", callback);
|
|
1506
1518
|
|
|
1507
1519
|
const results = await Promise.allSettled([p1, p2]);
|
|
1508
1520
|
|