mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-05 03:30:29 +00:00
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:
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user