Compaction: preserve opaque identifiers in summaries (openclaw#25553) thanks @rodrigouroz

Verified:
- pnpm install --frozen-lockfile
- pnpm build
- pnpm check
- pnpm test:macmini

Co-authored-by: rodrigouroz <384037+rodrigouroz@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Rodrigo Uroz
2026-02-27 11:14:05 -03:00
committed by GitHub
parent 84a88b2ace
commit 0fe6cf06b2
15 changed files with 344 additions and 4 deletions

View File

@@ -1,6 +1,7 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import type { ExtensionContext } from "@mariozechner/pi-coding-agent";
import { estimateTokens, generateSummary } from "@mariozechner/pi-coding-agent";
import type { AgentCompactionIdentifierPolicy } from "../config/types.agent-defaults.js";
import { retryAsync } from "../infra/retry.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { DEFAULT_CONTEXT_TOKENS } from "./defaults.js";
@@ -16,6 +17,46 @@ const DEFAULT_PARTS = 2;
const MERGE_SUMMARIES_INSTRUCTIONS =
"Merge these partial summaries into a single cohesive summary. Preserve decisions," +
" TODOs, open questions, and any constraints.";
const IDENTIFIER_PRESERVATION_INSTRUCTIONS =
"Preserve all opaque identifiers exactly as written (no shortening or reconstruction), " +
"including UUIDs, hashes, IDs, tokens, API keys, hostnames, IPs, ports, URLs, and file names.";
export type CompactionSummarizationInstructions = {
identifierPolicy?: AgentCompactionIdentifierPolicy;
identifierInstructions?: string;
};
function resolveIdentifierPreservationInstructions(
instructions?: CompactionSummarizationInstructions,
): string | undefined {
const policy = instructions?.identifierPolicy ?? "strict";
if (policy === "off") {
return undefined;
}
if (policy === "custom") {
const custom = instructions?.identifierInstructions?.trim();
return custom && custom.length > 0 ? custom : IDENTIFIER_PRESERVATION_INSTRUCTIONS;
}
return IDENTIFIER_PRESERVATION_INSTRUCTIONS;
}
export function buildCompactionSummarizationInstructions(
customInstructions?: string,
instructions?: CompactionSummarizationInstructions,
): string | undefined {
const custom = customInstructions?.trim();
const identifierPreservation = resolveIdentifierPreservationInstructions(instructions);
if (!identifierPreservation && !custom) {
return undefined;
}
if (!custom) {
return identifierPreservation;
}
if (!identifierPreservation) {
return `Additional focus:\n${custom}`;
}
return `${identifierPreservation}\n\nAdditional focus:\n${custom}`;
}
export function estimateMessagesTokens(messages: AgentMessage[]): number {
// SECURITY: toolResult.details can contain untrusted/verbose payloads; never include in LLM-facing compaction.
@@ -164,6 +205,7 @@ async function summarizeChunks(params: {
reserveTokens: number;
maxChunkTokens: number;
customInstructions?: string;
summarizationInstructions?: CompactionSummarizationInstructions;
previousSummary?: string;
}): Promise<string> {
if (params.messages.length === 0) {
@@ -174,7 +216,10 @@ async function summarizeChunks(params: {
const safeMessages = stripToolResultDetails(params.messages);
const chunks = chunkMessagesByMaxTokens(safeMessages, params.maxChunkTokens);
let summary = params.previousSummary;
const effectiveInstructions = buildCompactionSummarizationInstructions(
params.customInstructions,
params.summarizationInstructions,
);
for (const chunk of chunks) {
summary = await retryAsync(
() =>
@@ -184,7 +229,7 @@ async function summarizeChunks(params: {
params.reserveTokens,
params.apiKey,
params.signal,
params.customInstructions,
effectiveInstructions,
summary,
),
{
@@ -214,6 +259,7 @@ export async function summarizeWithFallback(params: {
maxChunkTokens: number;
contextWindow: number;
customInstructions?: string;
summarizationInstructions?: CompactionSummarizationInstructions;
previousSummary?: string;
}): Promise<string> {
const { messages, contextWindow } = params;
@@ -282,6 +328,7 @@ export async function summarizeInStages(params: {
maxChunkTokens: number;
contextWindow: number;
customInstructions?: string;
summarizationInstructions?: CompactionSummarizationInstructions;
previousSummary?: string;
parts?: number;
minMessagesForSplit?: number;
@@ -325,8 +372,9 @@ export async function summarizeInStages(params: {
timestamp: Date.now(),
}));
const mergeInstructions = params.customInstructions
? `${MERGE_SUMMARIES_INSTRUCTIONS}\n\nAdditional focus:\n${params.customInstructions}`
const custom = params.customInstructions?.trim();
const mergeInstructions = custom
? `${MERGE_SUMMARIES_INSTRUCTIONS}\n\n${custom}`
: MERGE_SUMMARIES_INSTRUCTIONS;
return summarizeWithFallback({