@vellumai/assistant 0.11.1 → 0.11.2-staging.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/Dockerfile +1 -3
- package/README.md +1 -1
- package/eslint.config.mjs +28 -8
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
- package/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
- package/node_modules/@vellumai/slack-text/src/index.test.ts +48 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +25 -6
- package/openapi.yaml +292 -5
- package/package.json +1 -1
- package/src/__tests__/agent-loop-resume-interrupted.test.ts +223 -0
- package/src/__tests__/byok-default-profile-ensure.test.ts +43 -0
- package/src/__tests__/cli-logger-boundary-guard.test.ts +87 -0
- package/src/__tests__/compaction-events.test.ts +139 -2
- package/src/__tests__/config-loader-backfill.test.ts +35 -3
- package/src/__tests__/config-schema.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +30 -0
- package/src/__tests__/conversation-agent-loop.test.ts +180 -7
- package/src/__tests__/conversation-error.test.ts +211 -4
- package/src/__tests__/conversation-options-turn-scoped-transport.test.ts +64 -0
- package/src/__tests__/conversation-queue.test.ts +126 -0
- package/src/__tests__/conversation-retry-route.test.ts +46 -7
- package/src/__tests__/conversation-slash.test.ts +4 -2
- package/src/__tests__/conversation-summarize-route.test.ts +7 -1
- package/src/__tests__/conversation-summarize-up-to.test.ts +71 -5
- package/src/__tests__/document-create-dedupe.test.ts +2 -1
- package/src/__tests__/document-find-replace.test.ts +2 -1
- package/src/__tests__/document-tool-security.test.ts +2 -1
- package/src/__tests__/document-update-default-surface.test.ts +2 -1
- package/src/__tests__/document-workspace-file.test.ts +467 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +290 -49
- package/src/__tests__/guardian-card-withdrawal.test.ts +91 -3
- package/src/__tests__/http-user-message-parity.test.ts +66 -0
- package/src/__tests__/list-messages-attachments.test.ts +87 -0
- package/src/__tests__/list-messages-provider-error.test.ts +143 -0
- package/src/__tests__/list-messages-system-card.test.ts +100 -0
- package/src/__tests__/llm-resolver.test.ts +19 -9
- package/src/__tests__/managed-profile-guard.test.ts +23 -0
- package/src/__tests__/notification-platform-adapter.test.ts +130 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +6 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +45 -0
- package/src/__tests__/plugin-api-model-profiles.test.ts +10 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
- package/src/__tests__/provider-error-scenarios.test.ts +140 -0
- package/src/__tests__/provider-send-message-override-profile.test.ts +95 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +66 -3
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +209 -0
- package/src/__tests__/skills.test.ts +27 -0
- package/src/__tests__/slack-channels-routes.test.ts +0 -2
- package/src/__tests__/slack-share-routes.test.ts +0 -3
- package/src/__tests__/slack-users-routes.test.ts +0 -2
- package/src/__tests__/subagent-tools.test.ts +11 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +58 -0
- package/src/__tests__/tool-result-spool.test.ts +5 -4
- package/src/__tests__/turn-boundary-resolution.test.ts +53 -0
- package/src/__tests__/turn-events-store.test.ts +26 -0
- package/src/__tests__/ui-visual-surface.test.ts +695 -0
- package/src/__tests__/unified-turn-context-visible-app.test.ts +99 -0
- package/src/__tests__/visible-app-context.test.ts +189 -0
- package/src/__tests__/workspace-git-service.test.ts +173 -33
- package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +157 -0
- package/src/__tests__/workspace-migration-138-backfill-home-feed-titles.test.ts +373 -0
- package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +137 -0
- package/src/agent/loop.ts +36 -7
- package/src/api/events/context-window-usage.ts +31 -0
- package/src/api/events/notification-intent.ts +8 -0
- package/src/api/events/ui-surface-pending.ts +35 -0
- package/src/api/index.ts +14 -0
- package/src/api/responses/conversation-message.ts +25 -4
- package/src/api/surfaces.ts +90 -1
- package/src/approvals/guardian-card-withdrawal.ts +66 -31
- package/src/approvals/guardian-decision-primitive.ts +4 -0
- package/src/calls/__tests__/call-setup-router.test.ts +156 -26
- package/src/calls/__tests__/voice-session-bridge.test.ts +201 -12
- package/src/calls/call-setup-router.ts +80 -36
- package/src/calls/voice-session-bridge.ts +173 -26
- package/src/cli/commands/inference.help.ts +3 -3
- package/src/cli/commands/notifications.help.ts +3 -2
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +42 -128
- package/src/cli/commands/platform/__tests__/credits.test.ts +9 -78
- package/src/cli/commands/platform/__tests__/helpers.ts +90 -0
- package/src/cli/commands/platform/__tests__/invoices.test.ts +238 -0
- package/src/cli/commands/platform/__tests__/plans.test.ts +9 -88
- package/src/cli/commands/platform/__tests__/status.test.ts +12 -87
- package/src/cli/commands/platform/__tests__/subscription.test.ts +9 -86
- package/src/cli/commands/platform/index.help.ts +92 -0
- package/src/cli/commands/platform/index.ts +7 -0
- package/src/cli/commands/platform/invoices.ts +132 -0
- package/src/cli/commands/usage.help.ts +1 -1
- package/src/cli/lib/list-installed-plugins.ts +2 -1
- package/src/config/__tests__/default-profile-catalog.test.ts +7 -10
- package/src/config/__tests__/deployment-context-defaults.test.ts +27 -5
- package/src/config/assistant-feature-flags.ts +7 -2
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -2
- package/src/config/bundled-skills/subagent/SKILL.md +3 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +3 -3
- package/src/config/bundled-skills/visualize/SKILL.md +163 -0
- package/src/config/call-site-defaults.ts +3 -2
- package/src/config/default-profile-catalog.ts +75 -70
- package/src/config/default-profile-names.ts +13 -28
- package/src/config/env-registry.ts +1 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/llm-resolver.ts +21 -5
- package/src/config/loader.ts +18 -11
- package/src/config/schemas/llm.ts +1 -9
- package/src/config/schemas/memory-retrospective.ts +9 -0
- package/src/config/schemas/monitoring.ts +28 -2
- package/src/config/schemas/workspace-git.ts +15 -0
- package/src/config/seed-inference-profiles.ts +19 -5
- package/src/context/post-turn-tool-result-truncation.ts +2 -2
- package/src/conversations/__tests__/message-consolidation.test.ts +54 -0
- package/src/conversations/message-consolidation.ts +17 -15
- package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +182 -0
- package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +181 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +90 -7
- package/src/daemon/conversation-agent-loop.ts +107 -22
- package/src/daemon/conversation-error.ts +169 -48
- package/src/daemon/conversation-messaging.ts +87 -4
- package/src/daemon/conversation-process.ts +57 -45
- package/src/daemon/conversation-runtime-assembly.ts +57 -0
- package/src/daemon/conversation-store.ts +36 -1
- package/src/daemon/conversation-surfaces.ts +68 -6
- package/src/daemon/conversation-turn-finalize.ts +76 -18
- package/src/daemon/conversation.ts +122 -27
- package/src/daemon/lifecycle.ts +9 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/surfaces.ts +3 -0
- package/src/documents/document-store.ts +247 -10
- package/src/home/__tests__/feed-types.test.ts +43 -8
- package/src/home/__tests__/feed-writer.test.ts +59 -0
- package/src/home/feed-types.ts +34 -7
- package/src/home/feed-writer.ts +15 -6
- package/src/live-voice/__tests__/activity-label.test.ts +95 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +86 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +310 -25
- package/src/live-voice/__tests__/live-voice-events.test.ts +4 -4
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +8 -4
- package/src/live-voice/__tests__/live-voice-vad.test.ts +9 -1
- package/src/live-voice/activity-label.ts +169 -0
- package/src/live-voice/live-activity-reporter.ts +35 -6
- package/src/live-voice/live-voice-session.ts +282 -41
- package/src/live-voice/protocol.ts +35 -0
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +84 -2
- package/src/messaging/providers/slack/adapter.ts +86 -19
- package/src/messaging/providers/slack/api.test.ts +85 -1
- package/src/messaging/providers/slack/api.ts +121 -288
- package/src/messaging/providers/slack/client.ts +20 -251
- package/src/messaging/providers/slack/send.test.ts +4 -9
- package/src/messaging/providers/slack/send.ts +1 -1
- package/src/messaging/providers/slack/types.ts +5 -0
- package/src/messaging/providers/slack/web-api-transport.test.ts +181 -0
- package/src/messaging/providers/slack/web-api-transport.ts +367 -0
- package/src/messaging/providers/slack/withdraw.ts +6 -14
- package/src/messaging/providers/telegram-bot/send.test.ts +31 -1
- package/src/messaging/providers/telegram-bot/send.ts +23 -2
- package/src/messaging/providers/telegram-bot/withdraw.test.ts +152 -0
- package/src/messaging/providers/telegram-bot/withdraw.ts +166 -0
- package/src/monitoring/__tests__/db-integrity-sample.test.ts +18 -4
- package/src/monitoring/__tests__/file-descriptors.test.ts +144 -0
- package/src/monitoring/file-descriptors.ts +262 -0
- package/src/monitoring/process-memory.ts +4 -11
- package/src/monitoring/resource-sampler.ts +5 -0
- package/src/monitoring/worker.ts +12 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +740 -0
- package/src/notifications/__tests__/broadcaster.test.ts +347 -7
- package/src/notifications/__tests__/copy-composer.test.ts +53 -3
- package/src/notifications/__tests__/decision-engine.test.ts +349 -29
- package/src/notifications/__tests__/deterministic-checks.test.ts +21 -0
- package/src/notifications/__tests__/edit-notification.test.ts +330 -0
- package/src/notifications/__tests__/guardian-delivery-recorder.test.ts +71 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +170 -12
- package/src/notifications/adapters/macos.ts +3 -0
- package/src/notifications/adapters/platform.ts +90 -14
- package/src/notifications/adapters/telegram.ts +6 -4
- package/src/notifications/assistant-reply-producer.ts +219 -0
- package/src/notifications/broadcaster.ts +523 -317
- package/src/notifications/copy-composer.ts +27 -6
- package/src/notifications/decision-engine.ts +154 -77
- package/src/notifications/deterministic-checks.ts +6 -7
- package/src/notifications/edit-notification.ts +8 -4
- package/src/notifications/emit-signal.ts +49 -16
- package/src/notifications/guardian-delivery-recorder.ts +8 -2
- package/src/notifications/home-feed-side-effect.ts +43 -6
- package/src/notifications/notification-utils.ts +39 -4
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +17 -0
- package/src/persistence/bookmark-crud.ts +18 -9
- package/src/persistence/conversation-attention-store.ts +26 -0
- package/src/persistence/conversation-crud.ts +79 -32
- package/src/persistence/conversation-queries.ts +30 -15
- package/src/persistence/conversation-title-service.ts +5 -170
- package/src/persistence/conversation-types.ts +192 -1
- package/src/persistence/db-init.ts +14 -1
- package/src/persistence/db-maintenance.ts +5 -4
- package/src/persistence/embeddings/__tests__/plugin-index-qdrant-init.test.ts +219 -0
- package/src/persistence/embeddings/__tests__/plugin-index.test.ts +22 -0
- package/src/persistence/embeddings/__tests__/worker-script-version.test.ts +76 -0
- package/src/persistence/embeddings/embedding-local.ts +2 -1
- package/src/persistence/embeddings/embedding-runtime-manager.ts +29 -5
- package/src/persistence/embeddings/plugin-index.ts +84 -12
- package/src/persistence/migrations/360-add-document-workspace-path.test.ts +110 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +39 -0
- package/src/persistence/planner-statistics.ts +14 -0
- package/src/persistence/schema/documents.ts +26 -11
- package/src/persistence/steps.ts +2 -0
- package/src/platform/client.test.ts +172 -2
- package/src/platform/client.ts +178 -55
- package/src/plugin-api/conversation-turn.ts +13 -0
- package/src/plugin-api/model-profiles.test.ts +6 -2
- package/src/plugins/defaults/memory/__tests__/bookmark-crud.test.ts +34 -0
- package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +22 -0
- package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +11 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +199 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +98 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +105 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +24 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +524 -0
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1 -1
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +9 -4
- package/src/plugins/defaults/memory/host-utils.ts +5 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +105 -1
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +64 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +36 -1
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +12 -3
- package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +1 -0
- package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +195 -1
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +1 -1
- package/src/plugins/defaults/memory/substrate/skill-store.ts +167 -29
- package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +131 -0
- package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +1 -0
- package/src/plugins/defaults/memory/v2/activation-log-store.ts +4 -0
- package/src/plugins/defaults/memory/v2/injection.ts +48 -3
- package/src/plugins/defaults/memory/v2/rerank-local.ts +6 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -1
- package/src/plugins/defaults/platform-hosted/routes/reengage.ts +1 -1
- package/src/plugins/defaults/turn-context/injectors.ts +1 -0
- package/src/plugins/defaults/turn-context/unified-turn-context.ts +26 -0
- package/src/plugins/types.ts +17 -0
- package/src/prompts/templates/system-sections.ts +2 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +43 -2
- package/src/providers/__tests__/vellum-mismatch-routing.test.ts +8 -0
- package/src/providers/call-site-routing.ts +77 -14
- package/src/providers/connection-resolution.ts +42 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +127 -1
- package/src/providers/inference/adapter-factory.ts +70 -12
- package/src/providers/model-catalog.ts +0 -11
- package/src/providers/model-intents.ts +11 -0
- package/src/providers/registry.ts +8 -0
- package/src/providers/retry.ts +85 -17
- package/src/providers/types.ts +8 -1
- package/src/runtime/__tests__/agent-wake.test.ts +181 -0
- package/src/runtime/agent-wake.ts +132 -9
- package/src/runtime/channel-approval-types.ts +25 -0
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +2 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +11 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +121 -9
- package/src/runtime/routes/__tests__/platform-invoice-routes.test.ts +392 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +7 -0
- package/src/runtime/routes/__tests__/workspace-commit-routes.test.ts +63 -0
- package/src/runtime/routes/consolidation-routes.ts +1 -1
- package/src/runtime/routes/conversation-management-routes.ts +18 -6
- package/src/runtime/routes/conversation-query-routes.ts +14 -23
- package/src/runtime/routes/conversation-routes.ts +113 -8
- package/src/runtime/routes/credential-routes.ts +1 -5
- package/src/runtime/routes/documents-routes.ts +207 -12
- package/src/runtime/routes/identity-routes.ts +3 -93
- package/src/runtime/routes/inbound-stages/admission-policy.test.ts +31 -1
- package/src/runtime/routes/inbound-stages/admission-policy.ts +18 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +52 -3
- package/src/runtime/routes/notification-routes.ts +4 -1
- package/src/runtime/routes/platform-routes.ts +238 -3
- package/src/runtime/routes/playground/guard.ts +1 -2
- package/src/runtime/routes/slack-channel-routes.ts +2 -4
- package/src/runtime/routes/workspace-commit-routes.ts +49 -2
- package/src/runtime/routes/workspace-routes.ts +11 -12
- package/src/runtime/routes/workspace-utils.ts +49 -2
- package/src/runtime/services/conversation-serializer.ts +2 -4
- package/src/subagent/__tests__/consult-context-gating.test.ts +98 -0
- package/src/subagent/__tests__/consult-context-skills.test.ts +82 -0
- package/src/subagent/__tests__/consult-context.test.ts +84 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +36 -0
- package/src/subagent/consult-context.ts +410 -0
- package/src/subagent/consult-prompt.ts +38 -5
- package/src/subagent/manager.ts +9 -4
- package/src/subagent/types.ts +8 -0
- package/src/telemetry/telemetry-event-sources.ts +7 -0
- package/src/telemetry/telemetry-wire-source.json +1 -1
- package/src/telemetry/telemetry-wire.generated.ts +1 -0
- package/src/telemetry/turn-events-store.ts +30 -1
- package/src/telemetry/types.ts +24 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
- package/src/tools/browser/pinned-tabs.ts +3 -1
- package/src/tools/subagent/spawn.ts +23 -1
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/ui-surface/definitions.ts +39 -1
- package/src/tools/ui-surface/surface-shape-docs.ts +38 -4
- package/src/tools/ui-surface/visual-validation.ts +787 -0
- package/src/tools/workflows/run-workflow.test.ts +1 -0
- package/src/util/__tests__/short-title.test.ts +229 -0
- package/src/util/__tests__/worker-compute.test.ts +65 -0
- package/src/util/cgroup-cpu.ts +93 -0
- package/src/util/errors.ts +27 -0
- package/src/util/process-tree.ts +19 -0
- package/src/util/short-title.ts +189 -0
- package/src/util/worker-compute.ts +83 -0
- package/src/workspace/byok-default-profile-ensure.ts +25 -15
- package/src/workspace/git-service.ts +79 -30
- package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +131 -0
- package/src/workspace/migrations/138-backfill-home-feed-titles.ts +179 -0
- package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +92 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/plugins/defaults/memory/substrate/constants.ts +0 -8
|
@@ -11,13 +11,17 @@ let mockManagedProxyCtx = {
|
|
|
11
11
|
};
|
|
12
12
|
let mockAssistantId = "";
|
|
13
13
|
let mockSecureKeys: Record<string, string | null | undefined> = {};
|
|
14
|
+
// Re-pointable so the probe-deadline tests can hang context resolution.
|
|
15
|
+
let mockResolveManagedProxyContext: () => Promise<
|
|
16
|
+
typeof mockManagedProxyCtx
|
|
17
|
+
> = async () => mockManagedProxyCtx;
|
|
14
18
|
|
|
15
19
|
// ---------------------------------------------------------------------------
|
|
16
20
|
// Module mocks
|
|
17
21
|
// ---------------------------------------------------------------------------
|
|
18
22
|
|
|
19
23
|
mock.module("../providers/platform-proxy/context.js", () => ({
|
|
20
|
-
resolveManagedProxyContext:
|
|
24
|
+
resolveManagedProxyContext: () => mockResolveManagedProxyContext(),
|
|
21
25
|
}));
|
|
22
26
|
|
|
23
27
|
mock.module("../config/env.js", () => ({
|
|
@@ -38,7 +42,11 @@ mock.module("../security/credential-key.js", () => ({
|
|
|
38
42
|
// Import under test (after mocks)
|
|
39
43
|
// ---------------------------------------------------------------------------
|
|
40
44
|
|
|
41
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
_resetConfiguredProbeCacheForTests,
|
|
47
|
+
isPlatformClientConfigured,
|
|
48
|
+
VellumPlatformClient,
|
|
49
|
+
} from "./client.js";
|
|
42
50
|
|
|
43
51
|
// ---------------------------------------------------------------------------
|
|
44
52
|
// Tests
|
|
@@ -56,6 +64,8 @@ describe("VellumPlatformClient", () => {
|
|
|
56
64
|
};
|
|
57
65
|
mockAssistantId = "asst-123";
|
|
58
66
|
mockSecureKeys = {};
|
|
67
|
+
mockResolveManagedProxyContext = async () => mockManagedProxyCtx;
|
|
68
|
+
_resetConfiguredProbeCacheForTests();
|
|
59
69
|
});
|
|
60
70
|
|
|
61
71
|
afterEach(() => {
|
|
@@ -119,6 +129,166 @@ describe("VellumPlatformClient", () => {
|
|
|
119
129
|
});
|
|
120
130
|
});
|
|
121
131
|
|
|
132
|
+
describe("isPlatformClientConfigured()", () => {
|
|
133
|
+
test("true when base url, api key, and assistant id are all present", async () => {
|
|
134
|
+
expect(await isPlatformClientConfigured()).toBe(true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("false when the platform assistant id is missing", async () => {
|
|
138
|
+
mockAssistantId = "";
|
|
139
|
+
|
|
140
|
+
expect(await isPlatformClientConfigured()).toBe(false);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("false when auth prerequisites are missing", async () => {
|
|
144
|
+
mockManagedProxyCtx = {
|
|
145
|
+
enabled: false,
|
|
146
|
+
platformBaseUrl: "",
|
|
147
|
+
assistantApiKey: "",
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
expect(await isPlatformClientConfigured()).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("hung resolution answers false at the deadline, then serves the late result from cache", async () => {
|
|
154
|
+
let releaseHang = () => {};
|
|
155
|
+
const hang = new Promise<void>((resolve) => {
|
|
156
|
+
releaseHang = resolve;
|
|
157
|
+
});
|
|
158
|
+
mockResolveManagedProxyContext = async () => {
|
|
159
|
+
await hang;
|
|
160
|
+
return mockManagedProxyCtx;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Resolution is hung and no result has ever settled -- the deadline
|
|
164
|
+
// must answer false instead of stalling the caller.
|
|
165
|
+
expect(await isPlatformClientConfigured()).toBe(false);
|
|
166
|
+
|
|
167
|
+
// Release the abandoned resolution; it settles true in the background
|
|
168
|
+
// and refreshes the cache.
|
|
169
|
+
releaseHang();
|
|
170
|
+
await Bun.sleep(10);
|
|
171
|
+
|
|
172
|
+
// Resolution hangs forever this time, but the cache answers true.
|
|
173
|
+
mockResolveManagedProxyContext = () => new Promise(() => {});
|
|
174
|
+
expect(await isPlatformClientConfigured()).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("concurrent calls share a single in-flight resolution", async () => {
|
|
178
|
+
let resolutionCount = 0;
|
|
179
|
+
mockResolveManagedProxyContext = async () => {
|
|
180
|
+
resolutionCount += 1;
|
|
181
|
+
await Bun.sleep(5);
|
|
182
|
+
return mockManagedProxyCtx;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const [first, second] = await Promise.all([
|
|
186
|
+
isPlatformClientConfigured(),
|
|
187
|
+
isPlatformClientConfigured(),
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
expect(first).toBe(true);
|
|
191
|
+
expect(second).toBe(true);
|
|
192
|
+
expect(resolutionCount).toBe(1);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("a settled flight clears, so the next call starts a fresh resolution", async () => {
|
|
196
|
+
let resolutionCount = 0;
|
|
197
|
+
mockResolveManagedProxyContext = async () => {
|
|
198
|
+
resolutionCount += 1;
|
|
199
|
+
return mockManagedProxyCtx;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
expect(await isPlatformClientConfigured()).toBe(true);
|
|
203
|
+
expect(await isPlatformClientConfigured()).toBe(true);
|
|
204
|
+
expect(resolutionCount).toBe(2);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test("a caller that finds an abandoned flight rotates in a fresh resolution", async () => {
|
|
208
|
+
let resolutionCount = 0;
|
|
209
|
+
// The first resolution hangs forever; only the rotated-in second one
|
|
210
|
+
// settles.
|
|
211
|
+
const pendingHangs = [new Promise<void>(() => {})];
|
|
212
|
+
mockResolveManagedProxyContext = async () => {
|
|
213
|
+
resolutionCount += 1;
|
|
214
|
+
const hang = pendingHangs.shift();
|
|
215
|
+
if (hang) {
|
|
216
|
+
await hang;
|
|
217
|
+
}
|
|
218
|
+
return mockManagedProxyCtx;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// The first caller abandons the hung flight at the deadline.
|
|
222
|
+
expect(await isPlatformClientConfigured()).toBe(false);
|
|
223
|
+
|
|
224
|
+
// The hung flight is marked abandoned, so the next caller rotates in a
|
|
225
|
+
// fresh resolution instead of joining it; the fresh flight settles
|
|
226
|
+
// immediately and answers live.
|
|
227
|
+
expect(await isPlatformClientConfigured()).toBe(true);
|
|
228
|
+
expect(resolutionCount).toBe(2);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("a later caller joins a flight nobody has abandoned", async () => {
|
|
232
|
+
let resolutionCount = 0;
|
|
233
|
+
let release = () => {};
|
|
234
|
+
// Gating the resolution keeps it in flight until the second caller has
|
|
235
|
+
// joined, so the assertion does not race a settle.
|
|
236
|
+
const gate = new Promise<void>((resolve) => {
|
|
237
|
+
release = resolve;
|
|
238
|
+
});
|
|
239
|
+
mockResolveManagedProxyContext = async () => {
|
|
240
|
+
resolutionCount += 1;
|
|
241
|
+
await gate;
|
|
242
|
+
return mockManagedProxyCtx;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const first = isPlatformClientConfigured();
|
|
246
|
+
await Bun.sleep(1);
|
|
247
|
+
const second = isPlatformClientConfigured();
|
|
248
|
+
release();
|
|
249
|
+
|
|
250
|
+
expect(await first).toBe(true);
|
|
251
|
+
expect(await second).toBe(true);
|
|
252
|
+
expect(resolutionCount).toBe(1);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("a rotated-out flight's late settle cannot overwrite the newer flight's cached result", async () => {
|
|
256
|
+
let releaseFirstHang = () => {};
|
|
257
|
+
const firstHang = new Promise<void>((resolve) => {
|
|
258
|
+
releaseFirstHang = resolve;
|
|
259
|
+
});
|
|
260
|
+
const pendingHangs = [firstHang];
|
|
261
|
+
mockResolveManagedProxyContext = async () => {
|
|
262
|
+
const hang = pendingHangs.shift();
|
|
263
|
+
if (hang) {
|
|
264
|
+
await hang;
|
|
265
|
+
}
|
|
266
|
+
return mockManagedProxyCtx;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// First caller abandons the hung flight; second caller rotates in a
|
|
270
|
+
// fresh flight that settles true and writes the cache.
|
|
271
|
+
expect(await isPlatformClientConfigured()).toBe(false);
|
|
272
|
+
expect(await isPlatformClientConfigured()).toBe(true);
|
|
273
|
+
|
|
274
|
+
// The rotated-out flight now settles resolving an unconfigured
|
|
275
|
+
// context. Generation fencing must keep it from clobbering the newer
|
|
276
|
+
// flight's cached `true`.
|
|
277
|
+
mockManagedProxyCtx = {
|
|
278
|
+
enabled: false,
|
|
279
|
+
platformBaseUrl: "",
|
|
280
|
+
assistantApiKey: "",
|
|
281
|
+
};
|
|
282
|
+
releaseFirstHang();
|
|
283
|
+
await Bun.sleep(10);
|
|
284
|
+
|
|
285
|
+
// Resolution hangs forever, so this answer can only come from the
|
|
286
|
+
// cache, which must still hold the newer flight's `true`.
|
|
287
|
+
mockResolveManagedProxyContext = () => new Promise(() => {});
|
|
288
|
+
expect(await isPlatformClientConfigured()).toBe(true);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
122
292
|
describe("fetch()", () => {
|
|
123
293
|
test("prepends base URL to path", async () => {
|
|
124
294
|
globalThis.fetch = mock(async (url: string | URL | Request) => {
|
package/src/platform/client.ts
CHANGED
|
@@ -34,6 +34,174 @@ export interface OwnerConsent {
|
|
|
34
34
|
shareDiagnosticsAcceptedVersion: string;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
interface PlatformClientConfig {
|
|
38
|
+
baseUrl: string;
|
|
39
|
+
apiKey: string;
|
|
40
|
+
assistantId: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the platform client's prerequisites.
|
|
45
|
+
*
|
|
46
|
+
* First tries the in-memory managed proxy context (available when the daemon
|
|
47
|
+
* has rehydrated env overrides). Falls back to reading platform credentials
|
|
48
|
+
* directly from the credential store so that standalone CLI invocations work
|
|
49
|
+
* without the daemon having run its rehydration step.
|
|
50
|
+
*
|
|
51
|
+
* Returns `null` when auth prerequisites are missing (not logged in, no API
|
|
52
|
+
* key). The assistant ID is resolved but not required.
|
|
53
|
+
*/
|
|
54
|
+
async function resolvePlatformClientConfig(): Promise<PlatformClientConfig | null> {
|
|
55
|
+
if (!arePlatformFeaturesEnabled()) {
|
|
56
|
+
log.debug("platform features disabled -- returning null");
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const ctx = await resolveManagedProxyContext();
|
|
61
|
+
|
|
62
|
+
let baseUrl = ctx.enabled ? ctx.platformBaseUrl : "";
|
|
63
|
+
let apiKey = ctx.enabled ? ctx.assistantApiKey : "";
|
|
64
|
+
let assistantId = getPlatformAssistantId();
|
|
65
|
+
|
|
66
|
+
// Fall back to credential store for values not yet rehydrated (standalone CLI).
|
|
67
|
+
if (!baseUrl) {
|
|
68
|
+
baseUrl =
|
|
69
|
+
(await getSecureKeyAsync(credentialKey("vellum", "platform_base_url"))) ??
|
|
70
|
+
"";
|
|
71
|
+
}
|
|
72
|
+
if (!apiKey) {
|
|
73
|
+
apiKey =
|
|
74
|
+
(await getSecureKeyAsync(credentialKey("vellum", "assistant_api_key"))) ??
|
|
75
|
+
"";
|
|
76
|
+
}
|
|
77
|
+
if (!assistantId) {
|
|
78
|
+
assistantId =
|
|
79
|
+
(
|
|
80
|
+
await getSecureKeyAsync(
|
|
81
|
+
credentialKey("vellum", "platform_assistant_id"),
|
|
82
|
+
)
|
|
83
|
+
)?.trim() ?? "";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!baseUrl || !apiKey) {
|
|
87
|
+
const level = _missingPrereqsWarned ? "debug" : "warn";
|
|
88
|
+
_missingPrereqsWarned = true;
|
|
89
|
+
log[level](
|
|
90
|
+
{
|
|
91
|
+
hasBaseUrl: !!baseUrl,
|
|
92
|
+
hasApiKey: !!apiKey,
|
|
93
|
+
hasAssistantId: !!assistantId,
|
|
94
|
+
managedProxyEnabled: ctx.enabled,
|
|
95
|
+
},
|
|
96
|
+
"Platform client prerequisites missing -- returning null",
|
|
97
|
+
);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { baseUrl, apiKey, assistantId };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Bounds the configured probe: resolution can hit up to three credential-store
|
|
105
|
+
// reads, and urgent-notification dispatch awaits this probe, so a slow
|
|
106
|
+
// credential backend must answer from cache instead of stalling the banner.
|
|
107
|
+
const CONFIGURED_PROBE_DEADLINE_MS = 500;
|
|
108
|
+
|
|
109
|
+
let lastKnownConfigured: boolean | null = null;
|
|
110
|
+
// Single-flight slot with rotation: concurrent probes share one resolution,
|
|
111
|
+
// while a flight some caller already gave up on is replaced so a hung
|
|
112
|
+
// credential backend cannot pin the slot (and the cache) stale until a 45s
|
|
113
|
+
// credential timeout finally settles it. Generation fencing keeps an
|
|
114
|
+
// out-of-order settle from a rotated-out flight from overwriting the result a
|
|
115
|
+
// newer flight already wrote.
|
|
116
|
+
interface ConfiguredProbeFlight {
|
|
117
|
+
promise: Promise<boolean>;
|
|
118
|
+
generation: number;
|
|
119
|
+
// Set once a caller's deadline elapsed on this flight. Rotation keys off
|
|
120
|
+
// this observed give-up rather than a wall-clock age, so the decision never
|
|
121
|
+
// lands on the deadline boundary, where drift between the timer's clock and
|
|
122
|
+
// Date.now() would decide whether a caller rejoins the flight it just
|
|
123
|
+
// abandoned.
|
|
124
|
+
abandoned: boolean;
|
|
125
|
+
}
|
|
126
|
+
let inFlightConfiguredProbe: ConfiguredProbeFlight | null = null;
|
|
127
|
+
let probeGeneration = 0;
|
|
128
|
+
let lastWrittenGeneration = 0;
|
|
129
|
+
|
|
130
|
+
export function _resetConfiguredProbeCacheForTests(): void {
|
|
131
|
+
lastKnownConfigured = null;
|
|
132
|
+
inFlightConfiguredProbe = null;
|
|
133
|
+
// Outstanding flights all carry generations at or below the current
|
|
134
|
+
// counter; raising the written floor to it blocks their settles from
|
|
135
|
+
// resurrecting the cache after a reset.
|
|
136
|
+
lastWrittenGeneration = probeGeneration;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function startOrJoinConfiguredProbe(): ConfiguredProbeFlight {
|
|
140
|
+
const existing = inFlightConfiguredProbe;
|
|
141
|
+
if (existing !== null && !existing.abandoned) {
|
|
142
|
+
return existing;
|
|
143
|
+
}
|
|
144
|
+
probeGeneration += 1;
|
|
145
|
+
const generation = probeGeneration;
|
|
146
|
+
const promise = resolvePlatformClientConfig()
|
|
147
|
+
.then((config) => config !== null && config.assistantId.length > 0)
|
|
148
|
+
.catch((err: unknown) => {
|
|
149
|
+
log.debug(
|
|
150
|
+
{ err },
|
|
151
|
+
"Configured probe failed -- treating as not configured",
|
|
152
|
+
);
|
|
153
|
+
return false;
|
|
154
|
+
})
|
|
155
|
+
.then((value) => {
|
|
156
|
+
if (generation > lastWrittenGeneration) {
|
|
157
|
+
lastKnownConfigured = value;
|
|
158
|
+
lastWrittenGeneration = generation;
|
|
159
|
+
}
|
|
160
|
+
if (inFlightConfiguredProbe?.generation === generation) {
|
|
161
|
+
inFlightConfiguredProbe = null;
|
|
162
|
+
}
|
|
163
|
+
return value;
|
|
164
|
+
});
|
|
165
|
+
const flight: ConfiguredProbeFlight = {
|
|
166
|
+
promise,
|
|
167
|
+
generation,
|
|
168
|
+
abandoned: false,
|
|
169
|
+
};
|
|
170
|
+
inFlightConfiguredProbe = flight;
|
|
171
|
+
return flight;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Whether the platform client can actually dispatch: auth prerequisites plus
|
|
176
|
+
* a nonempty platform assistant id (`PlatformPushAdapter.send()` fails fast
|
|
177
|
+
* without one). Constructs no client and makes no network requests.
|
|
178
|
+
*
|
|
179
|
+
* Bounded by {@link CONFIGURED_PROBE_DEADLINE_MS}: when config resolution is
|
|
180
|
+
* slower than the deadline, returns the last settled result (or `false` when
|
|
181
|
+
* none exists yet) and marks the flight abandoned. Concurrent calls share one
|
|
182
|
+
* in-flight resolution; the next call after an abandonment starts a fresh one,
|
|
183
|
+
* so a hung backend never pins the cache stale, and generation fencing keeps a
|
|
184
|
+
* rotated-out flight's late settle from overwriting a newer result.
|
|
185
|
+
*/
|
|
186
|
+
export async function isPlatformClientConfigured(): Promise<boolean> {
|
|
187
|
+
const flight = startOrJoinConfiguredProbe();
|
|
188
|
+
|
|
189
|
+
let deadlineTimer: ReturnType<typeof setTimeout> | undefined;
|
|
190
|
+
const deadline = new Promise<"deadline">((resolve) => {
|
|
191
|
+
deadlineTimer = setTimeout(
|
|
192
|
+
() => resolve("deadline"),
|
|
193
|
+
CONFIGURED_PROBE_DEADLINE_MS,
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
const raced = await Promise.race([flight.promise, deadline]);
|
|
197
|
+
clearTimeout(deadlineTimer);
|
|
198
|
+
if (raced === "deadline") {
|
|
199
|
+
flight.abandoned = true;
|
|
200
|
+
return lastKnownConfigured ?? false;
|
|
201
|
+
}
|
|
202
|
+
return raced;
|
|
203
|
+
}
|
|
204
|
+
|
|
37
205
|
export class VellumPlatformClient {
|
|
38
206
|
private readonly platformBaseUrl: string;
|
|
39
207
|
private readonly apiKey: string;
|
|
@@ -50,67 +218,22 @@ export class VellumPlatformClient {
|
|
|
50
218
|
}
|
|
51
219
|
|
|
52
220
|
/**
|
|
53
|
-
* Create a platform client
|
|
54
|
-
*
|
|
55
|
-
* First tries the in-memory managed proxy context (available when the daemon
|
|
56
|
-
* has rehydrated env overrides). Falls back to reading platform credentials
|
|
57
|
-
* directly from the credential store so that standalone CLI invocations work
|
|
58
|
-
* without the daemon having run its rehydration step.
|
|
221
|
+
* Create a platform client from {@link resolvePlatformClientConfig}.
|
|
59
222
|
*
|
|
60
223
|
* Returns `null` when auth prerequisites are missing (not logged in, no API
|
|
61
|
-
* key). The assistant ID is resolved but not required
|
|
62
|
-
* should check `platformAssistantId` themselves.
|
|
224
|
+
* key). The assistant ID is resolved but not required -- callers that need
|
|
225
|
+
* it should check `platformAssistantId` themselves.
|
|
63
226
|
*/
|
|
64
227
|
static async create(): Promise<VellumPlatformClient | null> {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const ctx = await resolveManagedProxyContext();
|
|
71
|
-
|
|
72
|
-
let baseUrl = ctx.enabled ? ctx.platformBaseUrl : "";
|
|
73
|
-
let apiKey = ctx.enabled ? ctx.assistantApiKey : "";
|
|
74
|
-
let assistantId = getPlatformAssistantId();
|
|
75
|
-
|
|
76
|
-
// Fall back to credential store for values not yet rehydrated (standalone CLI).
|
|
77
|
-
if (!baseUrl) {
|
|
78
|
-
baseUrl =
|
|
79
|
-
(await getSecureKeyAsync(
|
|
80
|
-
credentialKey("vellum", "platform_base_url"),
|
|
81
|
-
)) ?? "";
|
|
82
|
-
}
|
|
83
|
-
if (!apiKey) {
|
|
84
|
-
apiKey =
|
|
85
|
-
(await getSecureKeyAsync(
|
|
86
|
-
credentialKey("vellum", "assistant_api_key"),
|
|
87
|
-
)) ?? "";
|
|
88
|
-
}
|
|
89
|
-
if (!assistantId) {
|
|
90
|
-
assistantId =
|
|
91
|
-
(
|
|
92
|
-
await getSecureKeyAsync(
|
|
93
|
-
credentialKey("vellum", "platform_assistant_id"),
|
|
94
|
-
)
|
|
95
|
-
)?.trim() ?? "";
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (!baseUrl || !apiKey) {
|
|
99
|
-
const level = _missingPrereqsWarned ? "debug" : "warn";
|
|
100
|
-
_missingPrereqsWarned = true;
|
|
101
|
-
log[level](
|
|
102
|
-
{
|
|
103
|
-
hasBaseUrl: !!baseUrl,
|
|
104
|
-
hasApiKey: !!apiKey,
|
|
105
|
-
hasAssistantId: !!assistantId,
|
|
106
|
-
managedProxyEnabled: ctx.enabled,
|
|
107
|
-
},
|
|
108
|
-
"Platform client prerequisites missing — returning null",
|
|
109
|
-
);
|
|
228
|
+
const config = await resolvePlatformClientConfig();
|
|
229
|
+
if (!config) {
|
|
110
230
|
return null;
|
|
111
231
|
}
|
|
112
|
-
|
|
113
|
-
|
|
232
|
+
return new VellumPlatformClient(
|
|
233
|
+
config.baseUrl,
|
|
234
|
+
config.apiKey,
|
|
235
|
+
config.assistantId,
|
|
236
|
+
);
|
|
114
237
|
}
|
|
115
238
|
|
|
116
239
|
/**
|
|
@@ -67,6 +67,17 @@ export interface RunConversationTurnResult {
|
|
|
67
67
|
queued?: boolean;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Metadata stamped on the row that opens a plugin-driven turn. A plugin drives
|
|
72
|
+
* the turn on its own schedule, so the row is machine-initiated even when it
|
|
73
|
+
* lands in an ordinary standard conversation the user also types into: the
|
|
74
|
+
* `automated` marker is what keeps the reply out of the `chat.assistant_reply`
|
|
75
|
+
* push (see `isReplyPushIneligibleUserMessage`) and out of the memory
|
|
76
|
+
* extraction pass, alongside every other machine-authored prompt. It is not an
|
|
77
|
+
* echo-suppression marker, so the row still renders in the transcript.
|
|
78
|
+
*/
|
|
79
|
+
const PLUGIN_TURN_MESSAGE_METADATA = { automated: true } as const;
|
|
80
|
+
|
|
70
81
|
// ---------------------------------------------------------------------------
|
|
71
82
|
// Content conversion
|
|
72
83
|
// ---------------------------------------------------------------------------
|
|
@@ -210,6 +221,7 @@ export async function runConversationTurn(
|
|
|
210
221
|
onEvent,
|
|
211
222
|
requestId,
|
|
212
223
|
isInteractive: false,
|
|
224
|
+
metadata: { ...PLUGIN_TURN_MESSAGE_METADATA },
|
|
213
225
|
});
|
|
214
226
|
if (enqueueResult.rejected) {
|
|
215
227
|
throw new Error(
|
|
@@ -229,6 +241,7 @@ export async function runConversationTurn(
|
|
|
229
241
|
attachments,
|
|
230
242
|
onEvent,
|
|
231
243
|
isInteractive: false,
|
|
244
|
+
metadata: { ...PLUGIN_TURN_MESSAGE_METADATA },
|
|
232
245
|
...(options.callSite ? { callSite: options.callSite } : {}),
|
|
233
246
|
});
|
|
234
247
|
|
|
@@ -67,6 +67,7 @@ describe("getModelProfiles", () => {
|
|
|
67
67
|
"balanced",
|
|
68
68
|
"quality-optimized",
|
|
69
69
|
"cost-optimized",
|
|
70
|
+
"latency-optimized",
|
|
70
71
|
]);
|
|
71
72
|
});
|
|
72
73
|
|
|
@@ -85,6 +86,7 @@ describe("getModelProfiles", () => {
|
|
|
85
86
|
"balanced",
|
|
86
87
|
"cost-optimized",
|
|
87
88
|
"disabled",
|
|
89
|
+
"latency-optimized",
|
|
88
90
|
"quality-optimized",
|
|
89
91
|
]);
|
|
90
92
|
const disabled = result.find((p) => p.key === "disabled");
|
|
@@ -122,6 +124,7 @@ describe("getModelProfiles", () => {
|
|
|
122
124
|
"mix",
|
|
123
125
|
"balanced",
|
|
124
126
|
"cost-optimized",
|
|
127
|
+
"latency-optimized",
|
|
125
128
|
"quality-optimized",
|
|
126
129
|
]);
|
|
127
130
|
});
|
|
@@ -131,6 +134,7 @@ describe("getModelProfiles", () => {
|
|
|
131
134
|
expect(result.map((p) => p.key).sort()).toEqual([
|
|
132
135
|
"balanced",
|
|
133
136
|
"cost-optimized",
|
|
137
|
+
"latency-optimized",
|
|
134
138
|
"quality-optimized",
|
|
135
139
|
]);
|
|
136
140
|
for (const profile of result) {
|
|
@@ -150,7 +154,7 @@ describe("getModelProfiles", () => {
|
|
|
150
154
|
"Best results with the most capable model",
|
|
151
155
|
);
|
|
152
156
|
expect(result.find((p) => p.key === "cost-optimized")?.description).toBe(
|
|
153
|
-
"
|
|
157
|
+
"Cheapest responses, for high-volume work",
|
|
154
158
|
);
|
|
155
159
|
});
|
|
156
160
|
|
|
@@ -162,7 +166,7 @@ describe("getModelProfiles", () => {
|
|
|
162
166
|
// Tier descriptions are intent-only (no model names - LUM-2881), so the
|
|
163
167
|
// managed and BYOK columns read the same for this tier.
|
|
164
168
|
expect(result.find((p) => p.key === "cost-optimized")?.description).toBe(
|
|
165
|
-
"
|
|
169
|
+
"Cheapest responses, for high-volume work",
|
|
166
170
|
);
|
|
167
171
|
});
|
|
168
172
|
|
|
@@ -261,4 +261,38 @@ describe("bookmark-crud", () => {
|
|
|
261
261
|
/Message ghost not found/,
|
|
262
262
|
);
|
|
263
263
|
});
|
|
264
|
+
|
|
265
|
+
test("listBookmarks drives the join from message_bookmarks even with planner stats that predate the table", () => {
|
|
266
|
+
const sqlite = new Database(":memory:");
|
|
267
|
+
sqlite.exec("PRAGMA foreign_keys = ON");
|
|
268
|
+
const captured: string[] = [];
|
|
269
|
+
const db = drizzle(sqlite, {
|
|
270
|
+
schema,
|
|
271
|
+
logger: { logQuery: (query: string) => captured.push(query) },
|
|
272
|
+
});
|
|
273
|
+
const raw = getSqliteFrom(db);
|
|
274
|
+
bootstrapMessageTables(raw);
|
|
275
|
+
migrateMessageBookmarks(db);
|
|
276
|
+
for (let i = 0; i < 30; i++) {
|
|
277
|
+
seedConversationAndMessage(raw, {
|
|
278
|
+
conversationId: `conv-${i}`,
|
|
279
|
+
messageId: `msg-${i}`,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
// ANALYZE writes no sqlite_stat1 rows for an empty table, leaving
|
|
283
|
+
// message_bookmarks without statistics while messages gets them. This is
|
|
284
|
+
// the production state that made the planner scan messages.
|
|
285
|
+
raw.exec("ANALYZE");
|
|
286
|
+
|
|
287
|
+
listBookmarks(db);
|
|
288
|
+
const sql = captured.at(-1);
|
|
289
|
+
expect(sql).toBeDefined();
|
|
290
|
+
const plan = raw.query(`EXPLAIN QUERY PLAN ${sql}`).all() as Array<{
|
|
291
|
+
detail: string;
|
|
292
|
+
}>;
|
|
293
|
+
expect(plan[0]?.detail ?? "").toMatch(/^SCAN message_bookmarks/);
|
|
294
|
+
expect(plan.some((row) => row.detail.includes("SCAN messages"))).toBe(
|
|
295
|
+
false,
|
|
296
|
+
);
|
|
297
|
+
});
|
|
264
298
|
});
|
|
@@ -169,6 +169,18 @@ describe("buildExcerpt", () => {
|
|
|
169
169
|
expect(excerpt.startsWith("…")).toBe(true);
|
|
170
170
|
});
|
|
171
171
|
|
|
172
|
+
test("keeps the match near the front of the display excerpt", () => {
|
|
173
|
+
const prefix = "p".repeat(200);
|
|
174
|
+
const excerpt = buildExcerpt(
|
|
175
|
+
JSON.stringify([{ type: "text", text: `${prefix} alpha trailing text` }]),
|
|
176
|
+
"alpha",
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const alphaIndex = excerpt.indexOf("alpha");
|
|
180
|
+
expect(alphaIndex).toBeGreaterThan(0);
|
|
181
|
+
expect(alphaIndex).toBeLessThanOrEqual(32);
|
|
182
|
+
});
|
|
183
|
+
|
|
172
184
|
test("whole-query matches also respect tokenizer boundaries", () => {
|
|
173
185
|
const filler = "party planning notes ".repeat(10);
|
|
174
186
|
const excerpt = buildExcerpt(
|
|
@@ -337,6 +349,16 @@ describe("buildRecallEvidenceExcerpt", () => {
|
|
|
337
349
|
);
|
|
338
350
|
});
|
|
339
351
|
|
|
352
|
+
test("keeps the wide leading window for recall evidence", () => {
|
|
353
|
+
const prefix = "p".repeat(200);
|
|
354
|
+
const excerpt = buildRecallEvidenceExcerpt(
|
|
355
|
+
JSON.stringify([{ type: "text", text: `${prefix} alpha trailing text` }]),
|
|
356
|
+
"alpha",
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
expect(excerpt.indexOf("alpha")).toBeGreaterThan(50);
|
|
360
|
+
});
|
|
361
|
+
|
|
340
362
|
test("keeps the raw fallback for messages with no legible text", () => {
|
|
341
363
|
const raw = JSON.stringify([
|
|
342
364
|
{ type: "tool_use", id: "toolu_01", name: "bash", input: {} },
|
|
@@ -38,6 +38,15 @@ mock.module("../../../../persistence/conversation-crud.js", () => ({
|
|
|
38
38
|
reserveMessage: mock(async () => ({ id: "msg-reserve" })),
|
|
39
39
|
}));
|
|
40
40
|
|
|
41
|
+
// Stub the user-activity gate probe open. Its real logic is covered by
|
|
42
|
+
// memory-retrospective-accounting.test.ts and
|
|
43
|
+
// memory-retrospective-enqueue.test.ts; against this file's empty stub db it
|
|
44
|
+
// would report no activity and close the gate, masking the memory.enabled
|
|
45
|
+
// gate under test.
|
|
46
|
+
mock.module("../memory-retrospective-accounting.js", () => ({
|
|
47
|
+
hasQualifyingUserMessageAfter: () => true,
|
|
48
|
+
}));
|
|
49
|
+
|
|
41
50
|
// Stub the qdrant breaker so `enqueueMemoryJob` doesn't trip on it.
|
|
42
51
|
mock.module(
|
|
43
52
|
"../../../../persistence/embeddings/qdrant-circuit-breaker.js",
|
|
@@ -94,6 +103,8 @@ const stubDb = makeStubDb();
|
|
|
94
103
|
mock.module("../../../../persistence/db-connection.js", () => ({
|
|
95
104
|
getDb: () => stubDb,
|
|
96
105
|
getMemoryDb: () => stubDb,
|
|
106
|
+
// Truthy handle: consulted only as memory-db's availability gate.
|
|
107
|
+
getMemorySqlite: () => ({}),
|
|
97
108
|
}));
|
|
98
109
|
|
|
99
110
|
// Now load the real modules under test.
|