From 688e3f086395694a65e88457a3fa3ba4e0a3b74f Mon Sep 17 00:00:00 2001 From: Rodrigo Uroz Date: Thu, 12 Mar 2026 12:22:12 -0300 Subject: [PATCH] Compaction Runner: emit transcript updates post-compact (#25558) Merged via squash. Prepared head SHA: 8a858436ed31805124a9d096bd93ab90e5423672 Co-authored-by: rodrigouroz <384037+rodrigouroz@users.noreply.github.com> Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com> Reviewed-by: @jalehman --- CHANGELOG.md | 2 +- .../pi-embedded-runner/compact.hooks.test.ts | 21 +++++++++++++++++++ src/agents/pi-embedded-runner/compact.ts | 2 ++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 130fb9e20d1..9fb840ea5cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,7 +65,6 @@ Docs: https://docs.openclaw.ai - Gateway/node pending work: add narrow in-memory pending-work queue primitives (`node.pending.enqueue` / `node.pending.drain`) and wake-helper reuse as a foundation for dormant-node work delivery. (#41409) Thanks @mbelinky. - Git/runtime state: ignore the gateway-generated `.dev-state` file so local runtime state does not show up as untracked repo noise. (#41848) Thanks @smysle. - Exec/child commands: mark child command environments with `OPENCLAW_CLI` so subprocesses can detect when they were launched from the OpenClaw CLI. (#41411) Thanks @vincentkoc. - <<<<<<< HEAD - LLM Task/Lobster: add an optional `thinking` override so workflow calls can explicitly set embedded reasoning level with shared validation for invalid values and unsupported `xhigh` modes. (#15606) Thanks @xadenryan and @ImLukeF. - Mattermost/reply threading: add `channels.mattermost.replyToMode` for channel and group messages so top-level posts can start thread-scoped sessions without the manual reply-then-thread workaround. (#29587) Thanks @teconomix. @@ -247,6 +246,7 @@ Docs: https://docs.openclaw.ai - Agents/failover: recognize additional serialized network errno strings plus `EHOSTDOWN` and `EPIPE` structured codes so transient transport failures trigger timeout failover more reliably. (#42830) Thanks @jnMetaCode. - Telegram/model picker: make inline model button selections persist the chosen session model correctly, clear overrides when selecting the configured default, and include effective fallback models in `/models` button validation. (#40105) Thanks @avirweb. - Agents/embedded runner: carry provider-observed overflow token counts into compaction so overflow retries and diagnostics use the rejected live prompt size instead of only transcript estimates. (#40357) thanks @rabsef-bicrym. +- Agents/compaction transcript updates: emit a transcript-update event immediately after successful embedded compaction so downstream listeners observe the post-compact transcript without waiting for a later write. (#25558) thanks @rodrigouroz. ## 2026.3.7 diff --git a/src/agents/pi-embedded-runner/compact.hooks.test.ts b/src/agents/pi-embedded-runner/compact.hooks.test.ts index dc1511a5e05..9292028b5d4 100644 --- a/src/agents/pi-embedded-runner/compact.hooks.test.ts +++ b/src/agents/pi-embedded-runner/compact.hooks.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; +import { onSessionTranscriptUpdate } from "../../sessions/transcript-events.js"; const { hookRunner, @@ -430,6 +431,26 @@ describe("compactEmbeddedPiSessionDirect hooks", () => { tokenCount: 0, }); }); + it("emits a transcript update after successful compaction", async () => { + const listener = vi.fn(); + const cleanup = onSessionTranscriptUpdate(listener); + + try { + const result = await compactEmbeddedPiSessionDirect({ + sessionId: "session-1", + sessionKey: "agent:main:session-1", + sessionFile: " /tmp/session.jsonl ", + workspaceDir: "/tmp", + customInstructions: "focus on decisions", + }); + + expect(result.ok).toBe(true); + expect(listener).toHaveBeenCalledTimes(1); + expect(listener).toHaveBeenCalledWith({ sessionFile: "/tmp/session.jsonl" }); + } finally { + cleanup(); + } + }); it("registers the Ollama api provider before compaction", async () => { resolveModelMock.mockReturnValue({ diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index f4ea6819eae..a62ed2eecb0 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -21,6 +21,7 @@ import { generateSecureToken } from "../../infra/secure-random.js"; import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js"; import { type enqueueCommand, enqueueCommandInLane } from "../../process/command-queue.js"; import { isCronSessionKey, isSubagentSessionKey } from "../../routing/session-key.js"; +import { emitSessionTranscriptUpdate } from "../../sessions/transcript-events.js"; import { resolveSignalReactionLevel } from "../../signal/reaction-level.js"; import { resolveTelegramInlineButtonsScope } from "../../telegram/inline-buttons.js"; import { resolveTelegramReactionLevel } from "../../telegram/reaction-level.js"; @@ -808,6 +809,7 @@ export async function compactEmbeddedPiSessionDirect( const result = await compactWithSafetyTimeout(() => session.compact(params.customInstructions), ); + emitSessionTranscriptUpdate(params.sessionFile); // Estimate tokens after compaction by summing token estimates for remaining messages let tokensAfter: number | undefined; try {