pi-ui-extend 1.0.41 → 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 +50 -13
- package/dist/app/app.d.ts +13 -0
- package/dist/app/app.js +158 -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 +4 -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 +57 -35
- 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 +15 -1
- package/dist/app/commands/command-session-actions.js +9 -3
- package/dist/app/commands/reload-context-inventory.d.ts +11 -0
- package/dist/app/commands/reload-context-inventory.js +70 -0
- package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
- package/dist/app/extensions/extension-actions-controller.js +6 -0
- package/dist/app/extensions/subagent-catalog-state.d.ts +9 -0
- package/dist/app/extensions/subagent-catalog-state.js +23 -0
- 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/editor-panels.js +10 -4
- 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 +7 -1
- package/dist/app/session/session-lifecycle-controller.js +16 -1
- package/dist/app/session/tabs-controller.d.ts +26 -1
- package/dist/app/session/tabs-controller.js +454 -147
- package/dist/app/subagents/subagents-files.js +60 -1
- package/dist/app/subagents/subagents-model.d.ts +1 -0
- package/dist/app/subagents/subagents-model.js +18 -2
- 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 +16 -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 +25 -5
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/pi-tools-suite-schema.d.ts +88 -62
- package/dist/schemas/pi-tools-suite-schema.js +54 -83
- package/dist/schemas/pix-schema.d.ts +19 -2
- package/dist/schemas/pix-schema.js +47 -5
- package/dist/schemas/tasks-schema.d.ts +18 -0
- package/dist/schemas/tasks-schema.js +41 -0
- package/docs/concurrency.md +9 -1
- package/docs/desktop-mvp.md +22 -6
- package/docs/desktop-task-manager.md +148 -84
- package/docs/release.md +30 -6
- package/external/pi-tools-suite/README.md +197 -84
- package/external/pi-tools-suite/docs/evals.md +1 -1
- package/external/pi-tools-suite/docs/session-recovery.md +47 -14
- package/external/pi-tools-suite/docs/subagent-model-pools.md +48 -30
- 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/frontier-review.md +23 -0
- package/external/pi-tools-suite/src/async-subagents/agents/implement.md +1 -1
- package/external/pi-tools-suite/src/async-subagents/agents/presets.jsonc +16 -0
- package/external/pi-tools-suite/src/async-subagents/agents/research.md +5 -3
- 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 +15 -71
- package/external/pi-tools-suite/src/async-subagents/core/activity.ts +33 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-catalog.ts +5 -4
- 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 +8 -5
- 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 +160 -327
- 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 +21 -5
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +40 -26
- package/external/pi-tools-suite/src/async-subagents/core/state.ts +40 -0
- package/external/pi-tools-suite/src/async-subagents/core/types.ts +7 -0
- package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +51 -45
- package/external/pi-tools-suite/src/async-subagents/index.ts +69 -5
- package/external/pi-tools-suite/src/async-subagents/lib.ts +16 -9
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +8 -5
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +5 -4
- package/external/pi-tools-suite/src/async-subagents/types.ts +1 -0
- package/external/pi-tools-suite/src/coding-discipline/index.ts +90 -59
- package/external/pi-tools-suite/src/config.ts +55 -1
- package/external/pi-tools-suite/src/context-gateway/accounting-log.ts +282 -0
- package/external/pi-tools-suite/src/context-gateway/config.ts +77 -3
- package/external/pi-tools-suite/src/context-gateway/efficiency.ts +414 -0
- package/external/pi-tools-suite/src/context-gateway/enforcement.ts +222 -0
- package/external/pi-tools-suite/src/context-gateway/index.ts +206 -19
- package/external/pi-tools-suite/src/context-gateway/storeless-capabilities.ts +7 -7
- package/external/pi-tools-suite/src/context-gateway/telemetry.ts +101 -28
- package/external/pi-tools-suite/src/context-gateway/types.ts +16 -3
- package/external/pi-tools-suite/src/context-inventory.ts +99 -0
- package/external/pi-tools-suite/src/dcp/auto-compress-budget.ts +39 -4
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +128 -49
- package/external/pi-tools-suite/src/dcp/commands.ts +32 -156
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +177 -46
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +6 -58
- package/external/pi-tools-suite/src/dcp/compression-preview.ts +9 -0
- package/external/pi-tools-suite/src/dcp/compression-progress.ts +22 -0
- package/external/pi-tools-suite/src/dcp/config.ts +114 -17
- 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/prompts.ts +83 -192
- package/external/pi-tools-suite/src/dcp/protected-continuity.ts +175 -0
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +93 -3
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +6 -10
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +36 -33
- package/external/pi-tools-suite/src/dcp/pruner-tools.ts +6 -3
- 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 +78 -22
- package/external/pi-tools-suite/src/index.ts +7 -1
- 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 +2551 -0
- package/external/pi-tools-suite/src/session-recovery/index.ts +17 -0
- package/external/pi-tools-suite/src/shell-command-policy.ts +219 -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 +47 -19
- package/package.json +10 -9
- package/schemas/pi-tools-suite.json +466 -287
- package/schemas/pix.json +129 -11
- package/schemas/tasks.json +131 -0
- package/skills/simplify/SKILL.md +33 -5
- package/docs/desktop-markdown-media.md +0 -77
- package/external/pi-tools-suite/docs/browser-qa-subagent.md +0 -177
- package/external/pi-tools-suite/docs/dcp-emergency-current-turn.md +0 -102
- package/external/pi-tools-suite/src/async-subagents/agents/browser-qa.md +0 -598
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +0 -54
- package/external/pi-tools-suite/src/skill-installer/index.ts +0 -333
- 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-lite/SKILL.md +0 -140
- /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,
|
|
@@ -179,6 +65,7 @@ const HELP_TEXT = `DCP — Dynamic Context Pruning
|
|
|
179
65
|
Commands:
|
|
180
66
|
/dcp context — Show context window usage breakdown
|
|
181
67
|
/dcp stats — Show pruning statistics for this session
|
|
68
|
+
/dcp doctor — Show config compatibility diagnostics
|
|
182
69
|
/dcp sweep [N] — Prune last N tool outputs (default: all since last user msg)
|
|
183
70
|
/dcp manual — Show manual mode status
|
|
184
71
|
/dcp manual on — Enable manual mode (disable autonomous compression nudges)
|
|
@@ -189,6 +76,18 @@ function handleHelp(ctx: ExtensionCommandContext): void {
|
|
|
189
76
|
ctx.ui.notify(HELP_TEXT, "info")
|
|
190
77
|
}
|
|
191
78
|
|
|
79
|
+
function handleDoctor(ctx: ExtensionCommandContext, config: DcpConfig): void {
|
|
80
|
+
const issues = config.issues ?? []
|
|
81
|
+
const lines = ["DCP Doctor", ""]
|
|
82
|
+
if (issues.length === 0) {
|
|
83
|
+
lines.push("Config: no removed or ignored DCP settings detected.")
|
|
84
|
+
} else {
|
|
85
|
+
lines.push(`Config: ${issues.length} removed/ignored setting${issues.length === 1 ? "" : "s"}:`)
|
|
86
|
+
for (const issue of issues) lines.push(` - ${issue}`)
|
|
87
|
+
}
|
|
88
|
+
ctx.ui.notify(lines.join("\n"), issues.length > 0 ? "warning" : "info")
|
|
89
|
+
}
|
|
90
|
+
|
|
192
91
|
// ---------------------------------------------------------------------------
|
|
193
92
|
// Context usage
|
|
194
93
|
// ---------------------------------------------------------------------------
|
|
@@ -225,44 +124,16 @@ function handleContext(ctx: ExtensionCommandContext, state: DcpState): void {
|
|
|
225
124
|
// Stats
|
|
226
125
|
// ---------------------------------------------------------------------------
|
|
227
126
|
|
|
228
|
-
function handleStats(pi: ExtensionAPI, ctx: ExtensionCommandContext, state: DcpState): void {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
lines.push(` Compression blocks active: ${activeBlocks} / ${totalBlocks} total`)
|
|
239
|
-
lines.push(` Manual mode: ${state.manualMode ? "on" : "off"}`)
|
|
240
|
-
lines.push("")
|
|
241
|
-
lines.push("Nudge telemetry:")
|
|
242
|
-
lines.push(` Sent: ${fmt(nudgeStats.emitted)} emitted, ${fmt(nudgeStats.reapplied)} reapplied, ${fmt(nudgeStats.upgraded)} upgraded`)
|
|
243
|
-
lines.push(
|
|
244
|
-
` By type: ${NUDGE_TYPES.map((type) => `${nudgeLabel(type)}=${fmt(nudgeStats.byType[type])}`).join(", ")}`,
|
|
245
|
-
)
|
|
246
|
-
lines.push(
|
|
247
|
-
` Active anchors: ${fmt(activeAnchors)}${activeAnchors > 0
|
|
248
|
-
? ` (${NUDGE_TYPES.map((type) => `${nudgeLabel(type)}=${fmt(nudgeStats.activeByType[type])}`).join(", ")})`
|
|
249
|
-
: ""}`,
|
|
250
|
-
)
|
|
251
|
-
lines.push(` Cleared after compress: ${fmt(nudgeStats.clearedEvents)} time${nudgeStats.clearedEvents === 1 ? "" : "s"} (${fmt(nudgeStats.clearedAnchors)} anchor${nudgeStats.clearedAnchors === 1 ? "" : "s"})`)
|
|
252
|
-
lines.push(` Compliance proxy: ${fmt(nudgeStats.clearedEvents)} compress-after-nudge / ${fmt(totalNudgeEvents)} nudge event${totalNudgeEvents === 1 ? "" : "s"} (${pct(nudgeStats.clearedEvents, totalNudgeEvents)})`)
|
|
253
|
-
if (nudgeStats.last) {
|
|
254
|
-
lines.push(
|
|
255
|
-
` Last nudge: ${nudgeLabel(nudgeStats.last.type)} ${nudgeStats.last.event} at ${formatDate(nudgeStats.last.createdAt)} (${formatContextPercent(nudgeStats.last.contextPercent)})`,
|
|
256
|
-
)
|
|
257
|
-
} else {
|
|
258
|
-
lines.push(" Last nudge: none recorded")
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
sendChatSystemMessage(pi, DCP_STATS_MESSAGE_TYPE, lines.join("\n"), {
|
|
262
|
-
generatedAt: new Date().toISOString(),
|
|
263
|
-
activeAnchors,
|
|
264
|
-
nudgeEvents: totalNudgeEvents,
|
|
265
|
-
})
|
|
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() })
|
|
266
137
|
}
|
|
267
138
|
|
|
268
139
|
// ---------------------------------------------------------------------------
|
|
@@ -277,7 +148,7 @@ async function handleSweep(
|
|
|
277
148
|
): Promise<void> {
|
|
278
149
|
await ctx.waitForIdle()
|
|
279
150
|
|
|
280
|
-
const branch =
|
|
151
|
+
const branch = await readDcpJournalBranch(ctx) as any[]
|
|
281
152
|
|
|
282
153
|
// Build the full set of protected tool names.
|
|
283
154
|
const protectedTools = new Set<string>([
|
|
@@ -434,6 +305,7 @@ export function registerCommands(
|
|
|
434
305
|
const subcommands: AutocompleteItem[] = [
|
|
435
306
|
{ value: "context", label: "context", description: "Show context window usage breakdown" },
|
|
436
307
|
{ value: "stats", label: "stats", description: "Show pruning statistics" },
|
|
308
|
+
{ value: "doctor", label: "doctor", description: "Show config compatibility diagnostics" },
|
|
437
309
|
{ value: "sweep", label: "sweep", description: "Prune tool outputs" },
|
|
438
310
|
{ value: "manual", label: "manual", description: "Toggle manual mode" },
|
|
439
311
|
{ value: "compress", label: "compress", description: "Trigger LLM compression" },
|
|
@@ -466,7 +338,11 @@ export function registerCommands(
|
|
|
466
338
|
break
|
|
467
339
|
|
|
468
340
|
case "stats":
|
|
469
|
-
handleStats(pi, ctx, state)
|
|
341
|
+
await handleStats(pi, ctx, state)
|
|
342
|
+
break
|
|
343
|
+
|
|
344
|
+
case "doctor":
|
|
345
|
+
handleDoctor(ctx, config)
|
|
470
346
|
break
|
|
471
347
|
|
|
472
348
|
case "sweep": {
|
|
@@ -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"
|
|
@@ -30,8 +30,55 @@ import {
|
|
|
30
30
|
detectToolGroupSpans,
|
|
31
31
|
findConversationIndexEntry,
|
|
32
32
|
} from "./conversation-index.js"
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
previewManualCompressionProjection,
|
|
35
|
+
verifiedManualCompressionMessages,
|
|
36
|
+
} from "./compression-preview.js"
|
|
34
37
|
import { settleCompressionProgress } from "./compression-progress.js"
|
|
38
|
+
import { estimateMessageTokens } from "./pruner-metadata.js"
|
|
39
|
+
import {
|
|
40
|
+
buildSummarySourceManifest,
|
|
41
|
+
hashSummarySourceManifest,
|
|
42
|
+
prepareCompressionSummary,
|
|
43
|
+
type ModelSummaryAttempt,
|
|
44
|
+
} from "./auto-compress.js"
|
|
45
|
+
|
|
46
|
+
export const COMPRESS_TOOL_PARAMETERS = Type.Object({
|
|
47
|
+
topic: Type.String({
|
|
48
|
+
description:
|
|
49
|
+
"Short label (3-5 words) for display - e.g., 'Auth System Exploration'",
|
|
50
|
+
}),
|
|
51
|
+
ranges: Type.Optional(Type.Array(
|
|
52
|
+
Type.Object({
|
|
53
|
+
startId: Type.String({
|
|
54
|
+
description:
|
|
55
|
+
"First ID (mNNN/bN); never start inside a tool group—include its calling assistant.",
|
|
56
|
+
}),
|
|
57
|
+
endId: Type.String({
|
|
58
|
+
description:
|
|
59
|
+
"Last ID (mNNN/bN); include all results of the final tool group, including parallel calls.",
|
|
60
|
+
}),
|
|
61
|
+
summary: Type.Optional(Type.String({
|
|
62
|
+
description: "Optional; omit to use the configured DCP summarizer.",
|
|
63
|
+
})),
|
|
64
|
+
}),
|
|
65
|
+
{ description: "One or more ranges to compress" },
|
|
66
|
+
)),
|
|
67
|
+
messages: Type.Optional(Type.Array(
|
|
68
|
+
Type.Object({
|
|
69
|
+
messageId: Type.String({
|
|
70
|
+
description: "Raw message ID to compress individually (e.g. m001)",
|
|
71
|
+
}),
|
|
72
|
+
topic: Type.Optional(Type.String({
|
|
73
|
+
description: "Short label for this one-message summary; defaults to top-level topic",
|
|
74
|
+
})),
|
|
75
|
+
summary: Type.Optional(Type.String({
|
|
76
|
+
description: "Optional; omit to use the configured DCP summarizer.",
|
|
77
|
+
})),
|
|
78
|
+
}),
|
|
79
|
+
{ description: "Individual raw messages to compress surgically" },
|
|
80
|
+
)),
|
|
81
|
+
})
|
|
35
82
|
|
|
36
83
|
type MessageSkipKind =
|
|
37
84
|
| "duplicate"
|
|
@@ -51,7 +98,7 @@ interface MessageSkipIssue {
|
|
|
51
98
|
interface ResolvedRangePlan {
|
|
52
99
|
startId: string
|
|
53
100
|
endId: string
|
|
54
|
-
summary
|
|
101
|
+
summary?: string
|
|
55
102
|
startTimestamp: number
|
|
56
103
|
endTimestamp: number
|
|
57
104
|
startMessageId?: string
|
|
@@ -63,7 +110,7 @@ interface ResolvedRangePlan {
|
|
|
63
110
|
interface ResolvedMessagePlan {
|
|
64
111
|
messageId: string
|
|
65
112
|
topic: string
|
|
66
|
-
summary
|
|
113
|
+
summary?: string
|
|
67
114
|
timestamp: number
|
|
68
115
|
stableId?: string
|
|
69
116
|
sourceMembers: CompressionMember[]
|
|
@@ -72,6 +119,33 @@ interface ResolvedMessagePlan {
|
|
|
72
119
|
|
|
73
120
|
type ResolvedRangeBoundaries = Omit<ResolvedRangePlan, "sourceMembers" | "mutationMembers">
|
|
74
121
|
|
|
122
|
+
type ManualSummaryMode = "explicit" | "model" | "extractive" | "extractive-fallback"
|
|
123
|
+
|
|
124
|
+
function verifiedSelectionMessages(
|
|
125
|
+
state: DcpState,
|
|
126
|
+
sourceMembers: CompressionMember[],
|
|
127
|
+
): any[] {
|
|
128
|
+
const projection = verifiedManualCompressionMessages(state)
|
|
129
|
+
const byStableId = new Map(
|
|
130
|
+
state.conversationIndexSnapshot.map((entry) => [entry.stableId, entry] as const),
|
|
131
|
+
)
|
|
132
|
+
return sourceMembers.map((member) => {
|
|
133
|
+
const entry = byStableId.get(member.stableId)
|
|
134
|
+
if (!entry || entry.contentHash !== member.hash || !Number.isSafeInteger(entry.index)) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Manual compression summary source ${member.stableId} no longer matches the verified provider projection; refresh context and retry.`,
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
const message = projection[entry.index]
|
|
140
|
+
if (!message) {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Manual compression summary source ${member.stableId} is missing from the verified provider projection; refresh context and retry.`,
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
return message
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
75
149
|
function validateNonOverlappingRanges(plans: ResolvedRangeBoundaries[], state: DcpState): void {
|
|
76
150
|
const sorted = [...plans].sort((a, b) =>
|
|
77
151
|
compareCompressionBoundaries(
|
|
@@ -246,43 +320,7 @@ export function registerCompressTool(
|
|
|
246
320
|
label: COMPRESS_TOOL_DESCRIPTION.label,
|
|
247
321
|
description: COMPRESS_TOOL_DESCRIPTION.description,
|
|
248
322
|
promptSnippet: COMPRESS_TOOL_DESCRIPTION.promptSnippet ?? "Compress ranges of conversation into summaries to manage context",
|
|
249
|
-
parameters:
|
|
250
|
-
topic: Type.String({
|
|
251
|
-
description:
|
|
252
|
-
"Short label (3-5 words) for display - e.g., 'Auth System Exploration'",
|
|
253
|
-
}),
|
|
254
|
-
ranges: Type.Optional(Type.Array(
|
|
255
|
-
Type.Object({
|
|
256
|
-
startId: Type.String({
|
|
257
|
-
description:
|
|
258
|
-
"First ID (mNNN/bN); never start inside a tool group—include its calling assistant.",
|
|
259
|
-
}),
|
|
260
|
-
endId: Type.String({
|
|
261
|
-
description:
|
|
262
|
-
"Last ID (mNNN/bN); include all results of the final tool group, including parallel calls.",
|
|
263
|
-
}),
|
|
264
|
-
summary: Type.String({
|
|
265
|
-
description:
|
|
266
|
-
"Continuation-focused technical summary; avoid raw JSON/code/diffs unless a short literal is required",
|
|
267
|
-
}),
|
|
268
|
-
}),
|
|
269
|
-
{ description: "One or more ranges to compress" },
|
|
270
|
-
)),
|
|
271
|
-
messages: Type.Optional(Type.Array(
|
|
272
|
-
Type.Object({
|
|
273
|
-
messageId: Type.String({
|
|
274
|
-
description: "Raw message ID to compress individually (e.g. m001)",
|
|
275
|
-
}),
|
|
276
|
-
topic: Type.Optional(Type.String({
|
|
277
|
-
description: "Short label for this one-message summary; defaults to top-level topic",
|
|
278
|
-
})),
|
|
279
|
-
summary: Type.String({
|
|
280
|
-
description: "Continuation-focused technical summary replacing this raw message; avoid raw JSON/code/diffs unless required",
|
|
281
|
-
}),
|
|
282
|
-
}),
|
|
283
|
-
{ description: "Individual raw messages to compress surgically" },
|
|
284
|
-
)),
|
|
285
|
-
}),
|
|
323
|
+
parameters: COMPRESS_TOOL_PARAMETERS,
|
|
286
324
|
|
|
287
325
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
288
326
|
_signal?.throwIfAborted()
|
|
@@ -462,6 +500,93 @@ export function registerCompressTool(
|
|
|
462
500
|
}
|
|
463
501
|
assertDisjointSelections(rangePlans, preflightMessages)
|
|
464
502
|
|
|
503
|
+
const summaryPreparations: Array<{
|
|
504
|
+
selection: string
|
|
505
|
+
mode: ManualSummaryMode
|
|
506
|
+
summarizerModelRef?: string
|
|
507
|
+
attempts?: Array<{ ref: string; outcome: ModelSummaryAttempt["outcome"] }>
|
|
508
|
+
}> = []
|
|
509
|
+
const resolveSummary = async (input: {
|
|
510
|
+
startId: string
|
|
511
|
+
endId: string
|
|
512
|
+
topic: string
|
|
513
|
+
sourceMembers: CompressionMember[]
|
|
514
|
+
explicitSummary?: string
|
|
515
|
+
}): Promise<string> => {
|
|
516
|
+
const selection = input.startId === input.endId
|
|
517
|
+
? input.startId
|
|
518
|
+
: `${input.startId}..${input.endId}`
|
|
519
|
+
if (typeof input.explicitSummary === "string") {
|
|
520
|
+
summaryPreparations.push({ selection, mode: "explicit" })
|
|
521
|
+
return input.explicitSummary
|
|
522
|
+
}
|
|
523
|
+
const sourceMessages = verifiedSelectionMessages(state, input.sourceMembers)
|
|
524
|
+
const sourceManifest = buildSummarySourceManifest(sourceMessages)
|
|
525
|
+
const sourceHash = hashSummarySourceManifest(sourceManifest)
|
|
526
|
+
const prepared = await prepareCompressionSummary({
|
|
527
|
+
topic: input.topic,
|
|
528
|
+
messages: sourceMessages,
|
|
529
|
+
candidate: {
|
|
530
|
+
startId: input.startId,
|
|
531
|
+
endId: input.endId,
|
|
532
|
+
messageCount: sourceMessages.length,
|
|
533
|
+
estimatedTokens: sourceMessages.reduce((sum, message) => sum + estimateMessageTokens(message), 0),
|
|
534
|
+
includedBlockIds: [],
|
|
535
|
+
reason: "manual delegated summary",
|
|
536
|
+
},
|
|
537
|
+
modelRefs: summarizerModelRefs(effectiveConfig.compress.autoCompress),
|
|
538
|
+
timeoutMs: effectiveConfig.compress.autoCompress.timeoutMs,
|
|
539
|
+
modelRegistry: (ctx as any).modelRegistry,
|
|
540
|
+
signal: _signal,
|
|
541
|
+
sourceManifest,
|
|
542
|
+
})
|
|
543
|
+
assertCurrent()
|
|
544
|
+
const refreshedMessages = verifiedSelectionMessages(state, input.sourceMembers)
|
|
545
|
+
const refreshedHash = hashSummarySourceManifest(buildSummarySourceManifest(refreshedMessages))
|
|
546
|
+
if (refreshedHash !== sourceHash || prepared.sourceHash !== sourceHash) {
|
|
547
|
+
throw new Error(
|
|
548
|
+
`stale_plan: manual compression summary source changed while preparing ${selection}`,
|
|
549
|
+
)
|
|
550
|
+
}
|
|
551
|
+
const attempts = prepared.summarizerAttempts?.map(({ ref, outcome }) => ({ ref, outcome }))
|
|
552
|
+
summaryPreparations.push({
|
|
553
|
+
selection,
|
|
554
|
+
mode: prepared.representation,
|
|
555
|
+
summarizerModelRef: prepared.summarizerModelRef,
|
|
556
|
+
attempts,
|
|
557
|
+
})
|
|
558
|
+
log("compress.summary_prepared", {
|
|
559
|
+
toolCallId: _toolCallId,
|
|
560
|
+
selection,
|
|
561
|
+
mode: prepared.representation,
|
|
562
|
+
summarizerModelRef: prepared.summarizerModelRef,
|
|
563
|
+
summarizerAttempts: prepared.summarizerAttempts,
|
|
564
|
+
sourceHash: prepared.sourceHash,
|
|
565
|
+
sourceCoverage: prepared.sourceCoverage,
|
|
566
|
+
})
|
|
567
|
+
return prepared.text
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
for (const range of rangePlans) {
|
|
571
|
+
range.summary = await resolveSummary({
|
|
572
|
+
startId: range.startId,
|
|
573
|
+
endId: range.endId,
|
|
574
|
+
topic: params.topic,
|
|
575
|
+
sourceMembers: range.sourceMembers,
|
|
576
|
+
explicitSummary: range.summary,
|
|
577
|
+
})
|
|
578
|
+
}
|
|
579
|
+
for (const plan of preflightMessages) {
|
|
580
|
+
plan.summary = await resolveSummary({
|
|
581
|
+
startId: plan.messageId,
|
|
582
|
+
endId: plan.messageId,
|
|
583
|
+
topic: plan.topic,
|
|
584
|
+
sourceMembers: plan.sourceMembers,
|
|
585
|
+
explicitSummary: plan.summary,
|
|
586
|
+
})
|
|
587
|
+
}
|
|
588
|
+
const preflightMessageById = new Map(preflightMessages.map((plan) => [plan.messageId, plan] as const))
|
|
589
|
+
|
|
465
590
|
for (const range of rangePlans) {
|
|
466
591
|
try {
|
|
467
592
|
const anchor = resolveAnchorBoundary(range.endTimestamp, workingState, range.endMessageId)
|
|
@@ -478,7 +603,7 @@ export function registerCompressTool(
|
|
|
478
603
|
|
|
479
604
|
const created = createRangeCompressionBlock({
|
|
480
605
|
topic: params.topic,
|
|
481
|
-
summary: range.summary
|
|
606
|
+
summary: range.summary!,
|
|
482
607
|
startTimestamp: range.startTimestamp,
|
|
483
608
|
endTimestamp: range.endTimestamp,
|
|
484
609
|
startMessageId: range.startMessageId,
|
|
@@ -521,7 +646,6 @@ export function registerCompressTool(
|
|
|
521
646
|
const seenMessageIds = new Set<string>()
|
|
522
647
|
|
|
523
648
|
for (const entry of messages) {
|
|
524
|
-
const { summary } = entry
|
|
525
649
|
const messageId = typeof entry.messageId === "string" ? entry.messageId.trim() : ""
|
|
526
650
|
if (seenMessageIds.has(messageId)) {
|
|
527
651
|
skippedMessageIssues.push({ kind: "duplicate", messageId })
|
|
@@ -604,7 +728,7 @@ export function registerCompressTool(
|
|
|
604
728
|
|
|
605
729
|
const created = createRangeCompressionBlock({
|
|
606
730
|
topic: entry.topic ?? params.topic,
|
|
607
|
-
summary
|
|
731
|
+
summary: preflightMessageById.get(messageId)?.summary!,
|
|
608
732
|
startTimestamp: meta.timestamp,
|
|
609
733
|
endTimestamp: meta.timestamp,
|
|
610
734
|
startMessageId: meta.stableId,
|
|
@@ -620,8 +744,8 @@ export function registerCompressTool(
|
|
|
620
744
|
validatePlaceholders: false,
|
|
621
745
|
expandPlaceholders: false,
|
|
622
746
|
preparedProtectedFragments,
|
|
623
|
-
sourceMembers:
|
|
624
|
-
mutationMembers:
|
|
747
|
+
sourceMembers: preflightMessageById.get(messageId)?.sourceMembers,
|
|
748
|
+
mutationMembers: preflightMessageById.get(messageId)?.mutationMembers,
|
|
625
749
|
})
|
|
626
750
|
const block = created.block
|
|
627
751
|
newBlockIds.push(block.id)
|
|
@@ -671,6 +795,12 @@ export function registerCompressTool(
|
|
|
671
795
|
for (const block of workingState.compressionBlocks) {
|
|
672
796
|
if (newBlockIds.includes(block.id)) block.operationRequestHash = requestHash
|
|
673
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
|
+
}
|
|
674
804
|
assertCurrent()
|
|
675
805
|
let published = false
|
|
676
806
|
await persistState(ctx, workingState, {
|
|
@@ -735,6 +865,7 @@ export function registerCompressTool(
|
|
|
735
865
|
}
|
|
736
866
|
const resultDetails = {
|
|
737
867
|
...visualDetails,
|
|
868
|
+
summaryPreparations,
|
|
738
869
|
projectedBeforeTokens: projection?.projectedBeforeTokens,
|
|
739
870
|
projectedAfterTokens: projection?.projectedAfterTokens,
|
|
740
871
|
netGain: projection?.netGain,
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
import type { DcpConfig } from "./config.js"
|
|
9
9
|
import { estimateTokens } from "./pruner-metadata.js"
|
|
10
10
|
import { isToolRecordProtected } from "./pruner-tools.js"
|
|
11
|
+
import { toolRecordContinuity } from "./protected-continuity.js"
|
|
11
12
|
import { compareConversationStableIds, buildExactRangeMembership } from "./conversation-index.js"
|
|
12
13
|
import { createHash } from "node:crypto"
|
|
13
14
|
import { open, realpath } from "node:fs/promises"
|
|
@@ -570,10 +571,10 @@ function collectCurrentProtectedFragments(
|
|
|
570
571
|
if (!toolCallId) continue
|
|
571
572
|
const record = state.toolCalls.get(toolCallId)
|
|
572
573
|
if (!record || record.toolName === "compress" || !isToolRecordProtected(record, config)) continue
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
fragments.push(compressionProtectedFragment("tool", `tool:${toolCallId}`,
|
|
574
|
+
const continuity = toolRecordContinuity(record, config)
|
|
575
|
+
const text = continuity.text?.trim()
|
|
576
|
+
if (!text) continue
|
|
577
|
+
fragments.push(compressionProtectedFragment("tool", `tool:${toolCallId}`, text))
|
|
577
578
|
}
|
|
578
579
|
return mergeProtectedFragments(fragments)
|
|
579
580
|
}
|
|
@@ -657,51 +658,6 @@ function appendProtectedFragmentLedger(summary: string, fragments: CompressionPr
|
|
|
657
658
|
missing.map((fragment) => `\n\n${fragment.text}`).join("")
|
|
658
659
|
}
|
|
659
660
|
|
|
660
|
-
function appendProtectedToolOutputs(
|
|
661
|
-
summary: string,
|
|
662
|
-
startTimestamp: number,
|
|
663
|
-
endTimestamp: number,
|
|
664
|
-
state: DcpState,
|
|
665
|
-
config: DcpConfig,
|
|
666
|
-
ids: { startMessageId?: string; endMessageId?: string } = {},
|
|
667
|
-
): string {
|
|
668
|
-
const protectedOutputs: string[] = []
|
|
669
|
-
const seenToolCallIds = new Set<string>()
|
|
670
|
-
const boundaries = rangeBoundaries(startTimestamp, endTimestamp, ids)
|
|
671
|
-
|
|
672
|
-
for (const meta of state.messageMetaSnapshot.values()) {
|
|
673
|
-
if (meta.blockId !== undefined) continue
|
|
674
|
-
if (!Number.isFinite(meta.timestamp)) continue
|
|
675
|
-
if (!isCompressionBoundaryWithinRange(
|
|
676
|
-
{ timestamp: meta.timestamp, stableId: meta.stableId },
|
|
677
|
-
boundaries.start,
|
|
678
|
-
boundaries.end,
|
|
679
|
-
state,
|
|
680
|
-
)) continue
|
|
681
|
-
if (meta.role !== "toolResult" && meta.role !== "bashExecution") continue
|
|
682
|
-
|
|
683
|
-
const toolCallId = meta.toolCallId
|
|
684
|
-
if (!toolCallId || seenToolCallIds.has(toolCallId)) continue
|
|
685
|
-
const record = state.toolCalls.get(toolCallId)
|
|
686
|
-
if (!record) continue
|
|
687
|
-
|
|
688
|
-
// The compress tool's own JSON accounting output is control-plane data and
|
|
689
|
-
// rarely useful in future summaries; avoid recursively copying it.
|
|
690
|
-
if (record.toolName === "compress") continue
|
|
691
|
-
if (!isToolRecordProtected(record, config)) continue
|
|
692
|
-
|
|
693
|
-
const output = (record.outputText ?? meta.text ?? "").trim()
|
|
694
|
-
if (!output) continue
|
|
695
|
-
|
|
696
|
-
seenToolCallIds.add(toolCallId)
|
|
697
|
-
protectedOutputs.push(`\n### Tool: ${record.toolName}\n${output}`)
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
if (protectedOutputs.length === 0) return summary
|
|
701
|
-
const heading = "\n\nThe following protected tool outputs were included in this conversation section:"
|
|
702
|
-
return summary + heading + protectedOutputs.join("")
|
|
703
|
-
}
|
|
704
|
-
|
|
705
661
|
export function estimateVisibleRangeTokens(
|
|
706
662
|
startTimestamp: number,
|
|
707
663
|
endTimestamp: number,
|
|
@@ -945,20 +901,12 @@ export function createRangeCompressionBlock(
|
|
|
945
901
|
ids,
|
|
946
902
|
)
|
|
947
903
|
|
|
948
|
-
const expandedSummary = appendProtectedToolOutputs(
|
|
949
|
-
promptPreservedSummary,
|
|
950
|
-
startTimestamp,
|
|
951
|
-
endTimestamp,
|
|
952
|
-
state,
|
|
953
|
-
config,
|
|
954
|
-
ids,
|
|
955
|
-
)
|
|
956
904
|
const protectedFragments = mergeProtectedFragments(
|
|
957
905
|
collectInheritedProtectedFragments(coveredBlocks),
|
|
958
906
|
collectCurrentProtectedFragments(startTimestamp, endTimestamp, state, config, mode, ids),
|
|
959
907
|
preparedProtectedFragments,
|
|
960
908
|
)
|
|
961
|
-
const ledgerPreservedSummary = appendProtectedFragmentLedger(
|
|
909
|
+
const ledgerPreservedSummary = appendProtectedFragmentLedger(promptPreservedSummary, protectedFragments)
|
|
962
910
|
|
|
963
911
|
const block: CompressionBlock = {
|
|
964
912
|
id: state.nextBlockId++,
|
|
@@ -58,6 +58,15 @@ function verifiedSnapshot(state: DcpState): any[] {
|
|
|
58
58
|
return snapshot
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Return a detached, verified copy of the exact provider projection used by
|
|
63
|
+
* manual compression. Generated summaries must use this source rather than
|
|
64
|
+
* reconstructing history from timestamps or persisted session entries.
|
|
65
|
+
*/
|
|
66
|
+
export function verifiedManualCompressionMessages(state: DcpState): any[] {
|
|
67
|
+
return cloneProjectionMessages(verifiedSnapshot(state))
|
|
68
|
+
}
|
|
69
|
+
|
|
61
70
|
function stripProjectionMetadata(message: any): any {
|
|
62
71
|
const stripped = stripStaleDcpMetadataFromMessage(message)
|
|
63
72
|
return copyDcpProvenance(message, stripped)
|