Skip to content

Commit d1e190f

Browse files
committed
fix: preserve fast mode across retries
1 parent 2d42e52 commit d1e190f

4 files changed

Lines changed: 76 additions & 2 deletions

File tree

extensions/codex/src/app-server/run-attempt.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5716,6 +5716,7 @@ describe("runCodexAppServerAttempt", () => {
57165716
const harness = createStartedThreadHarness();
57175717
const params = createParams(sessionFile, workspaceDir);
57185718
params.verboseLevel = "full";
5719+
params.fastMode = "auto";
57195720
params.fastModeStartedAtMs = 1_000;
57205721
params.fastModeAutoOnSeconds = 30;
57215722
params.onToolResult = onToolResult;
@@ -5796,6 +5797,41 @@ describe("runCodexAppServerAttempt", () => {
57965797
]);
57975798
});
57985799

5800+
it("does not announce Codex fast auto progress for explicit fast mode", async () => {
5801+
const now = vi.spyOn(Date, "now").mockReturnValue(1_000);
5802+
const onToolResult = vi.fn();
5803+
const sessionFile = path.join(tempDir, "session.jsonl");
5804+
const workspaceDir = path.join(tempDir, "workspace");
5805+
const harness = createStartedThreadHarness();
5806+
const params = createParams(sessionFile, workspaceDir);
5807+
params.fastMode = "on";
5808+
params.fastModeStartedAtMs = 1_000;
5809+
params.fastModeAutoOnSeconds = 30;
5810+
params.onToolResult = onToolResult;
5811+
5812+
const run = runCodexAppServerAttempt(params);
5813+
await harness.waitForMethod("turn/start");
5814+
now.mockReturnValue(35_500);
5815+
await harness.notify({
5816+
method: "rawResponseItem/completed",
5817+
params: {
5818+
threadId: "thread-1",
5819+
turnId: "turn-1",
5820+
item: {
5821+
type: "function_call_output",
5822+
id: "call-raw-output",
5823+
call_id: "call-raw-output",
5824+
output: "tool output",
5825+
},
5826+
},
5827+
});
5828+
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
5829+
await run;
5830+
5831+
const texts = onToolResult.mock.calls.map(([payload]) => payload.text ?? "");
5832+
expect(texts.filter((text) => text.startsWith("💨Fast:"))).toEqual([]);
5833+
});
5834+
57995835
it("announces Codex app-server fast auto progress for snapshot-only tool results", async () => {
58005836
const now = vi.spyOn(Date, "now").mockReturnValue(1_000);
58015837
const onToolResult = vi.fn();
@@ -5805,6 +5841,7 @@ describe("runCodexAppServerAttempt", () => {
58055841
const harness = createStartedThreadHarness();
58065842
const params = createParams(sessionFile, workspaceDir);
58075843
params.verboseLevel = "full";
5844+
params.fastMode = "auto";
58085845
params.fastModeStartedAtMs = 1_000;
58095846
params.fastModeAutoOnSeconds = 30;
58105847
params.onToolResult = onToolResult;
@@ -5868,6 +5905,7 @@ describe("runCodexAppServerAttempt", () => {
58685905
const harness = createStartedThreadHarness();
58695906
const params = createParams(sessionFile, workspaceDir);
58705907
params.verboseLevel = "full";
5908+
params.fastMode = "auto";
58715909
params.fastModeStartedAtMs = 1_000;
58725910
params.fastModeAutoOnSeconds = 30;
58735911
params.onToolResult = onToolResult;
@@ -5919,6 +5957,7 @@ describe("runCodexAppServerAttempt", () => {
59195957
const harness = createStartedThreadHarness();
59205958
const params = createParams(sessionFile, workspaceDir);
59215959
params.verboseLevel = "full";
5960+
params.fastMode = "auto";
59225961
params.fastModeStartedAtMs = 1_000;
59235962
params.fastModeAutoOnSeconds = 30;
59245963
params.fastModeAutoProgressState = {

extensions/codex/src/app-server/run-attempt.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1817,7 +1817,11 @@ export async function runCodexAppServerAttempt(
18171817
}
18181818
};
18191819
const maybeAnnounceFastModeAutoOff = async (): Promise<void> => {
1820-
if (fastModeAutoStartedAtMs === undefined || fastModeAutoProgressState.offAnnounced) {
1820+
if (
1821+
params.fastMode !== "auto" ||
1822+
fastModeAutoStartedAtMs === undefined ||
1823+
fastModeAutoProgressState.offAnnounced
1824+
) {
18211825
return;
18221826
}
18231827
const next = resolveFastModeForElapsed({

src/agents/agent-command.live-model-switch.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,37 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => {
11461146
);
11471147
});
11481148

1149+
it("keeps the fast mode cutoff timestamp across live model switch retries", async () => {
1150+
let invocation = 0;
1151+
state.runWithModelFallbackMock.mockImplementation(async (params: FallbackRunnerParams) => {
1152+
invocation++;
1153+
const result = await params.run(params.provider, params.model);
1154+
if (invocation === 1) {
1155+
throw new LiveSessionModelSwitchError({
1156+
provider: "openai",
1157+
model: "gpt-5.4",
1158+
});
1159+
}
1160+
return {
1161+
result,
1162+
provider: params.provider,
1163+
model: params.model,
1164+
attempts: [],
1165+
};
1166+
});
1167+
state.runAgentAttemptMock.mockResolvedValue(makeSuccessResult("openai", "gpt-5.4"));
1168+
1169+
await runBasicAgentCommand();
1170+
1171+
const firstAttempt = mockCallArg(state.runAgentAttemptMock, 0) as {
1172+
fastModeStartedAtMs?: number;
1173+
};
1174+
const secondAttempt = mockCallArg(state.runAgentAttemptMock, 1) as {
1175+
fastModeStartedAtMs?: number;
1176+
};
1177+
expect(firstAttempt.fastModeStartedAtMs).toBe(secondAttempt.fastModeStartedAtMs);
1178+
});
1179+
11491180
it("uses an embedded queue rebound generation for terminal lifecycle and cleanup", async () => {
11501181
setupSingleAttemptFallback();
11511182
state.runAgentAttemptMock.mockImplementation(async (attemptParams: unknown) => {

src/agents/agent-command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1805,6 +1805,7 @@ async function agentCommandInternal(
18051805
const MAX_LIVE_SWITCH_RETRIES = 5;
18061806
let liveSwitchRetries = 0;
18071807
let autoFallbackPrimaryProbeInterruptedByLiveSwitch = false;
1808+
const fastModeStartedAtMs = Date.now();
18081809
const fallbackTrajectoryRecorder = createTrajectoryRuntimeRecorder({
18091810
cfg,
18101811
runId,
@@ -1832,7 +1833,6 @@ async function agentCommandInternal(
18321833

18331834
let fallbackAttemptIndex = 0;
18341835
attemptLifecycleState.currentTurnUserMessagePersisted = false;
1835-
const fastModeStartedAtMs = Date.now();
18361836
const fallbackResult = await runWithModelFallback<AgentAttemptResult>({
18371837
cfg,
18381838
provider,

0 commit comments

Comments
 (0)