Skip to content

Commit 9aa4250

Browse files
ZOOWHvincentkoc
andauthored
fix(cli): hide synthetic Claude reseed prompts (#99653)
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
1 parent c6ed9d8 commit 9aa4250

34 files changed

Lines changed: 2013 additions & 91 deletions

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

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1179,6 +1179,54 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => {
11791179
expect(firstAttempt.fastModeStartedAtMs).toBe(secondAttempt.fastModeStartedAtMs);
11801180
});
11811181

1182+
it("reuses durable user-turn proof across live model switch retries", async () => {
1183+
let fallbackInvocation = 0;
1184+
state.runWithModelFallbackMock.mockImplementation(async (params: FallbackRunnerParams) => {
1185+
fallbackInvocation += 1;
1186+
const result = await params.run(params.provider, params.model);
1187+
if (fallbackInvocation === 1) {
1188+
throw new LiveSessionModelSwitchError({
1189+
provider: "openai",
1190+
model: "gpt-5.4",
1191+
});
1192+
}
1193+
return {
1194+
result,
1195+
provider: params.provider,
1196+
model: params.model,
1197+
attempts: [],
1198+
};
1199+
});
1200+
state.runAgentAttemptMock.mockImplementation(async (attemptParams: unknown) => {
1201+
const attempt = attemptParams as {
1202+
userTurnTranscriptRecorder?: {
1203+
markRuntimePersisted: (message: { role: "user"; content: string }) => void;
1204+
};
1205+
};
1206+
if (state.runAgentAttemptMock.mock.calls.length === 1) {
1207+
attempt.userTurnTranscriptRecorder?.markRuntimePersisted({
1208+
role: "user",
1209+
content: "hello",
1210+
});
1211+
}
1212+
return makeSuccessResult("openai", "gpt-5.4");
1213+
});
1214+
1215+
await runBasicAgentCommand();
1216+
1217+
const firstAttempt = mockCallArg(state.runAgentAttemptMock, 0) as {
1218+
suppressPromptPersistenceOnRetry?: boolean;
1219+
userTurnTranscriptRecorder?: unknown;
1220+
};
1221+
const secondAttempt = mockCallArg(state.runAgentAttemptMock, 1) as {
1222+
suppressPromptPersistenceOnRetry?: boolean;
1223+
userTurnTranscriptRecorder?: unknown;
1224+
};
1225+
expect(secondAttempt.userTurnTranscriptRecorder).toBe(firstAttempt.userTurnTranscriptRecorder);
1226+
expect(firstAttempt.suppressPromptPersistenceOnRetry).toBe(false);
1227+
expect(secondAttempt.suppressPromptPersistenceOnRetry).toBe(true);
1228+
});
1229+
11821230
it("uses an embedded queue rebound generation for terminal lifecycle and cleanup", async () => {
11831231
setupSingleAttemptFallback();
11841232
state.runAgentAttemptMock.mockImplementation(async (attemptParams: unknown) => {
@@ -2778,9 +2826,12 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => {
27782826
expect(attemptCalls[1]?.suppressPromptPersistenceOnRetry).toBe(true);
27792827
});
27802828

2781-
it("suppresses prompt persistence for internal handoffs on every fallback attempt", async () => {
2829+
it("keeps a hook-blocked user turn suppressed across model fallback", async () => {
27822830
type AttemptCall = {
27832831
suppressPromptPersistenceOnRetry?: boolean;
2832+
userTurnTranscriptRecorder?: {
2833+
markBlocked: () => void;
2834+
};
27842835
};
27852836
const attemptCalls: AttemptCall[] = [];
27862837
state.runWithModelFallbackMock.mockImplementation(async (params: FallbackRunnerParams) => {
@@ -2795,9 +2846,53 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => {
27952846
});
27962847
state.runAgentAttemptMock.mockImplementation(async (attemptParams: AttemptCall) => {
27972848
attemptCalls.push(attemptParams);
2849+
if (attemptCalls.length === 1) {
2850+
attemptParams.userTurnTranscriptRecorder?.markBlocked();
2851+
}
27982852
return makeSuccessResult("openai", "gpt-5.4");
27992853
});
28002854

2855+
await runBasicAgentCommand();
2856+
2857+
expect(attemptCalls).toHaveLength(2);
2858+
expect(attemptCalls[1]?.userTurnTranscriptRecorder).toBe(
2859+
attemptCalls[0]?.userTurnTranscriptRecorder,
2860+
);
2861+
expect(attemptCalls[0]?.suppressPromptPersistenceOnRetry).toBe(false);
2862+
expect(attemptCalls[1]?.suppressPromptPersistenceOnRetry).toBe(true);
2863+
});
2864+
2865+
it("suppresses prompt persistence for internal handoffs on every fallback attempt", async () => {
2866+
type AttemptCall = {
2867+
suppressPromptPersistenceOnRetry?: boolean;
2868+
};
2869+
const attemptCalls: AttemptCall[] = [];
2870+
state.runWithModelFallbackMock.mockImplementation(async (params: FallbackRunnerParams) => {
2871+
const first = await params.run(params.provider, params.model);
2872+
const result = await params.run(params.provider, params.model);
2873+
return {
2874+
result,
2875+
provider: params.provider,
2876+
model: params.model,
2877+
attempts: [first],
2878+
};
2879+
});
2880+
state.runAgentAttemptMock.mockImplementation(async (attemptParams: AttemptCall) => {
2881+
attemptCalls.push(attemptParams);
2882+
const result = makeSuccessResult("openai", "gpt-5.4") as ReturnType<
2883+
typeof makeSuccessResult
2884+
> & {
2885+
meta: Record<string, unknown> & { executionTrace?: Record<string, unknown> };
2886+
};
2887+
result.meta.executionTrace = {
2888+
runner: "cli",
2889+
fallbackUsed: false,
2890+
winnerProvider: "openai",
2891+
winnerModel: "gpt-5.4",
2892+
};
2893+
return result;
2894+
});
2895+
28012896
await agentCommand({
28022897
message: "internal handoff",
28032898
to: "+1234567890",
@@ -2807,6 +2902,51 @@ describe("agentCommand – LiveSessionModelSwitchError retry", () => {
28072902
expect(attemptCalls).toHaveLength(2);
28082903
expect(attemptCalls[0]?.suppressPromptPersistenceOnRetry).toBe(true);
28092904
expect(attemptCalls[1]?.suppressPromptPersistenceOnRetry).toBe(true);
2905+
expectRecordFields(mockCallArg(state.persistCliTurnTranscriptMock), {
2906+
skipUserTurn: true,
2907+
});
2908+
});
2909+
2910+
it("preserves an explicit empty transcript message as user-turn omission", async () => {
2911+
setupSingleAttemptFallback();
2912+
state.runAgentAttemptMock.mockResolvedValue(makeSuccessResult("openai", "gpt-5.4"));
2913+
2914+
await agentCommand({
2915+
message: "synthetic announce prompt",
2916+
transcriptMessage: "",
2917+
to: "+1234567890",
2918+
});
2919+
2920+
const attempt = mockCallArg(state.runAgentAttemptMock) as {
2921+
suppressPromptPersistenceOnRetry?: boolean;
2922+
userTurnTranscriptRecorder?: { message?: unknown };
2923+
};
2924+
expect(attempt.suppressPromptPersistenceOnRetry).toBe(true);
2925+
expect(attempt.userTurnTranscriptRecorder?.message).toBeUndefined();
2926+
});
2927+
2928+
it("uses a tracker-only recorder for text plus image turns", async () => {
2929+
setupSingleAttemptFallback();
2930+
state.runAgentAttemptMock.mockResolvedValue(makeSuccessResult("openai", "gpt-5.4"));
2931+
2932+
await agentCommand({
2933+
message: "inspect this image",
2934+
transcriptMessage: "canonical image caption",
2935+
images: [{ type: "image", data: "aGVsbG8=", mimeType: "image/png" }],
2936+
to: "+1234567890",
2937+
});
2938+
2939+
const attempt = mockCallArg(state.runAgentAttemptMock) as {
2940+
transcriptBody?: string;
2941+
suppressPromptPersistenceOnRetry?: boolean;
2942+
userTurnTranscriptRecorder?: { message?: unknown };
2943+
};
2944+
expect(attempt.transcriptBody).toBe("canonical image caption");
2945+
expect(attempt.suppressPromptPersistenceOnRetry).toBe(false);
2946+
expect(attempt.userTurnTranscriptRecorder?.message).toMatchObject({
2947+
role: "user",
2948+
content: "canonical image caption",
2949+
});
28102950
});
28112951

28122952
it("propagates non-switch errors without retrying and emits lifecycle error", async () => {

src/agents/agent-command.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
repairProviderWrappedModelOverride,
5454
} from "../sessions/model-overrides.js";
5555
import { resolveSendPolicy } from "../sessions/send-policy.js";
56+
import { createUserTurnTranscriptRecorder } from "../sessions/user-turn-transcript.js";
5657
import { createLazyImportLoader } from "../shared/lazy-promise.js";
5758
import { resolveEffectiveAgentSkillFilter } from "../skills/discovery/agent-filter.js";
5859
import type { getRemoteSkillEligibility } from "../skills/runtime/remote.js";
@@ -108,6 +109,7 @@ import {
108109
mergeEmbeddedAgentRunResultForModelFallbackExhaustion,
109110
} from "./embedded-agent-runner/result-fallback-classifier.js";
110111
import { resolveFastModeState } from "./fast-mode.js";
112+
import { runAgentHarnessBeforeMessageWriteHook } from "./harness/hook-helpers.js";
111113
import { ensureSelectedAgentHarnessPlugin } from "./harness/runtime-plugin.js";
112114
import { resolveAvailableAgentHarnessPolicy } from "./harness/selection.js";
113115
import { prepareInternalSessionEffectsTranscript } from "./internal-session-effects.js";
@@ -1690,6 +1692,27 @@ async function agentCommandInternal(
16901692
lifecycleEnded: false,
16911693
};
16921694
const attemptLifecycleCallbacks = createAgentAttemptLifecycleCallbacks(attemptLifecycleState);
1695+
const suppressUserTurnPersistence =
1696+
opts.suppressPromptPersistence === true || opts.transcriptMessage === "";
1697+
const recorderTranscriptText = transcriptBody || undefined;
1698+
const userTurnTranscriptRecorder = createUserTurnTranscriptRecorder({
1699+
...(!suppressUserTurnPersistence && recorderTranscriptText
1700+
? { input: { text: recorderTranscriptText } }
1701+
: {}),
1702+
target: {
1703+
transcriptPath: attemptSessionFile,
1704+
sessionId,
1705+
agentId: sessionAgentId,
1706+
...(sessionKey ? { sessionKey } : {}),
1707+
cwd: cwd ?? workspaceDir,
1708+
config: cfg,
1709+
},
1710+
beforeMessageWrite: runAgentHarnessBeforeMessageWriteHook,
1711+
errorContext: "agent command user turn transcript",
1712+
});
1713+
if (suppressUserTurnPersistence) {
1714+
userTurnTranscriptRecorder.markBlocked();
1715+
}
16931716
let lifecycleFinishingEmitted = false;
16941717
const emitLifecycleFinishing = (runResult: AgentAttemptResult) => {
16951718
if (
@@ -1928,6 +1951,7 @@ async function agentCommandInternal(
19281951
workspaceDir,
19291952
cwd,
19301953
body,
1954+
transcriptBody,
19311955
isFallbackRetry,
19321956
resolvedThinkLevel,
19331957
fastMode,
@@ -1958,8 +1982,11 @@ async function agentCommandInternal(
19581982
!isNewSession ||
19591983
(await attemptExecutionRuntime.sessionFileHasContent(attemptSessionFile)),
19601984
suppressPromptPersistenceOnRetry:
1961-
opts.suppressPromptPersistence === true ||
1985+
suppressUserTurnPersistence ||
1986+
userTurnTranscriptRecorder.hasPersisted() ||
1987+
userTurnTranscriptRecorder.isBlocked() ||
19621988
(isFallbackRetry && attemptLifecycleState.currentTurnUserMessagePersisted),
1989+
userTurnTranscriptRecorder,
19631990
onUserMessagePersisted: attemptLifecycleCallbacks.onUserMessagePersisted,
19641991
onLifecycleGenerationChanged: (nextLifecycleGeneration) => {
19651992
lifecycleGeneration = nextLifecycleGeneration;
@@ -2220,6 +2247,10 @@ async function agentCommandInternal(
22202247
sessionCwd: effectiveCwd,
22212248
config: cfg,
22222249
embeddedAssistantGapFill,
2250+
skipUserTurn:
2251+
suppressUserTurnPersistence ||
2252+
userTurnTranscriptRecorder.hasPersisted() ||
2253+
userTurnTranscriptRecorder.isBlocked(),
22232254
});
22242255
sessionEntry = transcriptResult.sessionEntry;
22252256
sessionReboundDuringRun = transcriptResult.kind === "session-rebound";

0 commit comments

Comments
 (0)