pi-ui-extend 1.0.37 → 1.0.39
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 +1 -0
- package/dist/app/extensions/extension-ui-controller.js +3 -0
- package/dist/app/rendering/extension-entry-renderer.js +2 -0
- package/dist/bundled-extensions/question/contract.d.ts +3 -0
- package/dist/bundled-extensions/question/contract.js +24 -0
- package/dist/bundled-extensions/question/desktop.d.ts +11 -0
- package/dist/bundled-extensions/question/desktop.js +143 -0
- package/dist/bundled-extensions/question/index.d.ts +1 -0
- package/dist/bundled-extensions/question/index.js +5 -1
- package/dist/bundled-extensions/question/render.js +6 -0
- package/dist/bundled-extensions/question/result.js +71 -9
- package/dist/bundled-extensions/question/tool-description.js +4 -3
- package/dist/bundled-extensions/question/tui.js +127 -12
- package/dist/bundled-extensions/question/types.d.ts +23 -2
- package/dist/tool-renderers/question.js +20 -1
- package/docs/desktop-markdown-media.md +77 -0
- package/docs/desktop-mvp.md +134 -0
- package/docs/desktop-task-manager.md +124 -0
- package/external/pi-tools-suite/package.json +3 -3
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +9 -1
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +41 -2
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +13 -1
- package/external/pi-tools-suite/src/async-subagents/index.ts +6 -2
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/SKILL.md +65 -22
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/references/qa-design.md +23 -8
- package/external/pi-tools-suite/src/async-subagents/private-skills/browser-qa/scripts/browser-qa-runner.mjs +180 -41
- package/external/pi-tools-suite/src/async-subagents/subagent-overlay.ts +1 -1
- package/external/pi-tools-suite/src/dcp/auto-compress.ts +1 -1
- package/external/pi-tools-suite/src/dcp/compression-blocks.ts +1 -51
- package/external/pi-tools-suite/src/dcp/debug-log.ts +6 -0
- package/external/pi-tools-suite/src/dcp/index.ts +27 -125
- package/external/pi-tools-suite/src/dcp/prompts.ts +2 -2
- package/external/pi-tools-suite/src/dcp/provider-tool-results.ts +2 -1
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +31 -10
- package/external/pi-tools-suite/src/dcp/pruner-compression-blocks.ts +6 -7
- package/external/pi-tools-suite/src/dcp/pruner-message-ids.ts +153 -111
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +28 -17
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +61 -27
- package/external/pi-tools-suite/src/dcp/pruner.ts +21 -13
- package/external/pi-tools-suite/src/dcp/state.ts +59 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +4 -0
- package/external/pi-tools-suite/src/lib/rpc-session-state.ts +34 -0
- package/external/pi-tools-suite/src/todo/todo.ts +8 -3
- package/package.json +10 -4
|
@@ -45,13 +45,7 @@ import {
|
|
|
45
45
|
resolveContextThresholds,
|
|
46
46
|
estimateTokens,
|
|
47
47
|
} from "./pruner.js"
|
|
48
|
-
import {
|
|
49
|
-
stripStaleDcpMetadataFromAssistantMessage,
|
|
50
|
-
stripStaleDcpMetadataFromMessage,
|
|
51
|
-
} from "./pruner-metadata.js"
|
|
52
|
-
import {
|
|
53
|
-
buildMessageIdControlText,
|
|
54
|
-
} from "./pruner-message-ids.js"
|
|
48
|
+
import { stripStaleDcpMetadataFromMessage } from "./pruner-metadata.js"
|
|
55
49
|
import { summarizeDcpState, writeDcpDebugLog } from "./debug-log.js"
|
|
56
50
|
import type { DcpNudgeType } from "./pruner-types.js"
|
|
57
51
|
import { registerCompressTool } from "./compress-tool.js"
|
|
@@ -119,104 +113,6 @@ function isDcpControlPlaneMessage(message: any): boolean {
|
|
|
119
113
|
return message?.role === "custom" && DCP_CONTROL_PLANE_CUSTOM_TYPES.has(message.customType)
|
|
120
114
|
}
|
|
121
115
|
|
|
122
|
-
const DCP_PROVIDER_CONTROL_HEADER = "DCP message ID control data (do not quote or output):"
|
|
123
|
-
|
|
124
|
-
function appendTextToContent(content: unknown, text: string): unknown {
|
|
125
|
-
if (typeof content === "string") return `${content}\n\n${text}`
|
|
126
|
-
if (Array.isArray(content)) {
|
|
127
|
-
const textType = content.some((part: any) => part?.type === "input_text") ? "input_text" : "text"
|
|
128
|
-
return [...content, { type: textType, text }]
|
|
129
|
-
}
|
|
130
|
-
return text
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function appendDcpControlToMessages(messages: unknown, text: string): unknown {
|
|
134
|
-
if (!Array.isArray(messages)) return messages
|
|
135
|
-
const block = `${DCP_PROVIDER_CONTROL_HEADER}\n${text}`
|
|
136
|
-
|
|
137
|
-
// Keep DCP's volatile ID map at the tail of the provider context instead of
|
|
138
|
-
// mutating the stable system/developer prefix. Prefix cache reuse is much
|
|
139
|
-
// better when only the newest transcript item changes on each request.
|
|
140
|
-
let targetIndex = -1
|
|
141
|
-
for (let index = messages.length - 1; index >= 0; index--) {
|
|
142
|
-
const message = messages[index] as any
|
|
143
|
-
if (!message || typeof message !== "object") continue
|
|
144
|
-
// Responses items such as reasoning/function_call_output do not accept a
|
|
145
|
-
// `content` field. Keep a function output at the tail by appending to its
|
|
146
|
-
// valid `output` string; otherwise scan back to an actual message item.
|
|
147
|
-
if (typeof message.type === "string" && message.type !== "message" && message.role === undefined) {
|
|
148
|
-
if (message.type === "function_call_output" && typeof message.output === "string") {
|
|
149
|
-
return messages.map((candidate: any, candidateIndex) => candidateIndex === index
|
|
150
|
-
? { ...candidate, output: `${candidate.output}\n\n${block}` }
|
|
151
|
-
: candidate)
|
|
152
|
-
}
|
|
153
|
-
continue
|
|
154
|
-
}
|
|
155
|
-
if (message.role === "system" || message.role === "developer") continue
|
|
156
|
-
targetIndex = index
|
|
157
|
-
break
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (targetIndex < 0) {
|
|
161
|
-
targetIndex = messages.findIndex((message: any) =>
|
|
162
|
-
message?.role === "system" || message?.role === "developer"
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (targetIndex >= 0) {
|
|
167
|
-
return messages.map((message: any, index) => index === targetIndex
|
|
168
|
-
? { ...message, content: appendTextToContent(message.content, block) }
|
|
169
|
-
: message)
|
|
170
|
-
}
|
|
171
|
-
return [{ role: "system", content: block }, ...messages]
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function appendDcpControlToAnthropicSystem(system: unknown, text: string): unknown {
|
|
175
|
-
const block = `${DCP_PROVIDER_CONTROL_HEADER}\n${text}`
|
|
176
|
-
if (typeof system === "string") return `${system}\n\n${block}`
|
|
177
|
-
if (Array.isArray(system)) return [...system, { type: "text", text: block }]
|
|
178
|
-
if (system === undefined || system === null) return [{ type: "text", text: block }]
|
|
179
|
-
return system
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function appendDcpControlToGoogleSystemInstruction(systemInstruction: unknown, text: string): unknown {
|
|
183
|
-
const block = `${DCP_PROVIDER_CONTROL_HEADER}\n${text}`
|
|
184
|
-
if (typeof systemInstruction === "string") return `${systemInstruction}\n\n${block}`
|
|
185
|
-
if (systemInstruction === undefined || systemInstruction === null) return block
|
|
186
|
-
return systemInstruction
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function appendDcpControlToProviderPayload(payload: unknown, text: string): unknown {
|
|
190
|
-
if (Array.isArray(payload)) return appendDcpControlToMessages(payload, text)
|
|
191
|
-
if (!payload || typeof payload !== "object") return payload
|
|
192
|
-
const record = payload as Record<string, unknown>
|
|
193
|
-
|
|
194
|
-
if (Array.isArray(record.input)) {
|
|
195
|
-
return { ...record, input: appendDcpControlToMessages(record.input, text) }
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (Array.isArray(record.messages)) {
|
|
199
|
-
return { ...record, messages: appendDcpControlToMessages(record.messages, text) }
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if ("system" in record) {
|
|
203
|
-
return { ...record, system: appendDcpControlToAnthropicSystem(record.system, text) }
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (record.config && typeof record.config === "object") {
|
|
207
|
-
const config = record.config as Record<string, unknown>
|
|
208
|
-
return {
|
|
209
|
-
...record,
|
|
210
|
-
config: {
|
|
211
|
-
...config,
|
|
212
|
-
systemInstruction: appendDcpControlToGoogleSystemInstruction(config.systemInstruction, text),
|
|
213
|
-
},
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return payload
|
|
218
|
-
}
|
|
219
|
-
|
|
220
116
|
// ---------------------------------------------------------------------------
|
|
221
117
|
// Module export
|
|
222
118
|
// ---------------------------------------------------------------------------
|
|
@@ -344,15 +240,6 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
344
240
|
}
|
|
345
241
|
})
|
|
346
242
|
|
|
347
|
-
// ── 7b. message_end: never persist provider-echoed DCP control markers ─────
|
|
348
|
-
pi.on("message_end", async (event, ctx) => {
|
|
349
|
-
const effectiveConfig = configForContext(ctx)
|
|
350
|
-
if (!effectiveConfig.enabled || event.message?.role !== "assistant") return undefined
|
|
351
|
-
|
|
352
|
-
const sanitized = stripStaleDcpMetadataFromAssistantMessage(event.message)
|
|
353
|
-
return { message: sanitized }
|
|
354
|
-
})
|
|
355
|
-
|
|
356
243
|
// ── 8. tool_call: record input args for dedup / purge fingerprinting ───────
|
|
357
244
|
pi.on("tool_call", async (event, _ctx) => {
|
|
358
245
|
if (!state.toolCalls.has(event.toolCallId)) {
|
|
@@ -418,7 +305,7 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
418
305
|
inputMessages: event.messages.length,
|
|
419
306
|
filteredMessages: contextMessages.length,
|
|
420
307
|
outputMessages: messages.length,
|
|
421
|
-
messageIdControl: "
|
|
308
|
+
messageIdControl: "distributed-carriers",
|
|
422
309
|
state: summarizeDcpState(state),
|
|
423
310
|
...details,
|
|
424
311
|
}, ctx)
|
|
@@ -454,7 +341,20 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
454
341
|
await saveDcpState(ctx, state)
|
|
455
342
|
}
|
|
456
343
|
}
|
|
344
|
+
const prunedToolCountBeforeCheckpoint = state.prunedToolIds.size
|
|
457
345
|
let prunedMessages = applyPruning(contextMessages, state, effectiveConfig)
|
|
346
|
+
const automaticPrunesCommitted = state.prunedToolIds.size - prunedToolCountBeforeCheckpoint
|
|
347
|
+
if (automaticPrunesCommitted > 0) {
|
|
348
|
+
const clearedAnchors = clearDcpNudgeAnchors(state)
|
|
349
|
+
await saveDcpState(ctx, state)
|
|
350
|
+
writeDcpDebugLog(effectiveConfig, "prune.tool_checkpoint", {
|
|
351
|
+
committed: automaticPrunesCommitted,
|
|
352
|
+
clearedAnchors,
|
|
353
|
+
turn: state.currentTurn,
|
|
354
|
+
blockId: state.lastAutomaticPruneBlockId,
|
|
355
|
+
state: summarizeDcpState(state),
|
|
356
|
+
}, ctx)
|
|
357
|
+
}
|
|
458
358
|
let candidate = null as ReturnType<typeof detectCompressionCandidate>
|
|
459
359
|
let messageCandidates = [] as ReturnType<typeof detectMessageCompressionCandidates>
|
|
460
360
|
let emergencySelection = null as ReturnType<typeof analyzeEmergencyCurrentTurn> | null
|
|
@@ -746,6 +646,7 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
746
646
|
)
|
|
747
647
|
if (emergencyPruneResult.prunedToolCallIds.length > 0) {
|
|
748
648
|
prunedMessages = applyPruning(contextMessages, state, effectiveConfig)
|
|
649
|
+
const clearedAnchors = clearDcpNudgeAnchors(state)
|
|
749
650
|
state.consecutiveIgnoredStrongNudges = 0
|
|
750
651
|
emergencySelection = analyzeEmergencyCurrentTurn(prunedMessages, state, effectiveConfig)
|
|
751
652
|
messageCandidates = emergencyCurrentTurnMessageCandidates(emergencySelection, effectiveConfig)
|
|
@@ -758,6 +659,7 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
758
659
|
targetContextPercent,
|
|
759
660
|
targetRecoveryTokens,
|
|
760
661
|
prunedOutputs: emergencyPruneResult.prunedToolCallIds.length,
|
|
662
|
+
clearedAnchors,
|
|
761
663
|
estimatedTokensRecovered: emergencyPruneResult.estimatedTokensRecovered,
|
|
762
664
|
estimatedContextPercentAfter: Math.max(
|
|
763
665
|
0,
|
|
@@ -786,7 +688,7 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
786
688
|
prunedMessages,
|
|
787
689
|
state,
|
|
788
690
|
nudgeType,
|
|
789
|
-
{ contextPercent },
|
|
691
|
+
{ contextPercent, renderedReminder: nudgeText },
|
|
790
692
|
)
|
|
791
693
|
if (anchorResult.anchor) {
|
|
792
694
|
if (anchorResult.updated) {
|
|
@@ -836,10 +738,10 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
836
738
|
anchor.type === "context-strong" || anchor.type === "context-soft",
|
|
837
739
|
)
|
|
838
740
|
}
|
|
839
|
-
applyAnchoredNudges(prunedMessages, state, (anchor) =>
|
|
741
|
+
const nudgeApplication = applyAnchoredNudges(prunedMessages, state, (anchor) =>
|
|
840
742
|
appendConcreteNudgeGuidance(baseNudgeText(anchor.type), candidate, messageCandidates, state),
|
|
841
743
|
)
|
|
842
|
-
if (state.nudgeAnchors.length !== anchorsBeforeFinalization) {
|
|
744
|
+
if (state.nudgeAnchors.length !== anchorsBeforeFinalization || nudgeApplication.stateChanged) {
|
|
843
745
|
await saveDcpState(ctx, state)
|
|
844
746
|
}
|
|
845
747
|
|
|
@@ -851,7 +753,7 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
851
753
|
})
|
|
852
754
|
})
|
|
853
755
|
|
|
854
|
-
// ── 10b. before_provider_request:
|
|
756
|
+
// ── 10b. before_provider_request: observe accepted tool-result payloads ────
|
|
855
757
|
pi.on("before_provider_request", async (event, ctx) => {
|
|
856
758
|
const effectiveConfig = configForContext(ctx)
|
|
857
759
|
pendingProviderToolIds.clear()
|
|
@@ -868,16 +770,16 @@ export default async function dcpModule(pi: ExtensionAPI): Promise<void> {
|
|
|
868
770
|
}
|
|
869
771
|
}
|
|
870
772
|
|
|
871
|
-
const controlText = buildMessageIdControlText(state)
|
|
872
|
-
if (!controlText) return undefined
|
|
873
|
-
|
|
874
|
-
const payload = appendDcpControlToProviderPayload(event.payload, controlText)
|
|
875
773
|
writeDcpDebugLog(effectiveConfig, "provider_payload.message_ids", {
|
|
876
|
-
injected:
|
|
774
|
+
injected: false,
|
|
775
|
+
delivery: "distributed-carriers",
|
|
877
776
|
pendingToolResults: pendingProviderToolIds.size,
|
|
878
777
|
state: summarizeDcpState(state),
|
|
879
778
|
}, ctx)
|
|
880
|
-
|
|
779
|
+
// IDs are already attached to deterministic user/tool-result context
|
|
780
|
+
// carriers. Replacing the payload here would move metadata from the old
|
|
781
|
+
// tail to the new one and break strict append-only Responses continuation.
|
|
782
|
+
return undefined
|
|
881
783
|
})
|
|
882
784
|
|
|
883
785
|
// Promote pending IDs only after the provider accepted the exact payload.
|
|
@@ -118,9 +118,9 @@ You specify boundaries by ID using the injected metadata IDs present in the conv
|
|
|
118
118
|
- \`mNNN\` IDs identify raw messages (3 digits, zero-padded, e.g. \`m001\`, \`m042\`)
|
|
119
119
|
- \`bN\` IDs identify previously compressed blocks
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
Raw message IDs are provided in hidden DCP control metadata attached to stable user/tool-result carriers. IDs may be sparse; determine range order from where their carriers appear in context, not from the numeric suffix.
|
|
122
122
|
Some message-compression candidate hints include a low/medium/high priority; prefer high-priority stale message IDs for message-mode compression when a full range would be too broad.
|
|
123
|
-
|
|
123
|
+
Each carrier's metadata labels that carrier and any immediately preceding assistant message(s).
|
|
124
124
|
Treat these reference lines as boundary metadata only, not as tool result content.
|
|
125
125
|
|
|
126
126
|
Rules:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import type { ToolRecord } from "./state.js";
|
|
3
|
+
import { stripStaleDcpMetadataLines } from "./pruner-metadata.js";
|
|
3
4
|
|
|
4
5
|
export interface ProviderToolResultEvidence {
|
|
5
6
|
ids: Set<string>;
|
|
@@ -19,7 +20,7 @@ function signature(toolName: string, outputText: string): string {
|
|
|
19
20
|
return createHash("sha256")
|
|
20
21
|
.update(toolName)
|
|
21
22
|
.update("\u0000")
|
|
22
|
-
.update(outputText)
|
|
23
|
+
.update(stripStaleDcpMetadataLines(outputText))
|
|
23
24
|
.digest("hex");
|
|
24
25
|
}
|
|
25
26
|
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
extractBlockId,
|
|
7
7
|
messageText,
|
|
8
8
|
} from "./pruner-metadata.js";
|
|
9
|
+
import { stableMessageKeys } from "./pruner-message-ids.js";
|
|
9
10
|
|
|
10
11
|
interface CandidateBoundary {
|
|
11
12
|
id: string;
|
|
@@ -24,13 +25,27 @@ function isActiveBlockId(blockId: number, state: DcpState): boolean {
|
|
|
24
25
|
return state.compressionBlocks.some((block) => block.id === blockId && block.active);
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
function findCurrentMessageId(msg: any, state: DcpState): string | undefined {
|
|
28
|
+
function findCurrentMessageId(msg: any, stableKey: string, state: DcpState): string | undefined {
|
|
28
29
|
const role = msg?.role ?? "";
|
|
29
30
|
const timestamp = msg?.timestamp;
|
|
30
31
|
if (!Number.isFinite(timestamp)) return undefined;
|
|
31
32
|
|
|
32
33
|
for (const [id, meta] of state.messageMetaSnapshot) {
|
|
33
|
-
if (meta.
|
|
34
|
+
if (meta.stableId === stableKey) return id;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Backward compatibility for snapshots created before stable IDs were
|
|
38
|
+
// recorded. Never mix timestamp fallback with a modern stable-ID snapshot.
|
|
39
|
+
const isLegacySnapshot = [...state.messageMetaSnapshot.values()]
|
|
40
|
+
.every((meta) => meta.stableId === undefined);
|
|
41
|
+
if (!isLegacySnapshot) return undefined;
|
|
42
|
+
|
|
43
|
+
for (const [id, meta] of state.messageMetaSnapshot) {
|
|
44
|
+
if (
|
|
45
|
+
meta.timestamp === timestamp &&
|
|
46
|
+
meta.role === role &&
|
|
47
|
+
meta.blockId === undefined
|
|
48
|
+
) return id;
|
|
34
49
|
}
|
|
35
50
|
|
|
36
51
|
for (const [id, ts] of state.messageIdSnapshot) {
|
|
@@ -42,6 +57,7 @@ function findCurrentMessageId(msg: any, state: DcpState): string | undefined {
|
|
|
42
57
|
|
|
43
58
|
function resolveAddressableBoundaryId(
|
|
44
59
|
msg: any,
|
|
60
|
+
stableKey: string,
|
|
45
61
|
state: DcpState,
|
|
46
62
|
options: { allowBlocks: boolean },
|
|
47
63
|
): { id: string; blockId?: number; text: string } | null {
|
|
@@ -55,8 +71,9 @@ function resolveAddressableBoundaryId(
|
|
|
55
71
|
|
|
56
72
|
// Inline [dcp-id] markers are no longer injected into message content; the
|
|
57
73
|
// snapshot rebuilt by injectMessageIds() is the sole addressability source.
|
|
58
|
-
// Resolve the message
|
|
59
|
-
|
|
74
|
+
// Resolve the message ID by its persistent stable identity. Timestamp/role
|
|
75
|
+
// matching is retained only for legacy snapshots without stable IDs.
|
|
76
|
+
const currentId = findCurrentMessageId(msg, stableKey, state);
|
|
60
77
|
if (currentId) return { id: currentId, text };
|
|
61
78
|
|
|
62
79
|
return null;
|
|
@@ -73,15 +90,17 @@ export function detectCompressionCandidate(
|
|
|
73
90
|
if (contextPercent < settings.minContextPercent) return null;
|
|
74
91
|
|
|
75
92
|
const boundaries: CandidateBoundary[] = [];
|
|
76
|
-
|
|
77
|
-
|
|
93
|
+
const stableKeys = stableMessageKeys(messages);
|
|
94
|
+
for (let index = 0; index < messages.length; index++) {
|
|
95
|
+
const msg = messages[index];
|
|
96
|
+
const boundary = resolveAddressableBoundaryId(msg, stableKeys[index]!, _state, { allowBlocks: true });
|
|
78
97
|
if (!boundary) continue;
|
|
79
98
|
if (!Number.isFinite(msg.timestamp)) continue;
|
|
80
99
|
boundaries.push({
|
|
81
100
|
id: boundary.id,
|
|
82
101
|
role: msg.role ?? "",
|
|
83
102
|
timestamp: msg.timestamp,
|
|
84
|
-
tokenEstimate: estimateMessageTokens(msg),
|
|
103
|
+
tokenEstimate: _state.messageMetaSnapshot.get(boundary.id)?.tokenEstimate ?? estimateMessageTokens(msg),
|
|
85
104
|
blockId: boundary.blockId,
|
|
86
105
|
isSystemReminder: boundary.text.includes("<dcp-system-reminder>"),
|
|
87
106
|
});
|
|
@@ -153,15 +172,17 @@ export function detectMessageCompressionCandidates(
|
|
|
153
172
|
if (contextPercent < settings.minContextPercent) return [];
|
|
154
173
|
|
|
155
174
|
const boundaries: CandidateBoundary[] = [];
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
const stableKeys = stableMessageKeys(messages);
|
|
176
|
+
for (let index = 0; index < messages.length; index++) {
|
|
177
|
+
const msg = messages[index];
|
|
178
|
+
const boundary = resolveAddressableBoundaryId(msg, stableKeys[index]!, state, { allowBlocks: false });
|
|
158
179
|
if (!boundary || boundary.blockId !== undefined) continue;
|
|
159
180
|
if (!Number.isFinite(msg.timestamp)) continue;
|
|
160
181
|
boundaries.push({
|
|
161
182
|
id: boundary.id,
|
|
162
183
|
role: msg.role ?? "",
|
|
163
184
|
timestamp: msg.timestamp,
|
|
164
|
-
tokenEstimate: estimateMessageTokens(msg),
|
|
185
|
+
tokenEstimate: state.messageMetaSnapshot.get(boundary.id)?.tokenEstimate ?? estimateMessageTokens(msg),
|
|
165
186
|
isSystemReminder: boundary.text.includes("<dcp-system-reminder>"),
|
|
166
187
|
});
|
|
167
188
|
}
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import type { DcpConfig } from "./config.js";
|
|
2
2
|
import type { DcpState } from "./state.js";
|
|
3
3
|
import { PASSTHROUGH_ROLES, estimateMessageTokens } from "./pruner-metadata.js";
|
|
4
|
-
import {
|
|
4
|
+
import { stableMessageKeys } from "./pruner-message-ids.js";
|
|
5
5
|
import { writeDcpDebugLog } from "./debug-log.js";
|
|
6
6
|
|
|
7
|
-
function messageMatchesBoundary(msg: any, messageIndex: number, stableId: string | undefined, timestamp: number): boolean {
|
|
8
|
-
if (stableId && stableMessageId(msg, messageIndex) === stableId) return true;
|
|
9
|
-
return msg.timestamp === timestamp;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
7
|
function findBoundaryIndex(messages: any[], stableId: string | undefined, timestamp: number): number {
|
|
13
|
-
|
|
8
|
+
const stableKeys = stableId ? stableMessageKeys(messages) : [];
|
|
9
|
+
return messages.findIndex((message, index) => {
|
|
10
|
+
if (stableId && stableKeys[index] === stableId) return true;
|
|
11
|
+
return message.timestamp === timestamp;
|
|
12
|
+
});
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
export interface ReconcileInheritedBlocksResult {
|