pi-ui-extend 1.0.42 → 1.0.45
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/README.md +41 -12
- package/dist/app/app.d.ts +10 -0
- package/dist/app/app.js +132 -8
- package/dist/app/cli/install.js +2 -2
- package/dist/app/commands/command-controller.d.ts +4 -0
- package/dist/app/commands/command-controller.js +11 -0
- package/dist/app/commands/command-git-actions.d.ts +25 -0
- package/dist/app/commands/command-git-actions.js +381 -0
- package/dist/app/commands/command-host.d.ts +3 -0
- package/dist/app/commands/command-host.js +5 -2
- package/dist/app/commands/command-model-actions.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +51 -34
- package/dist/app/commands/command-navigation-actions.d.ts +1 -0
- package/dist/app/commands/command-navigation-actions.js +16 -3
- package/dist/app/commands/command-registry.d.ts +2 -0
- package/dist/app/commands/command-registry.js +14 -0
- package/dist/app/commands/reload-context-inventory.js +12 -1
- package/dist/app/input/autocomplete-controller.js +61 -34
- package/dist/app/input/input-action-controller.d.ts +2 -0
- package/dist/app/input/input-action-controller.js +8 -1
- package/dist/app/input/input-controller.d.ts +2 -1
- package/dist/app/input/input-controller.js +7 -2
- package/dist/app/input/prompt-enhancer-controller.js +53 -35
- package/dist/app/input/voice-controller.d.ts +41 -45
- package/dist/app/input/voice-controller.js +351 -419
- package/dist/app/model/model-usage-controller.js +17 -7
- package/dist/app/model/model-usage-status.d.ts +4 -1
- package/dist/app/model/model-usage-status.js +270 -46
- package/dist/app/popup/menu-items-controller.d.ts +13 -3
- package/dist/app/popup/menu-items-controller.js +37 -21
- package/dist/app/popup/popup-action-controller.d.ts +12 -2
- package/dist/app/popup/popup-action-controller.js +77 -24
- package/dist/app/popup/popup-menu-controller.d.ts +36 -14
- package/dist/app/popup/popup-menu-controller.js +239 -69
- package/dist/app/rendering/dcp-stats.d.ts +9 -4
- package/dist/app/rendering/dcp-stats.js +40 -425
- package/dist/app/rendering/popup-menu-renderer.d.ts +3 -5
- package/dist/app/rendering/popup-menu-renderer.js +42 -37
- package/dist/app/rendering/render-controller.js +23 -2
- package/dist/app/rendering/status-line-renderer.d.ts +5 -0
- package/dist/app/rendering/status-line-renderer.js +41 -5
- package/dist/app/rendering/tab-line-renderer.js +26 -20
- package/dist/app/runtime.d.ts +12 -1
- package/dist/app/runtime.js +120 -12
- package/dist/app/screen/mouse-controller.d.ts +2 -0
- package/dist/app/screen/mouse-controller.js +17 -7
- package/dist/app/screen/status-controller.d.ts +4 -0
- package/dist/app/screen/status-controller.js +5 -0
- package/dist/app/session/lazy-session-manager.js +34 -0
- package/dist/app/session/session-event-controller.d.ts +1 -0
- package/dist/app/session/session-event-controller.js +10 -1
- package/dist/app/session/session-history.d.ts +1 -0
- package/dist/app/session/session-history.js +12 -1
- package/dist/app/session/session-lifecycle-controller.d.ts +6 -1
- package/dist/app/session/session-lifecycle-controller.js +10 -1
- package/dist/app/session/tabs-controller.d.ts +25 -1
- package/dist/app/session/tabs-controller.js +442 -153
- package/dist/app/subagents/subagents-widget-controller.d.ts +1 -0
- package/dist/app/subagents/subagents-widget-controller.js +6 -0
- package/dist/app/types.d.ts +11 -1
- package/dist/app/workspace/workspace-actions-controller.js +10 -2
- package/dist/app/workspace/workspace-undo.d.ts +1 -0
- package/dist/app/workspace/workspace-undo.js +1 -0
- package/dist/bundled-extensions/question/index.js +9 -1
- package/dist/bundled-extensions/question/remote.d.ts +4 -0
- package/dist/bundled-extensions/question/remote.js +33 -0
- package/dist/bundled-extensions/telegram-connector/bot.d.ts +43 -0
- package/dist/bundled-extensions/telegram-connector/bot.js +166 -0
- package/dist/bundled-extensions/telegram-connector/config.d.ts +8 -0
- package/dist/bundled-extensions/telegram-connector/config.js +87 -0
- package/dist/bundled-extensions/telegram-connector/coordinator.d.ts +66 -0
- package/dist/bundled-extensions/telegram-connector/coordinator.js +413 -0
- package/dist/bundled-extensions/telegram-connector/index.d.ts +3 -0
- package/dist/bundled-extensions/telegram-connector/index.js +195 -0
- package/dist/bundled-extensions/terminal-bell/index.d.ts +0 -8
- package/dist/bundled-extensions/terminal-bell/index.js +0 -76
- package/dist/bundled-extensions/workspace-undo/index.d.ts +26 -0
- package/dist/bundled-extensions/workspace-undo/index.js +191 -0
- package/dist/config.d.ts +21 -2
- package/dist/config.js +159 -32
- package/dist/default-pix-config.js +20 -10
- package/dist/schemas/pi-tools-suite-schema.d.ts +8 -4
- package/dist/schemas/pi-tools-suite-schema.js +11 -5
- package/dist/schemas/pix-schema.d.ts +12 -2
- package/dist/schemas/pix-schema.js +37 -9
- package/dist/schemas/tasks-schema.js +1 -1
- package/docs/concurrency.md +3 -1
- package/docs/desktop-mvp.md +10 -5
- package/docs/desktop-task-manager.md +81 -28
- package/docs/release.md +30 -6
- package/external/pi-tools-suite/README.md +165 -39
- package/external/pi-tools-suite/docs/session-recovery.md +7 -0
- package/external/pi-tools-suite/docs/subagent-model-pools.md +12 -5
- package/external/pi-tools-suite/docs/ui-qa-subagent.md +440 -0
- package/external/pi-tools-suite/package.json +6 -1
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +2 -1
- package/external/pi-tools-suite/src/antigravity-auth/constants.ts +9 -3
- package/external/pi-tools-suite/src/antigravity-auth/headers.ts +41 -3
- package/external/pi-tools-suite/src/antigravity-auth/models.ts +94 -21
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +5 -17
- package/external/pi-tools-suite/src/antigravity-auth/payload.ts +72 -7
- package/external/pi-tools-suite/src/antigravity-auth/stream.ts +13 -1
- package/external/pi-tools-suite/src/async-subagents/agents/implement.md +1 -1
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/browser.mjs +346 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/desktop.mjs +1038 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/backends/tui.mjs +759 -0
- package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/scripts/browser-qa-runner.mjs +32 -23
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/chrome-devtools/chrome-devtools-provider.mjs +895 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/linux/linux-atspi.py +494 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/macos/macos-accessibility.swift +1085 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/bridge-client.mjs +50 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/native-terminal/native-terminal-host.mjs +501 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/drivers/windows/windows-uia.ps1 +454 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-auth.md +114 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-chrome-devtools.md +91 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser-playwright.md +145 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/browser.md +82 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-linux.md +26 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-macos.md +29 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop-windows.md +23 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/desktop.md +54 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-native-terminal.md +59 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui-pty.md +37 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/guides/tui.md +60 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa/scripts/ui-qa-runner.mjs +535 -0
- package/external/pi-tools-suite/src/async-subagents/agents/ui-qa.md +75 -0
- package/external/pi-tools-suite/src/async-subagents/commands.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +1 -1
- package/external/pi-tools-suite/src/async-subagents/core/agents-dir.ts +0 -2
- package/external/pi-tools-suite/src/async-subagents/core/browser-qa.ts +16 -2
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +40 -17
- package/external/pi-tools-suite/src/async-subagents/core/model-selection.ts +2 -1
- package/external/pi-tools-suite/src/async-subagents/core/registry.ts +158 -33
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +7 -4
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +34 -24
- package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +51 -45
- package/external/pi-tools-suite/src/async-subagents/lib.ts +9 -1
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +0 -1
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +2 -2
- package/external/pi-tools-suite/src/coding-discipline/index.ts +90 -59
- package/external/pi-tools-suite/src/config.ts +14 -0
- package/external/pi-tools-suite/src/context-gateway/accounting-log.ts +282 -0
- package/external/pi-tools-suite/src/context-gateway/config.ts +66 -1
- package/external/pi-tools-suite/src/context-gateway/efficiency.ts +414 -0
- package/external/pi-tools-suite/src/context-gateway/enforcement.ts +123 -3
- package/external/pi-tools-suite/src/context-gateway/index.ts +137 -4
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +5 -5
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +2 -2
- package/external/pi-tools-suite/src/context-gateway/types.ts +8 -1
- package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +39 -4
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +41 -14
- package/external/pi-tools-suite/src/dcp/commands.ts +14 -156
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +8 -2
- package/external/pi-tools-suite/src/dcp/compression-progress.ts +22 -0
- package/external/pi-tools-suite/src/dcp/config.ts +58 -4
- package/external/pi-tools-suite/src/dcp/conversation-index.ts +36 -9
- package/external/pi-tools-suite/src/dcp/diagnostics.ts +41 -0
- package/external/pi-tools-suite/src/dcp/fresh-tool-results.ts +38 -0
- package/external/pi-tools-suite/src/dcp/index.ts +338 -66
- package/external/pi-tools-suite/src/dcp/journal.ts +126 -4
- package/external/pi-tools-suite/src/dcp/progress-controller.ts +2 -1
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +92 -2
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +36 -33
- package/external/pi-tools-suite/src/dcp/pruner.ts +1 -0
- package/external/pi-tools-suite/src/dcp/routine-pressure.ts +86 -0
- package/external/pi-tools-suite/src/dcp/state.ts +9 -0
- package/external/pi-tools-suite/src/dcp/statistics.d.ts +8 -0
- package/external/pi-tools-suite/src/dcp/statistics.js +156 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +68 -16
- package/external/pi-tools-suite/src/lib/project.ts +36 -1
- package/external/pi-tools-suite/src/model-tools/index.ts +10 -7
- package/external/pi-tools-suite/src/repo-discovery/index.ts +304 -4
- package/external/pi-tools-suite/src/resource-registry/index.ts +282 -21
- package/external/pi-tools-suite/src/session-recovery/index.ts +17 -0
- package/external/pi-tools-suite/src/todo/index.ts +21 -0
- package/external/pi-tools-suite/src/todo/todo.ts +19 -1
- package/external/pi-tools-suite/src/tool-descriptions.ts +44 -15
- package/package.json +7 -8
- package/schemas/pi-tools-suite.json +44 -12
- package/schemas/pix.json +109 -15
- package/schemas/tasks.json +1 -2
- package/skills/simplify/SKILL.md +33 -5
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +0 -196
- package/external/pi-tools-suite/src/async-subagents/agents/browser-qa.md +0 -598
- package/skills/playwright-cli/SKILL.md +0 -420
- package/skills/playwright-cli/references/element-attributes.md +0 -23
- package/skills/playwright-cli/references/playwright-tests.md +0 -50
- package/skills/playwright-cli/references/request-mocking.md +0 -87
- package/skills/playwright-cli/references/running-code.md +0 -241
- package/skills/playwright-cli/references/session-management.md +0 -273
- package/skills/playwright-cli/references/spec-driven-testing.md +0 -311
- package/skills/playwright-cli/references/storage-state.md +0 -290
- package/skills/playwright-cli/references/test-generation.md +0 -142
- package/skills/playwright-cli/references/tracing.md +0 -154
- package/skills/playwright-cli/references/video-recording.md +0 -147
- package/skills/spec-wiki/SKILL.md +0 -55
- package/skills/spec-wiki/evals/evals.json +0 -132
- package/skills/spec-wiki/evals/files/bootstrap/architecture/rfc-007.md +0 -14
- package/skills/spec-wiki/evals/files/bootstrap/docs/how-to-pay.md +0 -4
- package/skills/spec-wiki/evals/files/bootstrap/docs/overview.md +0 -9
- package/skills/spec-wiki/evals/files/bootstrap/requirements/payment-behavior.md +0 -17
- package/skills/spec-wiki/evals/files/bootstrap/src/payments.ts +0 -12
- package/skills/spec-wiki/evals/files/bootstrap/tests/payments.test.ts +0 -11
- package/skills/spec-wiki/evals/files/focused/src/session.ts +0 -5
- package/skills/spec-wiki/evals/files/focused/tests/session.test.ts +0 -17
- package/skills/spec-wiki/evals/files/maintenance/low-signal/incoming/capture.txt +0 -2
- package/skills/spec-wiki/evals/files/maintenance/low-signal/src/capture.ts +0 -3
- package/skills/spec-wiki/evals/files/maintenance/move/docs/old-job-contract.md +0 -18
- package/skills/spec-wiki/evals/files/maintenance/move/src/job.ts +0 -5
- package/skills/spec-wiki/evals/files/maintenance/new-relation/docs/session-refresh.md +0 -24
- package/skills/spec-wiki/evals/files/maintenance/new-relation/incoming/refresh-worker.ts +0 -7
- package/skills/spec-wiki/evals/files/maintenance/new-relation/src/session.ts +0 -7
- package/skills/spec-wiki/evals/files/maintenance/no-impact/docs/auth-contract.md +0 -17
- package/skills/spec-wiki/evals/files/maintenance/no-impact/incoming/telemetry-format.ts +0 -3
- package/skills/spec-wiki/evals/files/maintenance/no-impact/src/auth.ts +0 -3
- package/skills/spec-wiki/evals/files/query/docs/how-to-run-jobs.md +0 -4
- package/skills/spec-wiki/evals/files/query/docs/spec-overview.md +0 -9
- package/skills/spec-wiki/evals/files/query/features/jobs/cancellation-contract.md +0 -15
- package/skills/spec-wiki/evals/files/query/features/jobs/retry-spec.md +0 -6
- package/skills/spec-wiki/evals/maintenance_regression.py +0 -311
- package/skills/spec-wiki/evals/retrieval-eval.md +0 -37
- package/skills/spec-wiki/evals/retrieval-evals.json +0 -37
- package/skills/spec-wiki/evals/trigger-evals.json +0 -94
- package/skills/spec-wiki/references/bootstrap.md +0 -100
- package/skills/spec-wiki/references/focused-spec.md +0 -117
- package/skills/spec-wiki/references/index-schema.md +0 -330
- package/skills/spec-wiki/references/maintenance.md +0 -134
- package/skills/spec-wiki/references/query.md +0 -74
- package/skills/spec-wiki/scripts/spec_wiki.py +0 -2076
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-auth.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/examples/qa-flow.example.jsonc +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.LICENSE +0 -0
- /package/external/pi-tools-suite/src/async-subagents/agents/{browser-qa → ui-qa/browser}/vendor/fflate.mjs +0 -0
|
@@ -128,6 +128,16 @@ function validateExactBlock(value: unknown): asserts value is CompressionBlock {
|
|
|
128
128
|
throw new DcpJournalError(`DCP journal block b${value.id} has invalid ${key}`);
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
+
if (value.commitMetrics !== undefined) {
|
|
132
|
+
const m = value.commitMetrics;
|
|
133
|
+
if (!isRecord(m) || typeof m.operationId !== "string" || !m.operationId ||
|
|
134
|
+
!["manual", "auto", "consolidation"].includes(m.kind as string) ||
|
|
135
|
+
!finiteNonNegativeInteger(m.beforeTokens) || !finiteNonNegativeInteger(m.afterTokens) ||
|
|
136
|
+
!finiteNonNegativeInteger(m.netGainTokens) || m.netGainTokens <= 0 ||
|
|
137
|
+
m.beforeTokens - m.afterTokens !== m.netGainTokens) {
|
|
138
|
+
throw new DcpJournalError(`DCP journal block b${value.id} has invalid commit metrics`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
131
141
|
for (const key of ["sourceMembers", "mutationMembers"] as const) {
|
|
132
142
|
const members = value[key];
|
|
133
143
|
if (!Array.isArray(members) || members.length === 0) {
|
|
@@ -438,13 +448,125 @@ function journalBranch(ctx: ExtensionContext): unknown[] {
|
|
|
438
448
|
}
|
|
439
449
|
}
|
|
440
450
|
|
|
451
|
+
function indexBranchParents(entries: readonly unknown[], requireFullChain: boolean): Map<string, string | null> | undefined {
|
|
452
|
+
const parents = new Map<string, string | null>();
|
|
453
|
+
let previousId: string | null = null;
|
|
454
|
+
for (const entry of entries) {
|
|
455
|
+
if (!isRecord(entry) || typeof entry.id !== "string" || !entry.id
|
|
456
|
+
|| parents.has(entry.id)) return undefined;
|
|
457
|
+
let parentId: string | null = null;
|
|
458
|
+
if (entry.parentId !== null) {
|
|
459
|
+
if (typeof entry.parentId !== "string" || !entry.parentId) return undefined;
|
|
460
|
+
parentId = entry.parentId;
|
|
461
|
+
}
|
|
462
|
+
if (requireFullChain && parentId !== previousId) return undefined;
|
|
463
|
+
parents.set(entry.id, parentId);
|
|
464
|
+
previousId = entry.id;
|
|
465
|
+
}
|
|
466
|
+
return parents;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* getBranch() on Pix's lazy manager is a JSONL presentation tail: it may omit
|
|
471
|
+
* older ancestors AND interleave abandoned branches. Do not "simplify" this
|
|
472
|
+
* into a positional prefix/suffix comparison or an ID-membership test.
|
|
473
|
+
*
|
|
474
|
+
* Walk parentId from the captured current leaf. Only the full read may supply
|
|
475
|
+
* ancestors missing from the tail; overlapping IDs must have identical parents
|
|
476
|
+
* (SDK entry identities/parent links are immutable). Both the old leaf and the
|
|
477
|
+
* read tip must lie on that walk. A side-branch tip merely present in the tail
|
|
478
|
+
* is not evidence. Missing links/cycles/conflicts deny the retry.
|
|
479
|
+
*
|
|
480
|
+
* This proves permission to re-read, NOT a publishable merged snapshot. Never
|
|
481
|
+
* return the tail/union as history or hydrate synchronously to validate it.
|
|
482
|
+
* Contract + counterexample: specs/dcp-statistics.md#lazy-tail-retry-invariant.
|
|
483
|
+
*/
|
|
484
|
+
function readSnapshotFollowsCurrentBranch(
|
|
485
|
+
entries: readonly unknown[],
|
|
486
|
+
currentEntries: readonly unknown[],
|
|
487
|
+
previousLeafId: string,
|
|
488
|
+
currentLeafId: string,
|
|
489
|
+
): boolean {
|
|
490
|
+
const readParents = indexBranchParents(entries, true);
|
|
491
|
+
const currentParents = indexBranchParents(currentEntries, false);
|
|
492
|
+
if (!readParents?.has(previousLeafId) || !currentParents?.has(currentLeafId)) return false;
|
|
493
|
+
|
|
494
|
+
const readTipId = (entries.at(-1) as { id: string }).id;
|
|
495
|
+
const visited = new Set<string>();
|
|
496
|
+
let cursor: string | null = currentLeafId;
|
|
497
|
+
let reachedPrevious = false;
|
|
498
|
+
let reachedReadTip = false;
|
|
499
|
+
while (cursor !== null) {
|
|
500
|
+
if (visited.has(cursor)) return false;
|
|
501
|
+
visited.add(cursor);
|
|
502
|
+
const currentParent = currentParents.get(cursor);
|
|
503
|
+
const readParent = readParents.get(cursor);
|
|
504
|
+
if (currentParent !== undefined && readParent !== undefined && currentParent !== readParent) return false;
|
|
505
|
+
// An absent old ancestor may be supplied by the full read, but a missing
|
|
506
|
+
// new descendant cannot be guessed. Null is a root, not a missing value.
|
|
507
|
+
const parent = currentParent !== undefined ? currentParent : readParent;
|
|
508
|
+
if (parent === undefined) return false;
|
|
509
|
+
reachedPrevious ||= cursor === previousLeafId;
|
|
510
|
+
reachedReadTip ||= cursor === readTipId;
|
|
511
|
+
cursor = parent;
|
|
512
|
+
}
|
|
513
|
+
return reachedPrevious && reachedReadTip;
|
|
514
|
+
}
|
|
515
|
+
|
|
441
516
|
export async function readDcpJournalBranch(ctx: ExtensionContext): Promise<unknown[]> {
|
|
442
517
|
const manager = ctx.sessionManager as any;
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
518
|
+
const sessionId = manager.getSessionId?.();
|
|
519
|
+
const hasLeafIdentity = typeof manager.getLeafId === "function";
|
|
520
|
+
const readFullBranch = typeof manager.readFullBranchEntries === "function"
|
|
521
|
+
? () => manager.readFullBranchEntries()
|
|
522
|
+
: undefined;
|
|
523
|
+
|
|
524
|
+
// Lazy full-history reads yield to the event loop. A normal append on the
|
|
525
|
+
// same active branch (for example, tool results arriving while a reloaded
|
|
526
|
+
// runtime is replaying its journal) advances the leaf without changing the
|
|
527
|
+
// branch owner. Retry that transient race so startup does not permanently
|
|
528
|
+
// block DCP. A real branch move/fork, owner replacement, or a branch that
|
|
529
|
+
// keeps moving still fails closed.
|
|
530
|
+
const maxAttempts = readFullBranch ? 3 : 1;
|
|
531
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
532
|
+
const leafId = manager.getLeafId?.();
|
|
533
|
+
const entries = readFullBranch ? await readFullBranch() : manager.getBranch();
|
|
534
|
+
if (ctx.sessionManager !== manager || manager.getSessionId?.() !== sessionId) {
|
|
535
|
+
throw new DcpJournalError("DCP branch changed during history read; rebuild context");
|
|
536
|
+
}
|
|
537
|
+
if (!Array.isArray(entries)) throw new DcpJournalError("DCP full branch is unavailable");
|
|
538
|
+
// A presentation cursor is not a complete SDK branch, even if it contains
|
|
539
|
+
// recent users or journal deltas. Never silently initialize from that tail.
|
|
540
|
+
if (entries[0]?.parentId != null) throw new DcpJournalError("DCP received an incomplete branch (presentation tail)");
|
|
541
|
+
|
|
542
|
+
const currentLeafId = manager.getLeafId?.();
|
|
543
|
+
if (currentLeafId === leafId) {
|
|
544
|
+
if (hasLeafIdentity) {
|
|
545
|
+
const readLeafId = entries.at(-1)?.id;
|
|
546
|
+
if ((leafId == null && entries.length > 0) || (leafId != null && readLeafId !== leafId)) {
|
|
547
|
+
throw new DcpJournalError("DCP branch changed during history read; rebuild context");
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return entries;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
let sameBranchAdvance = false;
|
|
554
|
+
if (leafId && currentLeafId) {
|
|
555
|
+
try {
|
|
556
|
+
const currentEntries = manager.getBranch();
|
|
557
|
+
sameBranchAdvance = Array.isArray(currentEntries)
|
|
558
|
+
&& manager.getLeafId?.() === currentLeafId
|
|
559
|
+
&& readSnapshotFollowsCurrentBranch(entries, currentEntries, leafId, currentLeafId);
|
|
560
|
+
} catch {
|
|
561
|
+
sameBranchAdvance = false;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (!sameBranchAdvance || attempt === maxAttempts - 1) {
|
|
565
|
+
throw new DcpJournalError("DCP branch changed during history read; rebuild context");
|
|
566
|
+
}
|
|
446
567
|
}
|
|
447
|
-
|
|
568
|
+
|
|
569
|
+
throw new DcpJournalError("DCP branch changed during history read; rebuild context");
|
|
448
570
|
}
|
|
449
571
|
|
|
450
572
|
function restoreLeafAfterAppendFailure(ctx: ExtensionContext, previousLeafId: string | null | undefined): void {
|
|
@@ -102,8 +102,17 @@ export interface CompressionCandidateSelectionOptions {
|
|
|
102
102
|
requiredSavingsTokens?: number;
|
|
103
103
|
/** Conservative estimator margin added to the required source size. */
|
|
104
104
|
estimatorMarginTokens?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Whether an automatic candidate may fold already-compressed block
|
|
107
|
+
* placeholders into a new block. Capacity-recovery plans disable this so an
|
|
108
|
+
* economical existing summary is not expanded again while rescuing raw tail
|
|
109
|
+
* growth. Manual/non-pressure consolidation keeps the historical default.
|
|
110
|
+
*/
|
|
111
|
+
allowCompressionBlocks?: boolean;
|
|
105
112
|
}
|
|
106
113
|
|
|
114
|
+
const MIN_AUTOMATIC_BLOCK_CONSOLIDATION = 8;
|
|
115
|
+
|
|
107
116
|
function selectOldestSafePrefix(
|
|
108
117
|
boundaries: CandidateBoundary[],
|
|
109
118
|
state: DcpState,
|
|
@@ -176,7 +185,9 @@ export function detectCompressionCandidate(
|
|
|
176
185
|
if (!settings.enabled) return null;
|
|
177
186
|
if (contextPercent < settings.minContextPercent) return null;
|
|
178
187
|
|
|
179
|
-
const boundaries = buildCandidateBoundaries(messages, _state, {
|
|
188
|
+
const boundaries = buildCandidateBoundaries(messages, _state, {
|
|
189
|
+
allowBlocks: options?.allowCompressionBlocks ?? true,
|
|
190
|
+
});
|
|
180
191
|
|
|
181
192
|
if (boundaries.length < settings.minMessages) return null;
|
|
182
193
|
|
|
@@ -224,6 +235,83 @@ export function detectCompressionCandidate(
|
|
|
224
235
|
};
|
|
225
236
|
}
|
|
226
237
|
|
|
238
|
+
/**
|
|
239
|
+
* Periodically fold a contiguous prefix of already-compressed summaries into
|
|
240
|
+
* one summary. Raw-tail recovery deliberately excludes bN blocks so it cannot
|
|
241
|
+
* recreate the incident's b1 -> b2 -> ... ladder; without a separate bounded
|
|
242
|
+
* consolidation step, however, a marathon turn can accumulate dozens of small
|
|
243
|
+
* active summaries until summary overhead itself fills the provider window.
|
|
244
|
+
*
|
|
245
|
+
* This detector is intentionally block-only: every selected boundary must be a
|
|
246
|
+
* synthetic block message and physically adjacent in the provider projection.
|
|
247
|
+
* Requiring a batch of blocks amortizes the intentional prefix rewrite and
|
|
248
|
+
* prevents one-old-summary-plus-a-tiny-tail recompression on every turn.
|
|
249
|
+
*/
|
|
250
|
+
export function detectCompressionBlockConsolidationCandidate(
|
|
251
|
+
messages: any[],
|
|
252
|
+
state: DcpState,
|
|
253
|
+
config: DcpConfig,
|
|
254
|
+
options?: CompressionCandidateSelectionOptions,
|
|
255
|
+
): CompressionCandidate | null {
|
|
256
|
+
const settings = config.compress.autoCandidates;
|
|
257
|
+
const boundaries = buildCandidateBoundaries(messages, state, { allowBlocks: true });
|
|
258
|
+
const minimumBlocks = MIN_AUTOMATIC_BLOCK_CONSOLIDATION;
|
|
259
|
+
const requiredSavings = Math.max(0, Math.floor(options?.requiredSavingsTokens ?? 0));
|
|
260
|
+
const estimatorMargin = Math.max(0, Math.floor(options?.estimatorMarginTokens ?? 0));
|
|
261
|
+
const targetSourceTokens = Math.max(settings.minTokens, requiredSavings + estimatorMargin);
|
|
262
|
+
|
|
263
|
+
let run: CandidateBoundary[] = [];
|
|
264
|
+
const chooseRun = (candidateRun: CandidateBoundary[]): CandidateBoundary[] | null => {
|
|
265
|
+
if (candidateRun.length < minimumBlocks) return null;
|
|
266
|
+
let selectedTokens = 0;
|
|
267
|
+
let selectedEnd = minimumBlocks - 1;
|
|
268
|
+
for (let index = 0; index < candidateRun.length; index++) {
|
|
269
|
+
selectedTokens += candidateRun[index]!.tokenEstimate;
|
|
270
|
+
if (index + 1 >= minimumBlocks && selectedTokens >= targetSourceTokens) {
|
|
271
|
+
selectedEnd = index;
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
selectedEnd = index;
|
|
275
|
+
}
|
|
276
|
+
return candidateRun.slice(0, Math.max(minimumBlocks, selectedEnd + 1));
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
for (const boundary of boundaries) {
|
|
280
|
+
const previous = run.at(-1);
|
|
281
|
+
const continuesBlockRun = boundary.blockId !== undefined &&
|
|
282
|
+
(!previous || boundary.messageIndex === previous.messageIndex + 1);
|
|
283
|
+
if (continuesBlockRun) {
|
|
284
|
+
run.push(boundary);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
const selected = chooseRun(run);
|
|
288
|
+
if (selected) {
|
|
289
|
+
const estimatedTokens = selected.reduce((sum, item) => sum + item.tokenEstimate, 0);
|
|
290
|
+
return {
|
|
291
|
+
startId: selected[0]!.id,
|
|
292
|
+
endId: selected[selected.length - 1]!.id,
|
|
293
|
+
messageCount: selected.length,
|
|
294
|
+
estimatedTokens,
|
|
295
|
+
includedBlockIds: selected.map((item) => item.blockId!),
|
|
296
|
+
reason: `automatic block-only consolidation of ${selected.length} adjacent summaries`,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
run = boundary.blockId !== undefined ? [boundary] : [];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const selected = chooseRun(run);
|
|
303
|
+
if (!selected) return null;
|
|
304
|
+
const estimatedTokens = selected.reduce((sum, item) => sum + item.tokenEstimate, 0);
|
|
305
|
+
return {
|
|
306
|
+
startId: selected[0]!.id,
|
|
307
|
+
endId: selected[selected.length - 1]!.id,
|
|
308
|
+
messageCount: selected.length,
|
|
309
|
+
estimatedTokens,
|
|
310
|
+
includedBlockIds: selected.map((item) => item.blockId!),
|
|
311
|
+
reason: `automatic block-only consolidation of ${selected.length} adjacent summaries`,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
227
315
|
/**
|
|
228
316
|
* Emergency-only range candidate for marathon turns.
|
|
229
317
|
*
|
|
@@ -257,7 +345,9 @@ export function detectEmergencyCompressionCandidate(
|
|
|
257
345
|
if (!emergencySettings.enabled) return null;
|
|
258
346
|
if (contextPercent <= maxContextPercent) return null;
|
|
259
347
|
|
|
260
|
-
const boundaries = buildCandidateBoundaries(messages, state, {
|
|
348
|
+
const boundaries = buildCandidateBoundaries(messages, state, {
|
|
349
|
+
allowBlocks: options?.allowCompressionBlocks ?? true,
|
|
350
|
+
});
|
|
261
351
|
if (boundaries.length < settings.minMessages) return null;
|
|
262
352
|
|
|
263
353
|
let latestUserIndex = -1;
|
|
@@ -32,31 +32,51 @@ function nudgeTypeLabel(type: DcpNudgeType): string {
|
|
|
32
32
|
|
|
33
33
|
function isRealAnchorCandidate(msg: any): boolean {
|
|
34
34
|
const role = msg?.role ?? "";
|
|
35
|
-
if (role !== "user" && role !== "
|
|
35
|
+
if (role !== "user" && role !== "toolResult") return false;
|
|
36
36
|
return msg?._dcpOrigin !== "block" && msg?._dcpOrigin !== "dcp-control";
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
function findAnchorMessage(messages: any[]): { msg: any; index: number; stableId: string; timestamp: number; role: string } | null {
|
|
39
|
+
function findAnchorMessage(messages: any[], freshToolResultIds?: ReadonlySet<string>): { msg: any; index: number; stableId: string; timestamp: number; role: string } | null {
|
|
40
40
|
const stableKeys = stableMessageKeys(messages);
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
41
|
+
// Only append to a fresh tail; never edit an earlier user/assistant prefix.
|
|
42
|
+
// Tool-result freshness requires a positive local lifecycle grant, not the
|
|
43
|
+
// absence of successful provider evidence (which is unknown after restart).
|
|
44
44
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
45
45
|
const msg = messages[i];
|
|
46
46
|
if (msg?._dcpOrigin === "dcp-control") continue;
|
|
47
|
-
if (
|
|
47
|
+
if (!isRealAnchorCandidate(msg) || !Number.isFinite(msg.timestamp)) return null;
|
|
48
|
+
if (msg.role === "toolResult") {
|
|
49
|
+
if (!freshToolResultIds?.has(msg.toolCallId)) return null;
|
|
50
|
+
// A duplicate/reused call ID is not an unambiguous fresh carrier.
|
|
51
|
+
if (messages.filter((item) => item?.role === "toolResult" && item.toolCallId === msg.toolCallId).length !== 1) return null;
|
|
52
|
+
}
|
|
48
53
|
return { msg, index: i, stableId: stableKeys[i]!, timestamp: msg.timestamp, role: msg.role };
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
return null;
|
|
52
57
|
}
|
|
53
58
|
|
|
54
|
-
export function hasCacheSafeNudgeCarrier(
|
|
55
|
-
|
|
59
|
+
export function hasCacheSafeNudgeCarrier(
|
|
60
|
+
messages: any[],
|
|
61
|
+
freshToolResultIds?: ReadonlySet<string>,
|
|
62
|
+
anchors: readonly DcpNudgeAnchor[] = [],
|
|
63
|
+
): boolean {
|
|
64
|
+
if (findAnchorMessage(messages, freshToolResultIds) !== null) return true;
|
|
65
|
+
// A previously published reminder remains deliverable without editing its
|
|
66
|
+
// carrier. Losing the grant to create a NEW reminder on a provider attempt
|
|
67
|
+
// must not make retries bypass the completed-opportunity patience policy.
|
|
68
|
+
const stableKeys = stableMessageKeys(messages);
|
|
69
|
+
return anchors.some((anchor) => typeof anchor.renderedReminder === "string" &&
|
|
70
|
+
anchor.renderedReminder.length > 0 &&
|
|
71
|
+
messages.some((message, index) => anchorMatchesMessage(anchor, message, stableKeys[index]!)));
|
|
56
72
|
}
|
|
57
73
|
|
|
58
74
|
function anchorMatchesMessage(anchor: DcpNudgeAnchor, msg: any, stableKey: string): boolean {
|
|
59
|
-
if (
|
|
75
|
+
if (!isRealAnchorCandidate(msg) || msg.role !== anchor.anchorRole) return false;
|
|
76
|
+
if (anchor.anchorStableId) return stableKey === anchor.anchorStableId;
|
|
77
|
+
// Legacy user anchors may be timestamp-only. Tool anchors must never rebind
|
|
78
|
+
// to another parallel result that happens to share their timestamp.
|
|
79
|
+
if (msg.role === "toolResult") return false;
|
|
60
80
|
return msg?.timestamp === anchor.anchorTimestamp;
|
|
61
81
|
}
|
|
62
82
|
|
|
@@ -78,24 +98,8 @@ function appendTextToMessage(msg: any, text: string): void {
|
|
|
78
98
|
return;
|
|
79
99
|
}
|
|
80
100
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
msg.content = [...msg.content, textBlock];
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Keep provider ordering constraints: assistant text must appear before
|
|
88
|
-
// toolCall blocks for models that enforce text/thinking/tool_use ordering.
|
|
89
|
-
const firstToolCallIdx = msg.content.findIndex((block: any) => block?.type === "toolCall");
|
|
90
|
-
if (firstToolCallIdx === -1) {
|
|
91
|
-
msg.content = [...msg.content, textBlock];
|
|
92
|
-
} else {
|
|
93
|
-
msg.content = [
|
|
94
|
-
...msg.content.slice(0, firstToolCallIdx),
|
|
95
|
-
textBlock,
|
|
96
|
-
...msg.content.slice(firstToolCallIdx),
|
|
97
|
-
];
|
|
98
|
-
}
|
|
101
|
+
// Only user/tool-result carriers reach here. Assistant items are immutable.
|
|
102
|
+
msg.content = [...msg.content, { type: "text", text: suffix }];
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
function insertBeforeReminderClose(reminder: string, detail: string): string {
|
|
@@ -174,7 +178,7 @@ export function upsertNudgeAnchor(
|
|
|
174
178
|
messages: any[],
|
|
175
179
|
state: DcpState,
|
|
176
180
|
type: DcpNudgeType,
|
|
177
|
-
options: { contextPercent?: number; renderedReminder?: string } = {},
|
|
181
|
+
options: { contextPercent?: number; renderedReminder?: string; freshToolResultIds?: ReadonlySet<string> } = {},
|
|
178
182
|
): { anchor: DcpNudgeAnchor | null; created: boolean; updated: boolean } {
|
|
179
183
|
const stableKeys = stableMessageKeys(messages);
|
|
180
184
|
let existing: DcpNudgeAnchor | null = null;
|
|
@@ -198,11 +202,10 @@ export function upsertNudgeAnchor(
|
|
|
198
202
|
state.lastNudge = undefined;
|
|
199
203
|
}
|
|
200
204
|
|
|
201
|
-
const target = findAnchorMessage(messages);
|
|
205
|
+
const target = findAnchorMessage(messages, options.freshToolResultIds);
|
|
202
206
|
if (!target) {
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
// provider continuation and break prefix equality.
|
|
207
|
+
// No positively fresh carrier: do not synthesize a disappearing tail or
|
|
208
|
+
// reinterpret unknown historical exposure as permission to edit history.
|
|
206
209
|
return { anchor: null, created: false, updated: false };
|
|
207
210
|
}
|
|
208
211
|
|
|
@@ -263,7 +266,7 @@ export function applyAnchoredNudges(
|
|
|
263
266
|
const reminder = selected.anchor.renderedReminder ?? render(selected.anchor);
|
|
264
267
|
if (materializedReminder) selected.anchor.renderedReminder = reminder;
|
|
265
268
|
const anchorMessage = messages[selected.index];
|
|
266
|
-
if (anchorMessage
|
|
269
|
+
if (!isRealAnchorCandidate(anchorMessage)) {
|
|
267
270
|
state.nudgeAnchors = [];
|
|
268
271
|
state.lastNudge = undefined;
|
|
269
272
|
return { rendered: false, stateChanged: true };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { canonicalMessageHash } from "./conversation-index.js";
|
|
2
|
+
import type { CompressionBlock } from "./state.js";
|
|
3
|
+
|
|
4
|
+
interface ProjectionFrame {
|
|
5
|
+
owner: string;
|
|
6
|
+
avoidedTokens: number;
|
|
7
|
+
blocks: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RoutinePressureEstimate {
|
|
11
|
+
projectedTokens: number;
|
|
12
|
+
adjustmentTokens: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function usableUsage(message: any): boolean {
|
|
16
|
+
if (message?.role !== "assistant" || ["error", "aborted"].includes(message.stopReason)) return false;
|
|
17
|
+
const usage = message.usage;
|
|
18
|
+
if (!usage) return false;
|
|
19
|
+
const tokens = usage.totalTokens || [usage.input, usage.output, usage.cacheRead, usage.cacheWrite]
|
|
20
|
+
.reduce((sum, value) => sum + (typeof value === "number" && Number.isFinite(value) ? value : 0), 0);
|
|
21
|
+
return typeof tokens === "number" && Number.isFinite(tokens) && tokens > 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function blockVersion(blocks: readonly CompressionBlock[]): string {
|
|
25
|
+
return blocks.filter((block) => block.active).map((block) => block.id).join(",");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Runtime-only calibration for ROUTINE nudges, never a provider-send budget.
|
|
30
|
+
* The SDK's last assistant usage still includes history removed by a later DCP
|
|
31
|
+
* rewrite. Subtract only the additional locally measured raw/projected delta
|
|
32
|
+
* since that assistant's correlated request. This keeps provider overhead and
|
|
33
|
+
* tail growth, and avoids counting older compression twice. Uncorrelated usage
|
|
34
|
+
* and lifecycle changes fall back to the existing conservative estimate.
|
|
35
|
+
*/
|
|
36
|
+
export class RoutinePressureTracker {
|
|
37
|
+
private prepared: ProjectionFrame | undefined;
|
|
38
|
+
private pending: ProjectionFrame | undefined;
|
|
39
|
+
private sample: (ProjectionFrame & { hash: string; timestamp: unknown }) | undefined;
|
|
40
|
+
|
|
41
|
+
reset(): void {
|
|
42
|
+
this.prepared = undefined;
|
|
43
|
+
this.pending = undefined;
|
|
44
|
+
this.sample = undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
prepare(owner: string, rawTokens: number, projectedTokens: number, blocks: readonly CompressionBlock[]): void {
|
|
48
|
+
this.prepared = { owner, avoidedTokens: rawTokens - projectedTokens, blocks: blockVersion(blocks) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
beforeRequest(): void {
|
|
52
|
+
this.pending = this.prepared;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
complete(message: any, correlated: boolean): void {
|
|
56
|
+
const pending = this.pending;
|
|
57
|
+
this.pending = undefined;
|
|
58
|
+
if (!usableUsage(message)) return;
|
|
59
|
+
this.sample = correlated && pending && typeof message.timestamp === "number" && Number.isFinite(message.timestamp)
|
|
60
|
+
? { ...pending, hash: canonicalMessageHash(message), timestamp: message.timestamp }
|
|
61
|
+
: undefined;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
estimate(owner: string, nativeTokens: number | null | undefined, raw: readonly any[],
|
|
65
|
+
rawTokens: number, projectedTokens: number, blocks: readonly CompressionBlock[]): RoutinePressureEstimate {
|
|
66
|
+
const fallback = Math.max(projectedTokens, nativeTokens ?? 0);
|
|
67
|
+
const unchanged = { projectedTokens: fallback, adjustmentTokens: 0 };
|
|
68
|
+
const sample = this.sample;
|
|
69
|
+
if (!sample || sample.owner !== owner || nativeTokens === null || nativeTokens === undefined) return unchanged;
|
|
70
|
+
const version = blockVersion(blocks);
|
|
71
|
+
if (!version || version === sample.blocks) return unchanged;
|
|
72
|
+
// Confirm the SDK is still based on this exact successful assistant, not
|
|
73
|
+
// an unrelated/new response, failed stream or a sample from another owner.
|
|
74
|
+
let latest: any;
|
|
75
|
+
for (let index = raw.length - 1; index >= 0; index--) {
|
|
76
|
+
if (usableUsage(raw[index])) {
|
|
77
|
+
latest = raw[index];
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (!latest || latest.timestamp !== sample.timestamp || canonicalMessageHash(latest) !== sample.hash) return unchanged;
|
|
82
|
+
const newSavings = Math.max(0, rawTokens - projectedTokens - sample.avoidedTokens);
|
|
83
|
+
const adjusted = Math.max(projectedTokens, nativeTokens - newSavings);
|
|
84
|
+
return { projectedTokens: adjusted, adjustmentTokens: fallback - adjusted };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -153,6 +153,15 @@ export interface CompressionBlock {
|
|
|
153
153
|
active: boolean
|
|
154
154
|
/** Token estimate for the summary text itself */
|
|
155
155
|
summaryTokenEstimate: number
|
|
156
|
+
/** One record per successful transaction (on its first block), never on retries.
|
|
157
|
+
* Optional for older journal blocks; absence means unmeasured, not zero gain. */
|
|
158
|
+
commitMetrics?: {
|
|
159
|
+
operationId: string
|
|
160
|
+
kind: "manual" | "auto" | "consolidation"
|
|
161
|
+
beforeTokens: number
|
|
162
|
+
afterTokens: number
|
|
163
|
+
netGainTokens: number
|
|
164
|
+
}
|
|
156
165
|
/** Wall-clock time the block was created (Date.now()) */
|
|
157
166
|
createdAt: number
|
|
158
167
|
/** Older compression blocks subsumed by this block during roll-up. */
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface DcpStatisticsInput {
|
|
2
|
+
branch?: readonly any[];
|
|
3
|
+
model?: any;
|
|
4
|
+
usage?: any;
|
|
5
|
+
historyStatus?: "full" | "unavailable";
|
|
6
|
+
}
|
|
7
|
+
export function collectDcpStatistics(input: DcpStatisticsInput): any;
|
|
8
|
+
export function formatDcpStatistics(input: DcpStatisticsInput): string;
|