fix(coding-agent): close terminal retry lifecycle
This commit is contained in:
@@ -2232,8 +2232,8 @@ export class AgentSession {
|
||||
const prewalk = this.#prewalk;
|
||||
if (!prewalk || context?.message.role !== "assistant") return;
|
||||
|
||||
const todoCalledThisTurn = context.toolResults.some(result => result.toolName === "todo");
|
||||
if (todoCalledThisTurn) {
|
||||
const todoSucceededThisTurn = context.toolResults.some(result => result.toolName === "todo" && !result.isError);
|
||||
if (todoSucceededThisTurn) {
|
||||
this.#prewalkTodoSeen = true;
|
||||
}
|
||||
|
||||
@@ -2268,9 +2268,6 @@ export class AgentSession {
|
||||
// not establish the list. The gate keys on the ACTIVE tool set, not the
|
||||
// registry: a registered-but-deactivated todo (e.g. a restricted
|
||||
// active-tool slate) is uncallable and would deadlock the switch.
|
||||
if (context.toolResults.some(result => result.toolName === "todo" && !result.isError)) {
|
||||
this.#prewalkTodoSeen = true;
|
||||
}
|
||||
const todoGateOpen = this.#prewalkTodoSeen || !this.getActiveToolNames().includes("todo");
|
||||
const action = todoGateOpen
|
||||
? context.toolResults.find(result => PREWALK_ACTION_TOOLS[result.toolName])
|
||||
@@ -4584,6 +4581,17 @@ export class AgentSession {
|
||||
if (this.#isClassifierRefusal(msg)) {
|
||||
this.#removeAssistantMessageFromActiveContext(msg);
|
||||
}
|
||||
if (msg.stopReason === "error" && this.#retryAttempt > 0) {
|
||||
const attempt = this.#retryAttempt;
|
||||
this.#retryAttempt = 0;
|
||||
await this.#emitSessionEvent({
|
||||
type: "auto_retry_end",
|
||||
success: false,
|
||||
attempt,
|
||||
finalError: msg.errorMessage,
|
||||
});
|
||||
this.#clearPendingRecoveredRetryErrors();
|
||||
}
|
||||
this.#resolveRetry();
|
||||
|
||||
if (!checkedCompaction) {
|
||||
|
||||
@@ -241,7 +241,11 @@ describe("AgentSession prewalk", () => {
|
||||
const failingTodoTool: AgentTool<typeof todoToolSchema, undefined> = {
|
||||
...todoTool,
|
||||
async execute() {
|
||||
throw new Error("todo update failed");
|
||||
return {
|
||||
content: [{ type: "text", text: "todo update failed" }],
|
||||
details: undefined,
|
||||
isError: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
const mock = createMockModel({
|
||||
|
||||
@@ -1683,6 +1683,54 @@ describe("AgentSession retry fallback", () => {
|
||||
expect(lastAssistant.errorMessage).toBe(envelopeError);
|
||||
});
|
||||
|
||||
it("closes the retry lifecycle when a retried turn ends with a non-retryable error", async () => {
|
||||
const model = getBundledModel("openai", "gpt-4o-mini");
|
||||
if (!model) throw new Error("Expected bundled OpenAI test model to exist");
|
||||
|
||||
const retryableError = "rate limit exceeded retry-after-ms=5";
|
||||
const terminalError = "invalid request: schema violation";
|
||||
const requestedModels: string[] = [];
|
||||
const mock = createMockModel();
|
||||
const agent = new Agent({
|
||||
getApiKey: requestedModel => `${requestedModel.provider}-test-key`,
|
||||
initialState: {
|
||||
model,
|
||||
systemPrompt: ["Test"],
|
||||
tools: [],
|
||||
messages: [],
|
||||
},
|
||||
streamFn: (requestedModel, context, options) => {
|
||||
requestedModels.push(`${requestedModel.provider}/${requestedModel.id}`);
|
||||
mock.push({ throw: requestedModels.length === 1 ? retryableError : terminalError });
|
||||
return mock.stream(requestedModel, context, options);
|
||||
},
|
||||
});
|
||||
const settings = Settings.isolated({
|
||||
"compaction.enabled": false,
|
||||
"retry.baseDelayMs": 5,
|
||||
"retry.maxRetries": 1,
|
||||
});
|
||||
session = new AgentSession({
|
||||
agent,
|
||||
sessionManager: SessionManager.inMemory(),
|
||||
settings,
|
||||
modelRegistry,
|
||||
});
|
||||
const { retryStartEvents, retryEndEvents } = trackRetryEvents(session);
|
||||
vi.spyOn(scheduler, "wait").mockResolvedValue(undefined);
|
||||
|
||||
await session.prompt("Retry once, then surface a terminal validation failure");
|
||||
await session.waitForIdle();
|
||||
|
||||
expect(requestedModels).toEqual([`${model.provider}/${model.id}`, `${model.provider}/${model.id}`]);
|
||||
expect(retryStartEvents).toHaveLength(1);
|
||||
expect(retryEndEvents).toEqual([
|
||||
expect.objectContaining({ success: false, attempt: 1, finalError: terminalError }),
|
||||
]);
|
||||
expect(session.retryAttempt).toBe(0);
|
||||
expect(getLastAssistantMessage(session).stopReason).toBe("error");
|
||||
});
|
||||
|
||||
it("auto-retries a bare Request was aborted error-stop turn (issue #5375)", async () => {
|
||||
const model = getBundledModel("openai", "gpt-4o-mini");
|
||||
if (!model) {
|
||||
|
||||
Reference in New Issue
Block a user