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
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
2
3
|
import { classifyShellCommand, type ShellCommandClassification } from "../shell-command-policy.js";
|
|
3
4
|
|
|
5
|
+
import {
|
|
6
|
+
contextGatewayAccountingLogDrain,
|
|
7
|
+
contextGatewayAccountingLogEnabled,
|
|
8
|
+
writeContextGatewayAccountingLog,
|
|
9
|
+
} from "./accounting-log.js";
|
|
4
10
|
import {
|
|
5
11
|
contextGatewayBudgetForClass,
|
|
6
12
|
loadContextGatewayConfig,
|
|
7
13
|
} from "./config.js";
|
|
14
|
+
import { ContextGatewayEfficiencyTracker } from "./efficiency.js";
|
|
8
15
|
import { planContextGatewayEnforcement } from "./enforcement.js";
|
|
9
16
|
import { classifyContextGatewayTool, ContextGatewayTelemetry } from "./telemetry.js";
|
|
10
17
|
import type {
|
|
@@ -18,20 +25,25 @@ import type {
|
|
|
18
25
|
export interface RegisterContextGatewayOptions {
|
|
19
26
|
loadConfig?: () => ContextGatewayResolvedConfig;
|
|
20
27
|
telemetry?: ContextGatewayTelemetry;
|
|
28
|
+
efficiency?: ContextGatewayEfficiencyTracker;
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
export interface RegisteredContextGateway extends ContextGatewayRuntimeState {
|
|
32
|
+
readonly efficiency: ContextGatewayEfficiencyTracker;
|
|
24
33
|
setRuntimeMode(mode: ContextGatewayMode): { ok: boolean; message: string };
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
function formatStatus(runtime: RegisteredContextGateway): string {
|
|
28
37
|
const telemetry = runtime.telemetry.snapshot();
|
|
38
|
+
const efficiency = runtime.efficiency.snapshot();
|
|
29
39
|
return [
|
|
30
40
|
`Context Gateway requested=${runtime.requestedMode}, effective=${runtime.effectiveMode}.`,
|
|
31
41
|
`Observed results=${telemetry.results}, errors=${telemetry.errors}, upstreamTruncated=${telemetry.upstreamTruncatedResults}, overBudget=${telemetry.overBudgetResults}, enforced=${telemetry.enforcedResults}, unbound results=${telemetry.unboundResults}.`,
|
|
32
42
|
`Repeat exact reads=${telemetry.repeatCandidateCount}, same-source different-range reads=${telemetry.sameSourceDifferentRangeCount}, retrieval calls=${telemetry.retrievalCalls}.`,
|
|
33
43
|
`Native policy results=${telemetry.nativePolicy.results}, refusals=${telemetry.nativePolicy.refusals}, full overrides=${telemetry.nativePolicy.fullOverrides}.`,
|
|
34
44
|
`Source contentBytes=${telemetry.contentBytes}, deliveredContentBytes=${telemetry.deliveredContentBytes}, actualBytesSaved=${telemetry.actualBytesSaved}, potentialBytesOverBudget=${telemetry.potentialBytesOverBudget}, detailsBytes=${telemetry.detailsBytes}.`,
|
|
45
|
+
`Efficiency gross≈${efficiency.grossEstimatedTokensSaved} tokens/${efficiency.grossBytesSaved} bytes, retrieval tax≈${efficiency.retrievalEstimatedTokens} tokens/${efficiency.retrievalBytes} bytes, conservative net≈${efficiency.conservativeNetEstimatedTokens} tokens/${efficiency.conservativeNetBytes} bytes.`,
|
|
46
|
+
`Provider attempts=${efficiency.providerAttempts}, completions=${efficiency.providerCompletions}, input=${efficiency.providerInputTokens}, cacheRead=${efficiency.providerCacheReadTokens}, cacheWrite=${efficiency.providerCacheWriteTokens}, output=${efficiency.providerOutputTokens}, total=${efficiency.providerTotalTokens}.`,
|
|
35
47
|
].join("\n");
|
|
36
48
|
}
|
|
37
49
|
|
|
@@ -39,11 +51,12 @@ function formatDoctor(runtime: RegisteredContextGateway): string {
|
|
|
39
51
|
const lines = [
|
|
40
52
|
`Context Gateway doctor: requested=${runtime.requestedMode}, effective=${runtime.effectiveMode}.`,
|
|
41
53
|
"Budgets: read=maxExactReadBytes; repo search/AST/structure=maxSearchBytes; other results=maxResultBytes; compact inline view=maxInlineBytes.",
|
|
42
|
-
"Enforce: recognised complete simple test/build output=bounded parser compact;
|
|
54
|
+
"Enforce: recognised complete simple test/build output=bounded parser compact; over-budget web_search/web_fetch with structured raw details=bounded recoverable compact; unsupported/unsafe results remain passthrough.",
|
|
43
55
|
"Read: full passthrough even when over budget; exact recovery/archive is not wired, so irreversible read truncation is disabled.",
|
|
44
56
|
"Repo tools: producer-native compact is supported with repoDiscovery.profile=native-compact; Gateway never post-hoc slices repo output.",
|
|
45
57
|
"External paths: MCP=current Pix adapter unsupported; direct parent browser capture=unsupported.",
|
|
46
58
|
"Storage: disabled; no durable archive, index, quota reservation, or retrieval tools are active.",
|
|
59
|
+
`Accounting log: ${contextGatewayAccountingLogEnabled(runtime.config) ? "enabled" : "disabled"}; scalar-only JSONL with bounded rotation, no result bodies, paths, or tool arguments.`,
|
|
47
60
|
];
|
|
48
61
|
for (const issue of runtime.config.issues) lines.push(`Config issue: ${issue}`);
|
|
49
62
|
return lines.join("\n");
|
|
@@ -59,14 +72,48 @@ export function registerContextGateway(
|
|
|
59
72
|
): RegisteredContextGateway {
|
|
60
73
|
const config = (options.loadConfig ?? (() => loadContextGatewayConfig()))();
|
|
61
74
|
const telemetry = options.telemetry ?? new ContextGatewayTelemetry();
|
|
75
|
+
const efficiency = options.efficiency ?? new ContextGatewayEfficiencyTracker();
|
|
62
76
|
let requestedMode = config.mode;
|
|
63
77
|
let effectiveMode: ContextGatewayEffectiveMode = requestedMode;
|
|
64
78
|
type InFlight = { toolClass: ContextGatewayToolClass; shell: ShellCommandClassification };
|
|
65
79
|
const inFlightToolCalls = new Map<string, InFlight>();
|
|
80
|
+
const runtimeId = randomUUID();
|
|
81
|
+
let accountingEpoch = 0;
|
|
82
|
+
let accountingEpochStartedAt = Date.now();
|
|
83
|
+
let accountingEpochActive = false;
|
|
84
|
+
|
|
85
|
+
const writeAccounting = (event: string, details: Record<string, unknown> = {}): void => {
|
|
86
|
+
writeContextGatewayAccountingLog(config, event, {
|
|
87
|
+
runtimeId,
|
|
88
|
+
sessionEpoch: accountingEpoch,
|
|
89
|
+
mode: effectiveMode,
|
|
90
|
+
...details,
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const accountingSummary = (): Record<string, unknown> => ({
|
|
95
|
+
durationMs: Math.max(0, Date.now() - accountingEpochStartedAt),
|
|
96
|
+
totals: efficiency.snapshot(),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const finalizeAccountingEpoch = (reason: string): void => {
|
|
100
|
+
if (!accountingEpochActive || effectiveMode === "off") return;
|
|
101
|
+
writeAccounting("session.summary", { reason, ...accountingSummary() });
|
|
102
|
+
accountingEpochActive = false;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const beginAccountingEpoch = (reason: string): void => {
|
|
106
|
+
efficiency.reset();
|
|
107
|
+
accountingEpoch += 1;
|
|
108
|
+
accountingEpochStartedAt = Date.now();
|
|
109
|
+
accountingEpochActive = effectiveMode !== "off";
|
|
110
|
+
if (accountingEpochActive) writeAccounting("session.start", { reason });
|
|
111
|
+
};
|
|
66
112
|
|
|
67
113
|
const resetRuntimeTelemetry = (): void => {
|
|
68
114
|
inFlightToolCalls.clear();
|
|
69
115
|
telemetry.reset();
|
|
116
|
+
efficiency.clearTransientBindings();
|
|
70
117
|
};
|
|
71
118
|
|
|
72
119
|
const runtime: RegisteredContextGateway = {
|
|
@@ -74,6 +121,7 @@ export function registerContextGateway(
|
|
|
74
121
|
get effectiveMode() { return effectiveMode; },
|
|
75
122
|
config,
|
|
76
123
|
telemetry,
|
|
124
|
+
efficiency,
|
|
77
125
|
setRuntimeMode(mode) {
|
|
78
126
|
if (mode !== effectiveMode && inFlightToolCalls.size > 0) {
|
|
79
127
|
return {
|
|
@@ -81,9 +129,21 @@ export function registerContextGateway(
|
|
|
81
129
|
message: `Context Gateway mode change requires a safe boundary; ${inFlightToolCalls.size} tool call(s) are still in flight.`,
|
|
82
130
|
};
|
|
83
131
|
}
|
|
132
|
+
const previousMode = effectiveMode;
|
|
133
|
+
if (previousMode !== mode) finalizeAccountingEpoch("mode-change");
|
|
84
134
|
requestedMode = mode;
|
|
85
135
|
effectiveMode = mode;
|
|
86
136
|
telemetry.clearTransientBindings();
|
|
137
|
+
efficiency.clearTransientBindings();
|
|
138
|
+
if (previousMode !== mode) {
|
|
139
|
+
writeContextGatewayAccountingLog(config, "mode.change", {
|
|
140
|
+
runtimeId,
|
|
141
|
+
sessionEpoch: accountingEpoch,
|
|
142
|
+
from: previousMode,
|
|
143
|
+
to: mode,
|
|
144
|
+
});
|
|
145
|
+
beginAccountingEpoch("mode-change");
|
|
146
|
+
}
|
|
87
147
|
return { ok: true, message: `Context Gateway runtime mode set to ${mode}; config was not changed.` };
|
|
88
148
|
},
|
|
89
149
|
};
|
|
@@ -98,7 +158,11 @@ export function registerContextGateway(
|
|
|
98
158
|
: { scope: "unknown", kind: "unknown" },
|
|
99
159
|
});
|
|
100
160
|
}
|
|
101
|
-
if (effectiveMode === "observe" || effectiveMode === "enforce")
|
|
161
|
+
if (effectiveMode === "observe" || effectiveMode === "enforce") {
|
|
162
|
+
telemetry.recordToolCall(event);
|
|
163
|
+
const accountingCall = efficiency.recordToolCall(event);
|
|
164
|
+
if (accountingCall) writeAccounting("tool.call", { ...accountingCall });
|
|
165
|
+
}
|
|
102
166
|
return undefined;
|
|
103
167
|
});
|
|
104
168
|
|
|
@@ -114,6 +178,7 @@ export function registerContextGateway(
|
|
|
114
178
|
const enforcement = effectiveMode === "enforce" && binding
|
|
115
179
|
? planContextGatewayEnforcement({
|
|
116
180
|
event,
|
|
181
|
+
toolCallId: typeof event.toolCallId === "string" ? event.toolCallId : undefined,
|
|
117
182
|
toolClass: binding.toolClass,
|
|
118
183
|
shell: binding.shell,
|
|
119
184
|
budgetBytes: classBudget,
|
|
@@ -123,7 +188,7 @@ export function registerContextGateway(
|
|
|
123
188
|
),
|
|
124
189
|
})
|
|
125
190
|
: undefined;
|
|
126
|
-
telemetry.recordToolResult(event, config.budgets, {
|
|
191
|
+
const observation = telemetry.recordToolResult(event, config.budgets, {
|
|
127
192
|
maxInlineBytes: config.budgets.maxInlineBytes,
|
|
128
193
|
...(enforcement ? {
|
|
129
194
|
delivery: {
|
|
@@ -133,7 +198,26 @@ export function registerContextGateway(
|
|
|
133
198
|
},
|
|
134
199
|
} : {}),
|
|
135
200
|
});
|
|
136
|
-
|
|
201
|
+
const accountingResult = efficiency.recordToolResult(
|
|
202
|
+
event,
|
|
203
|
+
observation,
|
|
204
|
+
enforcement?.content ?? event.content,
|
|
205
|
+
);
|
|
206
|
+
if (accountingResult) {
|
|
207
|
+
const efficiencySnapshot = efficiency.snapshot();
|
|
208
|
+
writeAccounting("tool.result", {
|
|
209
|
+
...accountingResult,
|
|
210
|
+
cumulative: {
|
|
211
|
+
grossBytesSaved: efficiencySnapshot.grossBytesSaved,
|
|
212
|
+
grossEstimatedTokensSaved: efficiencySnapshot.grossEstimatedTokensSaved,
|
|
213
|
+
retrievalBytes: efficiencySnapshot.retrievalBytes,
|
|
214
|
+
retrievalEstimatedTokens: efficiencySnapshot.retrievalEstimatedTokens,
|
|
215
|
+
conservativeNetBytes: efficiencySnapshot.conservativeNetBytes,
|
|
216
|
+
conservativeNetEstimatedTokens: efficiencySnapshot.conservativeNetEstimatedTokens,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
if (enforcement && enforcement.representation !== "passthrough") {
|
|
137
221
|
const originalDetails = event.details && typeof event.details === "object" && !Array.isArray(event.details)
|
|
138
222
|
? event.details as Record<string, unknown>
|
|
139
223
|
: {};
|
|
@@ -156,18 +240,58 @@ export function registerContextGateway(
|
|
|
156
240
|
return undefined;
|
|
157
241
|
});
|
|
158
242
|
|
|
243
|
+
pi.on("before_provider_request", async (_event, ctx) => {
|
|
244
|
+
if (effectiveMode === "off") return undefined;
|
|
245
|
+
const attempt = efficiency.recordProviderAttempt();
|
|
246
|
+
const model = (ctx as any)?.model;
|
|
247
|
+
writeAccounting("provider.request", {
|
|
248
|
+
attempt,
|
|
249
|
+
provider: typeof model?.provider === "string" ? model.provider : undefined,
|
|
250
|
+
model: typeof model?.id === "string" ? model.id : undefined,
|
|
251
|
+
});
|
|
252
|
+
return undefined;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
pi.on("message_end", async (event) => {
|
|
256
|
+
if (effectiveMode === "off" || event.message?.role !== "assistant") return;
|
|
257
|
+
const completion = efficiency.recordProviderCompletion(event.message as any);
|
|
258
|
+
const snapshot = efficiency.snapshot();
|
|
259
|
+
writeAccounting("provider.completion", {
|
|
260
|
+
...completion,
|
|
261
|
+
cumulative: {
|
|
262
|
+
providerInputTokens: snapshot.providerInputTokens,
|
|
263
|
+
providerOutputTokens: snapshot.providerOutputTokens,
|
|
264
|
+
providerCacheReadTokens: snapshot.providerCacheReadTokens,
|
|
265
|
+
providerCacheWriteTokens: snapshot.providerCacheWriteTokens,
|
|
266
|
+
providerTotalTokens: snapshot.providerTotalTokens,
|
|
267
|
+
providerCost: snapshot.providerCost,
|
|
268
|
+
grossEstimatedTokensSaved: snapshot.grossEstimatedTokensSaved,
|
|
269
|
+
retrievalEstimatedTokens: snapshot.retrievalEstimatedTokens,
|
|
270
|
+
conservativeNetEstimatedTokens: snapshot.conservativeNetEstimatedTokens,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
159
275
|
pi.on("session_start", async () => {
|
|
276
|
+
finalizeAccountingEpoch("session-start-reset");
|
|
160
277
|
resetRuntimeTelemetry();
|
|
278
|
+
beginAccountingEpoch("session-start");
|
|
161
279
|
return undefined;
|
|
162
280
|
});
|
|
163
281
|
|
|
164
282
|
pi.on("session_tree", async () => {
|
|
283
|
+
finalizeAccountingEpoch("session-tree");
|
|
165
284
|
resetRuntimeTelemetry();
|
|
285
|
+
beginAccountingEpoch("session-tree");
|
|
166
286
|
return undefined;
|
|
167
287
|
});
|
|
168
288
|
|
|
169
289
|
pi.on("session_shutdown", async () => {
|
|
290
|
+
finalizeAccountingEpoch("session-shutdown");
|
|
170
291
|
resetRuntimeTelemetry();
|
|
292
|
+
efficiency.reset();
|
|
293
|
+
accountingEpochActive = false;
|
|
294
|
+
await contextGatewayAccountingLogDrain();
|
|
171
295
|
return undefined;
|
|
172
296
|
});
|
|
173
297
|
|
|
@@ -206,7 +330,16 @@ export default function contextGatewayExtension(pi: ExtensionAPI): void {
|
|
|
206
330
|
}
|
|
207
331
|
|
|
208
332
|
export { accountContextGatewayParts } from "./accounting.js";
|
|
333
|
+
export {
|
|
334
|
+
contextGatewayAccountingLogDrain,
|
|
335
|
+
contextGatewayAccountingLogEnabled,
|
|
336
|
+
contextGatewayAccountingLogMaxBackups,
|
|
337
|
+
contextGatewayAccountingLogMaxBytes,
|
|
338
|
+
contextGatewayAccountingLogPath,
|
|
339
|
+
writeContextGatewayAccountingLog,
|
|
340
|
+
} from "./accounting-log.js";
|
|
209
341
|
export { loadContextGatewayConfig } from "./config.js";
|
|
342
|
+
export { ContextGatewayEfficiencyTracker, estimateContextGatewayTokens } from "./efficiency.js";
|
|
210
343
|
export { normalizeRedundantTruncationMetadata } from "./metadata-normalization.js";
|
|
211
344
|
export { STORELESS_CAPABILITIES, storelessCapability } from "./storeless-capabilities.js";
|
|
212
345
|
export { parseTestBuildOutput, planProspectiveTestOutputDelivery } from "./test-output-parser.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type StorelessCapabilityStatus = "supported" | "limited" | "unsupported";
|
|
2
|
-
export type StorelessCapabilityStrategy = "native-passthrough" | "metadata-only" | "pure-parser-candidate" | "pure-parser-compact" | "none";
|
|
2
|
+
export type StorelessCapabilityStrategy = "native-passthrough" | "metadata-only" | "pure-parser-candidate" | "pure-parser-compact" | "raw-session-recoverable-compact" | "none";
|
|
3
3
|
|
|
4
4
|
export interface StorelessCapabilityEntry {
|
|
5
5
|
surface:
|
|
@@ -13,7 +13,7 @@ export interface StorelessCapabilityEntry {
|
|
|
13
13
|
| "mcp-direct";
|
|
14
14
|
status: StorelessCapabilityStatus;
|
|
15
15
|
strategy: StorelessCapabilityStrategy;
|
|
16
|
-
lifetime: "current-result" | "producer-managed" | "visual-message" | "none";
|
|
16
|
+
lifetime: "current-result" | "producer-managed" | "raw-session" | "visual-message" | "none";
|
|
17
17
|
reason: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -41,9 +41,9 @@ export const STORELESS_CAPABILITIES: readonly StorelessCapabilityEntry[] = Objec
|
|
|
41
41
|
{
|
|
42
42
|
surface: "web-document",
|
|
43
43
|
status: "limited",
|
|
44
|
-
strategy: "
|
|
45
|
-
lifetime: "
|
|
46
|
-
reason: "
|
|
44
|
+
strategy: "raw-session-recoverable-compact",
|
|
45
|
+
lifetime: "raw-session",
|
|
46
|
+
reason: "In enforce mode, over-budget web_search/web_fetch results are compacted only when the producer supplied full structured details. Those raw details remain in the session toolResult and are recoverable by toolCallId through session-recovery; unsupported web shapes remain passthrough.",
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
49
|
surface: "structured-json",
|
|
@@ -254,7 +254,7 @@ export function observeToolResult(
|
|
|
254
254
|
toolClass?: ContextGatewayToolClass;
|
|
255
255
|
shellCommandScope?: "simple" | "compound" | "unknown";
|
|
256
256
|
maxInlineBytes?: number;
|
|
257
|
-
delivery?: { representation: "passthrough" | "test-build-compact"; contentBytes: number; textBytes: number };
|
|
257
|
+
delivery?: { representation: "passthrough" | "test-build-compact" | "web-recoverable-compact"; contentBytes: number; textBytes: number };
|
|
258
258
|
} = {},
|
|
259
259
|
): ContextGatewayObservation {
|
|
260
260
|
const toolName = typeof event.toolName === "string" ? event.toolName : "unknown";
|
|
@@ -367,7 +367,7 @@ export class ContextGatewayTelemetry {
|
|
|
367
367
|
recordToolResult(
|
|
368
368
|
event: ToolResultLike,
|
|
369
369
|
budget: number | ContextGatewayBudgets,
|
|
370
|
-
options: { delivery?: { representation: "passthrough" | "test-build-compact"; contentBytes: number; textBytes: number }; maxInlineBytes?: number } = {},
|
|
370
|
+
options: { delivery?: { representation: "passthrough" | "test-build-compact" | "web-recoverable-compact"; contentBytes: number; textBytes: number }; maxInlineBytes?: number } = {},
|
|
371
371
|
): ContextGatewayObservation {
|
|
372
372
|
const binding = typeof event.toolCallId === "string" ? this.bindings.get(event.toolCallId) : undefined;
|
|
373
373
|
const observation = observeToolResult(event, budget, {
|
|
@@ -10,10 +10,17 @@ export interface ContextGatewayBudgets {
|
|
|
10
10
|
maxSearchMatches: number;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export interface ContextGatewayAccountingLogConfig {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
maxBytes: number;
|
|
16
|
+
maxBackups: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
/** Parsed layered config. `enforce` is selective and fail-open for unsupported result classes. */
|
|
14
20
|
export interface ContextGatewayResolvedConfig {
|
|
15
21
|
mode: ContextGatewayMode;
|
|
16
22
|
budgets: ContextGatewayBudgets;
|
|
23
|
+
accountingLog: ContextGatewayAccountingLogConfig;
|
|
17
24
|
issues: string[];
|
|
18
25
|
}
|
|
19
26
|
|
|
@@ -48,7 +55,7 @@ export interface ContextGatewayObservedView {
|
|
|
48
55
|
}
|
|
49
56
|
|
|
50
57
|
export interface ContextGatewayObservedDelivery {
|
|
51
|
-
representation: "passthrough" | "test-build-compact";
|
|
58
|
+
representation: "passthrough" | "test-build-compact" | "web-recoverable-compact";
|
|
52
59
|
contentBytes: number;
|
|
53
60
|
textBytes: number;
|
|
54
61
|
}
|
|
@@ -5,13 +5,32 @@ import {
|
|
|
5
5
|
type AutoCompressionResult,
|
|
6
6
|
type CreateAutoCompressionBlockOptions,
|
|
7
7
|
} from "./auto-compress.js";
|
|
8
|
-
import { canonicalMessageHash } from "./conversation-index.js";
|
|
8
|
+
import { buildExactRangeMembership, canonicalMessageHash } from "./conversation-index.js";
|
|
9
9
|
import { stableMessageKeys } from "./pruner-message-ids.js";
|
|
10
10
|
import type { CompressionCandidate } from "./pruner-types.js";
|
|
11
11
|
import type { DcpState } from "./state.js";
|
|
12
12
|
|
|
13
13
|
const rejectedSources = new WeakMap<DcpState, { key: string; error: AutoCompressionBlockedError }>();
|
|
14
14
|
const FINALIZATION_GRACE_MS = 1_000;
|
|
15
|
+
const SUMMARY_DEADLINE_FRACTION = 0.75;
|
|
16
|
+
|
|
17
|
+
function exactCandidateFingerprint(
|
|
18
|
+
options: CreateAutoCompressionBlockOptions,
|
|
19
|
+
candidate: CompressionCandidate,
|
|
20
|
+
): { startId: string; endId: string; sourceMembers: Array<{ stableId: string; hash: string }> } | undefined {
|
|
21
|
+
const membership = buildExactRangeMembership(
|
|
22
|
+
options.state.conversationIndexSnapshot,
|
|
23
|
+
candidate.startId,
|
|
24
|
+
candidate.endId,
|
|
25
|
+
options.state,
|
|
26
|
+
);
|
|
27
|
+
if (!membership) return undefined;
|
|
28
|
+
return {
|
|
29
|
+
startId: candidate.startId,
|
|
30
|
+
endId: candidate.endId,
|
|
31
|
+
sourceMembers: membership.sourceMembers,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
15
34
|
|
|
16
35
|
/**
|
|
17
36
|
* A source-size estimate is not a net-savings estimate. Try the economical
|
|
@@ -25,14 +44,28 @@ export async function createBudgetedAutoCompressionBlock(
|
|
|
25
44
|
): Promise<AutoCompressionResult> {
|
|
26
45
|
options.signal?.throwIfAborted();
|
|
27
46
|
const epoch = options.state.sessionEpoch;
|
|
47
|
+
const primaryFingerprint = exactCandidateFingerprint(options, options.candidate);
|
|
48
|
+
const largestFingerprint = largestSafeCandidate
|
|
49
|
+
? exactCandidateFingerprint(options, largestSafeCandidate)
|
|
50
|
+
: undefined;
|
|
51
|
+
// Rejection memoization belongs to the exact source that was rejected, not
|
|
52
|
+
// to the whole provider tail. A long-running agent can append unrelated
|
|
53
|
+
// messages while the oldest safe candidate stays byte-identical; hashing the
|
|
54
|
+
// whole tail made that same impossible candidate run again after every tool
|
|
55
|
+
// result. Fall back to the full-context identity only when exact membership
|
|
56
|
+
// is unavailable, so memoization never guesses source equivalence.
|
|
57
|
+
const sourceIdentity = primaryFingerprint && (!largestSafeCandidate || largestFingerprint)
|
|
58
|
+
? { primary: primaryFingerprint, largest: largestFingerprint }
|
|
59
|
+
: {
|
|
60
|
+
source: options.messages.map(canonicalMessageHash),
|
|
61
|
+
ids: stableMessageKeys(options.messages),
|
|
62
|
+
};
|
|
28
63
|
const key = createHash("sha256").update(JSON.stringify({
|
|
29
64
|
epoch,
|
|
30
|
-
|
|
31
|
-
ids: stableMessageKeys(options.messages),
|
|
65
|
+
sourceIdentity,
|
|
32
66
|
config: options.config,
|
|
33
67
|
budget: options.requiredGainTokens ?? 0,
|
|
34
68
|
allowPartialGain: options.allowPartialGain === true,
|
|
35
|
-
largest: largestSafeCandidate,
|
|
36
69
|
})).digest("hex");
|
|
37
70
|
const rejected = rejectedSources.get(options.state);
|
|
38
71
|
if (rejected?.key === key) throw rejected.error;
|
|
@@ -41,6 +74,7 @@ export async function createBudgetedAutoCompressionBlock(
|
|
|
41
74
|
const onAbort = () => controller.abort(options.signal?.reason);
|
|
42
75
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
43
76
|
const timeout = Math.max(1, options.config.compress.autoCompress.timeoutMs);
|
|
77
|
+
const summaryTimeoutMs = Math.max(1, Math.floor(timeout * SUMMARY_DEADLINE_FRACTION));
|
|
44
78
|
// generateModelSummary owns the configured summarizer deadline and can
|
|
45
79
|
// deliberately recover from it with the deterministic extractive fallback.
|
|
46
80
|
// Keep a small bounded tail for that fallback and durable publication;
|
|
@@ -84,6 +118,7 @@ export async function createBudgetedAutoCompressionBlock(
|
|
|
84
118
|
...options,
|
|
85
119
|
candidate: candidates[attempt]!,
|
|
86
120
|
signal: controller.signal,
|
|
121
|
+
summaryTimeoutMs,
|
|
87
122
|
});
|
|
88
123
|
rejectedSources.delete(options.state);
|
|
89
124
|
return result;
|
|
@@ -16,7 +16,7 @@ import { createHash } from "node:crypto"
|
|
|
16
16
|
import type { Model, Api, ProviderHeaders } from "@earendil-works/pi-ai"
|
|
17
17
|
import { completeWithModelRegistry, type ModelCompletionRegistry } from "../model-completion.js"
|
|
18
18
|
import type { DcpState } from "./state.js"
|
|
19
|
-
import type
|
|
19
|
+
import { summarizerModelRefs, type DcpConfig } from "./config.js"
|
|
20
20
|
import type { CompressionCandidate } from "./pruner-types.js"
|
|
21
21
|
import { estimateMessageTokens, estimateTokens, stripStaleDcpMetadataLines } from "./pruner-metadata.js"
|
|
22
22
|
import {
|
|
@@ -37,11 +37,19 @@ import { settleCompressionProgress } from "./compression-progress.js"
|
|
|
37
37
|
|
|
38
38
|
export class AutoCompressionBlockedError extends Error {
|
|
39
39
|
readonly blockedReason: DcpBlockedReason
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
readonly summarizerFallbackUsed: boolean
|
|
41
|
+
readonly summarizerAttempts?: ModelSummaryAttempt[]
|
|
42
|
+
|
|
43
|
+
constructor(
|
|
44
|
+
blockedReason: DcpBlockedReason,
|
|
45
|
+
message: string,
|
|
46
|
+
details: { summarizerFallbackUsed?: boolean; summarizerAttempts?: ModelSummaryAttempt[] } = {},
|
|
47
|
+
) {
|
|
42
48
|
super(message)
|
|
43
49
|
this.name = "AutoCompressionBlockedError"
|
|
44
50
|
this.blockedReason = blockedReason
|
|
51
|
+
this.summarizerFallbackUsed = details.summarizerFallbackUsed === true
|
|
52
|
+
this.summarizerAttempts = details.summarizerAttempts
|
|
45
53
|
}
|
|
46
54
|
}
|
|
47
55
|
|
|
@@ -828,12 +836,12 @@ export async function prepareCompressionSummary(options: {
|
|
|
828
836
|
fallbackCandidate,
|
|
829
837
|
sourceManifest,
|
|
830
838
|
)
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
839
|
+
// Do not impose a second, fixed output ceiling on the deterministic safety
|
|
840
|
+
// floor. A large source can legitimately require more than 8192 tokens to
|
|
841
|
+
// preserve every explicit continuity checkpoint. The auto-compression path
|
|
842
|
+
// already measures the actual replacement against the exact source and
|
|
843
|
+
// rejects non-positive/full-projection gain below, so compression economics
|
|
844
|
+
// — not an unrelated constant — decide whether this fallback is usable.
|
|
837
845
|
return {
|
|
838
846
|
text,
|
|
839
847
|
representation: modelResult?.text
|
|
@@ -881,6 +889,8 @@ export interface CreateAutoCompressionBlockOptions {
|
|
|
881
889
|
requiredGainTokens?: number
|
|
882
890
|
/** Allow a positive safe commit that reduces, but does not fully settle, the current recovery debt. */
|
|
883
891
|
allowPartialGain?: boolean
|
|
892
|
+
/** Optional model-summary sub-deadline. The outer budgeted operation may reserve time for deterministic fallback/publication. */
|
|
893
|
+
summaryTimeoutMs?: number
|
|
884
894
|
/** Optional durable publication hook. Live state is not changed unless it succeeds. */
|
|
885
895
|
persistState?: (preparedState: DcpState, publication?: DcpJournalPublicationOptions) => Promise<void>
|
|
886
896
|
/** Optional pure projection preparation; included in the same durable generation. */
|
|
@@ -919,8 +929,9 @@ function createAutoCompressionWorkingState(state: DcpState): DcpState {
|
|
|
919
929
|
|
|
920
930
|
/**
|
|
921
931
|
* Create the auto-compression block. Selects the summary source based on
|
|
922
|
-
* `config.compress.autoCompress.summarizerModel
|
|
923
|
-
*
|
|
932
|
+
* `config.compress.autoCompress.summarizerModel` plus explicit
|
|
933
|
+
* `summarizerFallbackModels`: empty → programmatic digest; non-empty → model
|
|
934
|
+
* summary with ordered model fallbacks, then programmatic fallback. Delegates block
|
|
924
935
|
* creation to the shared `createRangeCompressionBlock` path so protected
|
|
925
936
|
* content (user messages, tool outputs, prompt info) is handled identically to
|
|
926
937
|
* a model-initiated compress.
|
|
@@ -991,8 +1002,8 @@ export async function createAutoCompressionBlock(
|
|
|
991
1002
|
topic,
|
|
992
1003
|
messages: messagesInRange,
|
|
993
1004
|
candidate: effectiveCandidate,
|
|
994
|
-
modelRefs: settings
|
|
995
|
-
timeoutMs: settings.timeoutMs,
|
|
1005
|
+
modelRefs: summarizerModelRefs(settings),
|
|
1006
|
+
timeoutMs: options.summaryTimeoutMs ?? settings.timeoutMs,
|
|
996
1007
|
modelRegistry,
|
|
997
1008
|
signal,
|
|
998
1009
|
})
|
|
@@ -1065,11 +1076,16 @@ export async function createAutoCompressionBlock(
|
|
|
1065
1076
|
const sourceExactEstimate = messagesInRange.reduce((sum, message) => sum + estimateMessageTokens(message), 0)
|
|
1066
1077
|
const replacementExactEstimate = estimateCompressionBlockReplacementTokens(created.block)
|
|
1067
1078
|
const projectedGain = sourceExactEstimate - replacementExactEstimate
|
|
1079
|
+
const blockedSummaryDetails = {
|
|
1080
|
+
summarizerFallbackUsed: preparedSummary.representation === "extractive-fallback",
|
|
1081
|
+
summarizerAttempts: preparedSummary.summarizerAttempts,
|
|
1082
|
+
}
|
|
1068
1083
|
if (projectedGain <= 0) {
|
|
1069
1084
|
throw new AutoCompressionBlockedError(
|
|
1070
1085
|
"non-positive-gain",
|
|
1071
1086
|
`Auto-compress rejected non-positive full-projection gain for ${effectiveCandidate.startId}..${effectiveCandidate.endId}: ` +
|
|
1072
1087
|
`source ${sourceExactEstimate} tokens, replacement ${replacementExactEstimate} tokens`,
|
|
1088
|
+
blockedSummaryDetails,
|
|
1073
1089
|
)
|
|
1074
1090
|
}
|
|
1075
1091
|
const requiredGainTokens = Math.max(0, Math.floor(options.requiredGainTokens ?? 0))
|
|
@@ -1078,6 +1094,7 @@ export async function createAutoCompressionBlock(
|
|
|
1078
1094
|
"budget-exhausted",
|
|
1079
1095
|
`Auto-compress projected gain for ${effectiveCandidate.startId}..${effectiveCandidate.endId} is below required budget recovery: ` +
|
|
1080
1096
|
`${projectedGain} < ${requiredGainTokens} tokens`,
|
|
1097
|
+
blockedSummaryDetails,
|
|
1081
1098
|
)
|
|
1082
1099
|
}
|
|
1083
1100
|
|
|
@@ -1089,12 +1106,22 @@ export async function createAutoCompressionBlock(
|
|
|
1089
1106
|
fullProjectedAfterTokens = finalProjection.reduce((sum, message) => sum + estimateMessageTokens(message), 0)
|
|
1090
1107
|
fullProjectionGain = fullBefore - fullProjectedAfterTokens
|
|
1091
1108
|
if (fullProjectionGain <= 0 || (fullProjectionGain < requiredGainTokens && !options.allowPartialGain)) {
|
|
1092
|
-
throw new AutoCompressionBlockedError(
|
|
1109
|
+
throw new AutoCompressionBlockedError(
|
|
1110
|
+
"budget-exhausted",
|
|
1111
|
+
`Final provider projection saves ${fullProjectionGain}, below required ${requiredGainTokens}; no state published`,
|
|
1112
|
+
blockedSummaryDetails,
|
|
1113
|
+
)
|
|
1093
1114
|
}
|
|
1094
1115
|
}
|
|
1095
1116
|
const pressureRelieved = workingState.compressionProgress
|
|
1096
1117
|
? settleCompressionProgress(workingState, fullProjectionGain, fullProjectedAfterTokens)
|
|
1097
1118
|
: true
|
|
1119
|
+
created.block.commitMetrics = {
|
|
1120
|
+
operationId: `auto:${created.block.id}`,
|
|
1121
|
+
kind: effectiveCandidate.includedBlockIds.length >= 2 && sourceCoverage.itemCount === effectiveCandidate.includedBlockIds.length ? "consolidation" : "auto",
|
|
1122
|
+
beforeTokens: fullProjectedAfterTokens + fullProjectionGain,
|
|
1123
|
+
afterTokens: fullProjectedAfterTokens, netGainTokens: fullProjectionGain,
|
|
1124
|
+
}
|
|
1098
1125
|
assertCurrent()
|
|
1099
1126
|
let published = false
|
|
1100
1127
|
if (options.persistState) await options.persistState(workingState, {
|