@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
|
@@ -287,7 +287,7 @@ describe("Memory Recall Quality", () => {
|
|
|
287
287
|
test("preferences are recalled when querying about user preferences", async () => {
|
|
288
288
|
const db = getDb();
|
|
289
289
|
const now = 1_700_000_000_000;
|
|
290
|
-
insertConversation(db, "conv-pref", now);
|
|
290
|
+
insertConversation(db, "conv-pref", now, 3);
|
|
291
291
|
insertMessage(
|
|
292
292
|
db,
|
|
293
293
|
"msg-pref-1",
|
|
@@ -408,7 +408,7 @@ describe("Memory Recall Quality", () => {
|
|
|
408
408
|
test("high-importance preferences outrank low-importance facts in recall", async () => {
|
|
409
409
|
const db = getDb();
|
|
410
410
|
const now = 1_700_000_100_000;
|
|
411
|
-
insertConversation(db, "conv-rank", now);
|
|
411
|
+
insertConversation(db, "conv-rank", now, 2);
|
|
412
412
|
|
|
413
413
|
// High-importance preference
|
|
414
414
|
insertMessage(
|
|
@@ -692,7 +692,7 @@ describe("Memory Recall Quality", () => {
|
|
|
692
692
|
const db = getDb();
|
|
693
693
|
const now = Date.now();
|
|
694
694
|
const oneMonthAgo = now - 30 * 24 * 60 * 60 * 1000;
|
|
695
|
-
insertConversation(db, "conv-stale", now);
|
|
695
|
+
insertConversation(db, "conv-stale", now, 2);
|
|
696
696
|
|
|
697
697
|
// Recent mention
|
|
698
698
|
insertMessage(
|
|
@@ -774,7 +774,7 @@ describe("Memory Recall Quality", () => {
|
|
|
774
774
|
test("frequently accessed items surface via recency search when seeded with segments", async () => {
|
|
775
775
|
const db = getDb();
|
|
776
776
|
const now = 1_700_000_400_000;
|
|
777
|
-
insertConversation(db, "conv-access", now);
|
|
777
|
+
insertConversation(db, "conv-access", now, 2);
|
|
778
778
|
|
|
779
779
|
// Frequently accessed item with segment
|
|
780
780
|
insertMessage(
|
|
@@ -885,7 +885,7 @@ describe("Memory Recall Quality", () => {
|
|
|
885
885
|
test("recency search surfaces segments when hybrid search is unavailable", async () => {
|
|
886
886
|
const db = getDb();
|
|
887
887
|
const now = 1_700_000_500_000;
|
|
888
|
-
insertConversation(db, "conv-multi", now);
|
|
888
|
+
insertConversation(db, "conv-multi", now, 1);
|
|
889
889
|
|
|
890
890
|
// Segment (recency source)
|
|
891
891
|
insertMessage(
|
|
@@ -894,7 +894,10 @@ describe("partial failure scenarios", () => {
|
|
|
894
894
|
const blockerWorkspace = join(testDir, "blocker-workspace");
|
|
895
895
|
mkdirSync(blockerWorkspace, { recursive: true });
|
|
896
896
|
// Create "data" as a regular file — mkdirSync("data/db") will fail
|
|
897
|
-
writeFileSync(
|
|
897
|
+
writeFileSync(
|
|
898
|
+
join(blockerWorkspace, "data"),
|
|
899
|
+
"I am a file, not a directory",
|
|
900
|
+
);
|
|
898
901
|
|
|
899
902
|
const resolver = new DefaultPathResolver(undefined, blockerWorkspace);
|
|
900
903
|
const result = commitImport({
|
|
@@ -299,7 +299,9 @@ describe("export data population", () => {
|
|
|
299
299
|
const archiveData = new Uint8Array(await res.arrayBuffer());
|
|
300
300
|
const entries = parseTarEntries(archiveData);
|
|
301
301
|
|
|
302
|
-
const dbEntry = entries.find(
|
|
302
|
+
const dbEntry = entries.find(
|
|
303
|
+
(e) => e.name === "workspace/data/db/assistant.db",
|
|
304
|
+
);
|
|
303
305
|
expect(dbEntry).toBeDefined();
|
|
304
306
|
expect(dbEntry!.data.length).toBe(SQLITE_HEADER.length);
|
|
305
307
|
// Verify the exported data matches the test fixture exactly
|
|
@@ -671,7 +671,12 @@ describe("commitImport", () => {
|
|
|
671
671
|
test("creates parent directories if they do not exist", () => {
|
|
672
672
|
// Use a workspace that does not exist yet
|
|
673
673
|
const nonexistentWorkspace = join(testDir, "new-workspace");
|
|
674
|
-
const expectedDbPath = join(
|
|
674
|
+
const expectedDbPath = join(
|
|
675
|
+
nonexistentWorkspace,
|
|
676
|
+
"data",
|
|
677
|
+
"db",
|
|
678
|
+
"assistant.db",
|
|
679
|
+
);
|
|
675
680
|
|
|
676
681
|
const dbData = new Uint8Array([0x01, 0x02, 0x03]);
|
|
677
682
|
const vbundle = createValidVBundle([
|
|
@@ -836,7 +841,11 @@ describe("commitImport — workspace clearing", () => {
|
|
|
836
841
|
{ path: "hooks/new-hook/hook.sh", data: hookData },
|
|
837
842
|
]);
|
|
838
843
|
|
|
839
|
-
const resolver = new DefaultPathResolver(
|
|
844
|
+
const resolver = new DefaultPathResolver(
|
|
845
|
+
undefined,
|
|
846
|
+
testDir,
|
|
847
|
+
externalHooksDir,
|
|
848
|
+
);
|
|
840
849
|
const result = commitImport({
|
|
841
850
|
archiveData: vbundle,
|
|
842
851
|
pathResolver: resolver,
|
|
@@ -847,7 +856,9 @@ describe("commitImport — workspace clearing", () => {
|
|
|
847
856
|
if (!result.ok) return;
|
|
848
857
|
|
|
849
858
|
// Hook written to the external hooks dir, not workspace/hooks/
|
|
850
|
-
expect(existsSync(join(externalHooksDir, "new-hook", "hook.sh"))).toBe(
|
|
859
|
+
expect(existsSync(join(externalHooksDir, "new-hook", "hook.sh"))).toBe(
|
|
860
|
+
true,
|
|
861
|
+
);
|
|
851
862
|
expect(
|
|
852
863
|
readFileSync(join(externalHooksDir, "new-hook", "hook.sh"), "utf8"),
|
|
853
864
|
).toBe("#!/bin/sh\necho new");
|
|
@@ -864,7 +875,10 @@ describe("commitImport — workspace clearing", () => {
|
|
|
864
875
|
);
|
|
865
876
|
|
|
866
877
|
const vbundle = createValidVBundle([
|
|
867
|
-
{
|
|
878
|
+
{
|
|
879
|
+
path: "skills/new-skill/SKILL.md",
|
|
880
|
+
data: new TextEncoder().encode("new"),
|
|
881
|
+
},
|
|
868
882
|
]);
|
|
869
883
|
|
|
870
884
|
const resolver = new DefaultPathResolver(undefined, testDir);
|
|
@@ -743,9 +743,7 @@ describe("DefaultPathResolver", () => {
|
|
|
743
743
|
undefined,
|
|
744
744
|
"/home/user/.vellum/workspace",
|
|
745
745
|
);
|
|
746
|
-
expect(
|
|
747
|
-
resolver.resolve("skills/../../../.ssh/authorized_keys"),
|
|
748
|
-
).toBeNull();
|
|
746
|
+
expect(resolver.resolve("skills/../../../.ssh/authorized_keys")).toBeNull();
|
|
749
747
|
});
|
|
750
748
|
|
|
751
749
|
test("returns null for skills paths when workspaceDir is not provided", () => {
|
|
@@ -98,7 +98,9 @@ describe("buildMultipartMime", () => {
|
|
|
98
98
|
"base64",
|
|
99
99
|
).toString("utf-8");
|
|
100
100
|
|
|
101
|
-
expect(decoded).toContain(
|
|
101
|
+
expect(decoded).toContain(
|
|
102
|
+
"To: victim@example.com Bcc: attacker@example.com",
|
|
103
|
+
);
|
|
102
104
|
expect(decoded).toContain("Subject: Fwd: Hello Cc: attacker@example.com");
|
|
103
105
|
expect(decoded).toContain("Cc: team@example.com X-Injected: yes");
|
|
104
106
|
expect(decoded).toContain("Bcc: audit@example.com X-Another: value");
|
|
@@ -108,5 +110,4 @@ describe("buildMultipartMime", () => {
|
|
|
108
110
|
expect(decoded).not.toContain("\r\nBcc: attacker@example.com");
|
|
109
111
|
expect(decoded).not.toContain("\r\nCc: attacker@example.com");
|
|
110
112
|
});
|
|
111
|
-
|
|
112
113
|
});
|
|
@@ -624,6 +624,17 @@ describe("access-request-helper unit tests", () => {
|
|
|
624
624
|
});
|
|
625
625
|
|
|
626
626
|
test("notifyGuardianOfAccessRequest skips vellum fallback for same-channel-only routing (telegram)", async () => {
|
|
627
|
+
// Set up a telegram guardian binding with the anchor principal so
|
|
628
|
+
// guardianResolutionSource resolves to "source-channel-contact" and
|
|
629
|
+
// sameChannelOnly is true.
|
|
630
|
+
createGuardianBinding({
|
|
631
|
+
channel: "telegram",
|
|
632
|
+
guardianExternalUserId: "guardian-user-456",
|
|
633
|
+
guardianDeliveryChatId: "guardian-chat-456",
|
|
634
|
+
guardianPrincipalId: anchorPrincipalId,
|
|
635
|
+
verifiedVia: "test",
|
|
636
|
+
});
|
|
637
|
+
|
|
627
638
|
mockEmitResult = {
|
|
628
639
|
signalId: "sig-no-vellum",
|
|
629
640
|
deduplicated: false,
|
|
@@ -657,7 +668,7 @@ describe("access-request-helper unit tests", () => {
|
|
|
657
668
|
(d) => d.destinationChannel === "telegram",
|
|
658
669
|
);
|
|
659
670
|
|
|
660
|
-
//
|
|
671
|
+
// Guardian IS verified on telegram → sameChannelOnly, no vellum fallback
|
|
661
672
|
expect(vellum).toBeUndefined();
|
|
662
673
|
expect(telegram).toBeDefined();
|
|
663
674
|
expect(telegram!.destinationChatId).toBe("guardian-chat-456");
|
|
@@ -185,6 +185,58 @@ describe("identity context in notification decision engine", () => {
|
|
|
185
185
|
expect(capturedSystemPrompt).not.toContain("</assistant-identity>");
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
+
test("large identity context is truncated in system prompt", async () => {
|
|
189
|
+
// Create an identity context that exceeds the 2000-char budget
|
|
190
|
+
mockIdentityContext = "A".repeat(3000);
|
|
191
|
+
|
|
192
|
+
configuredProvider = {
|
|
193
|
+
sendMessage: async (
|
|
194
|
+
_messages: unknown,
|
|
195
|
+
_tools: unknown,
|
|
196
|
+
systemPrompt: unknown,
|
|
197
|
+
) => {
|
|
198
|
+
capturedSystemPrompt = systemPrompt as string;
|
|
199
|
+
return { content: [] };
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
extractedToolUse = {
|
|
203
|
+
name: "record_notification_decision",
|
|
204
|
+
input: {
|
|
205
|
+
shouldNotify: true,
|
|
206
|
+
selectedChannels: ["vellum"],
|
|
207
|
+
reasoningSummary: "LLM decision with truncated identity",
|
|
208
|
+
renderedCopy: {
|
|
209
|
+
vellum: {
|
|
210
|
+
title: "Guardian Question",
|
|
211
|
+
body: "What is the gate code?",
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
dedupeKey: "identity-truncated-test",
|
|
215
|
+
confidence: 0.9,
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const signal = makeSignal();
|
|
220
|
+
await evaluateSignal(signal, ["vellum"] as NotificationChannel[]);
|
|
221
|
+
|
|
222
|
+
expect(capturedSystemPrompt).toBeDefined();
|
|
223
|
+
expect(capturedSystemPrompt).toContain("<assistant-identity>");
|
|
224
|
+
// The identity block should exist but should NOT contain the full 3000-char string
|
|
225
|
+
expect(capturedSystemPrompt).not.toContain("A".repeat(3000));
|
|
226
|
+
// It should contain the truncation marker
|
|
227
|
+
expect(capturedSystemPrompt).toContain("…[truncated]");
|
|
228
|
+
// The identity content within the block should be at most 2000 chars
|
|
229
|
+
const identityMatch = capturedSystemPrompt!.match(
|
|
230
|
+
/<assistant-identity>([\s\S]*?)<\/assistant-identity>/,
|
|
231
|
+
);
|
|
232
|
+
expect(identityMatch).toBeTruthy();
|
|
233
|
+
// The identity block includes the instruction text + the truncated context.
|
|
234
|
+
// Verify the raw identity portion is bounded.
|
|
235
|
+
const identityBlock = identityMatch![1];
|
|
236
|
+
expect(identityBlock).toContain("…[truncated]");
|
|
237
|
+
expect(identityBlock).not.toContain("A".repeat(2001));
|
|
238
|
+
});
|
|
239
|
+
|
|
188
240
|
test("fallback path does not include identity context", async () => {
|
|
189
241
|
mockIdentityContext = "I am Jarvis, a helpful assistant";
|
|
190
242
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
const mockGetApp = mock((_appId: string) => ({
|
|
4
|
+
id: "app-1",
|
|
5
|
+
providerKey: "google",
|
|
6
|
+
clientId: "client-1",
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
const mockListConnections = mock(() => [
|
|
10
|
+
{
|
|
11
|
+
id: "conn-1",
|
|
12
|
+
providerKey: "google",
|
|
13
|
+
accountInfo: "{\"email\":\"alice@example.com\"}",
|
|
14
|
+
grantedScopes: '["email","profile"]',
|
|
15
|
+
status: "active",
|
|
16
|
+
hasRefreshToken: 1,
|
|
17
|
+
expiresAt: 1735689600000,
|
|
18
|
+
createdAt: 1735689500000,
|
|
19
|
+
updatedAt: 1735689550000,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "conn-2",
|
|
23
|
+
providerKey: "google",
|
|
24
|
+
accountInfo: null,
|
|
25
|
+
grantedScopes: [],
|
|
26
|
+
status: "active",
|
|
27
|
+
hasRefreshToken: 0,
|
|
28
|
+
expiresAt: null,
|
|
29
|
+
createdAt: 1735689601000,
|
|
30
|
+
updatedAt: 1735689602000,
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
mock.module("../oauth/oauth-store.js", () => ({
|
|
35
|
+
deleteApp: mock(() => Promise.resolve()),
|
|
36
|
+
disconnectOAuthProvider: mock(() => Promise.resolve()),
|
|
37
|
+
getApp: mockGetApp,
|
|
38
|
+
getAppClientSecret: mock(() => Promise.resolve(undefined)),
|
|
39
|
+
getConnection: mock(() => undefined),
|
|
40
|
+
getProvider: mock(() => undefined),
|
|
41
|
+
listApps: mock(() => []),
|
|
42
|
+
listConnections: mockListConnections,
|
|
43
|
+
upsertApp: mock(() =>
|
|
44
|
+
Promise.resolve({
|
|
45
|
+
id: "app-1",
|
|
46
|
+
providerKey: "google",
|
|
47
|
+
clientId: "client-1",
|
|
48
|
+
createdAt: 1735689500000,
|
|
49
|
+
updatedAt: 1735689550000,
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
mock.module("../oauth/connect-orchestrator.js", () => ({
|
|
55
|
+
orchestrateOAuthConnect: mock(() =>
|
|
56
|
+
Promise.resolve({
|
|
57
|
+
success: true,
|
|
58
|
+
deferred: false,
|
|
59
|
+
grantedScopes: [],
|
|
60
|
+
accountInfo: null,
|
|
61
|
+
refreshTokenPresent: false,
|
|
62
|
+
}),
|
|
63
|
+
),
|
|
64
|
+
}));
|
|
65
|
+
|
|
66
|
+
import { oauthAppsRouteDefinitions } from "../runtime/routes/oauth-apps.js";
|
|
67
|
+
|
|
68
|
+
const routes = oauthAppsRouteDefinitions();
|
|
69
|
+
|
|
70
|
+
function getRoute(method: string, endpoint: string) {
|
|
71
|
+
const route = routes.find(
|
|
72
|
+
(r) => r.method === method && r.endpoint === endpoint,
|
|
73
|
+
);
|
|
74
|
+
if (!route) throw new Error(`Route not found: ${method} ${endpoint}`);
|
|
75
|
+
return route;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
describe("GET /v1/oauth/apps/:appId/connections", () => {
|
|
79
|
+
test("normalizes granted_scopes and has_refresh_token", async () => {
|
|
80
|
+
const req = new Request("http://localhost/v1/oauth/apps/app-1/connections");
|
|
81
|
+
const url = new URL(req.url);
|
|
82
|
+
const res = await getRoute("GET", "oauth/apps/:appId/connections").handler({
|
|
83
|
+
req,
|
|
84
|
+
url,
|
|
85
|
+
server: null as never,
|
|
86
|
+
authContext: null as never,
|
|
87
|
+
params: { appId: "app-1" },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(res.status).toBe(200);
|
|
91
|
+
const body = (await res.json()) as {
|
|
92
|
+
connections: Array<{
|
|
93
|
+
granted_scopes: unknown;
|
|
94
|
+
has_refresh_token: unknown;
|
|
95
|
+
}>;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
expect(body.connections[0]?.granted_scopes).toEqual(["email", "profile"]);
|
|
99
|
+
expect(body.connections[0]?.has_refresh_token).toBe(true);
|
|
100
|
+
expect(body.connections[1]?.granted_scopes).toEqual([]);
|
|
101
|
+
expect(body.connections[1]?.has_refresh_token).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
deleteApp,
|
|
47
47
|
deleteConnection,
|
|
48
48
|
disconnectOAuthProvider,
|
|
49
|
+
getActiveConnection,
|
|
49
50
|
getApp,
|
|
50
51
|
getAppByProviderAndClientId,
|
|
51
52
|
getConnection,
|
|
@@ -631,6 +632,120 @@ describe("connection operations", () => {
|
|
|
631
632
|
});
|
|
632
633
|
});
|
|
633
634
|
|
|
635
|
+
describe("getActiveConnection", () => {
|
|
636
|
+
test("returns the most recent active connection with no filters", async () => {
|
|
637
|
+
const app = await createTestApp("github", "client-1");
|
|
638
|
+
|
|
639
|
+
createConnection({
|
|
640
|
+
oauthAppId: app.id,
|
|
641
|
+
providerKey: "github",
|
|
642
|
+
grantedScopes: ["repo"],
|
|
643
|
+
hasRefreshToken: false,
|
|
644
|
+
createdAt: 1000,
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
const conn2 = createConnection({
|
|
648
|
+
oauthAppId: app.id,
|
|
649
|
+
providerKey: "github",
|
|
650
|
+
grantedScopes: ["repo", "user"],
|
|
651
|
+
hasRefreshToken: true,
|
|
652
|
+
createdAt: 2000,
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
const result = getActiveConnection("github");
|
|
656
|
+
expect(result).toBeDefined();
|
|
657
|
+
expect(result!.id).toBe(conn2.id);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
test("narrows by account when provided", async () => {
|
|
661
|
+
const app = await createTestApp("github", "client-1");
|
|
662
|
+
|
|
663
|
+
const conn1 = createConnection({
|
|
664
|
+
oauthAppId: app.id,
|
|
665
|
+
providerKey: "github",
|
|
666
|
+
accountInfo: "user1@example.com",
|
|
667
|
+
grantedScopes: ["repo"],
|
|
668
|
+
hasRefreshToken: false,
|
|
669
|
+
createdAt: 1000,
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
createConnection({
|
|
673
|
+
oauthAppId: app.id,
|
|
674
|
+
providerKey: "github",
|
|
675
|
+
accountInfo: "user2@example.com",
|
|
676
|
+
grantedScopes: ["repo"],
|
|
677
|
+
hasRefreshToken: false,
|
|
678
|
+
createdAt: 2000,
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
const result = getActiveConnection("github", {
|
|
682
|
+
account: "user1@example.com",
|
|
683
|
+
});
|
|
684
|
+
expect(result).toBeDefined();
|
|
685
|
+
expect(result!.id).toBe(conn1.id);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
test("narrows by clientId when provided", async () => {
|
|
689
|
+
const app1 = await createTestApp("github", "client-a");
|
|
690
|
+
const app2 = await createTestApp("github", "client-b");
|
|
691
|
+
|
|
692
|
+
const conn1 = createConnection({
|
|
693
|
+
oauthAppId: app1.id,
|
|
694
|
+
providerKey: "github",
|
|
695
|
+
grantedScopes: ["repo"],
|
|
696
|
+
hasRefreshToken: false,
|
|
697
|
+
createdAt: 1000,
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
createConnection({
|
|
701
|
+
oauthAppId: app2.id,
|
|
702
|
+
providerKey: "github",
|
|
703
|
+
grantedScopes: ["repo"],
|
|
704
|
+
hasRefreshToken: false,
|
|
705
|
+
createdAt: 2000,
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
const result = getActiveConnection("github", { clientId: "client-a" });
|
|
709
|
+
expect(result).toBeDefined();
|
|
710
|
+
expect(result!.id).toBe(conn1.id);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
test("returns undefined when clientId has no matching app", async () => {
|
|
714
|
+
const app = await createTestApp("github", "client-1");
|
|
715
|
+
|
|
716
|
+
createConnection({
|
|
717
|
+
oauthAppId: app.id,
|
|
718
|
+
providerKey: "github",
|
|
719
|
+
grantedScopes: ["repo"],
|
|
720
|
+
hasRefreshToken: false,
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
const result = getActiveConnection("github", {
|
|
724
|
+
clientId: "nonexistent",
|
|
725
|
+
});
|
|
726
|
+
expect(result).toBeUndefined();
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
test("skips revoked connections", async () => {
|
|
730
|
+
const app = await createTestApp("github", "client-1");
|
|
731
|
+
|
|
732
|
+
const conn = createConnection({
|
|
733
|
+
oauthAppId: app.id,
|
|
734
|
+
providerKey: "github",
|
|
735
|
+
grantedScopes: ["repo"],
|
|
736
|
+
hasRefreshToken: false,
|
|
737
|
+
});
|
|
738
|
+
updateConnection(conn.id, { status: "revoked" });
|
|
739
|
+
|
|
740
|
+
const result = getActiveConnection("github");
|
|
741
|
+
expect(result).toBeUndefined();
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
test("returns undefined when no connections exist", () => {
|
|
745
|
+
expect(getActiveConnection("github")).toBeUndefined();
|
|
746
|
+
});
|
|
747
|
+
});
|
|
748
|
+
|
|
634
749
|
describe("getConnectionByProvider", () => {
|
|
635
750
|
test("returns the most recent active connection", async () => {
|
|
636
751
|
const app = await createTestApp("github", "client-1");
|
|
@@ -68,9 +68,7 @@ mock.module("../util/retry.js", () => {
|
|
|
68
68
|
const causeCode = (error.cause as NodeJS.ErrnoException).code;
|
|
69
69
|
if (causeCode && retryableCodes.has(causeCode)) return true;
|
|
70
70
|
}
|
|
71
|
-
if (
|
|
72
|
-
RETRYABLE_NETWORK_MESSAGE_PATTERNS.some((p) => p.test(error.message))
|
|
73
|
-
) {
|
|
71
|
+
if (RETRYABLE_NETWORK_MESSAGE_PATTERNS.some((p) => p.test(error.message))) {
|
|
74
72
|
return true;
|
|
75
73
|
}
|
|
76
74
|
const cause = error.cause;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
mock.module("../util/logger.js", () => ({
|
|
4
|
+
getLogger: () =>
|
|
5
|
+
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
import { FailoverProvider } from "../providers/failover.js";
|
|
9
|
+
import type {
|
|
10
|
+
Message,
|
|
11
|
+
Provider,
|
|
12
|
+
ProviderResponse,
|
|
13
|
+
} from "../providers/types.js";
|
|
14
|
+
import { ProviderError } from "../util/errors.js";
|
|
15
|
+
|
|
16
|
+
const MESSAGES: Message[] = [
|
|
17
|
+
{ role: "user", content: [{ type: "text", text: "Hello" }] },
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
function successResponse(
|
|
21
|
+
overrides?: Partial<ProviderResponse>,
|
|
22
|
+
): ProviderResponse {
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: "text", text: "ok" }],
|
|
25
|
+
model: "test-model",
|
|
26
|
+
usage: { inputTokens: 10, outputTokens: 5 },
|
|
27
|
+
stopReason: "end_turn",
|
|
28
|
+
...overrides,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
describe("FailoverProvider actual provider propagation", () => {
|
|
33
|
+
test("stamps the winning provider when failover uses a fallback", async () => {
|
|
34
|
+
const primary: Provider = {
|
|
35
|
+
name: "openrouter",
|
|
36
|
+
async sendMessage() {
|
|
37
|
+
throw new ProviderError("down", "openrouter", 500);
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
const secondary: Provider = {
|
|
41
|
+
name: "fireworks",
|
|
42
|
+
async sendMessage() {
|
|
43
|
+
return successResponse();
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const provider = new FailoverProvider([primary, secondary]);
|
|
48
|
+
const response = await provider.sendMessage(MESSAGES);
|
|
49
|
+
|
|
50
|
+
expect(response.actualProvider).toBe("fireworks");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("preserves an inner provider's actual provider when already set", async () => {
|
|
54
|
+
const inner: Provider = {
|
|
55
|
+
name: "retry-wrapper",
|
|
56
|
+
async sendMessage() {
|
|
57
|
+
return successResponse({ actualProvider: "anthropic" });
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const provider = new FailoverProvider([inner]);
|
|
62
|
+
const response = await provider.sendMessage(MESSAGES);
|
|
63
|
+
|
|
64
|
+
expect(response.actualProvider).toBe("anthropic");
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -81,6 +81,23 @@ const mockAttachments: Array<{
|
|
|
81
81
|
let mockAttachmentIdCounter = 0;
|
|
82
82
|
|
|
83
83
|
mock.module("../memory/attachments-store.js", () => ({
|
|
84
|
+
attachFileBackedAttachmentToMessage: (
|
|
85
|
+
_messageId: string,
|
|
86
|
+
_position: number,
|
|
87
|
+
filename: string,
|
|
88
|
+
mimeType: string,
|
|
89
|
+
_filePath: string,
|
|
90
|
+
sizeBytes: number,
|
|
91
|
+
) => {
|
|
92
|
+
const att = {
|
|
93
|
+
id: `att-${++mockAttachmentIdCounter}`,
|
|
94
|
+
originalFilename: filename,
|
|
95
|
+
mimeType,
|
|
96
|
+
sizeBytes,
|
|
97
|
+
};
|
|
98
|
+
mockAttachments.push(att);
|
|
99
|
+
return att;
|
|
100
|
+
},
|
|
84
101
|
uploadFileBackedAttachment: (
|
|
85
102
|
filename: string,
|
|
86
103
|
mimeType: string,
|
|
@@ -109,7 +109,7 @@ describe("tool registry dynamic-tools tools", () => {
|
|
|
109
109
|
|
|
110
110
|
describe("tool manifest", () => {
|
|
111
111
|
test("eager module tool names list contains expected count", () => {
|
|
112
|
-
expect(eagerModuleToolNames.length).toBe(
|
|
112
|
+
expect(eagerModuleToolNames.length).toBe(9);
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
test("explicit tools list includes memory and credential tools", () => {
|
|
@@ -187,14 +187,9 @@ describe("baseline characterization: core app tool surface", () => {
|
|
|
187
187
|
|
|
188
188
|
const nonProxyAppTools = [
|
|
189
189
|
"app_create",
|
|
190
|
-
"app_list",
|
|
191
|
-
"app_query",
|
|
192
|
-
"app_update",
|
|
193
190
|
"app_delete",
|
|
194
|
-
"
|
|
195
|
-
"
|
|
196
|
-
"app_file_edit",
|
|
197
|
-
"app_file_write",
|
|
191
|
+
"app_generate_icon",
|
|
192
|
+
"app_refresh",
|
|
198
193
|
];
|
|
199
194
|
|
|
200
195
|
for (const name of nonProxyAppTools) {
|
|
@@ -159,7 +159,7 @@ mock.module("../providers/registry.js", () => {
|
|
|
159
159
|
getDefaultModel: (providerName: string) => {
|
|
160
160
|
const defaults: Record<string, string> = {
|
|
161
161
|
anthropic: "claude-opus-4-6",
|
|
162
|
-
openai: "gpt-5.
|
|
162
|
+
openai: "gpt-5.4",
|
|
163
163
|
gemini: "gemini-3-flash",
|
|
164
164
|
ollama: "llama3.2",
|
|
165
165
|
fireworks: "accounts/fireworks/models/kimi-k2p5",
|
|
@@ -116,7 +116,7 @@ describe("Runtime attachment metadata", () => {
|
|
|
116
116
|
);
|
|
117
117
|
|
|
118
118
|
// Upload and link an attachment using "self" as the assistantId
|
|
119
|
-
const stored = uploadAttachment("chart.png", "image/png", "
|
|
119
|
+
const stored = uploadAttachment("chart.png", "image/png", "iVBORw==");
|
|
120
120
|
linkAttachmentToMessage(assistantMsg.id, stored.id, 0);
|
|
121
121
|
|
|
122
122
|
const res = await fetch(
|
|
@@ -181,7 +181,11 @@ describe("Runtime attachment metadata", () => {
|
|
|
181
181
|
});
|
|
182
182
|
|
|
183
183
|
test("GET /attachments/:id returns attachment with payload", async () => {
|
|
184
|
-
const stored = uploadAttachment(
|
|
184
|
+
const stored = uploadAttachment(
|
|
185
|
+
"report.pdf",
|
|
186
|
+
"application/pdf",
|
|
187
|
+
"JVBERA==",
|
|
188
|
+
);
|
|
185
189
|
|
|
186
190
|
const res = await fetch(
|
|
187
191
|
`http://127.0.0.1:${port}/v1/attachments/${stored.id}`,
|
|
@@ -201,7 +205,7 @@ describe("Runtime attachment metadata", () => {
|
|
|
201
205
|
expect(body.filename).toBe("report.pdf");
|
|
202
206
|
expect(body.mimeType).toBe("application/pdf");
|
|
203
207
|
expect(body.kind).toBe("document");
|
|
204
|
-
expect(body.data).toBe("
|
|
208
|
+
expect(body.data).toBe("JVBERA==");
|
|
205
209
|
expect(body.sizeBytes).toBeGreaterThan(0);
|
|
206
210
|
});
|
|
207
211
|
|