@vellumai/assistant 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +54 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +111 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
- package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
- package/src/__tests__/app-dir-path-guard.test.ts +78 -0
- package/src/__tests__/app-executors.test.ts +1 -291
- package/src/__tests__/app-git-history.test.ts +4 -4
- package/src/__tests__/app-routes-csp.test.ts +1 -0
- package/src/__tests__/app-store-dir-names.test.ts +426 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -9
- package/src/__tests__/attachments-store.test.ts +169 -21
- package/src/__tests__/attachments.test.ts +115 -1
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/canonical-guardian-store.test.ts +38 -0
- package/src/__tests__/channel-reply-delivery.test.ts +55 -0
- package/src/__tests__/checker.test.ts +54 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/config-schema-cmd.test.ts +68 -21
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
- package/src/__tests__/conversation-agent-loop.test.ts +290 -2
- package/src/__tests__/conversation-attachments.test.ts +17 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
- package/src/__tests__/conversation-disk-view.test.ts +810 -0
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +551 -0
- package/src/__tests__/conversation-fork-route.test.ts +386 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
- package/src/__tests__/conversation-media-retry.test.ts +8 -2
- package/src/__tests__/conversation-queue.test.ts +36 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
- package/src/__tests__/conversation-skill-tools.test.ts +4 -9
- package/src/__tests__/conversation-slash-commands.test.ts +149 -0
- package/src/__tests__/conversation-store.test.ts +24 -21
- package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/conversation-title-service.test.ts +137 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-vault-unit.test.ts +5 -10
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/filesystem-tools.test.ts +4 -2
- package/src/__tests__/first-greeting.test.ts +80 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
- package/src/__tests__/history-repair.test.ts +103 -10
- package/src/__tests__/http-conversation-lineage.test.ts +251 -0
- package/src/__tests__/image-source-path-reinject.test.ts +136 -0
- package/src/__tests__/llm-context-normalization.test.ts +1116 -0
- package/src/__tests__/llm-context-route-provider.test.ts +217 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
- package/src/__tests__/media-generate-image.test.ts +47 -94
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-recall-quality.test.ts +5 -5
- package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
- package/src/__tests__/migration-export-http.test.ts +3 -1
- package/src/__tests__/migration-import-commit-http.test.ts +18 -4
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
- package/src/__tests__/mime-builder.test.ts +3 -2
- package/src/__tests__/non-member-access-request.test.ts +12 -1
- package/src/__tests__/notification-decision-identity.test.ts +52 -0
- package/src/__tests__/oauth-apps-routes.test.ts +103 -0
- package/src/__tests__/oauth-store.test.ts +115 -0
- package/src/__tests__/provider-error-scenarios.test.ts +1 -3
- package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
- package/src/__tests__/recording-handler.test.ts +17 -0
- package/src/__tests__/registry.test.ts +3 -8
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
- package/src/__tests__/schema-transforms.test.ts +165 -5
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/skill-feature-flags-integration.test.ts +18 -17
- package/src/__tests__/skill-feature-flags.test.ts +13 -13
- package/src/__tests__/skill-load-feature-flag.test.ts +4 -4
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -2
- package/src/__tests__/starter-task-flow.test.ts +1 -0
- package/src/__tests__/suggestion-routes.test.ts +443 -0
- package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +8 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
- package/src/__tests__/top-level-renderer.test.ts +22 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
- package/src/__tests__/web-fetch.test.ts +6 -2
- package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
- package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
- package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
- package/src/agent/attachments.ts +27 -1
- package/src/agent/loop.ts +29 -1
- package/src/avatar/traits-png-sync.ts +80 -25
- package/src/bundler/app-bundler.ts +4 -4
- package/src/calls/call-domain.ts +1 -0
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/auth.ts +92 -0
- package/src/cli/commands/avatar.ts +7 -6
- package/src/cli/commands/config.ts +2 -0
- package/src/cli/commands/oauth/providers.ts +29 -0
- package/src/cli/program.ts +12 -0
- package/src/cli.ts +15 -48
- package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
- package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
- package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
- package/src/config/bundled-tool-registry.ts +2 -14
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/loader.ts +64 -0
- package/src/config/raw-config-utils.ts +30 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/services.ts +8 -6
- package/src/contacts/contact-store.ts +13 -6
- package/src/contacts/contacts-write.ts +0 -1
- package/src/context/window-manager.ts +13 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +46 -42
- package/src/daemon/conversation-agent-loop.ts +56 -19
- package/src/daemon/conversation-attachments.ts +18 -36
- package/src/daemon/conversation-error.ts +2 -1
- package/src/daemon/conversation-history.ts +18 -4
- package/src/daemon/conversation-lifecycle.ts +39 -15
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +21 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +143 -20
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +51 -29
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +148 -0
- package/src/daemon/handlers/config-model.ts +71 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/history-repair.ts +28 -8
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +106 -64
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +19 -0
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/shared.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/upgrades.ts +23 -0
- package/src/daemon/server.ts +83 -12
- package/src/daemon/shutdown-handlers.ts +8 -5
- package/src/daemon/startup-error.ts +9 -0
- package/src/daemon/tool-side-effects.ts +11 -28
- package/src/events/tool-permission-telemetry-listener.ts +1 -3
- package/src/instrument.ts +0 -4
- package/src/media/app-icon-generator.ts +2 -2
- package/src/memory/app-git-service.ts +28 -16
- package/src/memory/app-store.ts +230 -41
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +442 -3
- package/src/memory/conversation-directories.ts +125 -0
- package/src/memory/conversation-disk-view.ts +390 -0
- package/src/memory/conversation-key-store.ts +17 -5
- package/src/memory/conversation-queries.ts +5 -1
- package/src/memory/conversation-title-service.ts +21 -49
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +42 -53
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-local.ts +1 -3
- package/src/memory/embedding-ollama.ts +1 -3
- package/src/memory/embedding-openai.ts +1 -3
- package/src/memory/indexer.ts +9 -7
- package/src/memory/items-extractor.ts +42 -13
- package/src/memory/job-handlers/conversation-starters.ts +6 -1
- package/src/memory/job-handlers/embedding.test.ts +1 -4
- package/src/memory/llm-request-log-store.ts +100 -1
- package/src/memory/migrations/102-alter-table-columns.ts +5 -0
- package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
- package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
- package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
- package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
- package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
- package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
- package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
- package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
- package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +6 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/oauth.ts +6 -0
- package/src/messaging/providers/gmail/mime-builder.ts +3 -1
- package/src/notifications/copy-composer.ts +26 -0
- package/src/notifications/decision-engine.ts +14 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +36 -0
- package/src/oauth/byo-connection.test.ts +1 -45
- package/src/oauth/byo-connection.ts +2 -8
- package/src/oauth/connect-orchestrator.ts +15 -11
- package/src/oauth/connection-resolver.test.ts +191 -0
- package/src/oauth/connection-resolver.ts +66 -38
- package/src/oauth/connection.ts +0 -1
- package/src/oauth/oauth-store.ts +97 -47
- package/src/oauth/platform-connection.test.ts +0 -1
- package/src/oauth/platform-connection.ts +11 -3
- package/src/oauth/seed-providers.ts +78 -3
- package/src/oauth/token-persistence.ts +16 -10
- package/src/permissions/checker.ts +62 -19
- package/src/prompts/system-prompt.ts +2 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -0
- package/src/providers/anthropic/client.ts +8 -1
- package/src/providers/failover.ts +4 -1
- package/src/providers/gemini/client.ts +50 -0
- package/src/providers/model-catalog.ts +92 -0
- package/src/providers/model-intents.ts +29 -20
- package/src/providers/openai/client.ts +49 -0
- package/src/providers/types.ts +2 -0
- package/src/runtime/access-request-helper.ts +16 -7
- package/src/runtime/auth/credential-service.ts +3 -1
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/btw-sidechain.ts +101 -0
- package/src/runtime/channel-reply-delivery.ts +17 -1
- package/src/runtime/http-router.ts +3 -1
- package/src/runtime/http-server.ts +196 -141
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +5 -1
- package/src/runtime/routes/access-request-decision.ts +41 -0
- package/src/runtime/routes/app-management-routes.ts +6 -3
- package/src/runtime/routes/app-routes.ts +7 -3
- package/src/runtime/routes/approval-routes.ts +1 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
- package/src/runtime/routes/attachment-routes.ts +45 -15
- package/src/runtime/routes/btw-routes.ts +21 -61
- package/src/runtime/routes/conversation-management-routes.ts +68 -0
- package/src/runtime/routes/conversation-query-routes.ts +180 -10
- package/src/runtime/routes/conversation-routes.ts +222 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1199 -0
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/memory-item-routes.test.ts +34 -0
- package/src/runtime/routes/memory-item-routes.ts +4 -0
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/oauth-apps.ts +291 -0
- package/src/runtime/routes/secret-routes.ts +28 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +9 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +3 -1
- package/src/tools/AGENTS.md +6 -10
- package/src/tools/apps/executors.ts +17 -232
- package/src/tools/claude-code/claude-code.ts +2 -3
- package/src/tools/credentials/vault.ts +7 -12
- package/src/tools/host-filesystem/read.ts +13 -10
- package/src/tools/network/__tests__/web-search.test.ts +4 -2
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +4 -21
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/tool-manifest.ts +0 -6
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/device-id.ts +28 -5
- package/src/util/platform.ts +6 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
- package/src/workspace/migrations/003-seed-device-id.ts +3 -4
- package/src/workspace/migrations/006-services-config.ts +5 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/top-level-renderer.ts +12 -0
- package/src/__tests__/asset-materialize-tool.test.ts +0 -523
- package/src/__tests__/asset-search-tool.test.ts +0 -536
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
- package/src/__tests__/media-visibility-policy.test.ts +0 -190
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
- package/src/daemon/media-visibility-policy.ts +0 -59
- package/src/tools/assets/materialize.ts +0 -248
- package/src/tools/assets/search.ts +0 -400
package/src/memory/retriever.ts
CHANGED
|
@@ -373,7 +373,7 @@ export async function buildMemoryRecall(
|
|
|
373
373
|
// those messages are no longer in the conversation history and memory is
|
|
374
374
|
// the only way they can influence the response.
|
|
375
375
|
if (conversationId) {
|
|
376
|
-
const inContextMessageIds =
|
|
376
|
+
const inContextMessageIds = getEffectiveInContextMessageIds(conversationId);
|
|
377
377
|
if (inContextMessageIds) {
|
|
378
378
|
for (const [key, c] of candidateMap) {
|
|
379
379
|
if (c.type === "segment") {
|
|
@@ -392,6 +392,51 @@ export async function buildMemoryRecall(
|
|
|
392
392
|
}
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
|
+
|
|
396
|
+
// ── Item filtering: exclude items whose ALL sources are in-context ──
|
|
397
|
+
// Items distilled from messages the model can already see are redundant.
|
|
398
|
+
// However, items with ANY source outside the in-context set carry
|
|
399
|
+
// cross-conversation information and must be preserved.
|
|
400
|
+
const itemCandidateIds = [...candidateMap.values()]
|
|
401
|
+
.filter((c) => c.type === "item")
|
|
402
|
+
.map((c) => c.id);
|
|
403
|
+
|
|
404
|
+
if (itemCandidateIds.length > 0) {
|
|
405
|
+
try {
|
|
406
|
+
const db = getDb();
|
|
407
|
+
const allSources = db
|
|
408
|
+
.select({
|
|
409
|
+
memoryItemId: memoryItemSources.memoryItemId,
|
|
410
|
+
messageId: memoryItemSources.messageId,
|
|
411
|
+
})
|
|
412
|
+
.from(memoryItemSources)
|
|
413
|
+
.where(inArray(memoryItemSources.memoryItemId, itemCandidateIds))
|
|
414
|
+
.all();
|
|
415
|
+
|
|
416
|
+
// Build item ID → source message IDs map
|
|
417
|
+
const itemSourceMap = new Map<string, string[]>();
|
|
418
|
+
for (const s of allSources) {
|
|
419
|
+
const existing = itemSourceMap.get(s.memoryItemId);
|
|
420
|
+
if (existing) existing.push(s.messageId);
|
|
421
|
+
else itemSourceMap.set(s.memoryItemId, [s.messageId]);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Filter items whose ALL sources are in-context
|
|
425
|
+
for (const [key, c] of candidateMap) {
|
|
426
|
+
if (c.type !== "item") continue;
|
|
427
|
+
const sourceMessageIds = itemSourceMap.get(c.id);
|
|
428
|
+
if (!sourceMessageIds || sourceMessageIds.length === 0) continue;
|
|
429
|
+
if (sourceMessageIds.every((mid) => inContextMessageIds.has(mid))) {
|
|
430
|
+
candidateMap.delete(key);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
} catch (err) {
|
|
434
|
+
log.warn(
|
|
435
|
+
{ err },
|
|
436
|
+
"Failed to fetch item sources for in-context filtering; skipping",
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
395
440
|
}
|
|
396
441
|
}
|
|
397
442
|
|
|
@@ -574,14 +619,22 @@ export async function buildMemoryRecall(
|
|
|
574
619
|
}
|
|
575
620
|
|
|
576
621
|
/**
|
|
577
|
-
* Get the set of message IDs that are
|
|
578
|
-
* window
|
|
579
|
-
*
|
|
580
|
-
*
|
|
622
|
+
* Get the set of message IDs that are effectively in the conversation's
|
|
623
|
+
* context window. This includes:
|
|
624
|
+
* 1. Messages still visible (not compacted) in the conversation history.
|
|
625
|
+
* 2. Fork-source message IDs — when a conversation is forked, messages are
|
|
626
|
+
* copied with new IDs but their metadata stores the original parent
|
|
627
|
+
* message ID as `forkSourceMessageId`. Segments sourced from those parent
|
|
628
|
+
* messages are redundant because the fork already contains their content.
|
|
629
|
+
*
|
|
630
|
+
* Uses `contextCompactedMessageCount` to determine the compaction offset:
|
|
631
|
+
* messages ordered by createdAt after that count are still visible to the model.
|
|
581
632
|
*
|
|
582
633
|
* Returns `null` if the conversation is not found (deleted, or no DB row).
|
|
583
634
|
*/
|
|
584
|
-
function
|
|
635
|
+
function getEffectiveInContextMessageIds(
|
|
636
|
+
conversationId: string,
|
|
637
|
+
): Set<string> | null {
|
|
585
638
|
try {
|
|
586
639
|
const db = getDb();
|
|
587
640
|
|
|
@@ -599,9 +652,9 @@ function getInContextMessageIds(conversationId: string): Set<string> | null {
|
|
|
599
652
|
|
|
600
653
|
const offset = conv.contextCompactedMessageCount;
|
|
601
654
|
|
|
602
|
-
// Fetch message IDs ordered by creation time
|
|
655
|
+
// Fetch message IDs and metadata ordered by creation time
|
|
603
656
|
const rows = db
|
|
604
|
-
.select({ id: messages.id })
|
|
657
|
+
.select({ id: messages.id, metadata: messages.metadata })
|
|
605
658
|
.from(messages)
|
|
606
659
|
.where(eq(messages.conversationId, conversationId))
|
|
607
660
|
.orderBy(asc(messages.createdAt))
|
|
@@ -609,7 +662,30 @@ function getInContextMessageIds(conversationId: string): Set<string> | null {
|
|
|
609
662
|
|
|
610
663
|
// Messages up to `offset` have been compacted out of context
|
|
611
664
|
const inContextRows = rows.slice(offset);
|
|
612
|
-
|
|
665
|
+
const idSet = new Set(inContextRows.map((r) => r.id));
|
|
666
|
+
|
|
667
|
+
// Also include fork-source message IDs from in-context messages.
|
|
668
|
+
// When a conversation is forked, each copied message's metadata contains
|
|
669
|
+
// `forkSourceMessageId` pointing to the original (parent or grandparent)
|
|
670
|
+
// message ID. Segments sourced from those original messages are redundant.
|
|
671
|
+
for (const row of inContextRows) {
|
|
672
|
+
if (!row.metadata) continue;
|
|
673
|
+
try {
|
|
674
|
+
const parsed = JSON.parse(row.metadata);
|
|
675
|
+
if (
|
|
676
|
+
parsed &&
|
|
677
|
+
typeof parsed === "object" &&
|
|
678
|
+
!Array.isArray(parsed) &&
|
|
679
|
+
typeof parsed.forkSourceMessageId === "string"
|
|
680
|
+
) {
|
|
681
|
+
idSet.add(parsed.forkSourceMessageId);
|
|
682
|
+
}
|
|
683
|
+
} catch {
|
|
684
|
+
// Invalid metadata JSON — skip, don't break filtering.
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
return idSet;
|
|
613
689
|
} catch (err) {
|
|
614
690
|
log.warn(
|
|
615
691
|
{ err },
|
|
@@ -26,12 +26,17 @@ export const conversations = sqliteTable(
|
|
|
26
26
|
memoryScopeId: text("memory_scope_id").notNull().default("default"),
|
|
27
27
|
originChannel: text("origin_channel"),
|
|
28
28
|
originInterface: text("origin_interface"),
|
|
29
|
+
forkParentConversationId: text("fork_parent_conversation_id"),
|
|
30
|
+
forkParentMessageId: text("fork_parent_message_id"),
|
|
29
31
|
isAutoTitle: integer("is_auto_title").notNull().default(1),
|
|
30
32
|
scheduleJobId: text("schedule_job_id"),
|
|
31
33
|
},
|
|
32
34
|
(table) => [
|
|
33
35
|
index("idx_conversations_updated_at").on(table.updatedAt),
|
|
34
36
|
index("idx_conversations_conversation_type").on(table.conversationType),
|
|
37
|
+
index("idx_conversations_fork_parent_conversation_id").on(
|
|
38
|
+
table.forkParentConversationId,
|
|
39
|
+
),
|
|
35
40
|
],
|
|
36
41
|
);
|
|
37
42
|
|
|
@@ -88,6 +93,7 @@ export const attachments = sqliteTable("attachments", {
|
|
|
88
93
|
dataBase64: text("data_base64").notNull(),
|
|
89
94
|
contentHash: text("content_hash"),
|
|
90
95
|
thumbnailBase64: text("thumbnail_base64"),
|
|
96
|
+
filePath: text("file_path"),
|
|
91
97
|
createdAt: integer("created_at").notNull(),
|
|
92
98
|
});
|
|
93
99
|
|
|
@@ -106,13 +106,19 @@ export const watcherEvents = sqliteTable("watcher_events", {
|
|
|
106
106
|
createdAt: integer("created_at").notNull(),
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
export const llmRequestLogs = sqliteTable(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
export const llmRequestLogs = sqliteTable(
|
|
110
|
+
"llm_request_logs",
|
|
111
|
+
{
|
|
112
|
+
id: text("id").primaryKey(),
|
|
113
|
+
conversationId: text("conversation_id").notNull(),
|
|
114
|
+
messageId: text("message_id"),
|
|
115
|
+
provider: text("provider"),
|
|
116
|
+
requestPayload: text("request_payload").notNull(),
|
|
117
|
+
responsePayload: text("response_payload").notNull(),
|
|
118
|
+
createdAt: integer("created_at").notNull(),
|
|
119
|
+
},
|
|
120
|
+
(table) => [index("idx_llm_request_logs_message_id").on(table.messageId)],
|
|
121
|
+
);
|
|
116
122
|
|
|
117
123
|
export const llmUsageEvents = sqliteTable(
|
|
118
124
|
"llm_usage_events",
|
|
@@ -18,6 +18,12 @@ export const oauthProviders = sqliteTable("oauth_providers", {
|
|
|
18
18
|
extraParams: text("extra_params"),
|
|
19
19
|
callbackTransport: text("callback_transport"),
|
|
20
20
|
pingUrl: text("ping_url"),
|
|
21
|
+
managedServiceConfigKey: text("managed_service_config_key"),
|
|
22
|
+
displayName: text("display_name"),
|
|
23
|
+
description: text("description"),
|
|
24
|
+
dashboardUrl: text("dashboard_url"),
|
|
25
|
+
clientIdPlaceholder: text("client_id_placeholder"),
|
|
26
|
+
requiresClientSecret: integer("requires_client_secret").notNull().default(1),
|
|
21
27
|
createdAt: integer("created_at").notNull(),
|
|
22
28
|
updatedAt: integer("updated_at").notNull(),
|
|
23
29
|
});
|
|
@@ -45,7 +45,9 @@ export function buildMultipartMime(options: MimeMessageOptions): string {
|
|
|
45
45
|
const sanitizedSubject = sanitizeHeaderValue(subject);
|
|
46
46
|
const sanitizedCc = cc ? sanitizeHeaderValue(cc) : undefined;
|
|
47
47
|
const sanitizedBcc = bcc ? sanitizeHeaderValue(bcc) : undefined;
|
|
48
|
-
const sanitizedInReplyTo = inReplyTo
|
|
48
|
+
const sanitizedInReplyTo = inReplyTo
|
|
49
|
+
? sanitizeHeaderValue(inReplyTo)
|
|
50
|
+
: undefined;
|
|
49
51
|
|
|
50
52
|
const headers = [
|
|
51
53
|
`To: ${sanitizedTo}`,
|
|
@@ -196,6 +196,14 @@ export function hasInviteFlowDirective(text: string | undefined): boolean {
|
|
|
196
196
|
* Build the deterministic access-request contract text from payload fields.
|
|
197
197
|
* This is the canonical baseline that enforcement can append when generated
|
|
198
198
|
* copy is missing required elements.
|
|
199
|
+
*
|
|
200
|
+
* Channel-agnostic by design: this function reads from the generic
|
|
201
|
+
* `contextPayload` and works identically regardless of which channel
|
|
202
|
+
* (Slack, Telegram, desktop, etc.) the notification is delivered to.
|
|
203
|
+
* When `guardianResolutionSource` is present and not `"source-channel-contact"`,
|
|
204
|
+
* the guardian was resolved via fallback (e.g. vellum anchor) rather than
|
|
205
|
+
* a verified same-channel contact — downstream copy or routing can use
|
|
206
|
+
* this to append verification CTAs like "Was this you?".
|
|
199
207
|
*/
|
|
200
208
|
export function buildAccessRequestContractText(
|
|
201
209
|
payload: Record<string, unknown>,
|
|
@@ -208,6 +216,15 @@ export function buildAccessRequestContractText(
|
|
|
208
216
|
? payload.previousMemberStatus
|
|
209
217
|
: undefined;
|
|
210
218
|
|
|
219
|
+
const guardianResolutionSource =
|
|
220
|
+
typeof payload.guardianResolutionSource === "string"
|
|
221
|
+
? payload.guardianResolutionSource
|
|
222
|
+
: undefined;
|
|
223
|
+
const sourceChannel =
|
|
224
|
+
typeof payload.sourceChannel === "string"
|
|
225
|
+
? payload.sourceChannel
|
|
226
|
+
: undefined;
|
|
227
|
+
|
|
211
228
|
const lines: string[] = [];
|
|
212
229
|
lines.push(buildAccessRequestIdentityLine(payload));
|
|
213
230
|
if (previousMemberStatus === "revoked") {
|
|
@@ -220,6 +237,15 @@ export function buildAccessRequestContractText(
|
|
|
220
237
|
);
|
|
221
238
|
}
|
|
222
239
|
lines.push(buildAccessRequestInviteDirective());
|
|
240
|
+
if (
|
|
241
|
+
(guardianResolutionSource === "vellum-anchor" ||
|
|
242
|
+
guardianResolutionSource === "none") &&
|
|
243
|
+
sourceChannel
|
|
244
|
+
) {
|
|
245
|
+
lines.push(
|
|
246
|
+
`Note: You haven't verified your identity on ${sourceChannel} yet. If this was you trying to message your assistant, say "help me verify as guardian on ${sourceChannel}" to set up direct access.`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
223
249
|
return lines.join("\n");
|
|
224
250
|
}
|
|
225
251
|
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from "../providers/provider-send-message.js";
|
|
23
23
|
import type { ModelIntent, Provider } from "../providers/types.js";
|
|
24
24
|
import { getLogger } from "../util/logger.js";
|
|
25
|
+
import { truncate } from "../util/truncate.js";
|
|
25
26
|
import {
|
|
26
27
|
buildConversationCandidates,
|
|
27
28
|
type ConversationCandidateSet,
|
|
@@ -55,6 +56,15 @@ const log = getLogger("notification-decision-engine");
|
|
|
55
56
|
const DECISION_TIMEOUT_MS = 15_000;
|
|
56
57
|
const PROMPT_VERSION = "v4";
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Maximum character budget for identity context injected into the notification
|
|
61
|
+
* decision prompt. We truncate to prevent oversized prompts when SOUL.md /
|
|
62
|
+
* IDENTITY.md / USER.md are large — exceeding the provider context window
|
|
63
|
+
* would cause the LLM call to fail and silently degrade to deterministic
|
|
64
|
+
* fallback for all notifications.
|
|
65
|
+
*/
|
|
66
|
+
const MAX_IDENTITY_CONTEXT_CHARS = 2000;
|
|
67
|
+
|
|
58
68
|
// ── System prompt ──────────────────────────────────────────────────────
|
|
59
69
|
|
|
60
70
|
function buildSystemPrompt(
|
|
@@ -790,7 +800,10 @@ async function classifyWithLLM(
|
|
|
790
800
|
const candidateContext = candidateSet
|
|
791
801
|
? (serializeCandidatesForPrompt(candidateSet) ?? undefined)
|
|
792
802
|
: undefined;
|
|
793
|
-
const
|
|
803
|
+
const rawIdentityContext = buildCoreIdentityContext();
|
|
804
|
+
const identityContext = rawIdentityContext
|
|
805
|
+
? truncate(rawIdentityContext, MAX_IDENTITY_CONTEXT_CHARS, "\n…[truncated]")
|
|
806
|
+
: undefined;
|
|
794
807
|
const systemPrompt = buildSystemPrompt(
|
|
795
808
|
availableChannels,
|
|
796
809
|
preferenceContext,
|
|
@@ -220,7 +220,7 @@ export async function emitNotificationSignal<TEventName extends string>(
|
|
|
220
220
|
sourceChannel: params.sourceChannel,
|
|
221
221
|
sourceContextId: params.sourceContextId,
|
|
222
222
|
attentionHints: params.attentionHints,
|
|
223
|
-
payload: params.contextPayload ?? {},
|
|
223
|
+
payload: (params.contextPayload ?? {}) as Record<string, unknown>,
|
|
224
224
|
dedupeKey: params.dedupeKey,
|
|
225
225
|
});
|
|
226
226
|
|
|
@@ -118,8 +118,44 @@ export interface AttentionHints {
|
|
|
118
118
|
|
|
119
119
|
export type RoutingIntent = "single_channel" | "multi_channel" | "all_channels";
|
|
120
120
|
|
|
121
|
+
// ── Typed context payloads ──────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* How the guardian was resolved for an access request.
|
|
125
|
+
*
|
|
126
|
+
* - `"source-channel-contact"` — Guardian was found via the originating channel's
|
|
127
|
+
* contact store and their principalId matches the assistant's anchor.
|
|
128
|
+
* - `"vellum-anchor"` — No same-channel guardian matched; fell back to the
|
|
129
|
+
* assistant's vellum guardian principal.
|
|
130
|
+
* - `"none"` — No guardian binding could be resolved at all.
|
|
131
|
+
*
|
|
132
|
+
* Downstream consumers (notification copy, routing) use this to decide whether
|
|
133
|
+
* to append a "Was this you?" CTA or route notifications beyond the source channel.
|
|
134
|
+
* This is channel-agnostic by design — any channel's access request that
|
|
135
|
+
* resolves to a non-source-channel guardian gets the same treatment.
|
|
136
|
+
*/
|
|
137
|
+
export type GuardianResolutionSource =
|
|
138
|
+
| "source-channel-contact"
|
|
139
|
+
| "vellum-anchor"
|
|
140
|
+
| "none";
|
|
141
|
+
|
|
142
|
+
export interface AccessRequestContextPayload {
|
|
143
|
+
requestId: string;
|
|
144
|
+
requestCode: string;
|
|
145
|
+
sourceChannel: string;
|
|
146
|
+
conversationExternalId: string;
|
|
147
|
+
actorExternalId: string;
|
|
148
|
+
actorDisplayName: string | null;
|
|
149
|
+
actorUsername: string | null;
|
|
150
|
+
senderIdentifier: string;
|
|
151
|
+
guardianBindingChannel: string | null;
|
|
152
|
+
guardianResolutionSource: GuardianResolutionSource;
|
|
153
|
+
previousMemberStatus: string | null;
|
|
154
|
+
}
|
|
155
|
+
|
|
121
156
|
export interface NotificationEventContextPayloadMap {
|
|
122
157
|
"guardian.question": GuardianQuestionPayload;
|
|
158
|
+
"ingress.access_request": AccessRequestContextPayload;
|
|
123
159
|
}
|
|
124
160
|
|
|
125
161
|
export type NotificationContextPayload<TEventName extends string = string> =
|
|
@@ -236,8 +236,6 @@ function createConnection(service = "integration:google"): BYOOAuthConnection {
|
|
|
236
236
|
providerKey: service,
|
|
237
237
|
baseUrl: "https://gmail.googleapis.com/gmail/v1/users/me",
|
|
238
238
|
accountInfo: null,
|
|
239
|
-
grantedScopes: ["read", "write"],
|
|
240
|
-
credentialService: service,
|
|
241
239
|
});
|
|
242
240
|
}
|
|
243
241
|
|
|
@@ -500,12 +498,11 @@ describe("resolveOAuthConnection", () => {
|
|
|
500
498
|
|
|
501
499
|
expect(conn).toBeInstanceOf(BYOOAuthConnection);
|
|
502
500
|
expect(conn.providerKey).toBe("integration:google");
|
|
503
|
-
expect(conn.grantedScopes).toEqual(["read", "write"]);
|
|
504
501
|
});
|
|
505
502
|
|
|
506
503
|
test("throws when no credential metadata exists", async () => {
|
|
507
504
|
await expect(resolveOAuthConnection("integration:unknown")).rejects.toThrow(
|
|
508
|
-
/No
|
|
505
|
+
/No active OAuth connection found for "integration:unknown"/,
|
|
509
506
|
);
|
|
510
507
|
});
|
|
511
508
|
|
|
@@ -517,45 +514,4 @@ describe("resolveOAuthConnection", () => {
|
|
|
517
514
|
/No base URL configured for "integration:custom-service"/,
|
|
518
515
|
);
|
|
519
516
|
});
|
|
520
|
-
|
|
521
|
-
test("resolves base URL via app's canonical providerKey for custom credential_service", async () => {
|
|
522
|
-
// Set up a well-known provider with a baseUrl
|
|
523
|
-
mockProviders.set("github", {
|
|
524
|
-
key: "github",
|
|
525
|
-
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
526
|
-
baseUrl: "https://api.github.com",
|
|
527
|
-
});
|
|
528
|
-
// The custom credential service has no provider entry of its own
|
|
529
|
-
// (getProvider("integration:github-work") returns undefined)
|
|
530
|
-
|
|
531
|
-
// App points to the canonical "github" provider
|
|
532
|
-
const appId = "app-github-work";
|
|
533
|
-
mockApps.set(appId, {
|
|
534
|
-
id: appId,
|
|
535
|
-
providerKey: "github",
|
|
536
|
-
clientId: "test-client-id",
|
|
537
|
-
clientSecretCredentialPath: `oauth_app/${appId}/client_secret`,
|
|
538
|
-
});
|
|
539
|
-
|
|
540
|
-
// Connection uses the custom credential service as its providerKey
|
|
541
|
-
const connId = "conn-github-work";
|
|
542
|
-
mockConnections.set("integration:github-work", {
|
|
543
|
-
id: connId,
|
|
544
|
-
providerKey: "integration:github-work",
|
|
545
|
-
oauthAppId: appId,
|
|
546
|
-
expiresAt: Date.now() + 3600 * 1000,
|
|
547
|
-
grantedScopes: JSON.stringify(["repo"]),
|
|
548
|
-
accountInfo: null,
|
|
549
|
-
});
|
|
550
|
-
await setSecureKeyAsync(
|
|
551
|
-
`oauth_connection/${connId}/access_token`,
|
|
552
|
-
"ghp-test-token",
|
|
553
|
-
);
|
|
554
|
-
|
|
555
|
-
const conn = await resolveOAuthConnection("integration:github-work");
|
|
556
|
-
|
|
557
|
-
expect(conn).toBeInstanceOf(BYOOAuthConnection);
|
|
558
|
-
expect(conn.providerKey).toBe("integration:github-work");
|
|
559
|
-
expect(conn.grantedScopes).toEqual(["repo"]);
|
|
560
|
-
});
|
|
561
517
|
});
|
|
@@ -25,31 +25,25 @@ export interface BYOOAuthConnectionOptions {
|
|
|
25
25
|
providerKey: string;
|
|
26
26
|
baseUrl: string;
|
|
27
27
|
accountInfo: string | null;
|
|
28
|
-
grantedScopes: string[];
|
|
29
|
-
credentialService: string;
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
export class BYOOAuthConnection implements OAuthConnection {
|
|
33
31
|
readonly id: string;
|
|
34
32
|
readonly providerKey: string;
|
|
35
33
|
readonly accountInfo: string | null;
|
|
36
|
-
readonly grantedScopes: string[];
|
|
37
34
|
|
|
38
35
|
private readonly baseUrl: string;
|
|
39
|
-
private readonly credentialService: string;
|
|
40
36
|
|
|
41
37
|
constructor(opts: BYOOAuthConnectionOptions) {
|
|
42
38
|
this.id = opts.id;
|
|
43
39
|
this.providerKey = opts.providerKey;
|
|
44
40
|
this.baseUrl = opts.baseUrl;
|
|
45
41
|
this.accountInfo = opts.accountInfo;
|
|
46
|
-
this.grantedScopes = opts.grantedScopes;
|
|
47
|
-
this.credentialService = opts.credentialService;
|
|
48
42
|
}
|
|
49
43
|
|
|
50
44
|
async request(req: OAuthConnectionRequest): Promise<OAuthConnectionResponse> {
|
|
51
45
|
return withValidToken(
|
|
52
|
-
this.
|
|
46
|
+
this.providerKey,
|
|
53
47
|
async (token) => {
|
|
54
48
|
const effectiveBaseUrl = req.baseUrl ?? this.baseUrl;
|
|
55
49
|
let fullUrl = `${effectiveBaseUrl}${req.path}`;
|
|
@@ -106,7 +100,7 @@ export class BYOOAuthConnection implements OAuthConnection {
|
|
|
106
100
|
}
|
|
107
101
|
|
|
108
102
|
async withToken<T>(fn: (token: string) => Promise<T>): Promise<T> {
|
|
109
|
-
return withValidToken(this.
|
|
103
|
+
return withValidToken(this.providerKey, fn, {
|
|
110
104
|
connectionId: this.id,
|
|
111
105
|
});
|
|
112
106
|
}
|
|
@@ -252,12 +252,13 @@ export async function orchestrateOAuthConnect(
|
|
|
252
252
|
prepared.completion
|
|
253
253
|
.then(async (result) => {
|
|
254
254
|
try {
|
|
255
|
-
let
|
|
255
|
+
let parsedAccountIdentifier: string | undefined;
|
|
256
256
|
|
|
257
|
-
//
|
|
257
|
+
// Parse account identifier from the provider's identity endpoint.
|
|
258
|
+
// Best-effort — format varies by provider and may fail.
|
|
258
259
|
if (behavior.identityVerifier) {
|
|
259
260
|
try {
|
|
260
|
-
|
|
261
|
+
parsedAccountIdentifier = await behavior.identityVerifier(
|
|
261
262
|
result.tokens.accessToken,
|
|
262
263
|
);
|
|
263
264
|
} catch {
|
|
@@ -270,19 +271,19 @@ export async function orchestrateOAuthConnect(
|
|
|
270
271
|
tokens: result.tokens,
|
|
271
272
|
grantedScopes: result.grantedScopes,
|
|
272
273
|
rawTokenResponse: result.rawTokenResponse,
|
|
273
|
-
|
|
274
|
+
parsedAccountIdentifier,
|
|
274
275
|
});
|
|
275
276
|
log.info(
|
|
276
277
|
{
|
|
277
278
|
service: resolvedService,
|
|
278
|
-
accountInfo: stored.accountInfo ??
|
|
279
|
+
accountInfo: stored.accountInfo ?? parsedAccountIdentifier,
|
|
279
280
|
},
|
|
280
281
|
"Deferred OAuth2 flow completed — tokens stored",
|
|
281
282
|
);
|
|
282
283
|
options.onDeferredComplete?.({
|
|
283
284
|
success: true,
|
|
284
285
|
service: resolvedService,
|
|
285
|
-
accountInfo: stored.accountInfo ??
|
|
286
|
+
accountInfo: stored.accountInfo ?? parsedAccountIdentifier,
|
|
286
287
|
});
|
|
287
288
|
} catch (err) {
|
|
288
289
|
log.error(
|
|
@@ -353,11 +354,14 @@ export async function orchestrateOAuthConnect(
|
|
|
353
354
|
: undefined,
|
|
354
355
|
);
|
|
355
356
|
|
|
356
|
-
//
|
|
357
|
-
|
|
357
|
+
// Parse account identifier from the provider's identity endpoint.
|
|
358
|
+
// Best-effort — format varies by provider and may fail.
|
|
359
|
+
let parsedAccountIdentifier: string | undefined;
|
|
358
360
|
if (behavior.identityVerifier) {
|
|
359
361
|
try {
|
|
360
|
-
|
|
362
|
+
parsedAccountIdentifier = await behavior.identityVerifier(
|
|
363
|
+
tokens.accessToken,
|
|
364
|
+
);
|
|
361
365
|
} catch {
|
|
362
366
|
// Non-fatal
|
|
363
367
|
}
|
|
@@ -368,14 +372,14 @@ export async function orchestrateOAuthConnect(
|
|
|
368
372
|
tokens,
|
|
369
373
|
grantedScopes,
|
|
370
374
|
rawTokenResponse,
|
|
371
|
-
|
|
375
|
+
parsedAccountIdentifier,
|
|
372
376
|
});
|
|
373
377
|
|
|
374
378
|
return {
|
|
375
379
|
success: true,
|
|
376
380
|
deferred: false,
|
|
377
381
|
grantedScopes,
|
|
378
|
-
accountInfo: accountInfo ??
|
|
382
|
+
accountInfo: accountInfo ?? parsedAccountIdentifier,
|
|
379
383
|
};
|
|
380
384
|
} catch (err: unknown) {
|
|
381
385
|
const message =
|