@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
|
@@ -150,9 +150,6 @@ describe("DefaultPathResolver prompts/USER.md translation", () => {
|
|
|
150
150
|
expect(resolver.resolve("prompts/SOUL.md")).toBe(
|
|
151
151
|
join(WORKSPACE_ROOT, "SOUL.md"),
|
|
152
152
|
);
|
|
153
|
-
expect(resolver.resolve("prompts/UPDATES.md")).toBe(
|
|
154
|
-
join(WORKSPACE_ROOT, "UPDATES.md"),
|
|
155
|
-
);
|
|
156
153
|
});
|
|
157
154
|
|
|
158
155
|
test("skips unknown prompt filenames regardless of guardian state", () => {
|
|
@@ -230,6 +227,71 @@ describe("analyzeImport for legacy prompts/USER.md", () => {
|
|
|
230
227
|
const userMd = report.files.find((f) => f.path === "prompts/USER.md");
|
|
231
228
|
expect(userMd!.action).toBe("skip");
|
|
232
229
|
});
|
|
230
|
+
|
|
231
|
+
test("retired workspace/UPDATES.md (current format) is a non-blocking skip", () => {
|
|
232
|
+
const resolver = new DefaultPathResolver(
|
|
233
|
+
WORKSPACE_ROOT,
|
|
234
|
+
undefined,
|
|
235
|
+
() => null,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
const { manifest } = buildVBundle({
|
|
239
|
+
files: [
|
|
240
|
+
{
|
|
241
|
+
path: "data/db/assistant.db",
|
|
242
|
+
data: new Uint8Array(),
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
path: "workspace/UPDATES.md",
|
|
246
|
+
data: new TextEncoder().encode("## Old release notes\n"),
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
...defaultV1Options(),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const report = analyzeImport({ manifest, pathResolver: resolver });
|
|
253
|
+
|
|
254
|
+
expect(report.can_import).toBe(true);
|
|
255
|
+
expect(report.conflicts).toHaveLength(0);
|
|
256
|
+
const updatesMd = report.files.find(
|
|
257
|
+
(f) => f.path === "workspace/UPDATES.md",
|
|
258
|
+
);
|
|
259
|
+
expect(updatesMd).toBeDefined();
|
|
260
|
+
expect(updatesMd!.action).toBe("skip");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("retired prompts/UPDATES.md in a legacy bundle is a non-blocking skip", () => {
|
|
264
|
+
const resolver = new DefaultPathResolver(
|
|
265
|
+
WORKSPACE_ROOT,
|
|
266
|
+
undefined,
|
|
267
|
+
() => null,
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const { manifest } = buildVBundle({
|
|
271
|
+
files: [
|
|
272
|
+
{
|
|
273
|
+
path: "data/db/assistant.db",
|
|
274
|
+
data: new Uint8Array(),
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
path: "prompts/UPDATES.md",
|
|
278
|
+
data: new TextEncoder().encode("## Old release notes\n"),
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
...defaultV1Options(),
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const report = analyzeImport({ manifest, pathResolver: resolver });
|
|
285
|
+
|
|
286
|
+
// Bundles exported while the update-bulletin feature existed must stay
|
|
287
|
+
// restorable — the retired file is dropped, never written back, and
|
|
288
|
+
// must not register an UNKNOWN_ARCHIVE_PATH conflict.
|
|
289
|
+
expect(report.can_import).toBe(true);
|
|
290
|
+
expect(report.conflicts).toHaveLength(0);
|
|
291
|
+
const updatesMd = report.files.find((f) => f.path === "prompts/UPDATES.md");
|
|
292
|
+
expect(updatesMd).toBeDefined();
|
|
293
|
+
expect(updatesMd!.action).toBe("skip");
|
|
294
|
+
});
|
|
233
295
|
});
|
|
234
296
|
|
|
235
297
|
// ---------------------------------------------------------------------------
|
|
@@ -358,6 +420,91 @@ describe("commitImport for legacy prompts/USER.md", () => {
|
|
|
358
420
|
expect(existsSync(join(USERS_DIR, "USER.md"))).toBe(false);
|
|
359
421
|
});
|
|
360
422
|
|
|
423
|
+
test("retired workspace/UPDATES.md (current format) is never written back", () => {
|
|
424
|
+
const resolver = new DefaultPathResolver(
|
|
425
|
+
WORKSPACE_ROOT,
|
|
426
|
+
undefined,
|
|
427
|
+
() => null,
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
const { archive } = buildVBundle({
|
|
431
|
+
files: [
|
|
432
|
+
{
|
|
433
|
+
path: "data/db/assistant.db",
|
|
434
|
+
data: new Uint8Array(),
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
path: "workspace/UPDATES.md",
|
|
438
|
+
data: new TextEncoder().encode("## Old release notes\n"),
|
|
439
|
+
},
|
|
440
|
+
],
|
|
441
|
+
...defaultV1Options(),
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
const result = commitImport({
|
|
445
|
+
archiveData: archive,
|
|
446
|
+
pathResolver: resolver,
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
expect(result.ok).toBe(true);
|
|
450
|
+
if (result.ok) {
|
|
451
|
+
expect(result.report.success).toBe(true);
|
|
452
|
+
expect(
|
|
453
|
+
result.report.files.find((f) => f.path === "workspace/UPDATES.md")
|
|
454
|
+
?.action,
|
|
455
|
+
).toBe("skipped");
|
|
456
|
+
expect(result.report.warnings.some((w) => w.includes("UPDATES.md"))).toBe(
|
|
457
|
+
false,
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// The retired bulletin file must not be resurrected in the workspace.
|
|
462
|
+
expect(existsSync(join(WORKSPACE_ROOT, "UPDATES.md"))).toBe(false);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
test("retired prompts/UPDATES.md skips silently — no warning, nothing written", () => {
|
|
466
|
+
const resolver = new DefaultPathResolver(
|
|
467
|
+
WORKSPACE_ROOT,
|
|
468
|
+
undefined,
|
|
469
|
+
() => null,
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
const { archive } = buildVBundle({
|
|
473
|
+
files: [
|
|
474
|
+
{
|
|
475
|
+
path: "data/db/assistant.db",
|
|
476
|
+
data: new Uint8Array(),
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
path: "prompts/UPDATES.md",
|
|
480
|
+
data: new TextEncoder().encode("## Old release notes\n"),
|
|
481
|
+
},
|
|
482
|
+
],
|
|
483
|
+
...defaultV1Options(),
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
const result = commitImport({
|
|
487
|
+
archiveData: archive,
|
|
488
|
+
pathResolver: resolver,
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
expect(result.ok).toBe(true);
|
|
492
|
+
if (result.ok) {
|
|
493
|
+
expect(result.report.success).toBe(true);
|
|
494
|
+
expect(
|
|
495
|
+
result.report.files.find((f) => f.path === "prompts/UPDATES.md")
|
|
496
|
+
?.action,
|
|
497
|
+
).toBe("skipped");
|
|
498
|
+
// Silent skip: the commit report must agree with preflight, which
|
|
499
|
+
// raises neither a conflict nor a warning for retired paths.
|
|
500
|
+
expect(
|
|
501
|
+
result.report.warnings.some((w) => w.includes("prompts/UPDATES.md")),
|
|
502
|
+
).toBe(false);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
expect(existsSync(join(WORKSPACE_ROOT, "UPDATES.md"))).toBe(false);
|
|
506
|
+
});
|
|
507
|
+
|
|
361
508
|
test("does NOT overwrite a customized users/<slug>.md", () => {
|
|
362
509
|
const slug = "captain.md";
|
|
363
510
|
const guardianPath = join(USERS_DIR, slug);
|
|
@@ -19,6 +19,7 @@ import { join, resolve } from "node:path";
|
|
|
19
19
|
|
|
20
20
|
import { resolveGuardianPersonaPath } from "../../prompts/persona-resolver.js";
|
|
21
21
|
import { getLogger } from "../../util/logger.js";
|
|
22
|
+
import { isRetiredArchivePath } from "./vbundle-import-policy.js";
|
|
22
23
|
import type { ManifestType } from "./vbundle-validator.js";
|
|
23
24
|
|
|
24
25
|
const log = getLogger("vbundle-import-analyzer");
|
|
@@ -30,12 +31,7 @@ const log = getLogger("vbundle-import-analyzer");
|
|
|
30
31
|
* on import, its content is translated to `users/<slug>.md` at the
|
|
31
32
|
* current guardian's location (see `DefaultPathResolver.resolve`).
|
|
32
33
|
*/
|
|
33
|
-
const ALLOWED_PROMPT_FILENAMES = new Set([
|
|
34
|
-
"IDENTITY.md",
|
|
35
|
-
"SOUL.md",
|
|
36
|
-
"USER.md",
|
|
37
|
-
"UPDATES.md",
|
|
38
|
-
]);
|
|
34
|
+
const ALLOWED_PROMPT_FILENAMES = new Set(["IDENTITY.md", "SOUL.md", "USER.md"]);
|
|
39
35
|
|
|
40
36
|
/** Archive path for the legacy guardian user persona file. */
|
|
41
37
|
const LEGACY_USER_MD_ARCHIVE_PATH = "prompts/USER.md";
|
|
@@ -123,6 +119,14 @@ export class DefaultPathResolver implements PathResolver {
|
|
|
123
119
|
return null;
|
|
124
120
|
}
|
|
125
121
|
|
|
122
|
+
// Retired-feature files (both legacy and workspace-format spellings)
|
|
123
|
+
// must never resolve to a disk target — resolving would write them
|
|
124
|
+
// back into the workspace on import. The analyzer and both importers
|
|
125
|
+
// turn the resulting null into a silent non-blocking skip.
|
|
126
|
+
if (isRetiredArchivePath(archivePath)) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
126
130
|
// New format: workspace/ prefix — maps directly into the workspace dir
|
|
127
131
|
if (archivePath.startsWith("workspace/") && this.workspaceDir) {
|
|
128
132
|
const relPath = archivePath.slice("workspace/".length);
|
|
@@ -288,6 +292,25 @@ export function analyzeImport(
|
|
|
288
292
|
continue;
|
|
289
293
|
}
|
|
290
294
|
|
|
295
|
+
// Retired-feature file from a legacy bundle: nothing consumes it
|
|
296
|
+
// anymore, so drop it silently instead of blocking the restore with
|
|
297
|
+
// an UNKNOWN_ARCHIVE_PATH conflict.
|
|
298
|
+
if (isRetiredArchivePath(fileEntry.path)) {
|
|
299
|
+
log.info(
|
|
300
|
+
{ path: fileEntry.path },
|
|
301
|
+
"Retired archive path in legacy bundle — will be skipped on import",
|
|
302
|
+
);
|
|
303
|
+
files.push({
|
|
304
|
+
path: fileEntry.path,
|
|
305
|
+
action: "skip",
|
|
306
|
+
bundle_size: fileEntry.size_bytes,
|
|
307
|
+
bundle_sha256: fileEntry.sha256,
|
|
308
|
+
current_size: null,
|
|
309
|
+
current_sha256: null,
|
|
310
|
+
});
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
|
|
291
314
|
// Unknown archive path — would have nowhere to write
|
|
292
315
|
conflicts.push({
|
|
293
316
|
code: "UNKNOWN_ARCHIVE_PATH",
|
|
@@ -34,6 +34,29 @@ export function isLegacyPersonaArchivePath(archivePath: string): boolean {
|
|
|
34
34
|
return archivePath === LEGACY_USER_MD_ARCHIVE_PATH;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Archive paths for retired features. Bundles exported while those features
|
|
39
|
+
* existed still contain these entries; preflight and both importers must
|
|
40
|
+
* treat them as silent non-blocking skips (no conflict, no warning) so old
|
|
41
|
+
* backups stay restorable without noise, and the files are never written
|
|
42
|
+
* back to the workspace.
|
|
43
|
+
*
|
|
44
|
+
* `UPDATES.md` carried the retired update-bulletin release notes. Legacy
|
|
45
|
+
* bundles archived it as `prompts/UPDATES.md`; current-format bundles walk
|
|
46
|
+
* the workspace dir, so it appears as `workspace/UPDATES.md`. Both spellings
|
|
47
|
+
* must be retired — `DefaultPathResolver.resolve()` short-circuits them to
|
|
48
|
+
* `null` before any prefix handling so the workspace-format entry cannot
|
|
49
|
+
* resolve normally and recreate the file on import.
|
|
50
|
+
*/
|
|
51
|
+
export const RETIRED_ARCHIVE_PATHS: ReadonlySet<string> = new Set([
|
|
52
|
+
"prompts/UPDATES.md",
|
|
53
|
+
"workspace/UPDATES.md",
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
export function isRetiredArchivePath(archivePath: string): boolean {
|
|
57
|
+
return RETIRED_ARCHIVE_PATHS.has(archivePath);
|
|
58
|
+
}
|
|
59
|
+
|
|
37
60
|
export function isConfigArchivePath(archivePath: string): boolean {
|
|
38
61
|
return CONFIG_ARCHIVE_PATHS.has(archivePath);
|
|
39
62
|
}
|
|
@@ -322,7 +322,10 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
|
|
|
322
322
|
const diskPath = pathResolver.resolve(fileEntry.path);
|
|
323
323
|
|
|
324
324
|
if (!diskPath) {
|
|
325
|
-
// Unknown archive path — skip it
|
|
325
|
+
// Unknown archive path — skip it. Retired-feature paths (see
|
|
326
|
+
// `policy.RETIRED_ARCHIVE_PATHS`) are expected in legacy bundles and
|
|
327
|
+
// skip silently so the import report matches preflight; anything else
|
|
328
|
+
// unresolvable earns a warning.
|
|
326
329
|
importedFiles.push({
|
|
327
330
|
path: fileEntry.path,
|
|
328
331
|
disk_path: "",
|
|
@@ -331,9 +334,11 @@ export function commitImport(options: ImportCommitOptions): ImportCommitResult {
|
|
|
331
334
|
sha256: fileEntry.sha256,
|
|
332
335
|
backup_path: null,
|
|
333
336
|
});
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
+
if (!policy.isRetiredArchivePath(fileEntry.path)) {
|
|
338
|
+
warnings.push(
|
|
339
|
+
`Skipped "${fileEntry.path}": no known disk target for this archive path`,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
337
342
|
continue;
|
|
338
343
|
}
|
|
339
344
|
|
|
@@ -695,9 +695,14 @@ export async function streamCommitImport(
|
|
|
695
695
|
sha256: expectedEntry.sha256,
|
|
696
696
|
backup_path: null,
|
|
697
697
|
});
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
698
|
+
// Retired-feature paths (see `policy.RETIRED_ARCHIVE_PATHS`) are
|
|
699
|
+
// expected in legacy bundles and skip silently so the import report
|
|
700
|
+
// matches preflight; anything else unresolvable earns a warning.
|
|
701
|
+
if (!policy.isRetiredArchivePath(archivePath)) {
|
|
702
|
+
warnings.push(
|
|
703
|
+
`Skipped "${archivePath}": no known disk target for this archive path`,
|
|
704
|
+
);
|
|
705
|
+
}
|
|
701
706
|
seen.add(archivePath);
|
|
702
707
|
onProgress?.({
|
|
703
708
|
archivePath,
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* interaction resumes, so re-querying on miss is the correct behavior.)
|
|
26
26
|
*
|
|
27
27
|
* Callers should treat the gate as advisory + defense-in-depth: prefer to
|
|
28
|
-
* skip at the service level (heartbeat,
|
|
28
|
+
* skip at the service level (heartbeat, etc.) so no run
|
|
29
29
|
* row / conversation row is created at all, and rely on the gate inside
|
|
30
30
|
* `runBackgroundJob` as the universal backstop.
|
|
31
31
|
*/
|
|
@@ -34,6 +34,7 @@ mock.module("../../../config/loader.js", () => ({
|
|
|
34
34
|
// Source + conversation-crud module mocks
|
|
35
35
|
// ---------------------------------------------------------------------
|
|
36
36
|
|
|
37
|
+
import type { CompactionLogEvent } from "../../../memory/compaction-log-store-clickhouse.js";
|
|
37
38
|
import type { LogRow } from "../../../memory/llm-request-log-store.js";
|
|
38
39
|
|
|
39
40
|
interface FakeSourceState {
|
|
@@ -41,6 +42,9 @@ interface FakeSourceState {
|
|
|
41
42
|
selectedCall: LogRow | null;
|
|
42
43
|
turnBounds: { startTime: number; endTime: number } | null;
|
|
43
44
|
compactionLogs: LogRow[];
|
|
45
|
+
/** null = compactionLogs destination not configured (legacy-only). */
|
|
46
|
+
compactionStoreEvents: CompactionLogEvent[] | null;
|
|
47
|
+
compactionStoreError: Error | null;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
const state: FakeSourceState = {
|
|
@@ -48,6 +52,8 @@ const state: FakeSourceState = {
|
|
|
48
52
|
selectedCall: null,
|
|
49
53
|
turnBounds: null,
|
|
50
54
|
compactionLogs: [],
|
|
55
|
+
compactionStoreEvents: null,
|
|
56
|
+
compactionStoreError: null,
|
|
51
57
|
};
|
|
52
58
|
|
|
53
59
|
// Records the inputs the handler passed to its collaborators so tests
|
|
@@ -63,8 +69,34 @@ const sourceCalls = {
|
|
|
63
69
|
afterCreatedAt: number | null;
|
|
64
70
|
beforeCreatedAt: number;
|
|
65
71
|
}>,
|
|
72
|
+
getEventsBetweenArgs: [] as Array<{
|
|
73
|
+
conversationId: string;
|
|
74
|
+
afterStartedAt: number | null;
|
|
75
|
+
beforeStartedAt: number;
|
|
76
|
+
}>,
|
|
66
77
|
};
|
|
67
78
|
|
|
79
|
+
mock.module("../../../memory/compaction-log-store-clickhouse.js", () => ({
|
|
80
|
+
getCompactionLogStore: () =>
|
|
81
|
+
state.compactionStoreEvents === null && state.compactionStoreError === null
|
|
82
|
+
? null
|
|
83
|
+
: {
|
|
84
|
+
getEventsBetween: async (
|
|
85
|
+
conversationId: string,
|
|
86
|
+
afterStartedAt: number | null,
|
|
87
|
+
beforeStartedAt: number,
|
|
88
|
+
) => {
|
|
89
|
+
sourceCalls.getEventsBetweenArgs.push({
|
|
90
|
+
conversationId,
|
|
91
|
+
afterStartedAt,
|
|
92
|
+
beforeStartedAt,
|
|
93
|
+
});
|
|
94
|
+
if (state.compactionStoreError) throw state.compactionStoreError;
|
|
95
|
+
return state.compactionStoreEvents ?? [];
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
}));
|
|
99
|
+
|
|
68
100
|
mock.module("../../../memory/conversation-crud.js", () => ({
|
|
69
101
|
getConversation: (id: string) =>
|
|
70
102
|
state.conversation && state.conversation.id === id
|
|
@@ -105,6 +137,7 @@ mock.module("../../../memory/llm-request-log-source.js", () => ({
|
|
|
105
137
|
|
|
106
138
|
// Imported AFTER the mocks so the handler picks up the fakes.
|
|
107
139
|
import {
|
|
140
|
+
projectCompactionLogEventToTrailEvent,
|
|
108
141
|
projectLogRowToCompactionTrailEvent,
|
|
109
142
|
ROUTES,
|
|
110
143
|
} from "../conversation-compaction-routes.js";
|
|
@@ -132,14 +165,52 @@ function fakeLogRow(overrides: Partial<LogRow> = {}): LogRow {
|
|
|
132
165
|
};
|
|
133
166
|
}
|
|
134
167
|
|
|
168
|
+
function fakeCompactionLogEvent(
|
|
169
|
+
overrides: Partial<CompactionLogEvent> = {},
|
|
170
|
+
): CompactionLogEvent {
|
|
171
|
+
return {
|
|
172
|
+
compactionId: "comp-1",
|
|
173
|
+
requestId: "req-1",
|
|
174
|
+
trigger: "budget",
|
|
175
|
+
startedAt: 3000,
|
|
176
|
+
finishedAt: 3500,
|
|
177
|
+
durationMs: 500,
|
|
178
|
+
preMessageCount: 12,
|
|
179
|
+
resultMessageCount: 4,
|
|
180
|
+
compacted: true,
|
|
181
|
+
previousEstimatedInputTokens: 900,
|
|
182
|
+
estimatedInputTokens: 300,
|
|
183
|
+
maxInputTokens: 1000,
|
|
184
|
+
thresholdTokens: 850,
|
|
185
|
+
compactedMessages: 10,
|
|
186
|
+
compactedPersistedMessages: 8,
|
|
187
|
+
preservedTailMessages: 2,
|
|
188
|
+
summaryCalls: 1,
|
|
189
|
+
summaryInputTokens: 880,
|
|
190
|
+
summaryOutputTokens: 120,
|
|
191
|
+
summaryModel: "test-model",
|
|
192
|
+
summaryFailed: false,
|
|
193
|
+
reason: "auto",
|
|
194
|
+
exhausted: false,
|
|
195
|
+
injectionMode: null,
|
|
196
|
+
autoCompressApplied: false,
|
|
197
|
+
summaryText: "summary text",
|
|
198
|
+
completed: true,
|
|
199
|
+
...overrides,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
135
203
|
beforeEach(() => {
|
|
136
204
|
state.conversation = null;
|
|
137
205
|
state.selectedCall = null;
|
|
138
206
|
state.turnBounds = null;
|
|
139
207
|
state.compactionLogs = [];
|
|
208
|
+
state.compactionStoreEvents = null;
|
|
209
|
+
state.compactionStoreError = null;
|
|
140
210
|
sourceCalls.getRequestLogByIdArgs.length = 0;
|
|
141
211
|
sourceCalls.getTurnTimeBoundsArgs.length = 0;
|
|
142
212
|
sourceCalls.getCompactionLogsBetweenArgs.length = 0;
|
|
213
|
+
sourceCalls.getEventsBetweenArgs.length = 0;
|
|
143
214
|
});
|
|
144
215
|
|
|
145
216
|
// ---------------------------------------------------------------------
|
|
@@ -353,10 +424,180 @@ describe("handleGetCompactionTrail — happy path", () => {
|
|
|
353
424
|
});
|
|
354
425
|
});
|
|
355
426
|
|
|
427
|
+
// ---------------------------------------------------------------------
|
|
428
|
+
// Handler — compaction-log store path
|
|
429
|
+
// ---------------------------------------------------------------------
|
|
430
|
+
|
|
431
|
+
describe("handleGetCompactionTrail — compaction log store", () => {
|
|
432
|
+
test("serves the trail from the store and skips the legacy projection", async () => {
|
|
433
|
+
state.conversation = { id: "conv-1" };
|
|
434
|
+
state.selectedCall = fakeLogRow({
|
|
435
|
+
id: "call-1",
|
|
436
|
+
conversationId: "conv-1",
|
|
437
|
+
createdAt: 5000,
|
|
438
|
+
});
|
|
439
|
+
state.turnBounds = { startTime: 2000, endTime: 9000 };
|
|
440
|
+
state.compactionStoreEvents = [fakeCompactionLogEvent()];
|
|
441
|
+
|
|
442
|
+
const result = await handler({
|
|
443
|
+
pathParams: { id: "conv-1" },
|
|
444
|
+
queryParams: { callId: "call-1" },
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// Same ±1ms-shifted turn window as the legacy path.
|
|
448
|
+
expect(sourceCalls.getEventsBetweenArgs).toEqual([
|
|
449
|
+
{
|
|
450
|
+
conversationId: "conv-1",
|
|
451
|
+
afterStartedAt: 1999,
|
|
452
|
+
beforeStartedAt: 9001,
|
|
453
|
+
},
|
|
454
|
+
]);
|
|
455
|
+
expect(sourceCalls.getCompactionLogsBetweenArgs).toEqual([]);
|
|
456
|
+
expect(result.events).toEqual([
|
|
457
|
+
{
|
|
458
|
+
id: "comp-1",
|
|
459
|
+
createdAt: 3000,
|
|
460
|
+
model: "test-model",
|
|
461
|
+
provider: null,
|
|
462
|
+
inputTokens: 880,
|
|
463
|
+
outputTokens: 120,
|
|
464
|
+
durationMs: 500,
|
|
465
|
+
responsePreview: "summary text",
|
|
466
|
+
requestMessageCount: 12,
|
|
467
|
+
stopReason: "auto",
|
|
468
|
+
estimatedCostUsd: null,
|
|
469
|
+
},
|
|
470
|
+
]);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("falls back to the legacy projection when the store has no rows for the window", async () => {
|
|
474
|
+
state.conversation = { id: "conv-1" };
|
|
475
|
+
state.selectedCall = fakeLogRow({
|
|
476
|
+
id: "call-1",
|
|
477
|
+
conversationId: "conv-1",
|
|
478
|
+
createdAt: 5000,
|
|
479
|
+
});
|
|
480
|
+
state.turnBounds = { startTime: 2000, endTime: 9000 };
|
|
481
|
+
state.compactionStoreEvents = [];
|
|
482
|
+
state.compactionLogs = [
|
|
483
|
+
fakeLogRow({ id: "compaction-legacy", conversationId: "conv-1" }),
|
|
484
|
+
];
|
|
485
|
+
|
|
486
|
+
const result = await handler({
|
|
487
|
+
pathParams: { id: "conv-1" },
|
|
488
|
+
queryParams: { callId: "call-1" },
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
expect(sourceCalls.getEventsBetweenArgs).toHaveLength(1);
|
|
492
|
+
expect(sourceCalls.getCompactionLogsBetweenArgs).toHaveLength(1);
|
|
493
|
+
expect(result.events).toHaveLength(1);
|
|
494
|
+
expect((result.events[0] as { id: string }).id).toBe("compaction-legacy");
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
test("falls back to the legacy projection when the store read throws", async () => {
|
|
498
|
+
state.conversation = { id: "conv-1" };
|
|
499
|
+
state.selectedCall = fakeLogRow({
|
|
500
|
+
id: "call-1",
|
|
501
|
+
conversationId: "conv-1",
|
|
502
|
+
createdAt: 5000,
|
|
503
|
+
});
|
|
504
|
+
state.turnBounds = { startTime: 2000, endTime: 9000 };
|
|
505
|
+
state.compactionStoreError = new Error("clickhouse unreachable");
|
|
506
|
+
state.compactionLogs = [
|
|
507
|
+
fakeLogRow({ id: "compaction-legacy", conversationId: "conv-1" }),
|
|
508
|
+
];
|
|
509
|
+
|
|
510
|
+
const result = await handler({
|
|
511
|
+
pathParams: { id: "conv-1" },
|
|
512
|
+
queryParams: { callId: "call-1" },
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
expect(sourceCalls.getCompactionLogsBetweenArgs).toHaveLength(1);
|
|
516
|
+
expect(result.events).toHaveLength(1);
|
|
517
|
+
expect((result.events[0] as { id: string }).id).toBe("compaction-legacy");
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
test("falls back to the legacy projection when any event is missing its end row", async () => {
|
|
521
|
+
state.conversation = { id: "conv-1" };
|
|
522
|
+
state.selectedCall = fakeLogRow({
|
|
523
|
+
id: "call-1",
|
|
524
|
+
conversationId: "conv-1",
|
|
525
|
+
createdAt: 5000,
|
|
526
|
+
});
|
|
527
|
+
state.turnBounds = { startTime: 2000, endTime: 9000 };
|
|
528
|
+
state.compactionStoreEvents = [
|
|
529
|
+
fakeCompactionLogEvent(),
|
|
530
|
+
fakeCompactionLogEvent({
|
|
531
|
+
compactionId: "comp-2",
|
|
532
|
+
finishedAt: null,
|
|
533
|
+
durationMs: null,
|
|
534
|
+
completed: false,
|
|
535
|
+
}),
|
|
536
|
+
];
|
|
537
|
+
state.compactionLogs = [
|
|
538
|
+
fakeLogRow({ id: "compaction-legacy", conversationId: "conv-1" }),
|
|
539
|
+
];
|
|
540
|
+
|
|
541
|
+
const result = await handler({
|
|
542
|
+
pathParams: { id: "conv-1" },
|
|
543
|
+
queryParams: { callId: "call-1" },
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
expect(sourceCalls.getEventsBetweenArgs).toHaveLength(1);
|
|
547
|
+
expect(sourceCalls.getCompactionLogsBetweenArgs).toHaveLength(1);
|
|
548
|
+
expect(result.events).toHaveLength(1);
|
|
549
|
+
expect((result.events[0] as { id: string }).id).toBe("compaction-legacy");
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
356
553
|
// ---------------------------------------------------------------------
|
|
357
554
|
// Projection unit tests
|
|
358
555
|
// ---------------------------------------------------------------------
|
|
359
556
|
|
|
557
|
+
describe("projectCompactionLogEventToTrailEvent", () => {
|
|
558
|
+
test("maps a completed event onto the wire shape", () => {
|
|
559
|
+
const event = projectCompactionLogEventToTrailEvent(
|
|
560
|
+
fakeCompactionLogEvent(),
|
|
561
|
+
);
|
|
562
|
+
expect(event).toEqual({
|
|
563
|
+
id: "comp-1",
|
|
564
|
+
createdAt: 3000,
|
|
565
|
+
model: "test-model",
|
|
566
|
+
provider: null,
|
|
567
|
+
inputTokens: 880,
|
|
568
|
+
outputTokens: 120,
|
|
569
|
+
durationMs: 500,
|
|
570
|
+
responsePreview: "summary text",
|
|
571
|
+
requestMessageCount: 12,
|
|
572
|
+
stopReason: "auto",
|
|
573
|
+
estimatedCostUsd: null,
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
test("maps an incomplete (start-only) event with nulls for end-phase fields", () => {
|
|
578
|
+
const event = projectCompactionLogEventToTrailEvent(
|
|
579
|
+
fakeCompactionLogEvent({
|
|
580
|
+
completed: false,
|
|
581
|
+
finishedAt: null,
|
|
582
|
+
durationMs: null,
|
|
583
|
+
summaryInputTokens: null,
|
|
584
|
+
summaryOutputTokens: null,
|
|
585
|
+
summaryModel: null,
|
|
586
|
+
reason: null,
|
|
587
|
+
summaryText: null,
|
|
588
|
+
}),
|
|
589
|
+
);
|
|
590
|
+
expect(event.durationMs).toBeNull();
|
|
591
|
+
expect(event.model).toBeNull();
|
|
592
|
+
expect(event.inputTokens).toBeNull();
|
|
593
|
+
expect(event.outputTokens).toBeNull();
|
|
594
|
+
expect(event.responsePreview).toBeNull();
|
|
595
|
+
expect(event.stopReason).toBeNull();
|
|
596
|
+
// The start row always carries the pre-compaction message count.
|
|
597
|
+
expect(event.requestMessageCount).toBe(12);
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
|
|
360
601
|
describe("projectLogRowToCompactionTrailEvent", () => {
|
|
361
602
|
test("returns null for every summary-derived field when payloads are empty", () => {
|
|
362
603
|
const event = projectLogRowToCompactionTrailEvent(fakeLogRow());
|