mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:20:43 +00:00
fix(pi-embedded-runner): suppress incomplete-turn warning after clean messaging-tool delivery
The agent runner was surfacing a '⚠️ Agent couldn't generate a response' warning even when the assistant had already sent user-visible content through a messaging tool and the turn ended cleanly. Treat that path as a successful delivery and skip the warning while keeping real failure modes (tool errors, stopReason=error, interrupted tool use) intact. Fixes #70396.
This commit is contained in:
committed by
Peter Steinberger
parent
93a1f5b3fa
commit
6cd9136f2d
@@ -45,6 +45,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Providers/SDK retry: cap long `Retry-After` sleeps in Stainless-based Anthropic/OpenAI model SDKs so 60s+ retry windows surface immediately for OpenClaw failover instead of blocking the run. (#68474) Thanks @jetd1.
|
||||
- Agents/TTS: preserve spoken text in TTS tool results while defusing reply directives in transcript content, so future turns remember voice replies without treating spoken `MEDIA:` or voice tags as delivery metadata. (#68869) Thanks @zqchris.
|
||||
- Providers/OpenAI: harden Voice Call realtime transcription against OpenAI Realtime session-update drift, forward language and prompt hints, and add live coverage for realtime STT.
|
||||
- Agents/Pi embedded runs: suppress the "⚠️ Agent couldn't generate a response" warning when the assistant already delivered user-visible content through a messaging tool and the turn ended cleanly (`stopReason=stop`/`end_turn`). Real failure modes (tool errors, provider `stopReason=error`, interrupted tool use) still surface the existing "verify before retrying" warning. Fixes #70396.
|
||||
- Providers/Moonshot: stop strict-sanitizing Kimi's native tool_call IDs (shaped like `functions.<name>:<index>`) on the OpenAI-compatible transport, so multi-turn agentic flows through Kimi K2.6 no longer break after 2-3 tool-calling rounds when the serving layer fails to match mangled IDs against the original tool definitions. Adds a `sanitizeToolCallIds` opt-out to the shared `openai-compatible` replay family helper and wires Moonshot to it. Fixes #62319. (#70030) Thanks @LeoDu0314.
|
||||
- Dependencies/security: override transitive `uuid` to `14.0.0`, clearing the runtime advisory across dependencies.
|
||||
- Codex harness: ignore dynamic tool descriptions when deciding whether to reuse a native app-server thread while still fingerprinting tool schemas, so channel-specific copy changes no longer reset otherwise compatible Codex conversations. (#69976) Thanks @chen-zhang-cs-code.
|
||||
|
||||
@@ -292,7 +292,7 @@ describe("runEmbeddedPiAgent incomplete-turn safety", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("does not retry reasoning-only turns after side effects", async () => {
|
||||
it("does not retry or warn on reasoning-only turns when a messaging tool already delivered", async () => {
|
||||
mockedClassifyFailoverReason.mockReturnValue(null);
|
||||
mockedRunEmbeddedAttempt.mockResolvedValueOnce(
|
||||
makeAttemptResult({
|
||||
@@ -300,7 +300,7 @@ describe("runEmbeddedPiAgent incomplete-turn safety", () => {
|
||||
didSendViaMessagingTool: true,
|
||||
lastAssistant: {
|
||||
role: "assistant",
|
||||
stopReason: "end_turn",
|
||||
stopReason: "stop",
|
||||
provider: "openai",
|
||||
model: "gpt-5.4",
|
||||
content: [
|
||||
@@ -322,8 +322,7 @@ describe("runEmbeddedPiAgent incomplete-turn safety", () => {
|
||||
});
|
||||
|
||||
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(1);
|
||||
expect(result.payloads?.[0]?.isError).toBe(true);
|
||||
expect(result.payloads?.[0]?.text).toContain("verify before retrying");
|
||||
expect(result.payloads).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not retry reasoning-only turns when the assistant ended in error", async () => {
|
||||
@@ -761,6 +760,49 @@ describe("runEmbeddedPiAgent incomplete-turn safety", () => {
|
||||
expect(incompleteTurnText).toBeNull();
|
||||
});
|
||||
|
||||
it("suppresses the incomplete-turn warning when a messaging tool delivered and the turn ended cleanly", () => {
|
||||
const incompleteTurnText = resolveIncompleteTurnPayloadText({
|
||||
payloadCount: 0,
|
||||
aborted: false,
|
||||
timedOut: false,
|
||||
attempt: makeAttemptResult({
|
||||
assistantTexts: [],
|
||||
didSendViaMessagingTool: true,
|
||||
lastAssistant: {
|
||||
role: "assistant",
|
||||
stopReason: "stop",
|
||||
provider: "ollama",
|
||||
model: "kimi-k2.6:cloud",
|
||||
content: [],
|
||||
} as unknown as EmbeddedRunAttemptResult["lastAssistant"],
|
||||
}),
|
||||
});
|
||||
|
||||
expect(incompleteTurnText).toBeNull();
|
||||
});
|
||||
|
||||
it("still surfaces the incomplete-turn warning after a messaging tool when the provider signalled an error", () => {
|
||||
const incompleteTurnText = resolveIncompleteTurnPayloadText({
|
||||
payloadCount: 0,
|
||||
aborted: false,
|
||||
timedOut: false,
|
||||
attempt: makeAttemptResult({
|
||||
assistantTexts: [],
|
||||
didSendViaMessagingTool: true,
|
||||
lastAssistant: {
|
||||
role: "assistant",
|
||||
stopReason: "error",
|
||||
provider: "ollama",
|
||||
model: "kimi-k2.6:cloud",
|
||||
errorMessage: "provider failed mid-turn",
|
||||
content: [],
|
||||
} as unknown as EmbeddedRunAttemptResult["lastAssistant"],
|
||||
}),
|
||||
});
|
||||
|
||||
expect(incompleteTurnText).toContain("verify before retrying");
|
||||
});
|
||||
|
||||
it("does not retry reasoning-only GPT turns after side effects", () => {
|
||||
const retryInstruction = resolveReasoningOnlyRetryInstruction({
|
||||
provider: "openai",
|
||||
|
||||
@@ -20,6 +20,7 @@ type IncompleteTurnAttempt = Pick<
|
||||
| "currentAttemptAssistant"
|
||||
| "yieldDetected"
|
||||
| "didSendDeterministicApprovalPrompt"
|
||||
| "didSendViaMessagingTool"
|
||||
| "lastToolError"
|
||||
| "lastAssistant"
|
||||
| "replayMetadata"
|
||||
@@ -179,6 +180,19 @@ export function resolveIncompleteTurnPayloadText(params: {
|
||||
}
|
||||
|
||||
const stopReason = params.attempt.lastAssistant?.stopReason;
|
||||
// If the assistant already delivered user-visible content via a messaging
|
||||
// tool during this turn and ended cleanly (stopReason=stop), do not surface
|
||||
// an incomplete-turn warning. The user has received the reply; a follow-up
|
||||
// "couldn't generate a response" bubble is a false positive. Real failure
|
||||
// modes (tool errors, provider stopReason=error, tool-use interruption)
|
||||
// still fall through to the normal incomplete-turn paths below.
|
||||
if (
|
||||
params.attempt.didSendViaMessagingTool &&
|
||||
!params.attempt.lastToolError &&
|
||||
stopReason === "stop"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const incompleteTerminalAssistant = isIncompleteTerminalAssistantTurn({
|
||||
hasAssistantVisibleText: params.payloadCount > 0,
|
||||
lastAssistant: params.attempt.lastAssistant,
|
||||
|
||||
Reference in New Issue
Block a user