Skip to content

Commit 20f60fd

Browse files
committed
test(codex): preserve mirrored transcript branch across turns
1 parent 8575801 commit 20f60fd

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

extensions/codex/src/app-server/transcript-mirror.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
makeAgentUserMessage,
1313
} from "openclaw/plugin-sdk/test-fixtures";
1414
import { afterEach, describe, expect, it } from "vitest";
15+
import { readSessionMessages } from "../../../../src/gateway/session-utils.fs.js";
1516
import { mirrorCodexAppServerTranscript } from "./transcript-mirror.js";
1617

1718
const tempDirs: string[] = [];
@@ -119,6 +120,74 @@ describe("mirrorCodexAppServerTranscript", () => {
119120
expect(records.slice(1)).toHaveLength(2);
120121
});
121122

123+
it("keeps previous turns on the active transcript branch when consecutive app-server turns mirror with different scopes", async () => {
124+
const sessionFile = await createTempSessionFile();
125+
const storePath = path.join(path.dirname(sessionFile), "sessions.json");
126+
127+
await mirrorCodexAppServerTranscript({
128+
sessionFile,
129+
sessionKey: "session-1",
130+
messages: [
131+
makeAgentUserMessage({
132+
content: [{ type: "text", text: "turn one user" }],
133+
timestamp: 1000,
134+
}),
135+
makeAgentAssistantMessage({
136+
content: [{ type: "text", text: "turn one assistant" }],
137+
timestamp: 1001,
138+
}),
139+
],
140+
idempotencyScope: "codex-app-server:thread-1:turn-1",
141+
});
142+
143+
await mirrorCodexAppServerTranscript({
144+
sessionFile,
145+
sessionKey: "session-1",
146+
messages: [
147+
makeAgentUserMessage({
148+
content: [{ type: "text", text: "turn two user" }],
149+
timestamp: 2000,
150+
}),
151+
makeAgentAssistantMessage({
152+
content: [{ type: "text", text: "turn two assistant" }],
153+
timestamp: 2001,
154+
}),
155+
],
156+
idempotencyScope: "codex-app-server:thread-1:turn-2",
157+
});
158+
159+
const raw = await fs.readFile(sessionFile, "utf8");
160+
const records = raw
161+
.trim()
162+
.split("\n")
163+
.filter(Boolean)
164+
.map(
165+
(line) =>
166+
JSON.parse(line) as {
167+
type?: string;
168+
id?: string;
169+
parentId?: string | null;
170+
message?: { role?: string };
171+
},
172+
)
173+
.filter((record) => record.type === "message");
174+
expect(records.map((record) => record.message?.role)).toEqual([
175+
"user",
176+
"assistant",
177+
"user",
178+
"assistant",
179+
]);
180+
for (let index = 1; index < records.length; index += 1) {
181+
expect(records[index]?.parentId).toBe(records[index - 1]?.id);
182+
}
183+
184+
const visibleHistory = readSessionMessages("session", storePath, sessionFile);
185+
expect(JSON.stringify(visibleHistory)).toContain("turn one user");
186+
expect(JSON.stringify(visibleHistory)).toContain("turn one assistant");
187+
expect(JSON.stringify(visibleHistory)).toContain("turn two user");
188+
expect(JSON.stringify(visibleHistory)).toContain("turn two assistant");
189+
});
190+
122191
it("runs before_message_write before appending mirrored transcript messages", async () => {
123192
initializeGlobalHookRunner(
124193
createMockPluginRegistry([

0 commit comments

Comments
 (0)