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
|
@@ -2,10 +2,11 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-c
|
|
|
2
2
|
import type { AutocompleteItem } from "@earendil-works/pi-tui"
|
|
3
3
|
import type { DcpState } from "./state.js"
|
|
4
4
|
import { modelKeysFromContext, resolveModelConfig, type DcpConfig } from "./config.js"
|
|
5
|
-
import type { DcpNudgeType } from "./pruner-types.js"
|
|
6
5
|
import { isToolRecordProtected, markToolPruned } from "./pruner.js"
|
|
7
6
|
import { ignoreStaleExtensionContextError, safeGetContextUsage } from "../context-usage.js"
|
|
8
7
|
import { captureDcpTransactionGuard, cloneDcpTransactionState, runDcpStateTransaction } from "./state-transaction.js"
|
|
8
|
+
import { readDcpJournalBranch } from "./journal.js"
|
|
9
|
+
import { formatDcpStatistics } from "./statistics.js"
|
|
9
10
|
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
11
12
|
// Constants
|
|
@@ -34,42 +35,6 @@ function fmt(n: number): string {
|
|
|
34
35
|
return n.toLocaleString()
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
const NUDGE_TYPES: DcpNudgeType[] = ["turn", "iteration", "context-soft", "context-strong"]
|
|
38
|
-
|
|
39
|
-
function pct(numerator: number, denominator: number): string {
|
|
40
|
-
if (denominator <= 0) return "n/a"
|
|
41
|
-
return `${((numerator / denominator) * 100).toFixed(1)}%`
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function nudgeLabel(type: DcpNudgeType): string {
|
|
45
|
-
switch (type) {
|
|
46
|
-
case "context-strong": return "context-strong"
|
|
47
|
-
case "context-soft": return "context-soft"
|
|
48
|
-
case "iteration": return "iteration"
|
|
49
|
-
case "turn": return "turn"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function isNudgeType(value: unknown): value is DcpNudgeType {
|
|
54
|
-
return typeof value === "string" && (NUDGE_TYPES as string[]).includes(value)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function customEntryData(entry: unknown, customType: string): Record<string, unknown> | undefined {
|
|
58
|
-
const record = entry as { type?: unknown; customType?: unknown; data?: unknown }
|
|
59
|
-
if (record?.type !== "custom" || record.customType !== customType) return undefined
|
|
60
|
-
if (!record.data || typeof record.data !== "object" || Array.isArray(record.data)) return undefined
|
|
61
|
-
return record.data as Record<string, unknown>
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function branchEntries(ctx: ExtensionCommandContext): any[] {
|
|
65
|
-
try {
|
|
66
|
-
const branch = ctx.sessionManager?.getBranch?.()
|
|
67
|
-
return Array.isArray(branch) ? branch : []
|
|
68
|
-
} catch {
|
|
69
|
-
return []
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
38
|
async function staleSafe(action: () => void | Promise<void>): Promise<void> {
|
|
74
39
|
try {
|
|
75
40
|
await action()
|
|
@@ -78,85 +43,6 @@ async function staleSafe(action: () => void | Promise<void>): Promise<void> {
|
|
|
78
43
|
}
|
|
79
44
|
}
|
|
80
45
|
|
|
81
|
-
interface DcpNudgeStats {
|
|
82
|
-
emitted: number
|
|
83
|
-
reapplied: number
|
|
84
|
-
upgraded: number
|
|
85
|
-
clearedEvents: number
|
|
86
|
-
clearedAnchors: number
|
|
87
|
-
byType: Record<DcpNudgeType, number>
|
|
88
|
-
activeByType: Record<DcpNudgeType, number>
|
|
89
|
-
last?: {
|
|
90
|
-
type: DcpNudgeType
|
|
91
|
-
event: "emitted" | "reapplied" | "upgraded"
|
|
92
|
-
createdAt?: number
|
|
93
|
-
contextPercent?: number | null
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function collectNudgeStats(ctx: ExtensionCommandContext, state: DcpState): DcpNudgeStats {
|
|
98
|
-
const stats: DcpNudgeStats = {
|
|
99
|
-
emitted: 0,
|
|
100
|
-
reapplied: 0,
|
|
101
|
-
upgraded: 0,
|
|
102
|
-
clearedEvents: 0,
|
|
103
|
-
clearedAnchors: 0,
|
|
104
|
-
byType: { "turn": 0, "iteration": 0, "context-soft": 0, "context-strong": 0 },
|
|
105
|
-
activeByType: { "turn": 0, "iteration": 0, "context-soft": 0, "context-strong": 0 },
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
for (const anchor of state.nudgeAnchors) {
|
|
109
|
-
if (isNudgeType(anchor.type)) stats.activeByType[anchor.type]++
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
for (const entry of branchEntries(ctx)) {
|
|
113
|
-
const data = customEntryData(entry, "dcp-nudge")
|
|
114
|
-
if (!data) continue
|
|
115
|
-
const event = data.event
|
|
116
|
-
if ((event === "emitted" || event === "reapplied" || event === "upgraded") && isNudgeType(data.type)) {
|
|
117
|
-
if (event === "emitted") stats.emitted++
|
|
118
|
-
else if (event === "reapplied") stats.reapplied++
|
|
119
|
-
else stats.upgraded++
|
|
120
|
-
stats.byType[data.type]++
|
|
121
|
-
const createdAt = typeof data.createdAt === "number" ? data.createdAt : undefined
|
|
122
|
-
const rawContextPercent = data.contextPercent
|
|
123
|
-
let contextPercent: number | null | undefined
|
|
124
|
-
if (typeof rawContextPercent === "number") contextPercent = rawContextPercent
|
|
125
|
-
else if (rawContextPercent === null) contextPercent = null
|
|
126
|
-
if (!stats.last || (createdAt ?? 0) >= (stats.last.createdAt ?? 0)) {
|
|
127
|
-
stats.last = { type: data.type, event, createdAt, contextPercent }
|
|
128
|
-
}
|
|
129
|
-
} else if (event === "cleared") {
|
|
130
|
-
stats.clearedEvents++
|
|
131
|
-
stats.clearedAnchors += typeof data.clearedAnchors === "number" ? Math.max(0, data.clearedAnchors) : 0
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (!stats.last && state.lastNudge && isNudgeType(state.lastNudge.type)) {
|
|
136
|
-
stats.last = {
|
|
137
|
-
type: state.lastNudge.type,
|
|
138
|
-
event: "emitted",
|
|
139
|
-
createdAt: state.lastNudge.createdAt,
|
|
140
|
-
contextPercent: typeof state.lastNudge.contextPercent === "number"
|
|
141
|
-
? state.lastNudge.contextPercent * 100
|
|
142
|
-
: undefined,
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return stats
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function formatDate(ts: number | undefined): string {
|
|
150
|
-
if (typeof ts !== "number" || !Number.isFinite(ts) || ts <= 0) return "unknown time"
|
|
151
|
-
return new Date(ts).toLocaleString()
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function formatContextPercent(value: number | null | undefined): string {
|
|
155
|
-
if (value === null) return "unknown context"
|
|
156
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return "unknown context"
|
|
157
|
-
return `${value.toFixed(1)}% context`
|
|
158
|
-
}
|
|
159
|
-
|
|
160
46
|
function sendChatSystemMessage(pi: ExtensionAPI, customType: string, content: string, details?: Record<string, unknown>): void {
|
|
161
47
|
pi.sendMessage({
|
|
162
48
|
customType,
|
|
@@ -238,44 +124,16 @@ function handleContext(ctx: ExtensionCommandContext, state: DcpState): void {
|
|
|
238
124
|
// Stats
|
|
239
125
|
// ---------------------------------------------------------------------------
|
|
240
126
|
|
|
241
|
-
function handleStats(pi: ExtensionAPI, ctx: ExtensionCommandContext, state: DcpState): void {
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
lines.push(` Compression blocks active: ${activeBlocks} / ${totalBlocks} total`)
|
|
252
|
-
lines.push(` Manual mode: ${state.manualMode ? "on" : "off"}`)
|
|
253
|
-
lines.push("")
|
|
254
|
-
lines.push("Nudge telemetry:")
|
|
255
|
-
lines.push(` Sent: ${fmt(nudgeStats.emitted)} emitted, ${fmt(nudgeStats.reapplied)} reapplied, ${fmt(nudgeStats.upgraded)} upgraded`)
|
|
256
|
-
lines.push(
|
|
257
|
-
` By type: ${NUDGE_TYPES.map((type) => `${nudgeLabel(type)}=${fmt(nudgeStats.byType[type])}`).join(", ")}`,
|
|
258
|
-
)
|
|
259
|
-
lines.push(
|
|
260
|
-
` Active anchors: ${fmt(activeAnchors)}${activeAnchors > 0
|
|
261
|
-
? ` (${NUDGE_TYPES.map((type) => `${nudgeLabel(type)}=${fmt(nudgeStats.activeByType[type])}`).join(", ")})`
|
|
262
|
-
: ""}`,
|
|
263
|
-
)
|
|
264
|
-
lines.push(` Cleared after compress: ${fmt(nudgeStats.clearedEvents)} time${nudgeStats.clearedEvents === 1 ? "" : "s"} (${fmt(nudgeStats.clearedAnchors)} anchor${nudgeStats.clearedAnchors === 1 ? "" : "s"})`)
|
|
265
|
-
lines.push(` Compliance proxy: ${fmt(nudgeStats.clearedEvents)} compress-after-nudge / ${fmt(totalNudgeEvents)} nudge event${totalNudgeEvents === 1 ? "" : "s"} (${pct(nudgeStats.clearedEvents, totalNudgeEvents)})`)
|
|
266
|
-
if (nudgeStats.last) {
|
|
267
|
-
lines.push(
|
|
268
|
-
` Last nudge: ${nudgeLabel(nudgeStats.last.type)} ${nudgeStats.last.event} at ${formatDate(nudgeStats.last.createdAt)} (${formatContextPercent(nudgeStats.last.contextPercent)})`,
|
|
269
|
-
)
|
|
270
|
-
} else {
|
|
271
|
-
lines.push(" Last nudge: none recorded")
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
sendChatSystemMessage(pi, DCP_STATS_MESSAGE_TYPE, lines.join("\n"), {
|
|
275
|
-
generatedAt: new Date().toISOString(),
|
|
276
|
-
activeAnchors,
|
|
277
|
-
nudgeEvents: totalNudgeEvents,
|
|
278
|
-
})
|
|
127
|
+
async function handleStats(pi: ExtensionAPI, ctx: ExtensionCommandContext, state: DcpState): Promise<void> {
|
|
128
|
+
const epoch = state.sessionEpoch
|
|
129
|
+
let branch: unknown[] = []
|
|
130
|
+
let historyStatus: "full" | "unavailable" = "full"
|
|
131
|
+
try { branch = await readDcpJournalBranch(ctx) }
|
|
132
|
+
catch { historyStatus = "unavailable" }
|
|
133
|
+
if (state.sessionEpoch !== epoch) return
|
|
134
|
+
sendChatSystemMessage(pi, DCP_STATS_MESSAGE_TYPE,
|
|
135
|
+
formatDcpStatistics({ branch, historyStatus, model: ctx.model, usage: safeGetContextUsage(ctx) }),
|
|
136
|
+
{ generatedAt: new Date().toISOString() })
|
|
279
137
|
}
|
|
280
138
|
|
|
281
139
|
// ---------------------------------------------------------------------------
|
|
@@ -290,7 +148,7 @@ async function handleSweep(
|
|
|
290
148
|
): Promise<void> {
|
|
291
149
|
await ctx.waitForIdle()
|
|
292
150
|
|
|
293
|
-
const branch =
|
|
151
|
+
const branch = await readDcpJournalBranch(ctx) as any[]
|
|
294
152
|
|
|
295
153
|
// Build the full set of protected tool names.
|
|
296
154
|
const protectedTools = new Set<string>([
|
|
@@ -480,7 +338,7 @@ export function registerCommands(
|
|
|
480
338
|
break
|
|
481
339
|
|
|
482
340
|
case "stats":
|
|
483
|
-
handleStats(pi, ctx, state)
|
|
341
|
+
await handleStats(pi, ctx, state)
|
|
484
342
|
break
|
|
485
343
|
|
|
486
344
|
case "doctor":
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { Type } from "typebox"
|
|
6
6
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"
|
|
7
7
|
import type { CompressionMember, DcpState } from "./state.js"
|
|
8
|
-
import { modelKeysFromContext, resolveModelConfig, type DcpConfig } from "./config.js"
|
|
8
|
+
import { modelKeysFromContext, resolveModelConfig, summarizerModelRefs, type DcpConfig } from "./config.js"
|
|
9
9
|
import { captureDcpTransactionGuard, cloneDcpTransactionState, runDcpStateTransaction } from "./state-transaction.js"
|
|
10
10
|
import { createHash } from "node:crypto"
|
|
11
11
|
import { clearDcpNudgeAnchors } from "./pruner.js"
|
|
@@ -534,7 +534,7 @@ export function registerCompressTool(
|
|
|
534
534
|
includedBlockIds: [],
|
|
535
535
|
reason: "manual delegated summary",
|
|
536
536
|
},
|
|
537
|
-
modelRefs: effectiveConfig.compress.autoCompress
|
|
537
|
+
modelRefs: summarizerModelRefs(effectiveConfig.compress.autoCompress),
|
|
538
538
|
timeoutMs: effectiveConfig.compress.autoCompress.timeoutMs,
|
|
539
539
|
modelRegistry: (ctx as any).modelRegistry,
|
|
540
540
|
signal: _signal,
|
|
@@ -795,6 +795,12 @@ export function registerCompressTool(
|
|
|
795
795
|
for (const block of workingState.compressionBlocks) {
|
|
796
796
|
if (newBlockIds.includes(block.id)) block.operationRequestHash = requestHash
|
|
797
797
|
}
|
|
798
|
+
const firstBlock = workingState.compressionBlocks.find((block) => block.id === newBlockIds[0])
|
|
799
|
+
if (firstBlock) firstBlock.commitMetrics = {
|
|
800
|
+
operationId: `manual:${_toolCallId}`, kind: "manual",
|
|
801
|
+
beforeTokens: projection.projectedBeforeTokens,
|
|
802
|
+
afterTokens: projection.projectedAfterTokens, netGainTokens: projection.netGain,
|
|
803
|
+
}
|
|
798
804
|
assertCurrent()
|
|
799
805
|
let published = false
|
|
800
806
|
await persistState(ctx, workingState, {
|
|
@@ -27,6 +27,28 @@ export function outstandingCompressionTokens(state: DcpState, input: Compression
|
|
|
27
27
|
return Math.max(0, previous.remainingTokens + growth + stricterTarget)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Bound one automatic recovery plan to what the *current* budget actually
|
|
32
|
+
* requires. `compressionProgress.remainingTokens` is deliberately cumulative:
|
|
33
|
+
* it follows content growth and stricter historical targets across callbacks.
|
|
34
|
+
* That debt is useful for progress accounting, but feeding the whole stale debt
|
|
35
|
+
* back into candidate sizing can make a later, much smaller capacity gap select
|
|
36
|
+
* an uneconomically huge range (and repeatedly fail with non-positive gain).
|
|
37
|
+
*/
|
|
38
|
+
export function compressionPlanningTokens(
|
|
39
|
+
state: DcpState,
|
|
40
|
+
input: CompressionProgressInput,
|
|
41
|
+
remainingTokens: number | undefined = outstandingCompressionTokens(state, input),
|
|
42
|
+
): number {
|
|
43
|
+
const requested = Math.max(1, Math.ceil(input.requiredTokens))
|
|
44
|
+
if (
|
|
45
|
+
remainingTokens === undefined ||
|
|
46
|
+
remainingTokens <= 0 ||
|
|
47
|
+
state.compressionProgress?.kind !== input.kind
|
|
48
|
+
) return requested
|
|
49
|
+
return Math.min(remainingTokens, requested)
|
|
50
|
+
}
|
|
51
|
+
|
|
30
52
|
/** Follow actual projection growth, not context callback/retry count. */
|
|
31
53
|
export function trackCompressionProgress(
|
|
32
54
|
state: DcpState,
|
|
@@ -65,8 +65,12 @@ export interface DcpConfig {
|
|
|
65
65
|
* Failed calls and insufficient commits do not reset patience. */
|
|
66
66
|
patience: number
|
|
67
67
|
/** Models to try, in order, for auto-compress and explicit generated summaries.
|
|
68
|
+
* The first entry is the primary summarizer. Additional entries remain
|
|
69
|
+
* supported for backward compatibility with the old inline fallback list.
|
|
68
70
|
* Empty array → deterministic programmatic digest (no model call). */
|
|
69
71
|
summarizerModel: string[]
|
|
72
|
+
/** Explicit fallback summarizers tried after `summarizerModel`, in order. */
|
|
73
|
+
summarizerFallbackModels: string[]
|
|
70
74
|
/** Hard ceiling in ms for a summarizer model call on either path. */
|
|
71
75
|
timeoutMs: number
|
|
72
76
|
}
|
|
@@ -144,6 +148,7 @@ const DEFAULT_CONFIG: DcpConfig = {
|
|
|
144
148
|
enabled: false,
|
|
145
149
|
patience: 2,
|
|
146
150
|
summarizerModel: [],
|
|
151
|
+
summarizerFallbackModels: [],
|
|
147
152
|
timeoutMs: 20000,
|
|
148
153
|
},
|
|
149
154
|
},
|
|
@@ -312,6 +317,55 @@ function normalizeModelKey(key: string | undefined): string | undefined {
|
|
|
312
317
|
return trimmed.length > 0 ? trimmed : undefined
|
|
313
318
|
}
|
|
314
319
|
|
|
320
|
+
export function summarizerModelRefs(
|
|
321
|
+
settings: DcpConfig["compress"]["autoCompress"],
|
|
322
|
+
): string[] {
|
|
323
|
+
const refs = [
|
|
324
|
+
...(Array.isArray(settings.summarizerModel) ? settings.summarizerModel : []),
|
|
325
|
+
...(Array.isArray(settings.summarizerFallbackModels) ? settings.summarizerFallbackModels : []),
|
|
326
|
+
]
|
|
327
|
+
const seen = new Set<string>()
|
|
328
|
+
const result: string[] = []
|
|
329
|
+
for (const ref of refs) {
|
|
330
|
+
if (typeof ref !== "string") continue
|
|
331
|
+
const normalized = ref.trim()
|
|
332
|
+
if (!normalized || seen.has(normalized)) continue
|
|
333
|
+
seen.add(normalized)
|
|
334
|
+
result.push(normalized)
|
|
335
|
+
}
|
|
336
|
+
return result
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function normalizeModelRefArray(value: unknown): string[] {
|
|
340
|
+
if (!Array.isArray(value)) return []
|
|
341
|
+
const seen = new Set<string>()
|
|
342
|
+
const result: string[] = []
|
|
343
|
+
for (const ref of value) {
|
|
344
|
+
if (typeof ref !== "string") continue
|
|
345
|
+
const normalized = ref.trim()
|
|
346
|
+
if (!normalized || seen.has(normalized)) continue
|
|
347
|
+
seen.add(normalized)
|
|
348
|
+
result.push(normalized)
|
|
349
|
+
}
|
|
350
|
+
return result
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function normalizeResolvedModelArrays(config: DcpConfig): DcpConfig {
|
|
354
|
+
const autoCompress = config.compress?.autoCompress
|
|
355
|
+
if (!autoCompress || typeof autoCompress !== "object") return config
|
|
356
|
+
return {
|
|
357
|
+
...config,
|
|
358
|
+
compress: {
|
|
359
|
+
...config.compress,
|
|
360
|
+
autoCompress: {
|
|
361
|
+
...autoCompress,
|
|
362
|
+
summarizerModel: normalizeModelRefArray(autoCompress.summarizerModel),
|
|
363
|
+
summarizerFallbackModels: normalizeModelRefArray(autoCompress.summarizerFallbackModels),
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
315
369
|
function escapeRegExp(text: string): string {
|
|
316
370
|
return text.replace(/[|\\{}()[\]^$+?.]/g, "\\$&")
|
|
317
371
|
}
|
|
@@ -415,17 +469,17 @@ export function resolveModelConfig(
|
|
|
415
469
|
modelKeys: Array<string | undefined> = [],
|
|
416
470
|
): DcpConfig {
|
|
417
471
|
const overrides = config.modelOverrides
|
|
418
|
-
if (!overrides || Object.keys(overrides).length === 0) return config
|
|
472
|
+
if (!overrides || Object.keys(overrides).length === 0) return normalizeResolvedModelArrays(config)
|
|
419
473
|
|
|
420
474
|
const matches = matchingModelEntries(overrides, modelKeys)
|
|
421
|
-
if (matches.length === 0) return config
|
|
475
|
+
if (matches.length === 0) return normalizeResolvedModelArrays(config)
|
|
422
476
|
|
|
423
477
|
let resolved = deepMerge(config, {})
|
|
424
478
|
for (const [, override] of matches) {
|
|
425
479
|
resolved = deepMerge(resolved, override as Partial<DcpConfig>)
|
|
426
480
|
}
|
|
427
481
|
|
|
428
|
-
return resolved
|
|
482
|
+
return normalizeResolvedModelArrays(resolved)
|
|
429
483
|
}
|
|
430
484
|
|
|
431
485
|
// ---------------------------------------------------------------------------
|
|
@@ -454,5 +508,5 @@ export function loadConfig(options: LoadConfigOptions = {}): DcpConfig {
|
|
|
454
508
|
)
|
|
455
509
|
config.issues = issues
|
|
456
510
|
|
|
457
|
-
return config
|
|
511
|
+
return normalizeResolvedModelArrays(config)
|
|
458
512
|
}
|
|
@@ -76,19 +76,43 @@ function stripDcpCarrierText(text: string): string {
|
|
|
76
76
|
return stripStaleDcpMetadataLines(text)
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
const OMIT_JSON_OBJECT_VALUE = Symbol("omit-json-object-value")
|
|
80
|
+
|
|
81
|
+
function canonicalHashValue(
|
|
82
|
+
value: unknown,
|
|
83
|
+
seen = new WeakSet<object>(),
|
|
84
|
+
arrayElement = false,
|
|
85
|
+
): unknown | typeof OMIT_JSON_OBJECT_VALUE {
|
|
80
86
|
if (value === null || typeof value === "string" || typeof value === "boolean") return value
|
|
81
|
-
|
|
87
|
+
// Exact membership is persisted in JSONL, so hash the value that can actually
|
|
88
|
+
// survive that persistence boundary. JSON.stringify serializes non-finite
|
|
89
|
+
// numbers as null, drops undefined/function/symbol object properties, and
|
|
90
|
+
// converts those values to null when they occupy an array slot. Encoding an
|
|
91
|
+
// in-memory-only sentinel here made otherwise unchanged toolResult.details
|
|
92
|
+
// fail exact replay after restart.
|
|
93
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null
|
|
82
94
|
if (typeof value === "bigint") return `${value}n`
|
|
83
|
-
if (typeof value === "undefined"
|
|
84
|
-
|
|
85
|
-
|
|
95
|
+
if (typeof value === "undefined" || typeof value === "function" || typeof value === "symbol") {
|
|
96
|
+
return arrayElement ? null : OMIT_JSON_OBJECT_VALUE
|
|
97
|
+
}
|
|
98
|
+
if (Array.isArray(value)) {
|
|
99
|
+
return value.map((item) => {
|
|
100
|
+
const canonical = canonicalHashValue(item, seen, true)
|
|
101
|
+
return canonical === OMIT_JSON_OBJECT_VALUE ? null : canonical
|
|
102
|
+
})
|
|
103
|
+
}
|
|
86
104
|
if (typeof value === "object") {
|
|
87
105
|
if (seen.has(value as object)) return "[cycle]"
|
|
106
|
+
const toJSON = (value as { toJSON?: unknown }).toJSON
|
|
107
|
+
if (typeof toJSON === "function") {
|
|
108
|
+
const serialized = (toJSON as () => unknown).call(value)
|
|
109
|
+
return canonicalHashValue(serialized, seen, arrayElement)
|
|
110
|
+
}
|
|
88
111
|
seen.add(value as object)
|
|
89
112
|
const output: Record<string, unknown> = {}
|
|
90
113
|
for (const key of Object.keys(value as Record<string, unknown>).sort()) {
|
|
91
|
-
|
|
114
|
+
const canonical = canonicalHashValue((value as Record<string, unknown>)[key], seen, false)
|
|
115
|
+
if (canonical !== OMIT_JSON_OBJECT_VALUE) output[key] = canonical
|
|
92
116
|
}
|
|
93
117
|
seen.delete(value as object)
|
|
94
118
|
return output
|
|
@@ -106,14 +130,17 @@ export function canonicalMessageHash(message: any): string {
|
|
|
106
130
|
if (message && typeof message === "object") {
|
|
107
131
|
for (const key of Object.keys(message).sort()) {
|
|
108
132
|
if (IDENTITY_ONLY_MESSAGE_KEYS.has(key)) continue
|
|
109
|
-
|
|
133
|
+
const value = key === "content"
|
|
110
134
|
? canonicalContentForMessageHash(message)
|
|
111
135
|
: canonicalHashValue(message[key])
|
|
136
|
+
if (value !== OMIT_JSON_OBJECT_VALUE) canonical[key] = value
|
|
112
137
|
}
|
|
113
138
|
} else {
|
|
114
|
-
|
|
139
|
+
const value = canonicalHashValue(message)
|
|
140
|
+
if (value !== OMIT_JSON_OBJECT_VALUE) canonical.value = value
|
|
115
141
|
}
|
|
116
|
-
|
|
142
|
+
const normalized = canonicalHashValue(canonical)
|
|
143
|
+
return createHash("sha256").update(JSON.stringify(normalized === OMIT_JSON_OBJECT_VALUE ? {} : normalized)).digest("hex")
|
|
117
144
|
}
|
|
118
145
|
|
|
119
146
|
const CANONICAL_HASH_RE = /^[a-f0-9]{64}$/i
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { DcpConfig } from "./config.js";
|
|
2
|
+
import { modelKeysFromContext } from "./config.js";
|
|
3
|
+
import type { DcpState } from "./state.js";
|
|
4
|
+
import { estimateMessageTokens, getActiveSummaryTokenEstimate, resolveContextThresholds } from "./pruner-metadata.js";
|
|
5
|
+
import { planDcpBudget } from "./progress-controller.js";
|
|
6
|
+
|
|
7
|
+
/** Scalar-only diagnostic snapshot; never stores raw messages or credentials.
|
|
8
|
+
* These records are observational, never journal/recovery authority. */
|
|
9
|
+
export function dcpRequestSnapshot(config: DcpConfig, state: DcpState, model: any,
|
|
10
|
+
usage: { tokens?: number | null; contextWindow?: number } | undefined,
|
|
11
|
+
raw: any[], projected: any[], reason: string) {
|
|
12
|
+
const contextWindow = Number.isFinite(model?.contextWindow) && model.contextWindow > 0
|
|
13
|
+
? model.contextWindow : usage?.contextWindow;
|
|
14
|
+
const rawTokens = raw.reduce((sum, message) => sum + estimateMessageTokens(message), 0);
|
|
15
|
+
const projectedTokens = projected.reduce((sum, message) => sum + estimateMessageTokens(message), 0);
|
|
16
|
+
const thresholds = resolveContextThresholds(config, modelKeysFromContext({ model } as any), contextWindow);
|
|
17
|
+
const budget = contextWindow ? planDcpBudget({
|
|
18
|
+
providerUsageTokens: usage?.tokens, repoProjectedTokens: projectedTokens, contextWindow,
|
|
19
|
+
reservedOutputTokens: model?.maxTokens ?? 0, reservedToolTokens: 0,
|
|
20
|
+
maxContextPercent: thresholds.maxContextPercent,
|
|
21
|
+
hardContextPercent: config.strategies.emergencyCurrentTurnPruning.hardContextPercent,
|
|
22
|
+
targetContextPercent: Math.min(config.strategies.emergencyCurrentTurnPruning.targetContextPercent, thresholds.maxContextPercent * 0.9),
|
|
23
|
+
summaryBufferEnabled: config.compress.summaryBuffer,
|
|
24
|
+
activeSummaryTokens: getActiveSummaryTokenEstimate(state), summaryBufferMaxBonusRatio: 0.05,
|
|
25
|
+
estimatorMarginTokens: Math.max(256, Math.ceil(contextWindow * 0.0025)),
|
|
26
|
+
}) : undefined;
|
|
27
|
+
const routineTokens = contextWindow ? thresholds.minContextPercent * contextWindow : undefined;
|
|
28
|
+
let pressure = "unknown";
|
|
29
|
+
if (budget && routineTokens !== undefined) {
|
|
30
|
+
pressure = budget.capacityExceeded ? "over capacity" : budget.hardPressure ? "hard" : budget.pressured ? "strong"
|
|
31
|
+
: budget.projectedBeforeTokens > routineTokens ? "routine" : "below routine threshold";
|
|
32
|
+
}
|
|
33
|
+
return { model: model?.provider && model?.id ? `${model.provider}/${model.id}` : undefined,
|
|
34
|
+
contextWindow, rawTokens, projectedTokens, inputCapacityTokens: budget?.inputCapacityTokens,
|
|
35
|
+
reservedOutputTokens: budget?.reservedOutputTokens, routineTokens, strongTokens: budget?.softHeadroomTokens,
|
|
36
|
+
hardTokens: budget?.hardHeadroomTokens, enabled: config.enabled, manualMode: state.manualMode,
|
|
37
|
+
autoEnabled: config.compress.autoCompress.enabled, ignored: state.consecutiveIgnoredNudges,
|
|
38
|
+
routineProjectedTokens: budget?.projectedBeforeTokens ?? projectedTokens,
|
|
39
|
+
routineUsageAdjustmentTokens: 0,
|
|
40
|
+
pressure, reason, createdAt: Date.now() };
|
|
41
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proof that a tool result was produced locally in this epoch and has never
|
|
3
|
+
* belonged to an attempted provider prefix. This is NOT provider-seen evidence:
|
|
4
|
+
* failed/ambiguous sends consume freshness too. Never persist or rehydrate it.
|
|
5
|
+
*/
|
|
6
|
+
export class FreshToolResultTracker {
|
|
7
|
+
private ready = false;
|
|
8
|
+
private readonly pending = new Map<string, string>();
|
|
9
|
+
private readonly fresh = new Set<string>();
|
|
10
|
+
|
|
11
|
+
reset(): void {
|
|
12
|
+
this.ready = false;
|
|
13
|
+
this.pending.clear();
|
|
14
|
+
this.fresh.clear();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Call before every send, not after successful completion. */
|
|
18
|
+
beforeProviderRequest(): void {
|
|
19
|
+
this.reset();
|
|
20
|
+
this.ready = true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
toolCall(id: string, name: string, alreadyKnown: boolean): void {
|
|
24
|
+
// Resumed/in-flight old calls and reused IDs cannot create a freshness grant.
|
|
25
|
+
if (!this.ready || alreadyKnown) return;
|
|
26
|
+
this.pending.set(id, name);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
toolResult(id: string, name: string): void {
|
|
30
|
+
const expectedName = this.pending.get(id);
|
|
31
|
+
this.pending.delete(id);
|
|
32
|
+
if (expectedName === name) this.fresh.add(id);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get eligibleIds(): ReadonlySet<string> {
|
|
36
|
+
return this.fresh;
|
|
37
|
+
}
|
|
38
|
+
}
|