






















@@ -28,6 +28,7 @@ import {
2828type JsonValue,
2929} from "./protocol.js";
3030import { readCodexMirroredSessionHistoryMessages } from "./session-history.js";
31+import { attachCodexMirrorIdentity } from "./transcript-mirror.js";
31323233export type CodexAppServerToolTelemetry = {
3334didSendViaMessagingTool: boolean;
@@ -185,23 +186,47 @@ export class CodexAppServerEventProjector {
185186assistantTexts.length > 0
186187 ? this.createAssistantMessage(assistantTexts.join("\n\n"))
187188 : undefined;
189+// Each snapshot entry is tagged with a stable mirror identity of the
190+// shape `${turnId}:${kind}`. The mirror's idempotency key is derived
191+// from this identity rather than from snapshot position or content
192+// hash, so:
193+// - Re-mirror of the same turn (retry) → same identity → no-op.
194+// - Re-emit of a prior turn's entry into a later turn's snapshot
195+// (the cross-turn drift mode named in #77012) → original identity
196+// is preserved → on-disk key still matches → also a no-op.
197+// - Two distinct turns where the user repeats verbatim content →
198+// distinct turnIds → distinct identities → both kept.
199+const turnId = this.turnId;
188200const messagesSnapshot: AgentMessage[] = [
189-{
190-role: "user",
191-content: this.params.prompt,
192-timestamp: Date.now(),
193-},
201+attachCodexMirrorIdentity(
202+{
203+role: "user",
204+content: this.params.prompt,
205+timestamp: Date.now(),
206+},
207+`${turnId}:prompt`,
208+),
194209];
195210// Codex owns the canonical thread. These mirror records keep enough local
196211// context for OpenClaw history, search, and future harness switching.
197212if (reasoningText) {
198-messagesSnapshot.push(this.createAssistantMirrorMessage("Codex reasoning", reasoningText));
213+messagesSnapshot.push(
214+attachCodexMirrorIdentity(
215+this.createAssistantMirrorMessage("Codex reasoning", reasoningText),
216+`${turnId}:reasoning`,
217+),
218+);
199219}
200220if (planText) {
201-messagesSnapshot.push(this.createAssistantMirrorMessage("Codex plan", planText));
221+messagesSnapshot.push(
222+attachCodexMirrorIdentity(
223+this.createAssistantMirrorMessage("Codex plan", planText),
224+`${turnId}:plan`,
225+),
226+);
202227}
203228if (lastAssistant) {
204-messagesSnapshot.push(lastAssistant);
229+messagesSnapshot.push(attachCodexMirrorIdentity(lastAssistant, `${turnId}:assistant`));
205230}
206231const turnFailed = this.completedTurn?.status === "failed";
207232const turnInterrupted = this.completedTurn?.status === "interrupted";
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。