@vellumai/assistant 0.6.4 → 0.6.5
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/.prettierignore +5 -0
- package/ARCHITECTURE.md +32 -36
- package/Dockerfile +12 -0
- package/README.md +3 -4
- package/bun.lock +8 -3
- package/docs/architecture/integrations.md +1 -20
- package/docs/architecture/security.md +16 -16
- package/docs/error-handling.md +111 -0
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +2 -1
- package/knip.json +9 -2
- package/node_modules/@vellumai/ces-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
- package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
- package/node_modules/@vellumai/credential-storage/package.json +2 -2
- package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
- package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
- package/node_modules/@vellumai/egress-proxy/package.json +2 -2
- package/openapi.yaml +123 -11
- package/package.json +6 -3
- package/scripts/generate-openapi.ts +50 -11
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
- package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
- package/src/__tests__/agent-loop.test.ts +112 -1
- package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
- package/src/__tests__/anthropic-provider.test.ts +171 -2
- package/src/__tests__/approval-cascade.test.ts +31 -10
- package/src/__tests__/approval-routes-http.test.ts +134 -10
- package/src/__tests__/assistant-attachments.test.ts +44 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
- package/src/__tests__/browser-skill-endstate.test.ts +51 -182
- package/src/__tests__/btw-routes.test.ts +47 -1
- package/src/__tests__/call-controller.test.ts +1 -2
- package/src/__tests__/call-site-routing-provider.test.ts +214 -0
- package/src/__tests__/catalog-cache.test.ts +27 -4
- package/src/__tests__/channel-approval-routes.test.ts +4 -4
- package/src/__tests__/channel-reply-delivery.test.ts +300 -2
- package/src/__tests__/checker.test.ts +428 -501
- package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
- package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/config-analysis.test.ts +11 -28
- package/src/__tests__/config-loader-backfill.test.ts +174 -0
- package/src/__tests__/config-loader-corrupt.test.ts +183 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
- package/src/__tests__/config-schema-cmd.test.ts +11 -5
- package/src/__tests__/config-schema.test.ts +427 -114
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +72 -73
- package/src/__tests__/contacts-write.test.ts +4 -4
- package/src/__tests__/context-token-estimator.test.ts +191 -1
- package/src/__tests__/context-window-manager.test.ts +530 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
- package/src/__tests__/conversation-agent-loop.test.ts +412 -82
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
- package/src/__tests__/conversation-error.test.ts +37 -6
- package/src/__tests__/conversation-history-web-search.test.ts +6 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
- package/src/__tests__/conversation-lifecycle.test.ts +336 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
- package/src/__tests__/conversation-process-callsite.test.ts +306 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
- package/src/__tests__/conversation-queue.test.ts +41 -26
- package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
- package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
- package/src/__tests__/conversation-skill-tools.test.ts +12 -146
- package/src/__tests__/conversation-slash-queue.test.ts +34 -19
- package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
- package/src/__tests__/conversation-speed-override.test.ts +30 -11
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -2
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
- package/src/__tests__/conversation-unread-route.test.ts +2 -2
- package/src/__tests__/conversation-usage.test.ts +3 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
- package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
- package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
- package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
- package/src/__tests__/credential-vault-unit.test.ts +135 -19
- package/src/__tests__/credentials-cli.test.ts +1 -9
- package/src/__tests__/cross-provider-web-search.test.ts +84 -0
- package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
- package/src/__tests__/delete-propagation.test.ts +437 -0
- package/src/__tests__/dm-backfill.test.ts +417 -0
- package/src/__tests__/dm-persistence.test.ts +227 -0
- package/src/__tests__/edit-propagation.test.ts +280 -0
- package/src/__tests__/ephemeral-permissions.test.ts +93 -3
- package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
- package/src/__tests__/estimator-calibration.test.ts +213 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
- package/src/__tests__/file-write-tool.test.ts +151 -1
- package/src/__tests__/filing-service.test.ts +255 -0
- package/src/__tests__/gemini-provider.test.ts +0 -3
- package/src/__tests__/guardian-grant-minting.test.ts +8 -0
- package/src/__tests__/headless-browser-interactions.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +96 -15
- package/src/__tests__/host-shell-tool.test.ts +124 -18
- package/src/__tests__/http-user-message-parity.test.ts +29 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
- package/src/__tests__/intent-routing.test.ts +1 -40
- package/src/__tests__/llm-catalog-parity.test.ts +174 -0
- package/src/__tests__/llm-context-normalization.test.ts +121 -0
- package/src/__tests__/llm-resolver.test.ts +214 -0
- package/src/__tests__/llm-schema.test.ts +223 -0
- package/src/__tests__/managed-proxy-context.test.ts +6 -2
- package/src/__tests__/messaging-skill-split.test.ts +3 -34
- package/src/__tests__/migration-import-from-url.test.ts +684 -0
- package/src/__tests__/model-intents.test.ts +9 -83
- package/src/__tests__/notification-decision-fallback.test.ts +0 -10
- package/src/__tests__/notification-decision-identity.test.ts +0 -9
- package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
- package/src/__tests__/oauth-store.test.ts +10 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
- package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
- package/src/__tests__/openai-provider.test.ts +7 -0
- package/src/__tests__/openai-responses-provider.test.ts +396 -0
- package/src/__tests__/openrouter-provider-only.test.ts +135 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
- package/src/__tests__/permission-mode.test.ts +16 -0
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +13 -13
- package/src/__tests__/pkb-autoinject.test.ts +37 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +50 -3
- package/src/__tests__/profiler-routes.test.ts +1 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
- package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
- package/src/__tests__/provider-error-scenarios.test.ts +135 -6
- package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
- package/src/__tests__/provider-registry-ollama.test.ts +1 -2
- package/src/__tests__/proxy-approval-callback.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +560 -0
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
- package/src/__tests__/risk-classifier-parity.test.ts +230 -0
- package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
- package/src/__tests__/secret-ingress-http.test.ts +28 -0
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
- package/src/__tests__/secret-scanner-executor.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +29 -1
- package/src/__tests__/server-history-render.test.ts +31 -0
- package/src/__tests__/shell-parser-property.test.ts +13 -13
- package/src/__tests__/skill-cache-store.test.ts +182 -0
- package/src/__tests__/skills.test.ts +19 -33
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-skill.test.ts +3 -8
- package/src/__tests__/starter-bundle.test.ts +35 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
- package/src/__tests__/suggestion-routes.test.ts +160 -3
- package/src/__tests__/system-prompt.test.ts +22 -35
- package/src/__tests__/task-runner.test.ts +3 -1
- package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
- package/src/__tests__/thread-backfill.test.ts +941 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
- package/src/__tests__/tool-executor.test.ts +60 -94
- package/src/__tests__/trust-store.test.ts +442 -109
- package/src/__tests__/update-bulletin-job.test.ts +389 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
- package/src/__tests__/voice-session-bridge.test.ts +39 -0
- package/src/__tests__/volume-security-guard.test.ts +3 -2
- package/src/__tests__/web-search-history.test.ts +337 -0
- package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
- package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
- package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
- package/src/__tests__/workspace-policy.test.ts +1 -13
- package/src/acp/client-handler.ts +1 -2
- package/src/agent/loop.ts +209 -17
- package/src/avatar/resvg-lazy.test.ts +136 -0
- package/src/avatar/resvg-lazy.ts +82 -9
- package/src/avatar/traits-png-sync.ts +21 -1
- package/src/browser/__tests__/operations.test.ts +163 -0
- package/src/browser/identifiers.ts +51 -0
- package/src/browser/operations.ts +660 -0
- package/src/browser/types.ts +81 -0
- package/src/calls/guardian-question-copy.ts +2 -2
- package/src/calls/telephony-stt-routing.ts +1 -1
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/commands/__tests__/attachment.test.ts +438 -0
- package/src/cli/commands/__tests__/browser.test.ts +554 -0
- package/src/cli/commands/__tests__/cache.test.ts +623 -0
- package/src/cli/commands/__tests__/email-list.test.ts +6 -0
- package/src/cli/commands/__tests__/email-send.test.ts +93 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
- package/src/cli/commands/__tests__/task.test.ts +913 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
- package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
- package/src/cli/commands/__tests__/ui.test.ts +1215 -0
- package/src/cli/commands/__tests__/watchers.test.ts +716 -0
- package/src/cli/commands/attachment.ts +182 -0
- package/src/cli/commands/browser.ts +350 -0
- package/src/cli/commands/cache.ts +341 -0
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/config.ts +6 -6
- package/src/cli/commands/conversations-import.ts +347 -0
- package/src/cli/commands/conversations.ts +14 -1
- package/src/cli/commands/email.ts +234 -194
- package/src/cli/commands/image-generation.ts +300 -0
- package/src/cli/commands/inference.ts +200 -0
- package/src/cli/commands/memory.ts +127 -17
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
- package/src/cli/commands/stt.ts +339 -0
- package/src/cli/commands/task.ts +795 -0
- package/src/cli/commands/trust.ts +50 -19
- package/src/cli/commands/tts.ts +273 -0
- package/src/cli/commands/ui.ts +670 -0
- package/src/cli/commands/watchers.ts +509 -0
- package/src/cli/lib/daemon-credential-client.ts +0 -19
- package/src/cli/program.ts +23 -4
- package/src/cli.ts +0 -37
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +2 -2
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-tool-registry.ts +0 -175
- package/src/config/env.ts +7 -2
- package/src/config/feature-flag-registry.json +25 -9
- package/src/config/llm-resolver.ts +128 -0
- package/src/config/loader.ts +194 -10
- package/src/config/raw-config-utils.ts +30 -2
- package/src/config/sanitize-for-transfer.ts +35 -0
- package/src/config/schema.ts +30 -41
- package/src/config/schemas/analysis.ts +3 -22
- package/src/config/schemas/calls.ts +0 -4
- package/src/config/schemas/filing.ts +2 -7
- package/src/config/schemas/heartbeat.ts +0 -5
- package/src/config/schemas/inference.ts +3 -23
- package/src/config/schemas/llm.ts +318 -0
- package/src/config/schemas/memory-processing.ts +1 -9
- package/src/config/schemas/notifications.ts +4 -11
- package/src/config/schemas/platform.ts +3 -9
- package/src/config/schemas/security.ts +33 -0
- package/src/config/schemas/services.ts +9 -4
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/schemas/tts.ts +53 -0
- package/src/config/schemas/updates.ts +1 -1
- package/src/config/schemas/workspace-git.ts +3 -40
- package/src/config/skills.ts +2 -2
- package/src/context/__tests__/compact-prompt.test.ts +45 -0
- package/src/context/__tests__/microcompact.test.ts +805 -0
- package/src/context/estimator-calibration.ts +136 -0
- package/src/context/microcompact.ts +443 -0
- package/src/context/prompts/compact.md +12 -0
- package/src/context/token-estimator.ts +61 -3
- package/src/context/window-manager.ts +229 -25
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/executable-discovery.ts +19 -8
- package/src/credential-execution/process-manager.test.ts +109 -0
- package/src/credential-execution/process-manager.ts +65 -2
- package/src/daemon/approval-generators.ts +29 -4
- package/src/daemon/assistant-attachments.ts +24 -13
- package/src/daemon/classifier.ts +2 -2
- package/src/daemon/config-watcher.ts +0 -1
- package/src/daemon/context-overflow-reducer.ts +4 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
- package/src/daemon/conversation-agent-loop.ts +462 -80
- package/src/daemon/conversation-attachments.ts +2 -6
- package/src/daemon/conversation-error.ts +36 -1
- package/src/daemon/conversation-lifecycle.ts +30 -6
- package/src/daemon/conversation-messaging.ts +73 -4
- package/src/daemon/conversation-process.ts +10 -4
- package/src/daemon/conversation-queue-manager.ts +3 -0
- package/src/daemon/conversation-runtime-assembly.ts +760 -29
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +389 -1
- package/src/daemon/conversation-tool-setup.ts +10 -5
- package/src/daemon/conversation-usage.ts +1 -1
- package/src/daemon/conversation.ts +118 -30
- package/src/daemon/external-skills-bootstrap.ts +41 -0
- package/src/daemon/guardian-action-generators.ts +34 -14
- package/src/daemon/handlers/config-model.test.ts +86 -0
- package/src/daemon/handlers/config-model.ts +54 -12
- package/src/daemon/handlers/conversations.ts +9 -2
- package/src/daemon/handlers/shared.ts +39 -11
- package/src/daemon/handlers/skills.ts +2 -2
- package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
- package/src/daemon/lifecycle.ts +76 -14
- package/src/daemon/message-types/conversations.ts +14 -0
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/trust.ts +0 -2
- package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
- package/src/daemon/pkb-context-tracker.test.ts +169 -0
- package/src/daemon/pkb-context-tracker.ts +125 -0
- package/src/daemon/pkb-reminder-builder.test.ts +70 -0
- package/src/daemon/pkb-reminder-builder.ts +31 -0
- package/src/daemon/providers-setup.ts +6 -0
- package/src/daemon/server.ts +117 -9
- package/src/daemon/tool-side-effects.ts +0 -9
- package/src/daemon/watch-handler.ts +4 -4
- package/src/daemon/web-search-history.ts +126 -0
- package/src/events/domain-events.ts +0 -1
- package/src/filing/filing-service.ts +9 -10
- package/src/heartbeat/heartbeat-service.ts +76 -28
- package/src/home/__tests__/feed-scheduler.test.ts +39 -11
- package/src/home/__tests__/rollup-producer.test.ts +44 -0
- package/src/home/assistant-feed-authoring.ts +4 -0
- package/src/home/emit-feed-event.ts +4 -0
- package/src/home/feed-scheduler.ts +20 -4
- package/src/home/feed-types.ts +56 -2
- package/src/home/relationship-state-writer.ts +2 -2
- package/src/home/rollup-producer.ts +34 -5
- package/src/home/suggested-prompts.ts +101 -0
- package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
- package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
- package/src/ipc/__tests__/socket-path.test.ts +73 -0
- package/src/ipc/__tests__/task-ipc.test.ts +577 -0
- package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
- package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
- package/src/ipc/cli-client.ts +2 -1
- package/src/ipc/cli-server.ts +26 -8
- package/src/ipc/gateway-client.ts +4 -4
- package/src/ipc/routes/attachment.ts +114 -0
- package/src/ipc/routes/browser-context.ts +61 -0
- package/src/ipc/routes/browser.ts +96 -0
- package/src/ipc/routes/cache.ts +96 -0
- package/src/ipc/routes/index.ts +17 -1
- package/src/ipc/routes/task-queue.ts +226 -0
- package/src/ipc/routes/task.ts +173 -0
- package/src/ipc/routes/ui-request.ts +50 -0
- package/src/ipc/routes/watcher.ts +203 -0
- package/src/ipc/socket-path.ts +100 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
- package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
- package/src/memory/admin.ts +18 -0
- package/src/memory/conversation-analyze-job.ts +14 -13
- package/src/memory/conversation-attention-store.ts +13 -6
- package/src/memory/conversation-crud.ts +103 -3
- package/src/memory/conversation-group-migration.ts +38 -6
- package/src/memory/conversation-title-service.ts +7 -4
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-backend.ts +1 -1
- package/src/memory/graph/compaction.ts +299 -0
- package/src/memory/graph/consolidation.ts +4 -4
- package/src/memory/graph/conversation-graph-memory.ts +89 -29
- package/src/memory/graph/extraction.test.ts +272 -2
- package/src/memory/graph/extraction.ts +173 -51
- package/src/memory/graph/graph-search.test.ts +92 -0
- package/src/memory/graph/graph-search.ts +4 -1
- package/src/memory/graph/narrative.ts +2 -2
- package/src/memory/graph/pattern-scan.ts +2 -2
- package/src/memory/graph/retriever.test.ts +459 -0
- package/src/memory/graph/retriever.ts +230 -48
- package/src/memory/graph/store.ts +41 -0
- package/src/memory/graph/tool-handlers.ts +27 -0
- package/src/memory/graph/tools.ts +6 -1
- package/src/memory/indexer.ts +5 -5
- package/src/memory/job-handlers/conversation-starters.ts +23 -20
- package/src/memory/job-handlers/summarization.ts +2 -2
- package/src/memory/job-utils.ts +7 -1
- package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
- package/src/memory/jobs/embed-pkb-file.ts +54 -0
- package/src/memory/jobs-store.ts +44 -3
- package/src/memory/jobs-worker.ts +4 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-index.test.ts +368 -0
- package/src/memory/pkb/pkb-index.ts +255 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
- package/src/memory/pkb/pkb-reconcile.ts +148 -0
- package/src/memory/pkb/pkb-search.test.ts +438 -0
- package/src/memory/pkb/pkb-search.ts +137 -0
- package/src/memory/pkb/types.ts +53 -0
- package/src/memory/qdrant-client.ts +122 -1
- package/src/memory/slack-thread-store.ts +37 -0
- package/src/messaging/providers/gmail/adapter.ts +6 -16
- package/src/messaging/providers/gmail/client.ts +22 -0
- package/src/messaging/providers/gmail/types.ts +7 -0
- package/src/messaging/providers/slack/adapter.ts +14 -2
- package/src/messaging/providers/slack/backfill.test.ts +257 -0
- package/src/messaging/providers/slack/backfill.ts +101 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
- package/src/messaging/providers/slack/message-metadata.ts +123 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
- package/src/messaging/providers/slack/render-transcript.ts +443 -0
- package/src/messaging/style-analyzer.ts +5 -2
- package/src/notifications/README.md +9 -5
- package/src/notifications/decision-engine.ts +3 -9
- package/src/notifications/preference-extractor.ts +2 -6
- package/src/oauth/oauth-store.ts +1 -0
- package/src/oauth/platform-connection.test.ts +47 -0
- package/src/oauth/platform-connection.ts +15 -5
- package/src/oauth/seed-providers.ts +4 -2
- package/src/permissions/approval-policy.test.ts +948 -0
- package/src/permissions/approval-policy.ts +257 -0
- package/src/permissions/bash-risk-classifier.test.ts +1208 -0
- package/src/permissions/bash-risk-classifier.ts +707 -0
- package/src/permissions/checker.ts +217 -708
- package/src/permissions/command-registry.test.ts +535 -0
- package/src/permissions/command-registry.ts +825 -0
- package/src/permissions/defaults.ts +26 -78
- package/src/permissions/file-risk-classifier.test.ts +535 -0
- package/src/permissions/file-risk-classifier.ts +274 -0
- package/src/permissions/risk-types.ts +205 -0
- package/src/permissions/secret-prompter.ts +53 -2
- package/src/permissions/skill-risk-classifier.test.ts +311 -0
- package/src/permissions/skill-risk-classifier.ts +214 -0
- package/src/permissions/trust-client.ts +52 -25
- package/src/permissions/trust-store-interface.ts +1 -6
- package/src/permissions/trust-store.ts +161 -62
- package/src/permissions/types.ts +23 -14
- package/src/permissions/web-risk-classifier.test.ts +170 -0
- package/src/permissions/web-risk-classifier.ts +89 -0
- package/src/permissions/workspace-policy.ts +1 -16
- package/src/platform/client.ts +19 -1
- package/src/prompts/persona-resolver.ts +3 -3
- package/src/prompts/system-prompt.ts +19 -20
- package/src/prompts/templates/SOUL.md +2 -2
- package/src/prompts/update-bulletin-job.ts +190 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
- package/src/providers/__tests__/retry-callsite.test.ts +424 -0
- package/src/providers/anthropic/client.ts +183 -14
- package/src/providers/call-site-routing.ts +71 -0
- package/src/providers/gemini/client.ts +65 -2
- package/src/providers/managed-proxy/constants.ts +2 -1
- package/src/providers/model-catalog.ts +501 -33
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +57 -1
- package/src/providers/openai/responses-provider.ts +86 -9
- package/src/providers/openrouter/client.ts +76 -9
- package/src/providers/provider-env-vars.ts +56 -0
- package/src/providers/provider-send-message.ts +22 -5
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/registry.ts +19 -8
- package/src/providers/retry.ts +174 -39
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
- package/src/providers/speech-to-text/provider-catalog.ts +17 -0
- package/src/providers/speech-to-text/resolve.ts +7 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
- package/src/providers/speech-to-text/xai-realtime.ts +796 -0
- package/src/providers/speech-to-text/xai.test.ts +155 -0
- package/src/providers/speech-to-text/xai.ts +97 -0
- package/src/providers/types.ts +93 -3
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +43 -2
- package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
- package/src/runtime/agent-wake.ts +63 -22
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/btw-sidechain.ts +13 -3
- package/src/runtime/channel-reply-delivery.ts +106 -2
- package/src/runtime/decision-token.ts +116 -0
- package/src/runtime/gateway-client.ts +2 -2
- package/src/runtime/http-router.ts +32 -0
- package/src/runtime/http-server.ts +52 -1
- package/src/runtime/http-types.ts +23 -1
- package/src/runtime/interactive-ui.ts +362 -0
- package/src/runtime/invite-instruction-generator.ts +2 -2
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
- package/src/runtime/migrations/gcs-signed-url.ts +162 -0
- package/src/runtime/migrations/vbundle-importer.ts +154 -9
- package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
- package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
- package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
- package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
- package/src/runtime/migrations/vbundle-validator.ts +15 -6
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
- package/src/runtime/routes/approval-routes.ts +12 -17
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
- package/src/runtime/routes/avatar-routes.ts +20 -4
- package/src/runtime/routes/btw-routes.ts +1 -4
- package/src/runtime/routes/conversation-management-routes.ts +20 -2
- package/src/runtime/routes/conversation-routes.ts +133 -27
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +6 -4
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/guardian-approval-interception.ts +33 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
- package/src/runtime/routes/home-feed-routes.ts +120 -2
- package/src/runtime/routes/inbound-message-handler.ts +912 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
- package/src/runtime/routes/integrations/slack/channel.ts +25 -3
- package/src/runtime/routes/llm-context-normalization.ts +23 -1
- package/src/runtime/routes/migration-routes.ts +720 -124
- package/src/runtime/routes/settings-routes.ts +4 -2
- package/src/runtime/routes/trust-rules-routes.ts +30 -14
- package/src/runtime/routes/work-items-routes.test.ts +1 -1
- package/src/runtime/routes/work-items-routes.ts +3 -2
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
- package/src/runtime/services/analyze-conversation.ts +12 -16
- package/src/runtime/skill-route-registry.ts +28 -6
- package/src/schedule/scheduler.ts +8 -0
- package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
- package/src/security/__tests__/untrusted-content.test.ts +109 -0
- package/src/security/oauth2.ts +98 -35
- package/src/security/secure-keys.ts +7 -8
- package/src/security/token-manager.ts +27 -13
- package/src/security/untrusted-content.ts +102 -0
- package/src/skills/catalog-cache.ts +26 -7
- package/src/skills/catalog-install.ts +31 -3
- package/src/skills/skill-cache-store.ts +97 -0
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
- package/src/stt/daemon-batch-transcriber.ts +33 -0
- package/src/stt/stt-stream-session.ts +8 -1
- package/src/stt/types.ts +5 -1
- package/src/subagent/manager.ts +41 -13
- package/src/tasks/ephemeral-permissions.ts +9 -4
- package/src/telemetry/usage-telemetry-reporter.ts +27 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
- package/src/tools/browser/browser-execution.ts +65 -38
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
- package/src/tools/credentials/tool-policy.ts +39 -5
- package/src/tools/credentials/vault.ts +9 -4
- package/src/tools/executor.ts +4 -0
- package/src/tools/filesystem/write.ts +52 -0
- package/src/tools/host-terminal/host-shell.ts +45 -5
- package/src/tools/memory/register.test.ts +185 -0
- package/src/tools/memory/register.ts +3 -1
- package/src/tools/network/web-fetch.ts +20 -10
- package/src/tools/network/web-search.ts +19 -4
- package/src/tools/permission-checker.ts +36 -15
- package/src/tools/policy-context.ts +25 -8
- package/src/tools/registry.ts +55 -3
- package/src/tools/side-effects.ts +0 -11
- package/src/tools/skills/execute.ts +2 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/terminal/backends/native.ts +51 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +1 -0
- package/src/tools/tool-manifest.ts +6 -21
- package/src/tools/types.ts +12 -3
- package/src/tools/verification-control-plane-policy.ts +1 -1
- package/src/tts/__tests__/provider-adapters.test.ts +240 -13
- package/src/tts/provider-catalog.ts +18 -0
- package/src/tts/providers/index.ts +2 -0
- package/src/tts/providers/xai-provider.ts +224 -0
- package/src/tts/types.ts +46 -0
- package/src/types/tar-stream.d.ts +66 -0
- package/src/util/json.ts +17 -0
- package/src/util/platform.ts +2 -2
- package/src/util/pricing.ts +15 -5
- package/src/watcher/engine.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +134 -8
- package/src/watcher/providers/outlook-calendar.ts +42 -2
- package/src/workspace/git-service.ts +23 -4
- package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
- package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
- package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
- package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
- package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
- package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/provider-commit-message-generator.ts +19 -38
- package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
- package/src/__tests__/gmail-archive-gate.test.ts +0 -246
- package/src/__tests__/gmail-preferences.test.ts +0 -117
- package/src/__tests__/outlook-attachments.test.ts +0 -301
- package/src/__tests__/outlook-automation-tools.test.ts +0 -425
- package/src/__tests__/outlook-categories.test.ts +0 -212
- package/src/__tests__/outlook-compose-tools.test.ts +0 -325
- package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
- package/src/__tests__/outlook-follow-up.test.ts +0 -196
- package/src/__tests__/outlook-trash.test.ts +0 -77
- package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
- package/src/__tests__/update-bulletin-format.test.ts +0 -181
- package/src/__tests__/update-bulletin-state.test.ts +0 -135
- package/src/__tests__/update-bulletin.test.ts +0 -478
- package/src/__tests__/update-template-contract.test.ts +0 -29
- package/src/cli/commands/doctor.ts +0 -341
- package/src/config/bundled-skills/browser/SKILL.md +0 -88
- package/src/config/bundled-skills/browser/TOOLS.json +0 -516
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
- package/src/config/bundled-skills/gmail/SKILL.md +0 -221
- package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
- package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
- package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
- package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/google-calendar/types.ts +0 -97
- package/src/config/bundled-skills/outlook/SKILL.md +0 -196
- package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
- package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
- package/src/config/bundled-skills/slack/SKILL.md +0 -108
- package/src/config/bundled-skills/tasks/SKILL.md +0 -37
- package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
- package/src/config/bundled-skills/tasks/icon.svg +0 -34
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
- package/src/config/bundled-skills/watcher/SKILL.md +0 -31
- package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
- package/src/prompts/templates/UPDATES.md +0 -50
- package/src/prompts/update-bulletin-format.ts +0 -85
- package/src/prompts/update-bulletin-state.ts +0 -58
- package/src/prompts/update-bulletin-template-path.ts +0 -13
- package/src/prompts/update-bulletin.ts +0 -139
- package/src/shared/provider-env-vars.ts +0 -19
- package/src/tools/watcher/create.ts +0 -86
- package/src/tools/watcher/delete.ts +0 -36
- package/src/tools/watcher/digest.ts +0 -54
- package/src/tools/watcher/list.ts +0 -83
- package/src/tools/watcher/update.ts +0 -71
package/src/security/oauth2.ts
CHANGED
|
@@ -790,9 +790,22 @@ export async function startOAuth2Flow(
|
|
|
790
790
|
);
|
|
791
791
|
}
|
|
792
792
|
|
|
793
|
+
// Retry constants for transient failures during token refresh.
|
|
794
|
+
const REFRESH_MAX_RETRIES = 3;
|
|
795
|
+
const REFRESH_INITIAL_DELAY_MS = 500;
|
|
796
|
+
const REFRESH_MAX_DELAY_MS = 4_000;
|
|
797
|
+
|
|
798
|
+
function isRetryableRefreshError(status: number): boolean {
|
|
799
|
+
return status >= 500 || status === 429;
|
|
800
|
+
}
|
|
801
|
+
|
|
793
802
|
/**
|
|
794
803
|
* Refresh an OAuth2 access token using a refresh token.
|
|
795
804
|
* Supports both PKCE (no secret) and client_secret flows.
|
|
805
|
+
*
|
|
806
|
+
* Retries up to {@link REFRESH_MAX_RETRIES} times on transient failures
|
|
807
|
+
* (network errors, 5xx, 429) with exponential backoff + jitter. Credential
|
|
808
|
+
* errors (400 invalid_grant/invalid_client, 401, 403) fail immediately.
|
|
796
809
|
*/
|
|
797
810
|
export async function refreshOAuth2Token(
|
|
798
811
|
tokenExchangeUrl: string,
|
|
@@ -830,45 +843,95 @@ export async function refreshOAuth2Token(
|
|
|
830
843
|
}
|
|
831
844
|
}
|
|
832
845
|
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
headers,
|
|
836
|
-
body:
|
|
837
|
-
bodyFormat === "json" ? JSON.stringify(body) : new URLSearchParams(body),
|
|
838
|
-
});
|
|
846
|
+
const requestBody =
|
|
847
|
+
bodyFormat === "json" ? JSON.stringify(body) : new URLSearchParams(body);
|
|
839
848
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
849
|
+
let lastError: Error | undefined;
|
|
850
|
+
|
|
851
|
+
for (let attempt = 0; attempt <= REFRESH_MAX_RETRIES; attempt++) {
|
|
852
|
+
if (attempt > 0) {
|
|
853
|
+
const baseDelay = Math.min(
|
|
854
|
+
REFRESH_INITIAL_DELAY_MS * 2 ** (attempt - 1),
|
|
855
|
+
REFRESH_MAX_DELAY_MS,
|
|
856
|
+
);
|
|
857
|
+
const jitter = Math.random() * baseDelay * 0.5;
|
|
858
|
+
const delay = baseDelay + jitter;
|
|
859
|
+
log.info(
|
|
860
|
+
{ attempt, delayMs: Math.round(delay) },
|
|
861
|
+
"Retrying OAuth2 token refresh after transient failure",
|
|
862
|
+
);
|
|
863
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
let resp: Response;
|
|
844
867
|
try {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
868
|
+
resp = await fetch(tokenExchangeUrl, {
|
|
869
|
+
method: "POST",
|
|
870
|
+
headers,
|
|
871
|
+
body: requestBody,
|
|
872
|
+
});
|
|
873
|
+
} catch (err) {
|
|
874
|
+
// Network error (DNS, connection refused, timeout)
|
|
875
|
+
lastError =
|
|
876
|
+
err instanceof Error ? err : new Error(`Network error: ${String(err)}`);
|
|
877
|
+
log.warn(
|
|
878
|
+
{ err: lastError, attempt },
|
|
879
|
+
"OAuth2 token refresh network error",
|
|
880
|
+
);
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
if (!resp.ok) {
|
|
885
|
+
const rawBody = await resp.text().catch(() => "");
|
|
886
|
+
const safeDetail: Record<string, unknown> = {};
|
|
887
|
+
let errorCode = "";
|
|
888
|
+
try {
|
|
889
|
+
const parsed = JSON.parse(rawBody) as Record<string, unknown>;
|
|
890
|
+
if (parsed.error) {
|
|
891
|
+
safeDetail.error = String(parsed.error);
|
|
892
|
+
errorCode = String(parsed.error);
|
|
893
|
+
}
|
|
894
|
+
if (parsed.error_description)
|
|
895
|
+
safeDetail.error_description = String(parsed.error_description);
|
|
896
|
+
} catch {
|
|
897
|
+
safeDetail.error = "[non-JSON response]";
|
|
849
898
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
899
|
+
|
|
900
|
+
const detail = errorCode
|
|
901
|
+
? `HTTP ${resp.status}: ${errorCode}`
|
|
902
|
+
: `HTTP ${resp.status}`;
|
|
903
|
+
|
|
904
|
+
// Credential errors fail immediately — no retry will help.
|
|
905
|
+
if (!isRetryableRefreshError(resp.status)) {
|
|
906
|
+
log.error(
|
|
907
|
+
{ status: resp.status, ...safeDetail },
|
|
908
|
+
"OAuth2 token refresh failed",
|
|
909
|
+
);
|
|
910
|
+
throw new Error(`OAuth2 token refresh failed (${detail})`);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
lastError = new Error(`OAuth2 token refresh failed (${detail})`);
|
|
914
|
+
log.warn(
|
|
915
|
+
{ status: resp.status, attempt, ...safeDetail },
|
|
916
|
+
"OAuth2 token refresh transient failure",
|
|
917
|
+
);
|
|
918
|
+
continue;
|
|
854
919
|
}
|
|
855
|
-
log.error(
|
|
856
|
-
{ status: resp.status, ...safeDetail },
|
|
857
|
-
"OAuth2 token refresh failed",
|
|
858
|
-
);
|
|
859
|
-
const detail = errorCode
|
|
860
|
-
? `HTTP ${resp.status}: ${errorCode}`
|
|
861
|
-
: `HTTP ${resp.status}`;
|
|
862
|
-
throw new Error(`OAuth2 token refresh failed (${detail})`);
|
|
863
|
-
}
|
|
864
920
|
|
|
865
|
-
|
|
921
|
+
const data = (await resp.json()) as Record<string, unknown>;
|
|
866
922
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
923
|
+
return {
|
|
924
|
+
accessToken: data.access_token as string,
|
|
925
|
+
refreshToken: (data.refresh_token as string | undefined) ?? refreshToken,
|
|
926
|
+
expiresIn: data.expires_in as number | undefined,
|
|
927
|
+
scope: data.scope as string | undefined,
|
|
928
|
+
tokenType: data.token_type as string | undefined,
|
|
929
|
+
};
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
log.error(
|
|
933
|
+
{ attempts: REFRESH_MAX_RETRIES + 1 },
|
|
934
|
+
"OAuth2 token refresh failed after all retries",
|
|
935
|
+
);
|
|
936
|
+
throw lastError ?? new Error("OAuth2 token refresh failed after retries");
|
|
874
937
|
}
|
|
@@ -26,7 +26,7 @@ import type {
|
|
|
26
26
|
|
|
27
27
|
import { getIsContainerized } from "../config/env-registry.js";
|
|
28
28
|
import type { CesClient } from "../credential-execution/client.js";
|
|
29
|
-
import {
|
|
29
|
+
import { getAnyProviderEnvVar } from "../providers/provider-env-vars.js";
|
|
30
30
|
import { getLogger } from "../util/logger.js";
|
|
31
31
|
import { createCesCredentialBackend } from "./ces-credential-client.js";
|
|
32
32
|
import { CesRpcCredentialBackend } from "./ces-rpc-credential-backend.js";
|
|
@@ -510,16 +510,15 @@ export async function bulkSetSecureKeysAsync(
|
|
|
510
510
|
// Provider API key lookup — secure store + env var fallback
|
|
511
511
|
// ---------------------------------------------------------------------------
|
|
512
512
|
|
|
513
|
-
/**
|
|
514
|
-
* Env var names keyed by provider.
|
|
515
|
-
* Ollama is intentionally omitted — it doesn't require an API key.
|
|
516
|
-
*/
|
|
517
|
-
const PROVIDER_ENV_VARS: Record<string, string> = PROVIDER_ENV_VAR_NAMES;
|
|
518
|
-
|
|
519
513
|
/**
|
|
520
514
|
* Retrieve a provider API key, checking secure storage first and falling
|
|
521
515
|
* back to the corresponding `<PROVIDER>_API_KEY` environment variable.
|
|
522
516
|
*
|
|
517
|
+
* Env var names are resolved via `getAnyProviderEnvVar`, which covers both
|
|
518
|
+
* LLM providers (sourced from `PROVIDER_CATALOG`) and search providers
|
|
519
|
+
* (sourced from `SEARCH_PROVIDER_ENV_VAR_NAMES`). Keyless providers (e.g.
|
|
520
|
+
* Ollama) return `undefined` and fall through to a stored-only lookup.
|
|
521
|
+
*
|
|
523
522
|
* Use this instead of raw `getSecureKeyAsync` when looking up provider
|
|
524
523
|
* API keys so that env-var-only setups continue to work.
|
|
525
524
|
*/
|
|
@@ -528,7 +527,7 @@ export async function getProviderKeyAsync(
|
|
|
528
527
|
): Promise<string | undefined> {
|
|
529
528
|
const stored = await getSecureKeyAsync(provider);
|
|
530
529
|
if (stored) return stored;
|
|
531
|
-
const envVar =
|
|
530
|
+
const envVar = getAnyProviderEnvVar(provider);
|
|
532
531
|
return envVar ? process.env[envVar] : undefined;
|
|
533
532
|
}
|
|
534
533
|
|
|
@@ -232,7 +232,8 @@ async function doRefresh(service: string, connId: string): Promise<string> {
|
|
|
232
232
|
tokenExchangeBodyFormat,
|
|
233
233
|
);
|
|
234
234
|
} catch (err) {
|
|
235
|
-
|
|
235
|
+
const credential = isCredentialError(err);
|
|
236
|
+
circuitBreaker.recordFailure(connId, credential);
|
|
236
237
|
if (circuitBreaker.isOpen(connId)) {
|
|
237
238
|
const state = circuitBreaker.getState(connId)!;
|
|
238
239
|
log.warn(
|
|
@@ -244,7 +245,7 @@ async function doRefresh(service: string, connId: string): Promise<string> {
|
|
|
244
245
|
"Token refresh circuit breaker opened — skipping refresh attempts until cooldown expires",
|
|
245
246
|
);
|
|
246
247
|
}
|
|
247
|
-
if (
|
|
248
|
+
if (credential) {
|
|
248
249
|
const msg = err instanceof Error ? err.message : String(err);
|
|
249
250
|
throw new TokenExpiredError(
|
|
250
251
|
service,
|
|
@@ -269,17 +270,30 @@ async function doRefresh(service: string, connId: string): Promise<string> {
|
|
|
269
270
|
);
|
|
270
271
|
}
|
|
271
272
|
|
|
272
|
-
// Update oauth_connection row with new expiry.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
)
|
|
273
|
+
// Update oauth_connection row with new expiry. Retry once on failure
|
|
274
|
+
// to reduce the risk of stale expiresAt metadata in SQLite while the
|
|
275
|
+
// actual token in secure storage is valid.
|
|
276
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
277
|
+
try {
|
|
278
|
+
updateConnection(connId, {
|
|
279
|
+
expiresAt: persisted.expiresAt,
|
|
280
|
+
hasRefreshToken: persisted.hasRefreshToken,
|
|
281
|
+
});
|
|
282
|
+
break;
|
|
283
|
+
} catch (err) {
|
|
284
|
+
if (attempt === 0) {
|
|
285
|
+
log.warn(
|
|
286
|
+
{ err, service },
|
|
287
|
+
"Failed to update oauth_connection after refresh, retrying",
|
|
288
|
+
);
|
|
289
|
+
} else {
|
|
290
|
+
log.error(
|
|
291
|
+
{ err, service, expiresAt: persisted.expiresAt },
|
|
292
|
+
"Failed to update oauth_connection after refresh — token is valid " +
|
|
293
|
+
"in secure storage but SQLite expiry metadata is stale",
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
283
297
|
}
|
|
284
298
|
|
|
285
299
|
circuitBreaker.recordSuccess(connId);
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural defenses against prompt injection from external content.
|
|
3
|
+
*
|
|
4
|
+
* All external data (emails, messages, web pages, calendar events, etc.)
|
|
5
|
+
* should be wrapped via `wrapUntrustedContent()` before entering the LLM
|
|
6
|
+
* conversation context. The wrapper:
|
|
7
|
+
*
|
|
8
|
+
* 1. Delimits external content with `<external_content>` XML boundaries so
|
|
9
|
+
* the model can distinguish data from instructions.
|
|
10
|
+
* 2. Escapes boundary-breaking sequences within the content.
|
|
11
|
+
* 3. Enforces per-source character budgets to prevent context flooding.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Types
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export type UntrustedContentSource =
|
|
19
|
+
| "email"
|
|
20
|
+
| "slack"
|
|
21
|
+
| "web"
|
|
22
|
+
| "calendar"
|
|
23
|
+
| "webhook"
|
|
24
|
+
| "search"
|
|
25
|
+
| "tool_result";
|
|
26
|
+
|
|
27
|
+
export interface WrapOptions {
|
|
28
|
+
/** Which external source produced this content. */
|
|
29
|
+
source: UntrustedContentSource;
|
|
30
|
+
/** Origin identifier (sender email, URL, etc.). Sanitized before inclusion. */
|
|
31
|
+
sourceDetail?: string;
|
|
32
|
+
/** Override the default character budget for this source. */
|
|
33
|
+
maxChars?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Per-source character budgets
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
const DEFAULT_BUDGETS: Record<UntrustedContentSource, number> = {
|
|
41
|
+
email: 20_000,
|
|
42
|
+
slack: 10_000,
|
|
43
|
+
web: 40_000,
|
|
44
|
+
calendar: 5_000,
|
|
45
|
+
webhook: 10_000,
|
|
46
|
+
search: 15_000,
|
|
47
|
+
tool_result: 20_000,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// Core API
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Wrap external content in structural XML boundaries.
|
|
56
|
+
*
|
|
57
|
+
* The returned string is safe to include directly in an LLM message - the
|
|
58
|
+
* model will see it delimited as third-party data.
|
|
59
|
+
*/
|
|
60
|
+
export function wrapUntrustedContent(
|
|
61
|
+
content: string,
|
|
62
|
+
options: WrapOptions,
|
|
63
|
+
): string {
|
|
64
|
+
const budget = options.maxChars ?? DEFAULT_BUDGETS[options.source];
|
|
65
|
+
const escaped = escapeContentBoundaries(content);
|
|
66
|
+
const truncated = truncateWithNotice(escaped, budget);
|
|
67
|
+
const detail = options.sourceDetail
|
|
68
|
+
? ` origin="${sanitizeAttr(options.sourceDetail)}"`
|
|
69
|
+
: "";
|
|
70
|
+
return `<external_content source="${options.source}"${detail}>\n${truncated}\n</external_content>`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Escape sequences that could break out of the `<external_content>` wrapper.
|
|
75
|
+
* Case-insensitive to cover mixed-case evasion attempts.
|
|
76
|
+
*/
|
|
77
|
+
export function escapeContentBoundaries(content: string): string {
|
|
78
|
+
return content.replace(
|
|
79
|
+
/<\/external_content/gi,
|
|
80
|
+
(match) => `<${match.slice(1)}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Helpers
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
/** Sanitize a value for use as an XML attribute (no quotes, angle brackets, newlines). */
|
|
89
|
+
function sanitizeAttr(value: string): string {
|
|
90
|
+
return value.replace(/[<>"&\r\n]/g, "").slice(0, 200);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Truncate content to a character budget, appending a notice if truncated. */
|
|
94
|
+
function truncateWithNotice(content: string, maxChars: number): string {
|
|
95
|
+
if (content.length <= maxChars) {
|
|
96
|
+
return content;
|
|
97
|
+
}
|
|
98
|
+
return (
|
|
99
|
+
content.slice(0, maxChars) +
|
|
100
|
+
`\n[... truncated at ${maxChars.toLocaleString()} characters]`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -12,19 +12,38 @@ const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
12
12
|
let cachedCatalog: CatalogSkill[] | null = null;
|
|
13
13
|
let cacheTimestamp = 0;
|
|
14
14
|
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the Vellum catalog with in-memory caching.
|
|
17
|
+
*
|
|
18
|
+
* When a local first-party catalog is available (dev mode or compiled binary
|
|
19
|
+
* with bundled skills), merge it with the remote catalog so skills published
|
|
20
|
+
* after the build still show up. Local entries take precedence by id. If the
|
|
21
|
+
* remote fetch fails and a local catalog exists, fall back to it so listings
|
|
22
|
+
* keep working offline. Mirrors `resolveCatalog()` in `catalog-install.ts`.
|
|
23
|
+
*/
|
|
16
24
|
export async function getCatalog(): Promise<CatalogSkill[]> {
|
|
17
25
|
if (cachedCatalog && Date.now() - cacheTimestamp < CACHE_TTL_MS) {
|
|
18
26
|
return cachedCatalog;
|
|
19
27
|
}
|
|
20
28
|
const repoSkillsDir = getRepoSkillsDir();
|
|
29
|
+
const local = repoSkillsDir ? readLocalCatalog(repoSkillsDir) : [];
|
|
21
30
|
let catalog: CatalogSkill[];
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
catalog =
|
|
27
|
-
}
|
|
31
|
+
try {
|
|
32
|
+
const remote = await fetchCatalog();
|
|
33
|
+
if (local.length > 0) {
|
|
34
|
+
const localIds = new Set(local.map((s) => s.id));
|
|
35
|
+
catalog = [...local, ...remote.filter((s) => !localIds.has(s.id))];
|
|
36
|
+
} else {
|
|
37
|
+
catalog = remote;
|
|
38
|
+
}
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (local.length > 0) {
|
|
41
|
+
log.warn(
|
|
42
|
+
{ err },
|
|
43
|
+
"Failed to fetch Vellum catalog, falling back to bundled local catalog",
|
|
44
|
+
);
|
|
45
|
+
catalog = local;
|
|
46
|
+
} else {
|
|
28
47
|
log.warn(
|
|
29
48
|
{ err },
|
|
30
49
|
"Failed to fetch Vellum catalog, using stale cache or empty",
|
|
@@ -52,14 +52,42 @@ export function getSkillsIndexPath(): string {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
* Resolve the
|
|
56
|
-
*
|
|
55
|
+
* Resolve the directory containing a `catalog.json` and first-party skill
|
|
56
|
+
* sources — either bundled next to a compiled binary (e.g. `Vellum.app`) or
|
|
57
|
+
* in the dev repo.
|
|
58
|
+
*
|
|
59
|
+
* Both `getCatalog()` in `catalog-cache.ts` and `resolveCatalog()` below
|
|
60
|
+
* merge the local catalog with the remote one so skills published after a
|
|
61
|
+
* release still show up; the local catalog is used as an offline fallback
|
|
62
|
+
* when the remote fetch fails.
|
|
57
63
|
*/
|
|
58
64
|
export function getRepoSkillsDir(): string | undefined {
|
|
65
|
+
const importDir = import.meta.dir;
|
|
66
|
+
|
|
67
|
+
if (importDir.startsWith("/$bunfs/")) {
|
|
68
|
+
const execDir = dirname(process.execPath);
|
|
69
|
+
// macOS .app bundle: binary in Contents/MacOS/, resources in Contents/Resources/
|
|
70
|
+
const resourcesPath = join(
|
|
71
|
+
execDir,
|
|
72
|
+
"..",
|
|
73
|
+
"Resources",
|
|
74
|
+
"first-party-skills",
|
|
75
|
+
);
|
|
76
|
+
if (existsSync(join(resourcesPath, "catalog.json"))) {
|
|
77
|
+
return resourcesPath;
|
|
78
|
+
}
|
|
79
|
+
// Next to the binary (non-app-bundle compiled deployments)
|
|
80
|
+
const execDirPath = join(execDir, "first-party-skills");
|
|
81
|
+
if (existsSync(join(execDirPath, "catalog.json"))) {
|
|
82
|
+
return execDirPath;
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
59
87
|
if (!process.env.VELLUM_DEV) return undefined;
|
|
60
88
|
|
|
61
89
|
// assistant/src/skills/catalog-install.ts -> ../../../skills/
|
|
62
|
-
const candidate = join(
|
|
90
|
+
const candidate = join(importDir, "..", "..", "..", "skills");
|
|
63
91
|
if (existsSync(join(candidate, "catalog.json"))) {
|
|
64
92
|
return candidate;
|
|
65
93
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { randomBytes } from "crypto";
|
|
2
|
+
|
|
3
|
+
/** Default time-to-live for cache entries: 30 minutes. */
|
|
4
|
+
const DEFAULT_TTL_MS = 30 * 60_000;
|
|
5
|
+
|
|
6
|
+
/** Default maximum number of entries before LRU eviction kicks in. */
|
|
7
|
+
const DEFAULT_MAX_ENTRIES = 64;
|
|
8
|
+
|
|
9
|
+
interface CacheEntry {
|
|
10
|
+
data: unknown;
|
|
11
|
+
expiresAt: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Daemon-process singleton in-memory cache with TTL and LRU eviction.
|
|
16
|
+
*
|
|
17
|
+
* - Keys are auto-generated 16-char hex strings unless an explicit key is provided.
|
|
18
|
+
* - TTL defaults to 30 minutes; per-entry override via `ttlMs`.
|
|
19
|
+
* - Uses Map insertion order for LRU; `get` refreshes position.
|
|
20
|
+
* - At capacity, the oldest entry is evicted before a new insert.
|
|
21
|
+
* - Expired entries are lazily evicted on `get`.
|
|
22
|
+
*/
|
|
23
|
+
const _store = new Map<string, CacheEntry>();
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Store a value in the cache.
|
|
27
|
+
*
|
|
28
|
+
* If `options.key` is provided, the entry is upserted at that key
|
|
29
|
+
* (refreshing insertion order and resetting expiry).
|
|
30
|
+
* Otherwise a random 16-char hex key is generated.
|
|
31
|
+
*/
|
|
32
|
+
export function setCacheEntry(
|
|
33
|
+
data: unknown,
|
|
34
|
+
options?: { key?: string; ttlMs?: number },
|
|
35
|
+
): { key: string } {
|
|
36
|
+
const key = options?.key ?? randomBytes(8).toString("hex");
|
|
37
|
+
const ttl = options?.ttlMs ?? DEFAULT_TTL_MS;
|
|
38
|
+
|
|
39
|
+
// Upsert: delete first so the re-insert moves to the end of the Map
|
|
40
|
+
// (refreshes LRU position).
|
|
41
|
+
if (_store.has(key)) {
|
|
42
|
+
_store.delete(key);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// LRU eviction: if at capacity after removing a potential existing key,
|
|
46
|
+
// drop the oldest entry (first key in Map iteration order).
|
|
47
|
+
if (_store.size >= DEFAULT_MAX_ENTRIES) {
|
|
48
|
+
const oldest = _store.keys().next().value;
|
|
49
|
+
if (oldest !== undefined) _store.delete(oldest);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_store.set(key, { data, expiresAt: Date.now() + ttl });
|
|
53
|
+
return { key };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Retrieve a value from the cache.
|
|
58
|
+
*
|
|
59
|
+
* Returns `null` if the key does not exist or the entry has expired.
|
|
60
|
+
* On a hit, the entry is moved to the end of the Map (LRU refresh).
|
|
61
|
+
*/
|
|
62
|
+
export function getCacheEntry(key: string): { data: unknown } | null {
|
|
63
|
+
const entry = _store.get(key);
|
|
64
|
+
if (!entry) return null;
|
|
65
|
+
|
|
66
|
+
// Lazy TTL eviction.
|
|
67
|
+
if (Date.now() >= entry.expiresAt) {
|
|
68
|
+
_store.delete(key);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// LRU refresh: delete + re-set moves the entry to the tail.
|
|
73
|
+
_store.delete(key);
|
|
74
|
+
_store.set(key, entry);
|
|
75
|
+
|
|
76
|
+
return { data: entry.data };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Remove a cache entry by key. Returns `true` if an entry was deleted,
|
|
81
|
+
* `false` if the key was not present (idempotent).
|
|
82
|
+
*/
|
|
83
|
+
export function deleteCacheEntry(key: string): boolean {
|
|
84
|
+
return _store.delete(key);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Clear all entries — exposed for test isolation only. */
|
|
88
|
+
export function clearCacheForTests(): void {
|
|
89
|
+
_store.clear();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Visible-for-testing internals. */
|
|
93
|
+
export const _internals = {
|
|
94
|
+
store: _store,
|
|
95
|
+
DEFAULT_TTL_MS,
|
|
96
|
+
DEFAULT_MAX_ENTRIES,
|
|
97
|
+
};
|
|
@@ -45,6 +45,19 @@ mock.module("../../providers/speech-to-text/google-gemini.js", () => ({
|
|
|
45
45
|
},
|
|
46
46
|
}));
|
|
47
47
|
|
|
48
|
+
let mockXAITranscribeResult: { text: string } = { text: "" };
|
|
49
|
+
let mockXAITranscribeError: Error | null = null;
|
|
50
|
+
|
|
51
|
+
mock.module("../../providers/speech-to-text/xai.js", () => ({
|
|
52
|
+
XAIProvider: class MockXAIProvider {
|
|
53
|
+
constructor(_apiKey: string) {}
|
|
54
|
+
async transcribe(_audio: Buffer, _mimeType: string, _signal?: AbortSignal) {
|
|
55
|
+
if (mockXAITranscribeError) throw mockXAITranscribeError;
|
|
56
|
+
return mockXAITranscribeResult;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
60
|
+
|
|
48
61
|
// Dynamic import so mocks are active when the module loads.
|
|
49
62
|
const { createDaemonBatchTranscriber, normalizeSttError } =
|
|
50
63
|
await import("../daemon-batch-transcriber.js");
|
|
@@ -61,6 +74,8 @@ describe("createDaemonBatchTranscriber", () => {
|
|
|
61
74
|
mockDeepgramTranscribeError = null;
|
|
62
75
|
mockGeminiTranscribeResult = { text: "" };
|
|
63
76
|
mockGeminiTranscribeError = null;
|
|
77
|
+
mockXAITranscribeResult = { text: "" };
|
|
78
|
+
mockXAITranscribeError = null;
|
|
64
79
|
});
|
|
65
80
|
|
|
66
81
|
// -------------------------------------------------------------------------
|
|
@@ -305,6 +320,67 @@ describe("createDaemonBatchTranscriber", () => {
|
|
|
305
320
|
expect(createDaemonBatchTranscriber(null, "google-gemini")).toBeNull();
|
|
306
321
|
expect(createDaemonBatchTranscriber(undefined, "google-gemini")).toBeNull();
|
|
307
322
|
});
|
|
323
|
+
|
|
324
|
+
// -------------------------------------------------------------------------
|
|
325
|
+
// Provider identity — xAI
|
|
326
|
+
// -------------------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
test("reports providerId as xai when created with xai", () => {
|
|
329
|
+
const transcriber = createDaemonBatchTranscriber("xai-test-key", "xai");
|
|
330
|
+
expect(transcriber).not.toBeNull();
|
|
331
|
+
expect(transcriber!.providerId).toBe("xai");
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test("reports boundaryId as daemon-batch for xai", () => {
|
|
335
|
+
const transcriber = createDaemonBatchTranscriber("xai-test-key", "xai");
|
|
336
|
+
expect(transcriber!.boundaryId).toBe("daemon-batch");
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// -------------------------------------------------------------------------
|
|
340
|
+
// Successful transcription — xAI
|
|
341
|
+
// -------------------------------------------------------------------------
|
|
342
|
+
|
|
343
|
+
test("delegates transcription to the xAI provider", async () => {
|
|
344
|
+
mockXAITranscribeResult = { text: "Hello from xAI" };
|
|
345
|
+
|
|
346
|
+
const transcriber = createDaemonBatchTranscriber("xai-test-key", "xai");
|
|
347
|
+
const result = await transcriber!.transcribe({
|
|
348
|
+
audio: Buffer.from("fake-audio"),
|
|
349
|
+
mimeType: "audio/ogg",
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
expect(result).toEqual({ text: "Hello from xAI" });
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// -------------------------------------------------------------------------
|
|
356
|
+
// Error propagation — xAI
|
|
357
|
+
// -------------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
test("propagates xAI errors unchanged", async () => {
|
|
360
|
+
const original = new Error("xAI API error (401): Invalid credentials");
|
|
361
|
+
mockXAITranscribeError = original;
|
|
362
|
+
|
|
363
|
+
const transcriber = createDaemonBatchTranscriber("xai-test-key", "xai");
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
await transcriber!.transcribe({
|
|
367
|
+
audio: Buffer.from("audio"),
|
|
368
|
+
mimeType: "audio/wav",
|
|
369
|
+
});
|
|
370
|
+
expect.unreachable("should have thrown");
|
|
371
|
+
} catch (err) {
|
|
372
|
+
expect(err).toBe(original);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
// -------------------------------------------------------------------------
|
|
377
|
+
// Null on missing key — xAI
|
|
378
|
+
// -------------------------------------------------------------------------
|
|
379
|
+
|
|
380
|
+
test("returns null for xai when no API key is provided", () => {
|
|
381
|
+
expect(createDaemonBatchTranscriber(null, "xai")).toBeNull();
|
|
382
|
+
expect(createDaemonBatchTranscriber(undefined, "xai")).toBeNull();
|
|
383
|
+
});
|
|
308
384
|
});
|
|
309
385
|
|
|
310
386
|
// ---------------------------------------------------------------------------
|