From c33cec04d93087bc9a5e8992522daab5d493bf32 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Fri, 19 Jun 2026 08:56:13 +0800 Subject: [PATCH] refactor(agents): remove stale file transcript rewrite --- scripts/check-session-accessor-boundary.mjs | 1 - .../transcript-rewrite.test.ts | 323 +----------------- .../transcript-rewrite.ts | 60 ---- .../check-session-accessor-boundary.test.ts | 10 - 4 files changed, 1 insertion(+), 393 deletions(-) diff --git a/scripts/check-session-accessor-boundary.mjs b/scripts/check-session-accessor-boundary.mjs index 425377d1a65b..490c2ca29856 100644 --- a/scripts/check-session-accessor-boundary.mjs +++ b/scripts/check-session-accessor-boundary.mjs @@ -32,7 +32,6 @@ const legacyWriterNames = new Set([ const legacyTranscriptWriterNames = new Set([ "appendSessionTranscriptMessage", "emitSessionTranscriptUpdate", - "rewriteTranscriptEntriesInSessionFile", ]); const sessionCreateLifecycleWriterNames = new Set([ "applySessionStoreEntryPatch", diff --git a/src/agents/embedded-agent-runner/transcript-rewrite.test.ts b/src/agents/embedded-agent-runner/transcript-rewrite.test.ts index fe789fb0a810..6929c2b52074 100644 --- a/src/agents/embedded-agent-runner/transcript-rewrite.test.ts +++ b/src/agents/embedded-agent-runner/transcript-rewrite.test.ts @@ -20,7 +20,6 @@ vi.mock("../session-write-lock.js", () => ), ); -let rewriteTranscriptEntriesInSessionFile: typeof import("./transcript-rewrite.js").rewriteTranscriptEntriesInSessionFile; let rewriteTranscriptEntriesInSessionManager: typeof import("./transcript-rewrite.js").rewriteTranscriptEntriesInSessionManager; let rewriteTranscriptEntriesInRuntimeTranscript: typeof import("./transcript-rewrite.js").rewriteTranscriptEntriesInRuntimeTranscript; let onSessionTranscriptUpdate: typeof import("../../sessions/transcript-events.js").onSessionTranscriptUpdate; @@ -50,10 +49,6 @@ function createTextContent(text: string) { return [{ type: "text", text }]; } -function getMessageContent(message: AgentMessage): unknown { - return "content" in message ? message.content : undefined; -} - function createReadRewriteSession(options?: { tailAssistantText?: string }) { // Read rewrite fixtures include a suffix assistant turn so branch rewrites // must re-append downstream entries after replacing the tool result. @@ -162,7 +157,6 @@ beforeAll(async () => { ({ installSessionToolResultGuard } = await import("../session-tool-result-guard.js")); ({ rewriteTranscriptEntriesInRuntimeTranscript, - rewriteTranscriptEntriesInSessionFile, rewriteTranscriptEntriesInSessionManager, } = await import("./transcript-rewrite.js")); }); @@ -309,7 +303,7 @@ describe("rewriteTranscriptEntriesInSessionManager", () => { }); }); -describe("rewriteTranscriptEntriesInSessionFile", () => { +describe("rewriteTranscriptEntriesInRuntimeTranscript", () => { it("does not create session metadata for missing runtime transcripts", async () => { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-transcript-rewrite-runtime-")); const storePath = path.join(dir, "sessions.json"); @@ -418,319 +412,4 @@ describe("rewriteTranscriptEntriesInSessionFile", () => { } }); - it("aborts under the write lock when the active suffix contains an unexpected entry", async () => { - const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-transcript-rewrite-guard-")); - const sessionManager = SessionManager.create(dir, dir); - const entryIds = appendSessionMessages(sessionManager, [ - asAppendMessage({ - role: "user", - content: "start", - timestamp: 1, - }), - asAppendMessage({ - role: "assistant", - content: createTextContent("source reply media"), - timestamp: 2, - }), - asAppendMessage({ - role: "assistant", - content: createTextContent("source reply text"), - timestamp: 3, - }), - asAppendMessage({ - role: "user", - content: "concurrent append", - timestamp: 4, - }), - ]); - const sessionFile = requireString(sessionManager.getSessionFile(), "persisted session file"); - const mediaEntryId = entryIds[1]; - const textEntryId = entryIds[2]; - const listener = vi.fn(); - const cleanup = onSessionTranscriptUpdate(listener); - - try { - const result = await rewriteTranscriptEntriesInSessionFile({ - sessionFile, - sessionKey: "agent:main:test", - request: { - allowedRewriteSuffixEntryIds: [mediaEntryId, textEntryId], - replacements: [ - { - entryId: mediaEntryId, - message: asAppendMessage({ - role: "assistant", - content: createTextContent("rewritten source reply media"), - timestamp: 2, - }) as AgentMessage, - }, - ], - }, - }); - - expect(result).toMatchObject({ - changed: false, - reason: "rewrite suffix guard failed", - }); - expect(listener).not.toHaveBeenCalled(); - - const unchangedSession = SessionManager.open(sessionFile); - expect(getBranchMessages(unchangedSession).map(getMessageContent)).toEqual([ - "start", - createTextContent("source reply media"), - createTextContent("source reply text"), - "concurrent append", - ]); - } finally { - cleanup(); - } - }); - - it("rewrites a guarded side branch and restores the active navigation state", async () => { - const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-transcript-rewrite-side-")); - const sessionFile = path.join(dir, "session.jsonl"); - await fs.writeFile( - sessionFile, - [ - { - type: "session", - version: 3, - id: "session-side-rewrite", - timestamp: "2026-06-15T00:00:00.000Z", - cwd: dir, - }, - { - type: "message", - id: "active-root", - parentId: null, - timestamp: "2026-06-15T00:00:01.000Z", - message: { role: "user", content: "active root", timestamp: 1 }, - }, - { - type: "message", - id: "side-mirror", - parentId: "active-root", - timestamp: "2026-06-15T00:00:02.000Z", - message: { - role: "assistant", - content: createTextContent("source reply before rewrite"), - timestamp: 2, - }, - }, - { - type: "leaf", - id: "active-leaf", - parentId: "side-mirror", - timestamp: "2026-06-15T00:00:03.000Z", - targetId: "active-root", - appendParentId: "side-mirror", - appendMode: "side", - }, - ] - .map((entry) => JSON.stringify(entry)) - .join("\n") + "\n", - "utf-8", - ); - - const result = await rewriteTranscriptEntriesInSessionFile({ - sessionFile, - sessionKey: "agent:main:test", - request: { - allowedRewriteSuffixEntryIds: ["side-mirror"], - replacements: [ - { - entryId: "side-mirror", - message: asAppendMessage({ - role: "assistant", - content: createTextContent("source reply after rewrite"), - timestamp: 2, - }) as AgentMessage, - }, - ], - }, - }); - - expect(result).toMatchObject({ changed: true, rewrittenEntries: 1 }); - const records = (await fs.readFile(sessionFile, "utf-8")) - .trim() - .split("\n") - .map( - (line) => - JSON.parse(line) as { - type?: string; - id?: string; - parentId?: string | null; - targetId?: string | null; - appendParentId?: string | null; - appendMode?: "side"; - message?: AgentMessage; - }, - ); - const rewrittenSideEntry = records.findLast( - (entry) => - entry.type === "message" && - JSON.stringify(entry.message).includes("source reply after rewrite"), - ); - expect(rewrittenSideEntry).toMatchObject({ parentId: "active-root" }); - expect(records.at(-1)).toMatchObject({ - type: "leaf", - parentId: rewrittenSideEntry?.id, - targetId: "active-root", - appendParentId: "side-mirror", - appendMode: "side", - }); - - const reopened = SessionManager.open(sessionFile, dir, dir); - expect(getBranchMessages(reopened).map(getMessageContent)).toEqual(["active root"]); - const nextId = reopened.appendMessage( - asAppendMessage({ role: "user", content: "active continuation", timestamp: 3 }), - ); - expect(reopened.getEntry(nextId)).toMatchObject({ parentId: "active-root" }); - expect(reopened.getEntry(nextId)).not.toHaveProperty("appendMode"); - }); - - it("rejects a rewrite batch split across active and side branches", async () => { - const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-transcript-rewrite-mixed-")); - const sessionFile = path.join(dir, "session.jsonl"); - const records = [ - { - type: "session", - version: 3, - id: "session-mixed-rewrite", - timestamp: "2026-06-15T00:00:00.000Z", - cwd: dir, - }, - { - type: "message", - id: "root", - parentId: null, - timestamp: "2026-06-15T00:00:01.000Z", - message: { role: "user", content: "root", timestamp: 1 }, - }, - { - type: "message", - id: "active-mirror", - parentId: "root", - timestamp: "2026-06-15T00:00:02.000Z", - message: { role: "assistant", content: createTextContent("active"), timestamp: 2 }, - }, - { - type: "message", - id: "side-mirror", - parentId: "root", - timestamp: "2026-06-15T00:00:03.000Z", - message: { role: "assistant", content: createTextContent("side"), timestamp: 3 }, - }, - { - type: "leaf", - id: "active-leaf", - parentId: "side-mirror", - timestamp: "2026-06-15T00:00:04.000Z", - targetId: "active-mirror", - }, - ]; - const original = records.map((entry) => JSON.stringify(entry)).join("\n") + "\n"; - await fs.writeFile(sessionFile, original, "utf-8"); - - const result = await rewriteTranscriptEntriesInSessionFile({ - sessionFile, - sessionKey: "agent:main:test", - request: { - allowedRewriteSuffixEntryIds: ["active-mirror", "side-mirror"], - replacements: [ - { - entryId: "active-mirror", - message: asAppendMessage({ - role: "assistant", - content: createTextContent("active rewritten"), - timestamp: 2, - }) as AgentMessage, - }, - { - entryId: "side-mirror", - message: asAppendMessage({ - role: "assistant", - content: createTextContent("side rewritten"), - timestamp: 3, - }) as AgentMessage, - }, - ], - }, - }); - - expect(result).toMatchObject({ - changed: false, - reason: "rewrite targets span multiple branches", - }); - expect(await fs.readFile(sessionFile, "utf-8")).toBe(original); - }); - - it("emits transcript updates when the active branch changes without opening a manager", async () => { - const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-transcript-rewrite-")); - const sessionManager = SessionManager.create(dir, dir); - const entryIds = appendSessionMessages(sessionManager, [ - asAppendMessage({ - role: "user", - content: "run tool", - timestamp: 1, - }), - asAppendMessage({ - role: "toolResult", - toolCallId: "call_1", - toolName: "exec", - content: createTextContent("before rewrite"), - isError: false, - timestamp: 2, - }), - asAppendMessage({ - role: "assistant", - content: createTextContent("summarized"), - timestamp: 3, - }), - ]); - const sessionFile = requireString(sessionManager.getSessionFile(), "persisted session file"); - const toolResultEntryId = entryIds[1]; - - const openSpy = vi.spyOn(SessionManager, "open").mockImplementation(() => { - throw new Error("SessionManager.open should not be used for file rewrites"); - }); - const listener = vi.fn(); - const cleanup = onSessionTranscriptUpdate(listener); - - try { - const result = await rewriteTranscriptEntriesInSessionFile({ - sessionFile, - sessionKey: "agent:main:test", - request: { - replacements: [ - { - entryId: toolResultEntryId, - message: createToolResultReplacement("exec", "[file_ref:file_abc]", 2), - }, - ], - }, - }); - - expect(result.changed).toBe(true); - expect(acquireSessionWriteLockMock).toHaveBeenCalledWith({ - sessionFile, - staleMs: 1_800_000, - timeoutMs: 60_000, - maxHoldMs: 300_000, - }); - expect(acquireSessionWriteLockReleaseMock).toHaveBeenCalledTimes(1); - expect(listener).toHaveBeenCalledWith({ sessionFile, sessionKey: "agent:main:test" }); - - openSpy.mockRestore(); - const rewrittenSession = SessionManager.open(sessionFile); - const rewrittenToolResult = getBranchMessages(rewrittenSession)[1] as Extract< - AgentMessage, - { role: "toolResult" } - >; - expect(rewrittenToolResult.content).toEqual([{ type: "text", text: "[file_ref:file_abc]" }]); - } finally { - cleanup(); - openSpy.mockRestore(); - } - }); }); diff --git a/src/agents/embedded-agent-runner/transcript-rewrite.ts b/src/agents/embedded-agent-runner/transcript-rewrite.ts index 7998548e4d53..aad1dd08f68d 100644 --- a/src/agents/embedded-agent-runner/transcript-rewrite.ts +++ b/src/agents/embedded-agent-runner/transcript-rewrite.ts @@ -18,7 +18,6 @@ import { import { SessionManager } from "../sessions/index.js"; import { log } from "./logger.js"; import { - persistTranscriptStateMutation, readTranscriptFileState, type TranscriptFileState, type TranscriptPersistedEntry, @@ -493,62 +492,3 @@ export async function rewriteTranscriptEntriesInRuntimeTranscript(params: { await sessionLock?.release(); } } - -/** - * Rewrites a named transcript file artifact. Runtime callers should prefer - * rewriteTranscriptEntriesInRuntimeTranscript with agent/session scope. - */ -export async function rewriteTranscriptEntriesInSessionFile(params: { - sessionFile: string; - sessionId?: string; - sessionKey?: string; - agentId?: string; - request: TranscriptRewriteRequest; - config?: SessionWriteLockAcquireTimeoutConfig; -}): Promise { - let sessionLock: Awaited> | undefined; - try { - sessionLock = await acquireSessionWriteLock({ - sessionFile: params.sessionFile, - ...resolveSessionWriteLockOptions(params.config), - }); - const state = await readTranscriptFileState(params.sessionFile); - const result = rewriteTranscriptEntriesInState({ - state, - replacements: params.request.replacements, - ...(params.request.allowedRewriteSuffixEntryIds - ? { allowedRewriteSuffixEntryIds: params.request.allowedRewriteSuffixEntryIds } - : {}), - }); - if (result.changed) { - await persistTranscriptStateMutation({ - sessionFile: params.sessionFile, - state, - appendedEntries: result.appendedEntries, - }); - emitSessionTranscriptUpdate({ - sessionFile: params.sessionFile, - sessionKey: params.sessionKey, - ...(params.agentId ? { agentId: params.agentId } : {}), - }); - log.info( - `[transcript-rewrite] rewrote ${result.rewrittenEntries} entr` + - `${result.rewrittenEntries === 1 ? "y" : "ies"} ` + - `bytesFreed=${result.bytesFreed} ` + - `sessionKey=${params.sessionKey ?? params.sessionId ?? "unknown"}`, - ); - } - return result; - } catch (err) { - const reason = formatErrorMessage(err); - log.warn(`[transcript-rewrite] failed: ${reason}`); - return { - changed: false, - bytesFreed: 0, - rewrittenEntries: 0, - reason, - }; - } finally { - await sessionLock?.release(); - } -} diff --git a/test/scripts/check-session-accessor-boundary.test.ts b/test/scripts/check-session-accessor-boundary.test.ts index f17e8734ef61..d23a9abcb7d1 100644 --- a/test/scripts/check-session-accessor-boundary.test.ts +++ b/test/scripts/check-session-accessor-boundary.test.ts @@ -228,15 +228,10 @@ describe("session accessor boundary guard", () => { findTranscriptWriterBoundaryViolations(` import { appendSessionTranscriptMessage } from "../config/sessions/transcript-append.js"; import { emitSessionTranscriptUpdate as emitUpdate } from "../sessions/transcript-events.js"; - import { rewriteTranscriptEntriesInSessionFile } from "../agents/embedded-agent-runner/transcript-rewrite.js"; `), ).toEqual([ { line: 2, reason: 'imports legacy transcript writer "appendSessionTranscriptMessage"' }, { line: 3, reason: 'imports legacy transcript writer "emitSessionTranscriptUpdate"' }, - { - line: 4, - reason: 'imports legacy transcript writer "rewriteTranscriptEntriesInSessionFile"', - }, ]); }); @@ -246,16 +241,11 @@ describe("session accessor boundary guard", () => { appendSessionTranscriptMessage({ transcriptPath, message }); transcriptEvents.emitSessionTranscriptUpdate({ sessionFile }); transcriptAppend["appendSessionTranscriptMessage"]({ transcriptPath, message }); - transcriptRewrite.rewriteTranscriptEntriesInSessionFile({ sessionFile, request }); `), ).toEqual([ { line: 2, reason: 'calls legacy transcript writer "appendSessionTranscriptMessage"' }, { line: 3, reason: 'references legacy transcript writer "emitSessionTranscriptUpdate"' }, { line: 4, reason: 'references legacy transcript writer "appendSessionTranscriptMessage"' }, - { - line: 5, - reason: 'references legacy transcript writer "rewriteTranscriptEntriesInSessionFile"', - }, ]); });