

























@@ -30,7 +30,7 @@ import {
3030resolvePlanningOnlyRetryLimit,
3131resolvePlanningOnlyRetryInstruction,
3232isIncompleteTerminalAssistantTurn,
33-resolveIncompleteTurnPayloadText,
33+resolveIncompleteTurnPayloadText as resolveIncompleteTurnPayloadTextCore,
3434resolveReasoningOnlyRetryInstruction,
3535STRICT_AGENTIC_BLOCKED_TEXT,
3636resolveReplayInvalidFlag,
@@ -43,6 +43,14 @@ import type { EmbeddedRunAttemptResult } from "./run/types.js";
43434444let runEmbeddedAgent: typeof import("./run.js").runEmbeddedAgent;
454546+function resolveIncompleteTurnPayloadText(
47+params: Omit<Parameters<typeof resolveIncompleteTurnPayloadTextCore>[0], "externalAbort"> & {
48+externalAbort?: boolean;
49+},
50+): string | null {
51+return resolveIncompleteTurnPayloadTextCore({ externalAbort: false, ...params });
52+}
53+4654describe("runEmbeddedAgent incomplete-turn safety", () => {
4755beforeAll(async () => {
4856({ runEmbeddedAgent } = await loadRunOverflowCompactionHarness());
@@ -129,6 +137,38 @@ describe("runEmbeddedAgent incomplete-turn safety", () => {
129137expect(result.payloads?.[0]?.text).toContain("verify before retrying");
130138});
131139140+it("surfaces internal aborts after tool-use as visible incomplete-turn failures", async () => {
141+mockedClassifyFailoverReason.mockReturnValue(null);
142+mockedRunEmbeddedAttempt.mockResolvedValueOnce(
143+makeAttemptResult({
144+aborted: true,
145+externalAbort: false,
146+assistantTexts: [],
147+toolMetas: [{ toolName: "web_search", meta: "query=next voice note" }],
148+lastAssistant: {
149+role: "assistant",
150+stopReason: "toolUse",
151+provider: "openai",
152+model: "gpt-5.5",
153+content: [],
154+} as unknown as EmbeddedRunAttemptResult["lastAssistant"],
155+}),
156+);
157+158+const result = await runEmbeddedAgent({
159+ ...overflowBaseRunParams,
160+provider: "openai",
161+model: "gpt-5.5",
162+runId: "run-internal-abort-tool-use-incomplete",
163+});
164+165+expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(1);
166+expect(result.payloads).toEqual([
167+{ text: "⚠️ Agent couldn't generate a response. Please try again.", isError: true },
168+]);
169+expect(result.meta?.livenessState).toBe("abandoned");
170+});
171+132172it("synthesizes a silent cron payload from a trailing current-attempt NO_REPLY tool result", () => {
133173const payload = resolveSilentToolResultReplyPayload({
134174isCronTrigger: true,
@@ -1305,11 +1345,22 @@ describe("runEmbeddedAgent incomplete-turn safety", () => {
13051345const explicitCancellationText = resolveIncompleteTurnPayloadText({
13061346payloadCount: interruptedToolOnlyAttempt.assistantTexts.length,
13071347aborted: true,
1348+externalAbort: true,
13081349timedOut: false,
13091350attempt: interruptedToolOnlyAttempt,
13101351});
1311135213121353expect(explicitCancellationText).toBeNull();
1354+1355+const internalAbortText = resolveIncompleteTurnPayloadText({
1356+payloadCount: interruptedToolOnlyAttempt.assistantTexts.length,
1357+aborted: true,
1358+externalAbort: false,
1359+timedOut: false,
1360+attempt: interruptedToolOnlyAttempt,
1361+});
1362+1363+expect(internalAbortText).toContain("couldn't generate a response");
13131364});
1314136513151366it("allows a same-prompt retry only for replay-safe missing assistant turns", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。