@vellumai/assistant 0.8.11 → 0.8.12-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/ARCHITECTURE.md +15 -17
- package/README.md +0 -6
- package/bun.lock +6 -122
- package/node_modules/@vellumai/gateway-client/bun.lock +1 -0
- package/node_modules/@vellumai/gateway-client/package.json +3 -1
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -1
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +87 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +3 -5
- package/openapi.yaml +633 -4
- package/package.json +1 -3
- package/src/__tests__/adaptive-thinking-repair.test.ts +185 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +7 -6
- package/src/__tests__/anthropic-provider.test.ts +129 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -1
- package/src/__tests__/btw-routes.test.ts +7 -34
- package/src/__tests__/checker.test.ts +6 -12
- package/src/__tests__/config-loader-backfill.test.ts +4 -2
- package/src/__tests__/config-loader-quarantine-notice.test.ts +167 -0
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-error.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +8 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/disk-pressure-tools.test.ts +1 -1
- package/src/__tests__/exploration-drift-hook.test.ts +692 -0
- package/src/__tests__/filing-service.test.ts +8 -3
- package/src/__tests__/guardian-action-store.test.ts +0 -167
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -1
- package/src/__tests__/heartbeat-service.test.ts +5 -2
- package/src/__tests__/identity-intro-cache.test.ts +12 -5
- package/src/__tests__/identity-routes.test.ts +16 -57
- package/src/__tests__/injector-chain.test.ts +8 -3
- package/src/__tests__/injector-config-quarantine-notice.test.ts +115 -0
- package/src/__tests__/llm-catalog-parity.test.ts +16 -0
- package/src/__tests__/llm-usage-store.test.ts +11 -0
- package/src/__tests__/log-export-workspace.test.ts +468 -3
- package/src/__tests__/memory-v2-static-injector.test.ts +22 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +19 -8
- package/src/__tests__/openai-provider.test.ts +34 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +0 -1
- package/src/__tests__/recurrence-engine.test.ts +45 -0
- package/src/__tests__/schedule-routes.test.ts +34 -0
- package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
- package/src/__tests__/script-proxy-conversation-manager.test.ts +10 -5
- package/src/__tests__/secret-fixtures.ts +20 -0
- package/src/__tests__/skill-tool-factory.test.ts +49 -0
- package/src/__tests__/subagent-role-registry.test.ts +24 -1
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +109 -11
- package/src/__tests__/tool-approval-handler.test.ts +85 -0
- package/src/__tests__/tool-audit-listener.test.ts +86 -0
- package/src/__tests__/tool-error-hook.test.ts +1 -0
- package/src/__tests__/tool-result-spool.test.ts +337 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/validate-input.test.ts +95 -1
- package/src/__tests__/workspace-migration-098-remove-stale-updates-bulletin-file.test.ts +65 -0
- package/src/__tests__/workspace-migration-099-disable-cache-one-shot-callsites.test.ts +139 -0
- package/src/__tests__/workspace-migration-100-upgrade-quality-profile-to-fable-5.test.ts +174 -0
- package/src/__tests__/workspace-migration-101-upgrade-balanced-economy-to-minimax-m3.test.ts +162 -0
- package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +45 -95
- package/src/acp/__tests__/agent-process.test.ts +315 -2
- package/src/acp/__tests__/prepare-agent-env.test.ts +79 -5
- package/src/acp/agent-process.ts +163 -34
- package/src/acp/prepare-agent-env.ts +55 -15
- package/src/agent/loop.ts +81 -24
- package/src/api/events/usage-progress.ts +28 -0
- package/src/api/index.ts +6 -0
- package/src/background-wake/wake-intent-hooks.test.ts +2 -0
- package/src/bundler/app-bundler.ts +25 -42
- package/src/bundler/app-compiler.ts +8 -0
- package/src/calls/call-controller.ts +1 -1
- package/src/cli/commands/plugins.ts +248 -15
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +318 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +16 -9
- package/src/cli/lib/__tests__/plugin-artifact.test.ts +183 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +158 -0
- package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +245 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +307 -0
- package/src/cli/lib/inspect-plugin.ts +252 -0
- package/src/cli/lib/install-from-github.ts +214 -21
- package/src/cli/lib/list-installed-plugins.ts +17 -6
- package/src/cli/lib/plugin-artifact.ts +103 -0
- package/src/cli/lib/plugin-details.ts +18 -1
- package/src/cli/lib/plugin-fingerprint.ts +197 -0
- package/src/cli/lib/upgrade-plugin.ts +225 -0
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -2
- package/src/config/call-site-defaults.ts +13 -2
- package/src/config/feature-flag-registry.json +8 -16
- package/src/config/loader.ts +52 -59
- package/src/config/schema.ts +0 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +10 -0
- package/src/config/schemas/llm.ts +10 -0
- package/src/config/schemas/memory-v2.ts +13 -0
- package/src/config/schemas/memory-v3.ts +92 -0
- package/src/config/seed-inference-profiles.ts +4 -8
- package/src/context/post-turn-tool-result-truncation.ts +32 -18
- package/src/context/tool-result-spool.ts +104 -0
- package/src/credential-execution/feature-gates.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +41 -16
- package/src/daemon/conversation-error.ts +6 -15
- package/src/daemon/conversation.ts +9 -0
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/lifecycle.ts +1 -20
- package/src/daemon/message-types/conversations.ts +2 -15
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +40 -9
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
- package/src/home/__tests__/home-content-refresh.test.ts +114 -0
- package/src/home/__tests__/suggested-prompts.test.ts +86 -5
- package/src/home/home-content-refresh.ts +43 -31
- package/src/home/home-greeting-cache.ts +8 -1
- package/src/home/home-greeting.ts +13 -9
- package/src/home/suggested-prompts.ts +77 -24
- package/src/ipc/routes/trust-rules.test.ts +66 -72
- package/src/media/image-credentials.ts +2 -2
- package/src/memory/__tests__/compaction-log-store-clickhouse.test.ts +432 -0
- package/src/memory/{compaction-log-writer-clickhouse.ts → compaction-log-store-clickhouse.ts} +264 -55
- package/src/memory/conversation-attention-store.ts +1 -0
- package/src/memory/conversation-bootstrap.ts +18 -9
- package/src/memory/conversation-crud.ts +12 -2
- package/src/memory/conversation-title-service.ts +53 -9
- package/src/memory/delivery-channels.ts +0 -69
- package/src/memory/graph/extraction-job.ts +0 -15
- package/src/memory/guardian-action-store.ts +1 -376
- package/src/memory/llm-usage-store.ts +5 -1
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +2 -2
- package/src/memory/v2/__tests__/consolidation-job.test.ts +183 -2
- package/src/memory/v2/__tests__/injection.test.ts +70 -0
- package/src/memory/v2/__tests__/static-context.test.ts +12 -0
- package/src/memory/v2/consolidation-job.ts +93 -9
- package/src/memory/v2/injection.ts +53 -0
- package/src/memory/v2/prompts/consolidation.ts +1 -0
- package/src/memory/v2/static-context.ts +13 -1
- package/src/memory/v2/sweep-job.ts +1 -1
- package/src/memory/v2/types.ts +5 -0
- package/src/plugin-api/types.ts +7 -0
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +300 -0
- package/src/plugins/defaults/exploration-drift/package.json +15 -0
- package/src/plugins/defaults/index.ts +25 -0
- package/src/plugins/defaults/memory-retrieval/injectors.ts +132 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/card.test.ts +92 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fresh-set.test.ts +52 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +136 -5
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +17 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +6 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +5 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +68 -4
- package/src/plugins/defaults/memory-v3-shadow/card.ts +49 -5
- package/src/plugins/defaults/memory-v3-shadow/fresh-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.test.ts +169 -0
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.ts +178 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +115 -26
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -9
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +144 -22
- package/src/plugins/defaults/memory-v3-shadow/types.ts +24 -6
- package/src/plugins/defaults/title-generate/hooks/stop.ts +13 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +16 -0
- package/src/prompts/cache-boundary.ts +17 -0
- package/src/prompts/sections.ts +50 -17
- package/src/prompts/system-prompt.ts +12 -4
- package/src/prompts/templates/system-sections.ts +22 -0
- package/src/providers/__tests__/unparseable-tool-args.test.ts +53 -0
- package/src/providers/anthropic/client.ts +74 -28
- package/src/providers/gemini/client.ts +5 -1
- package/src/providers/minimax/client.ts +9 -0
- package/src/providers/model-catalog.ts +28 -0
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/chat-completions-provider.ts +4 -2
- package/src/providers/openai/responses-provider.ts +7 -2
- package/src/providers/retry.ts +8 -0
- package/src/providers/types.ts +11 -0
- package/src/providers/unparseable-tool-args.ts +56 -0
- package/src/runtime/AGENTS.md +6 -0
- package/src/runtime/__tests__/agent-wake.test.ts +2 -2
- package/src/runtime/agent-wake.ts +5 -5
- package/src/runtime/background-job-runner.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +150 -3
- package/src/runtime/migrations/vbundle-import-analyzer.ts +29 -6
- package/src/runtime/migrations/vbundle-import-policy.ts +23 -0
- package/src/runtime/migrations/vbundle-importer.ts +9 -4
- package/src/runtime/migrations/vbundle-streaming-importer.ts +8 -3
- package/src/runtime/pre-first-message-gate.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +241 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +132 -0
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +97 -185
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +348 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +3 -3
- package/src/runtime/routes/btw-routes.ts +0 -14
- package/src/runtime/routes/conversation-compaction-routes.ts +86 -19
- package/src/runtime/routes/conversation-list-routes.ts +77 -5
- package/src/runtime/routes/conversation-management-routes.ts +54 -0
- package/src/runtime/routes/conversation-query-routes.ts +79 -4
- package/src/runtime/routes/gateway-log-routes.ts +14 -64
- package/src/runtime/routes/home-feed-routes.ts +10 -0
- package/src/runtime/routes/identity-intro-cache.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +76 -20
- package/src/runtime/routes/inbound-message-handler.ts +0 -36
- package/src/runtime/routes/log-export-routes.ts +143 -96
- package/src/runtime/routes/plugins-routes.ts +380 -0
- package/src/runtime/routes/redact-staged-export.ts +259 -0
- package/src/runtime/routes/schedule-routes.ts +19 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -67
- package/src/schedule/recurrence-engine.ts +34 -0
- package/src/schedule/scheduler.ts +1 -0
- package/src/security/redact-json.ts +61 -0
- package/src/skills/validate-input.ts +41 -1
- package/src/subagent/types.ts +26 -1
- package/src/telemetry/types.ts +15 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +6 -1
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/apps/executors.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +19 -8
- package/src/tools/tool-approval-handler.ts +31 -0
- package/src/usage/types.ts +8 -1
- package/src/util/platform.ts +16 -0
- package/src/watcher/engine.ts +1 -0
- package/src/workspace/adaptive-thinking-repair.ts +113 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +70 -67
- package/src/workspace/migrations/098-remove-stale-updates-bulletin-file.ts +31 -0
- package/src/workspace/migrations/099-disable-cache-one-shot-callsites.ts +81 -0
- package/src/workspace/migrations/100-upgrade-quality-profile-to-fable-5.ts +86 -0
- package/src/workspace/migrations/101-upgrade-balanced-economy-to-minimax-m3.ts +70 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +0 -202
- package/src/__tests__/conversation-starters-cadence.test.ts +0 -161
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -322
- package/src/__tests__/guardian-action-followup-store.test.ts +0 -373
- package/src/__tests__/guardian-action-late-reply.test.ts +0 -1083
- package/src/__tests__/update-bulletin-job.test.ts +0 -292
- package/src/config/schemas/updates.ts +0 -14
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +0 -227
- package/src/memory/conversation-starters-cadence.ts +0 -78
- package/src/prompts/update-bulletin-job.ts +0 -180
- package/src/runtime/guardian-action-followup-executor.ts +0 -306
|
@@ -96,9 +96,14 @@ mock.module("../util/logger.js", () => ({
|
|
|
96
96
|
}),
|
|
97
97
|
}));
|
|
98
98
|
|
|
99
|
-
// Mock conversation title service
|
|
99
|
+
// Mock conversation title service. `deriveDeterministicTitle` mirrors the
|
|
100
|
+
// real implementation's systemHint passthrough so bootstrap-created
|
|
101
|
+
// conversations carry their job hint as the title.
|
|
100
102
|
mock.module("../memory/conversation-title-service.js", () => ({
|
|
101
103
|
GENERATING_TITLE: "Generating title...",
|
|
104
|
+
AUTO_TITLE_DETERMINISTIC: 2,
|
|
105
|
+
deriveDeterministicTitle: (context: { systemHint?: string }) =>
|
|
106
|
+
context.systemHint ?? "Untitled Conversation",
|
|
102
107
|
queueGenerateConversationTitle: () => {},
|
|
103
108
|
}));
|
|
104
109
|
|
|
@@ -230,12 +235,12 @@ describe("FilingService", () => {
|
|
|
230
235
|
);
|
|
231
236
|
});
|
|
232
237
|
|
|
233
|
-
test("creates background conversation with
|
|
238
|
+
test("creates background conversation with the deterministic filing title", async () => {
|
|
234
239
|
const service = createService();
|
|
235
240
|
await service.runOnce();
|
|
236
241
|
|
|
237
242
|
expect(createdConversations).toHaveLength(1);
|
|
238
|
-
expect(createdConversations[0].title).toBe("
|
|
243
|
+
expect(createdConversations[0].title).toBe("Knowledge base filing");
|
|
239
244
|
expect(createdConversations[0].conversationType).toBe("background");
|
|
240
245
|
// Confirms FilingService routes through runBackgroundJob:
|
|
241
246
|
// source="filing" + runner-default groupId="system:background".
|
|
@@ -17,13 +17,8 @@ import {
|
|
|
17
17
|
cancelGuardianActionRequest,
|
|
18
18
|
createGuardianActionDelivery,
|
|
19
19
|
createGuardianActionRequest,
|
|
20
|
-
expireGuardianActionRequest,
|
|
21
20
|
getDeliveriesByRequestId,
|
|
22
|
-
getExpiredDeliveriesByConversation,
|
|
23
|
-
getFollowupDeliveriesByConversation,
|
|
24
21
|
getGuardianActionRequest,
|
|
25
|
-
getPendingDeliveriesByConversation,
|
|
26
|
-
startFollowupFromExpiredRequest,
|
|
27
22
|
updateDeliveryStatus,
|
|
28
23
|
} from "../memory/guardian-action-store.js";
|
|
29
24
|
import { conversations } from "../memory/schema.js";
|
|
@@ -58,168 +53,6 @@ describe("guardian-action-store", () => {
|
|
|
58
53
|
resetTables();
|
|
59
54
|
});
|
|
60
55
|
|
|
61
|
-
// ── Helper to create a pending request+delivery targeting a conversation ──
|
|
62
|
-
function createPendingRequestWithDelivery(
|
|
63
|
-
convId: string,
|
|
64
|
-
deliveryConvId: string,
|
|
65
|
-
) {
|
|
66
|
-
ensureConversation(convId);
|
|
67
|
-
const session = createCallSession({
|
|
68
|
-
conversationId: convId,
|
|
69
|
-
provider: "twilio",
|
|
70
|
-
fromNumber: "+15550001111",
|
|
71
|
-
toNumber: "+15550002222",
|
|
72
|
-
});
|
|
73
|
-
const pq = createPendingQuestion(session.id, `Question for ${convId}`);
|
|
74
|
-
const request = createGuardianActionRequest({
|
|
75
|
-
kind: "ask_guardian",
|
|
76
|
-
sourceChannel: "phone",
|
|
77
|
-
sourceConversationId: convId,
|
|
78
|
-
callSessionId: session.id,
|
|
79
|
-
pendingQuestionId: pq.id,
|
|
80
|
-
questionText: pq.questionText,
|
|
81
|
-
expiresAt: Date.now() + 60_000,
|
|
82
|
-
});
|
|
83
|
-
const delivery = createGuardianActionDelivery({
|
|
84
|
-
requestId: request.id,
|
|
85
|
-
destinationChannel: "vellum",
|
|
86
|
-
destinationConversationId: deliveryConvId,
|
|
87
|
-
});
|
|
88
|
-
updateDeliveryStatus(delivery.id, "sent");
|
|
89
|
-
return { request, delivery };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// ── getPendingDeliveriesByConversation ──────────────────────────────
|
|
93
|
-
|
|
94
|
-
test("getPendingDeliveriesByConversation returns all pending deliveries for a conversation", () => {
|
|
95
|
-
const sharedConvId = "shared-pending-conv";
|
|
96
|
-
ensureConversation(sharedConvId);
|
|
97
|
-
|
|
98
|
-
const { request: req1 } = createPendingRequestWithDelivery(
|
|
99
|
-
"source-conv-p1",
|
|
100
|
-
sharedConvId,
|
|
101
|
-
);
|
|
102
|
-
const { request: req2 } = createPendingRequestWithDelivery(
|
|
103
|
-
"source-conv-p2",
|
|
104
|
-
sharedConvId,
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
const deliveries = getPendingDeliveriesByConversation(sharedConvId);
|
|
108
|
-
expect(deliveries).toHaveLength(2);
|
|
109
|
-
|
|
110
|
-
const requestIds = deliveries.map((d) => d.requestId);
|
|
111
|
-
expect(requestIds).toContain(req1.id);
|
|
112
|
-
expect(requestIds).toContain(req2.id);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test("getPendingDeliveriesByConversation returns single delivery (fast path preserved)", () => {
|
|
116
|
-
const convId = "single-pending-conv";
|
|
117
|
-
ensureConversation(convId);
|
|
118
|
-
|
|
119
|
-
const { request } = createPendingRequestWithDelivery(
|
|
120
|
-
"source-conv-single-p",
|
|
121
|
-
convId,
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
const deliveries = getPendingDeliveriesByConversation(convId);
|
|
125
|
-
expect(deliveries).toHaveLength(1);
|
|
126
|
-
expect(deliveries[0].requestId).toBe(request.id);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("getPendingDeliveriesByConversation returns empty for non-matching conversation", () => {
|
|
130
|
-
ensureConversation("other-conv");
|
|
131
|
-
createPendingRequestWithDelivery("source-conv-no-match", "other-conv");
|
|
132
|
-
|
|
133
|
-
const deliveries = getPendingDeliveriesByConversation("nonexistent-conv");
|
|
134
|
-
expect(deliveries).toHaveLength(0);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// ── getExpiredDeliveriesByConversation ──────────────────────────────
|
|
138
|
-
|
|
139
|
-
test("getExpiredDeliveriesByConversation returns all expired deliveries for a conversation", () => {
|
|
140
|
-
const sharedConvId = "shared-expired-conv";
|
|
141
|
-
ensureConversation(sharedConvId);
|
|
142
|
-
|
|
143
|
-
const { request: req1 } = createPendingRequestWithDelivery(
|
|
144
|
-
"source-conv-e1",
|
|
145
|
-
sharedConvId,
|
|
146
|
-
);
|
|
147
|
-
const { request: req2 } = createPendingRequestWithDelivery(
|
|
148
|
-
"source-conv-e2",
|
|
149
|
-
sharedConvId,
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
expireGuardianActionRequest(req1.id, "sweep_timeout");
|
|
153
|
-
expireGuardianActionRequest(req2.id, "sweep_timeout");
|
|
154
|
-
|
|
155
|
-
const deliveries = getExpiredDeliveriesByConversation(sharedConvId);
|
|
156
|
-
expect(deliveries).toHaveLength(2);
|
|
157
|
-
|
|
158
|
-
const requestIds = deliveries.map((d) => d.requestId);
|
|
159
|
-
expect(requestIds).toContain(req1.id);
|
|
160
|
-
expect(requestIds).toContain(req2.id);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("getExpiredDeliveriesByConversation excludes deliveries with followup already started", () => {
|
|
164
|
-
const convId = "expired-with-followup-conv";
|
|
165
|
-
ensureConversation(convId);
|
|
166
|
-
|
|
167
|
-
const { request: req1 } = createPendingRequestWithDelivery(
|
|
168
|
-
"source-conv-ef1",
|
|
169
|
-
convId,
|
|
170
|
-
);
|
|
171
|
-
const { request: req2 } = createPendingRequestWithDelivery(
|
|
172
|
-
"source-conv-ef2",
|
|
173
|
-
convId,
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
expireGuardianActionRequest(req1.id, "sweep_timeout");
|
|
177
|
-
expireGuardianActionRequest(req2.id, "sweep_timeout");
|
|
178
|
-
|
|
179
|
-
// Start followup on req1 — only req2 should remain in the expired query
|
|
180
|
-
startFollowupFromExpiredRequest(req1.id, "late answer");
|
|
181
|
-
|
|
182
|
-
const deliveries = getExpiredDeliveriesByConversation(convId);
|
|
183
|
-
expect(deliveries).toHaveLength(1);
|
|
184
|
-
expect(deliveries[0].requestId).toBe(req2.id);
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
// ── getFollowupDeliveriesByConversation ─────────────────────────────
|
|
188
|
-
|
|
189
|
-
test("getFollowupDeliveriesByConversation returns all awaiting_guardian_choice deliveries", () => {
|
|
190
|
-
const convId = "shared-followup-conv";
|
|
191
|
-
ensureConversation(convId);
|
|
192
|
-
|
|
193
|
-
const { request: req1 } = createPendingRequestWithDelivery(
|
|
194
|
-
"source-conv-f1",
|
|
195
|
-
convId,
|
|
196
|
-
);
|
|
197
|
-
const { request: req2 } = createPendingRequestWithDelivery(
|
|
198
|
-
"source-conv-f2",
|
|
199
|
-
convId,
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
expireGuardianActionRequest(req1.id, "sweep_timeout");
|
|
203
|
-
expireGuardianActionRequest(req2.id, "sweep_timeout");
|
|
204
|
-
|
|
205
|
-
startFollowupFromExpiredRequest(req1.id, "late answer 1");
|
|
206
|
-
startFollowupFromExpiredRequest(req2.id, "late answer 2");
|
|
207
|
-
|
|
208
|
-
const deliveries = getFollowupDeliveriesByConversation(convId);
|
|
209
|
-
expect(deliveries).toHaveLength(2);
|
|
210
|
-
|
|
211
|
-
const requestIds = deliveries.map((d) => d.requestId);
|
|
212
|
-
expect(requestIds).toContain(req1.id);
|
|
213
|
-
expect(requestIds).toContain(req2.id);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test("getFollowupDeliveriesByConversation returns empty for non-matching conversation", () => {
|
|
217
|
-
const deliveries = getFollowupDeliveriesByConversation("nonexistent-conv");
|
|
218
|
-
expect(deliveries).toHaveLength(0);
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
// ── cancelGuardianActionRequest ─────────────────────────────────────
|
|
222
|
-
|
|
223
56
|
test("cancelGuardianActionRequest cancels both pending and sent deliveries", () => {
|
|
224
57
|
const conversationId = "conv-guardian-cancel";
|
|
225
58
|
ensureConversation(conversationId);
|
|
@@ -65,7 +65,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
65
65
|
saveRawConfig: () => {},
|
|
66
66
|
getNestedValue: () => undefined,
|
|
67
67
|
setNestedValue: () => {},
|
|
68
|
-
|
|
68
|
+
_writeQuarantineNotice: () => {},
|
|
69
69
|
}));
|
|
70
70
|
|
|
71
71
|
mock.module("../config/skill-state.js", () => ({
|
|
@@ -37,7 +37,7 @@ mock.module("../config/loader.js", () => ({
|
|
|
37
37
|
getNestedValue: () => undefined,
|
|
38
38
|
setNestedValue: () => {},
|
|
39
39
|
API_KEY_PROVIDERS: [],
|
|
40
|
-
|
|
40
|
+
_writeQuarantineNotice: () => {},
|
|
41
41
|
invalidateConfigCache: () => {},
|
|
42
42
|
}));
|
|
43
43
|
|
|
@@ -102,6 +102,9 @@ mock.module("../prompts/persona-resolver.js", () => ({
|
|
|
102
102
|
|
|
103
103
|
mock.module("../memory/conversation-title-service.js", () => ({
|
|
104
104
|
GENERATING_TITLE: "Generating title...",
|
|
105
|
+
AUTO_TITLE_DETERMINISTIC: 2,
|
|
106
|
+
deriveDeterministicTitle: (context: { systemHint?: string }) =>
|
|
107
|
+
context.systemHint ?? "Untitled Conversation",
|
|
105
108
|
queueGenerateConversationTitle: () => {},
|
|
106
109
|
}));
|
|
107
110
|
|
|
@@ -250,6 +250,9 @@ mock.module("../notifications/emit-signal.js", () => ({
|
|
|
250
250
|
// Mock conversation title service
|
|
251
251
|
mock.module("../memory/conversation-title-service.js", () => ({
|
|
252
252
|
GENERATING_TITLE: "Generating title...",
|
|
253
|
+
AUTO_TITLE_DETERMINISTIC: 2,
|
|
254
|
+
deriveDeterministicTitle: (context: { systemHint?: string }) =>
|
|
255
|
+
context.systemHint ?? "Untitled Conversation",
|
|
253
256
|
queueGenerateConversationTitle: () => {},
|
|
254
257
|
}));
|
|
255
258
|
|
|
@@ -497,12 +500,12 @@ describe("HeartbeatService", () => {
|
|
|
497
500
|
expect(processMessageCalls[0].content).toContain("Check in with yourself");
|
|
498
501
|
});
|
|
499
502
|
|
|
500
|
-
test("creates background conversation with
|
|
503
|
+
test("creates background conversation with the deterministic heartbeat title", async () => {
|
|
501
504
|
const service = createService();
|
|
502
505
|
await service.runOnce();
|
|
503
506
|
|
|
504
507
|
expect(createdConversations).toHaveLength(1);
|
|
505
|
-
expect(createdConversations[0].title).toBe("
|
|
508
|
+
expect(createdConversations[0].title).toBe("Heartbeat");
|
|
506
509
|
expect(createdConversations[0].conversationType).toBe("background");
|
|
507
510
|
});
|
|
508
511
|
|
|
@@ -119,19 +119,26 @@ describe("identity intro cache", () => {
|
|
|
119
119
|
|
|
120
120
|
setCachedIntro(["Hello!"]);
|
|
121
121
|
|
|
122
|
-
const
|
|
123
|
-
|
|
122
|
+
const fourHoursAndOneMinuteAgo = String(
|
|
123
|
+
Date.now() - (4 * 60 + 1) * 60 * 1000,
|
|
124
|
+
);
|
|
125
|
+
checkpointStore.set("identity:intro:cached_at", fourHoursAndOneMinuteAgo);
|
|
124
126
|
|
|
125
127
|
expect(getCachedIntro()).toBeNull();
|
|
126
128
|
});
|
|
127
129
|
|
|
128
|
-
test("returns cached greetings when within TTL (
|
|
130
|
+
test("returns cached greetings when within TTL (3 hours 59 minutes ago)", () => {
|
|
129
131
|
workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
|
|
130
132
|
|
|
131
133
|
setCachedIntro(["Hello!"]);
|
|
132
134
|
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
+
const threeHoursAndFiftyNineMinutesAgo = String(
|
|
136
|
+
Date.now() - (3 * 60 + 59) * 60 * 1000,
|
|
137
|
+
);
|
|
138
|
+
checkpointStore.set(
|
|
139
|
+
"identity:intro:cached_at",
|
|
140
|
+
threeHoursAndFiftyNineMinutesAgo,
|
|
141
|
+
);
|
|
135
142
|
|
|
136
143
|
const cached = getCachedIntro();
|
|
137
144
|
expect(cached).not.toBeNull();
|
|
@@ -73,13 +73,6 @@ mock.module("../runtime/btw-sidechain.js", () => ({
|
|
|
73
73
|
}),
|
|
74
74
|
}));
|
|
75
75
|
|
|
76
|
-
const assistantFeatureFlags: Record<string, boolean> = {};
|
|
77
|
-
|
|
78
|
-
mock.module("../config/assistant-feature-flags.js", () => ({
|
|
79
|
-
isAssistantFeatureFlagEnabled: (key: string) =>
|
|
80
|
-
assistantFeatureFlags[key] ?? false,
|
|
81
|
-
}));
|
|
82
|
-
|
|
83
76
|
import {
|
|
84
77
|
handleDetailedHealth,
|
|
85
78
|
handleReadyz,
|
|
@@ -206,9 +199,6 @@ beforeEach(() => {
|
|
|
206
199
|
sidechainCalls.length = 0;
|
|
207
200
|
sidechainText = "";
|
|
208
201
|
sidechainResultPromise = null;
|
|
209
|
-
for (const key of Object.keys(assistantFeatureFlags)) {
|
|
210
|
-
delete assistantFeatureFlags[key];
|
|
211
|
-
}
|
|
212
202
|
});
|
|
213
203
|
|
|
214
204
|
afterEach(() => {
|
|
@@ -576,53 +566,7 @@ describe("identity routes — createdAt selection", () => {
|
|
|
576
566
|
});
|
|
577
567
|
|
|
578
568
|
describe("identity routes — intro greetings", () => {
|
|
579
|
-
test("returns static fallback and does not generate when the dynamic greetings flag is off", async () => {
|
|
580
|
-
const workspaceDir = getWorkspaceDir();
|
|
581
|
-
writeFileSync(
|
|
582
|
-
join(workspaceDir, "IDENTITY.md"),
|
|
583
|
-
"# Identity\n\n- **Name:** Example Assistant\n",
|
|
584
|
-
"utf-8",
|
|
585
|
-
);
|
|
586
|
-
writeFileSync(
|
|
587
|
-
join(workspaceDir, "SOUL.md"),
|
|
588
|
-
"# Soul\n\nNo explicit greetings section here.\n",
|
|
589
|
-
"utf-8",
|
|
590
|
-
);
|
|
591
|
-
|
|
592
|
-
const route = ROUTES.find(
|
|
593
|
-
(candidate) => candidate.operationId === "identity_intro",
|
|
594
|
-
);
|
|
595
|
-
expect(route).toBeDefined();
|
|
596
|
-
|
|
597
|
-
const body = route!.handler({}) as {
|
|
598
|
-
greetings: string[];
|
|
599
|
-
text: string;
|
|
600
|
-
source: string;
|
|
601
|
-
refreshing: boolean;
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
expect(body).toEqual({
|
|
605
|
-
greetings: [
|
|
606
|
-
"What are we working on?",
|
|
607
|
-
"I'm here whenever you need me.",
|
|
608
|
-
"What's on your mind?",
|
|
609
|
-
"Ready when you are.",
|
|
610
|
-
],
|
|
611
|
-
text: "What are we working on?",
|
|
612
|
-
source: "fallback",
|
|
613
|
-
refreshing: false,
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
await Promise.resolve();
|
|
617
|
-
await Promise.resolve();
|
|
618
|
-
|
|
619
|
-
expect(getConfiguredProviderCalls).toEqual([]);
|
|
620
|
-
expect(sidechainCalls).toEqual([]);
|
|
621
|
-
});
|
|
622
|
-
|
|
623
569
|
test("returns fallback immediately, generates personalized greetings in the background, then reuses the cache", async () => {
|
|
624
|
-
assistantFeatureFlags["empty-state-dynamic-greetings"] = true;
|
|
625
|
-
|
|
626
570
|
const workspaceDir = getWorkspaceDir();
|
|
627
571
|
writeFileSync(
|
|
628
572
|
join(workspaceDir, "IDENTITY.md"),
|
|
@@ -655,7 +599,9 @@ describe("identity routes — intro greetings", () => {
|
|
|
655
599
|
);
|
|
656
600
|
expect(route).toBeDefined();
|
|
657
601
|
|
|
658
|
-
const body = route!.handler({
|
|
602
|
+
const body = route!.handler({
|
|
603
|
+
queryParams: { localHour: "8", localMinute: "15" },
|
|
604
|
+
}) as {
|
|
659
605
|
greetings: string[];
|
|
660
606
|
text: string;
|
|
661
607
|
source: string;
|
|
@@ -683,6 +629,14 @@ describe("identity routes — intro greetings", () => {
|
|
|
683
629
|
expect(sidechainCalls).toHaveLength(1);
|
|
684
630
|
expect(sidechainCalls[0]?.callSite).toBe("emptyStateGreeting");
|
|
685
631
|
expect(sidechainCalls[0]?.tools).toEqual([]);
|
|
632
|
+
expect(sidechainCalls[0]?.content).toContain("Generate 5 short");
|
|
633
|
+
expect(sidechainCalls[0]?.content).not.toContain("Current time of day:");
|
|
634
|
+
expect(sidechainCalls[0]?.content).toContain(
|
|
635
|
+
"do not mention the current time",
|
|
636
|
+
);
|
|
637
|
+
expect(sidechainCalls[0]?.content).toMatch(
|
|
638
|
+
/Current user-local time for subtle tone only: morning \(08:15\)\.$/,
|
|
639
|
+
);
|
|
686
640
|
expect(sidechainCalls[0]?.content).toContain("JSON array");
|
|
687
641
|
expect(sidechainCalls[0]?.systemPrompt).toContain(
|
|
688
642
|
"Identity sentinel: chartreuse compass.",
|
|
@@ -695,6 +649,9 @@ describe("identity routes — intro greetings", () => {
|
|
|
695
649
|
"Charting the next useful thing?",
|
|
696
650
|
"I brought the compass. Where to?",
|
|
697
651
|
"Ready to make this lighter.",
|
|
652
|
+
"Morning momentum?",
|
|
653
|
+
"Five options, one good start.",
|
|
654
|
+
"A useful next step?",
|
|
698
655
|
]),
|
|
699
656
|
hadTextDeltas: false,
|
|
700
657
|
response: { content: [] },
|
|
@@ -718,6 +675,8 @@ describe("identity routes — intro greetings", () => {
|
|
|
718
675
|
"Charting the next useful thing?",
|
|
719
676
|
"I brought the compass. Where to?",
|
|
720
677
|
"Ready to make this lighter.",
|
|
678
|
+
"Five options, one good start.",
|
|
679
|
+
"A useful next step?",
|
|
721
680
|
],
|
|
722
681
|
text: "Charting the next useful thing?",
|
|
723
682
|
source: "cache",
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
*
|
|
6
6
|
* 1. The default injectors ({@link defaultInjectors}) are listed in the
|
|
7
7
|
* documented order (disk-pressure-warning → workspace-context →
|
|
8
|
-
* background-turn → unified-turn-context →
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* background-turn → unified-turn-context → config-quarantine-notice →
|
|
9
|
+
* pkb-context → pkb-reminder → memory-v2-static → now-md →
|
|
10
|
+
* active-documents → document-comments → subagent-status →
|
|
11
|
+
* slack-messages → thread-focus).
|
|
11
12
|
* 2. The assembled {@link injectorChain} sorts the defaults together with the
|
|
12
13
|
* memory-v3 injector by ascending `order`, so memory-v3 (order 1000) lands
|
|
13
14
|
* last.
|
|
@@ -298,6 +299,7 @@ describe("injector chain", () => {
|
|
|
298
299
|
"workspace-context",
|
|
299
300
|
"background-turn",
|
|
300
301
|
"unified-turn-context",
|
|
302
|
+
"config-quarantine-notice",
|
|
301
303
|
"pkb-context",
|
|
302
304
|
"pkb-reminder",
|
|
303
305
|
"memory-v2-static",
|
|
@@ -324,6 +326,9 @@ describe("injector chain", () => {
|
|
|
324
326
|
expect(byName.get("unified-turn-context")).toBe(
|
|
325
327
|
DEFAULT_INJECTOR_ORDER.unifiedTurnContext,
|
|
326
328
|
);
|
|
329
|
+
expect(byName.get("config-quarantine-notice")).toBe(
|
|
330
|
+
DEFAULT_INJECTOR_ORDER.configQuarantineNotice,
|
|
331
|
+
);
|
|
327
332
|
expect(byName.get("pkb-context")).toBe(DEFAULT_INJECTOR_ORDER.pkbContext);
|
|
328
333
|
expect(byName.get("pkb-reminder")).toBe(DEFAULT_INJECTOR_ORDER.pkbReminder);
|
|
329
334
|
expect(byName.get("memory-v2-static")).toBe(
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
DEFAULT_INJECTOR_ORDER,
|
|
7
|
+
defaultInjectors,
|
|
8
|
+
} from "../plugins/defaults/memory-retrieval/injectors.js";
|
|
9
|
+
import type { Injector, TurnContext } from "../plugins/types.js";
|
|
10
|
+
import { getConfigQuarantineNoticePath } from "../util/platform.js";
|
|
11
|
+
|
|
12
|
+
function findInjector(name: string): Injector {
|
|
13
|
+
const injector = defaultInjectors.find(
|
|
14
|
+
(candidate) => candidate.name === name,
|
|
15
|
+
);
|
|
16
|
+
if (!injector) {
|
|
17
|
+
throw new Error(`injector '${name}' not registered`);
|
|
18
|
+
}
|
|
19
|
+
return injector;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function makeContext(overrides: Partial<TurnContext> = {}): TurnContext {
|
|
23
|
+
return {
|
|
24
|
+
requestId: "req-test",
|
|
25
|
+
conversationId: "conv-test",
|
|
26
|
+
turnIndex: 0,
|
|
27
|
+
trust: { sourceChannel: "vellum", trustClass: "guardian" },
|
|
28
|
+
...overrides,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const NOTICE_PATH = getConfigQuarantineNoticePath();
|
|
33
|
+
|
|
34
|
+
function writeNotice(quarantinedAt: string): void {
|
|
35
|
+
const dir = dirname(NOTICE_PATH);
|
|
36
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
37
|
+
writeFileSync(
|
|
38
|
+
NOTICE_PATH,
|
|
39
|
+
JSON.stringify(
|
|
40
|
+
{
|
|
41
|
+
quarantinedAt,
|
|
42
|
+
quarantinePath: "/ws/config.json.corrupt-x.json",
|
|
43
|
+
originalPath: "/ws/config.json",
|
|
44
|
+
},
|
|
45
|
+
null,
|
|
46
|
+
2,
|
|
47
|
+
),
|
|
48
|
+
"utf-8",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function clearNotice(): void {
|
|
53
|
+
rmSync(NOTICE_PATH, { force: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const injector = findInjector("config-quarantine-notice");
|
|
57
|
+
|
|
58
|
+
describe("config-quarantine-notice injector", () => {
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
clearNotice();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
afterEach(() => {
|
|
64
|
+
clearNotice();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("registered at the expected order with prepend-user-tail placement", async () => {
|
|
68
|
+
expect(injector.order).toBe(DEFAULT_INJECTOR_ORDER.configQuarantineNotice);
|
|
69
|
+
writeNotice(new Date().toISOString());
|
|
70
|
+
const block = await injector.produce(makeContext());
|
|
71
|
+
expect(block?.placement).toBe("prepend-user-tail");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("returns null when no sentinel is present", async () => {
|
|
75
|
+
const block = await injector.produce(makeContext());
|
|
76
|
+
expect(block).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("injects a config-reset block when the sentinel is present and fresh", async () => {
|
|
80
|
+
writeNotice(new Date().toISOString());
|
|
81
|
+
|
|
82
|
+
const block = await injector.produce(makeContext());
|
|
83
|
+
expect(block).not.toBeNull();
|
|
84
|
+
expect(block?.id).toBe("config-quarantine-notice");
|
|
85
|
+
expect(block?.text).toContain("<config_reset_notice>");
|
|
86
|
+
expect(block?.text).toContain("</config_reset_notice>");
|
|
87
|
+
expect(block?.text).toContain("/ws/config.json.corrupt-x.json");
|
|
88
|
+
// The sentinel is left in place for subsequent turns until it ages out.
|
|
89
|
+
expect(existsSync(NOTICE_PATH)).toBe(true);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("injects nothing on non-guardian turns even when the sentinel is fresh", async () => {
|
|
93
|
+
writeNotice(new Date().toISOString());
|
|
94
|
+
|
|
95
|
+
for (const trustClass of ["trusted_contact", "unknown"] as const) {
|
|
96
|
+
const block = await injector.produce(
|
|
97
|
+
makeContext({ trust: { sourceChannel: "slack", trustClass } }),
|
|
98
|
+
);
|
|
99
|
+
expect(block).toBeNull();
|
|
100
|
+
}
|
|
101
|
+
// The sentinel is untouched — it remains for guardian turns.
|
|
102
|
+
expect(existsSync(NOTICE_PATH)).toBe(true);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("deletes the sentinel and injects nothing when it is stale (>7 days)", async () => {
|
|
106
|
+
const eightDaysAgo = new Date(
|
|
107
|
+
Date.now() - 8 * 24 * 60 * 60 * 1000,
|
|
108
|
+
).toISOString();
|
|
109
|
+
writeNotice(eightDaysAgo);
|
|
110
|
+
|
|
111
|
+
const block = await injector.produce(makeContext());
|
|
112
|
+
expect(block).toBeNull();
|
|
113
|
+
expect(existsSync(NOTICE_PATH)).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -309,6 +309,22 @@ describe("LLM catalog parity: daemon vs client", () => {
|
|
|
309
309
|
});
|
|
310
310
|
});
|
|
311
311
|
|
|
312
|
+
test("MiniMax catalog includes MiniMax M3", () => {
|
|
313
|
+
const minimax = PROVIDER_CATALOG.find((entry) => entry.id === "minimax");
|
|
314
|
+
expect(
|
|
315
|
+
minimax?.models.find((model) => model.id === "MiniMax-M3"),
|
|
316
|
+
).toMatchObject({
|
|
317
|
+
displayName: "MiniMax M3",
|
|
318
|
+
contextWindowTokens: 1000000,
|
|
319
|
+
defaultContextWindowTokens: 200000,
|
|
320
|
+
maxOutputTokens: 512000,
|
|
321
|
+
longContextMode: "native-model",
|
|
322
|
+
supportsThinking: true,
|
|
323
|
+
supportsVision: true,
|
|
324
|
+
supportsToolUse: true,
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
312
328
|
// -----------------------------------------------------------------------
|
|
313
329
|
// pricing.ts ↔ model-catalog.ts parity
|
|
314
330
|
//
|
|
@@ -1653,6 +1653,17 @@ describe("queryUnreportedUsageEvents", () => {
|
|
|
1653
1653
|
expect(events[0].turnIndex).toBe(0);
|
|
1654
1654
|
});
|
|
1655
1655
|
|
|
1656
|
+
test("surfaces llmCallCount on unreported events", () => {
|
|
1657
|
+
insertEventAt(1000, { llmCallCount: 4 });
|
|
1658
|
+
insertEventAt(2000, {});
|
|
1659
|
+
|
|
1660
|
+
const events = queryUnreportedUsageEvents(0, undefined, 100);
|
|
1661
|
+
expect(events).toHaveLength(2);
|
|
1662
|
+
expect(events[0].llmCallCount).toBe(4);
|
|
1663
|
+
// recordUsageEvent defaults llmCallCount to 1 when unset.
|
|
1664
|
+
expect(events[1].llmCallCount).toBe(1);
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1656
1667
|
test("surfaces raw_usage on unreported events", () => {
|
|
1657
1668
|
// The telemetry reporter consumes `queryUnreportedUsageEvents` output
|
|
1658
1669
|
// directly and forwards it to BigQuery. If the SELECT projection
|