Skip to content

Commit d842ec4

Browse files
committed
fix: tighten delivery mirror dedupe (#67185) (thanks @andyylin)
1 parent e95efa4 commit d842ec4

3 files changed

Lines changed: 73 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Docs: https://docs.openclaw.ai
4646
- OpenAI Codex/CLI: keep resumed `codex exec resume` runs on the safe non-interactive path without reintroducing the removed dangerous bypass flag by passing the supported `--skip-git-repo-check` resume arg that real Codex CLI requires outside trusted git directories. (#67666) Thanks @plgonzalezrx8.
4747
- Codex/app-server: parse Desktop-originated app-server user agents such as `Codex Desktop/0.118.0`, keeping the version gate working when the Codex CLI inherits a multi-word originator. (#64666) Thanks @cyrusaf.
4848
- Cron/announce delivery: keep isolated announce `NO_REPLY` stripping case-insensitive across direct and text delivery, preserve structured media-only sends when a caption strips silent, and derive main-session awareness from the cleaned payloads so silent captions no longer leak stale `NO_REPLY` text. (#65016) Thanks @BKF-Gitty.
49+
- Sessions/Codex: skip redundant `delivery-mirror` transcript appends only when the latest assistant message has the same visible text, preventing duplicate visible replies on Codex-backed turns without suppressing repeated answers across turns. (#67185) Thanks @andyylin.
4950

5051
## 2026.4.15-beta.1
5152

src/config/sessions/transcript.test.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,76 @@ describe("appendAssistantMessageToSessionTranscript", () => {
164164
}
165165
});
166166

167+
it("does not reuse an older matching assistant message across turns", async () => {
168+
writeTranscriptStore();
169+
170+
const olderResult = await appendExactAssistantMessageToSessionTranscript({
171+
sessionKey,
172+
storePath: fixture.storePath(),
173+
message: {
174+
role: "assistant",
175+
content: [{ type: "text", text: "Repeated answer" }],
176+
api: "openai-responses",
177+
provider: "codex",
178+
model: "gpt-5.4",
179+
usage: {
180+
input: 0,
181+
output: 0,
182+
cacheRead: 0,
183+
cacheWrite: 0,
184+
totalTokens: 0,
185+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
186+
},
187+
stopReason: "stop",
188+
timestamp: Date.now(),
189+
},
190+
});
191+
192+
const latestResult = await appendExactAssistantMessageToSessionTranscript({
193+
sessionKey,
194+
storePath: fixture.storePath(),
195+
message: {
196+
role: "assistant",
197+
content: [{ type: "text", text: "Different latest answer" }],
198+
api: "openai-responses",
199+
provider: "codex",
200+
model: "gpt-5.4",
201+
usage: {
202+
input: 0,
203+
output: 0,
204+
cacheRead: 0,
205+
cacheWrite: 0,
206+
totalTokens: 0,
207+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
208+
},
209+
stopReason: "stop",
210+
timestamp: Date.now(),
211+
},
212+
});
213+
214+
const mirrorResult = await appendAssistantMessageToSessionTranscript({
215+
sessionKey,
216+
text: "Repeated answer",
217+
storePath: fixture.storePath(),
218+
});
219+
220+
expect(olderResult.ok).toBe(true);
221+
expect(latestResult.ok).toBe(true);
222+
expect(mirrorResult.ok).toBe(true);
223+
if (olderResult.ok && latestResult.ok && mirrorResult.ok) {
224+
expect(mirrorResult.messageId).not.toBe(olderResult.messageId);
225+
expect(mirrorResult.messageId).not.toBe(latestResult.messageId);
226+
227+
const lines = fs.readFileSync(mirrorResult.sessionFile, "utf-8").trim().split("\n");
228+
expect(lines.length).toBe(4);
229+
230+
const messageLine = JSON.parse(lines[3]);
231+
expect(messageLine.message.provider).toBe("openclaw");
232+
expect(messageLine.message.model).toBe("delivery-mirror");
233+
expect(messageLine.message.content[0].text).toBe("Repeated answer");
234+
}
235+
});
236+
167237
it("finds session entry using normalized (lowercased) key", async () => {
168238
const storeKey = "agent:main:bluebubbles:direct:+15551234567";
169239
const store = {

src/config/sessions/transcript.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,9 +265,6 @@ function isRedundantDeliveryMirror(message: SessionTranscriptAssistantMessage):
265265
}
266266

267267
function extractAssistantMessageText(message: SessionTranscriptAssistantMessage): string | null {
268-
if (typeof message.text === "string" && message.text.trim()) {
269-
return message.text.trim();
270-
}
271268
if (!Array.isArray(message.content)) {
272269
return null;
273270
}
@@ -314,11 +311,12 @@ async function findLatestEquivalentAssistantMessageId(
314311
}
315312
const candidateText = extractAssistantMessageText(candidate);
316313
if (candidateText !== expectedText) {
317-
continue;
314+
return undefined;
318315
}
319316
if (typeof parsed.id === "string" && parsed.id) {
320317
return parsed.id;
321318
}
319+
return undefined;
322320
} catch {
323321
continue;
324322
}

0 commit comments

Comments
 (0)