Files
openclaw/src/agents/pi-embedded-subscribe.handlers.compaction.ts
Jari Mustonen 4f6955fb11 fix(hooks): pass sessionFile and sessionKey in after_compaction hook (#40781)
Merged via squash.

Prepared head SHA: 11e85f8651
Co-authored-by: jarimustonen <1272053+jarimustonen@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-03-17 08:30:37 -07:00

112 lines
3.8 KiB
TypeScript

import type { AgentEvent } from "@mariozechner/pi-agent-core";
import { emitAgentEvent } from "../infra/agent-events.js";
import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
import type { EmbeddedPiSubscribeContext } from "./pi-embedded-subscribe.handlers.types.js";
import { makeZeroUsageSnapshot } from "./usage.js";
export function handleAutoCompactionStart(ctx: EmbeddedPiSubscribeContext) {
ctx.state.compactionInFlight = true;
ctx.ensureCompactionPromise();
ctx.log.debug(`embedded run compaction start: runId=${ctx.params.runId}`);
emitAgentEvent({
runId: ctx.params.runId,
stream: "compaction",
data: { phase: "start" },
});
void ctx.params.onAgentEvent?.({
stream: "compaction",
data: { phase: "start" },
});
// Run before_compaction plugin hook (fire-and-forget)
const hookRunner = getGlobalHookRunner();
if (hookRunner?.hasHooks("before_compaction")) {
void hookRunner
.runBeforeCompaction(
{
messageCount: ctx.params.session.messages?.length ?? 0,
messages: ctx.params.session.messages,
sessionFile: ctx.params.session.sessionFile,
},
{
sessionKey: ctx.params.sessionKey,
},
)
.catch((err) => {
ctx.log.warn(`before_compaction hook failed: ${String(err)}`);
});
}
}
export function handleAutoCompactionEnd(
ctx: EmbeddedPiSubscribeContext,
evt: AgentEvent & { willRetry?: unknown; result?: unknown; aborted?: unknown },
) {
ctx.state.compactionInFlight = false;
const willRetry = Boolean(evt.willRetry);
// Increment counter whenever compaction actually produced a result,
// regardless of willRetry. Overflow-triggered compaction sets willRetry=true
// (the framework retries the LLM request), but the compaction itself succeeded
// and context was trimmed — the counter must reflect that. (#38905)
const hasResult = evt.result != null;
const wasAborted = Boolean(evt.aborted);
if (hasResult && !wasAborted) {
ctx.incrementCompactionCount?.();
}
if (willRetry) {
ctx.noteCompactionRetry();
ctx.resetForCompactionRetry();
ctx.log.debug(`embedded run compaction retry: runId=${ctx.params.runId}`);
} else {
ctx.maybeResolveCompactionWait();
clearStaleAssistantUsageOnSessionMessages(ctx);
}
emitAgentEvent({
runId: ctx.params.runId,
stream: "compaction",
data: { phase: "end", willRetry, completed: hasResult && !wasAborted },
});
void ctx.params.onAgentEvent?.({
stream: "compaction",
data: { phase: "end", willRetry, completed: hasResult && !wasAborted },
});
// Run after_compaction plugin hook (fire-and-forget)
if (!willRetry) {
const hookRunnerEnd = getGlobalHookRunner();
if (hookRunnerEnd?.hasHooks("after_compaction")) {
void hookRunnerEnd
.runAfterCompaction(
{
messageCount: ctx.params.session.messages?.length ?? 0,
compactedCount: ctx.getCompactionCount(),
sessionFile: ctx.params.session.sessionFile,
},
{ sessionKey: ctx.params.sessionKey },
)
.catch((err) => {
ctx.log.warn(`after_compaction hook failed: ${String(err)}`);
});
}
}
}
function clearStaleAssistantUsageOnSessionMessages(ctx: EmbeddedPiSubscribeContext): void {
const messages = ctx.params.session.messages;
if (!Array.isArray(messages)) {
return;
}
for (const message of messages) {
if (!message || typeof message !== "object") {
continue;
}
const candidate = message as { role?: unknown; usage?: unknown };
if (candidate.role !== "assistant") {
continue;
}
// pi-coding-agent expects assistant usage to exist when computing context usage.
// Reset stale snapshots to zeros instead of deleting the field.
candidate.usage = makeZeroUsageSnapshot();
}
}