diff --git a/plugins/provider-codex/src/bridge/bridge.ts b/plugins/provider-codex/src/bridge/bridge.ts index 043249502..82a216411 100644 --- a/plugins/provider-codex/src/bridge/bridge.ts +++ b/plugins/provider-codex/src/bridge/bridge.ts @@ -349,6 +349,13 @@ interface CodexBridgeSession { openedItemIds: Set; /** Codex-id space; open turns settle as failed if the child dies. */ openCodexTurnIds: Set; + /** + * Codex-id space; every turn this session has emitted turn/started for. + * Thread-state snapshots for any other turn are replays (codex re-emits the + * rollout's last-turn usage on thread/resume and thread/fork) and must not + * be scoped to a bridge turn id bb has never seen (#1727). + */ + startedCodexTurnIds: Set; identityAnnounced: boolean; /** * Events translated before the session's identity is known (codex can emit @@ -625,6 +632,23 @@ function toCanonicalEvents( if (event.type === "turn/started" && event.scope.kind === "turn") { session.openCodexTurnIds.add(event.scope.turnId); + session.startedCodexTurnIds.add(event.scope.turnId); + } + // Replayed thread-state snapshot (thread/resume, thread/fork): the turn it + // names was never started in this session, so its bridge-minted turn id + // would be unknown to bb. Context-window usage is session state and may be + // thread-scoped; token usage is turn-only and, on resume, duplicates the + // snapshot bb already persisted for that turn, so drop it. + if ( + (event.type === "thread/tokenUsage/updated" || + event.type === "thread/contextWindowUsage/updated") && + event.scope.kind === "turn" && + !session.startedCodexTurnIds.has(event.scope.turnId) + ) { + if (event.type === "thread/contextWindowUsage/updated") { + out.push(remapEvent(session, { ...event, scope: { kind: "thread" } })); + } + return out; } if (event.type === "turn/completed" && event.scope.kind === "turn") { session.openCodexTurnIds.delete(event.scope.turnId); @@ -1066,6 +1090,7 @@ async function constructThreadSession( ), openedItemIds: new Set(), openCodexTurnIds: new Set(), + startedCodexTurnIds: new Set(), identityAnnounced: false, pendingPreIdentityEvents: [], openWorkReported: false,