mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 15:23:43 +00:00
fix(cron): treat transient tool error payloads as recoverable (openclaw#29527) thanks @Sid-Qin
Verified: - pnpm install --frozen-lockfile - pnpm check - pnpm test -- --run src/cron/isolated-agent.uses-last-non-empty-agent-text-as.test.ts Co-authored-by: Sid-Qin <201593046+Sid-Qin@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -197,6 +197,50 @@ describe("runCronIsolatedAgentTurn", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("treats transient error payloads as non-fatal when a later success payload exists", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
mockEmbeddedPayloads([
|
||||
{
|
||||
text: "⚠️ ✍️ Write: failed",
|
||||
isError: true,
|
||||
},
|
||||
{
|
||||
text: "Write completed successfully.",
|
||||
isError: false,
|
||||
},
|
||||
]);
|
||||
const { res } = await runCronTurn(home, {
|
||||
jobPayload: DEFAULT_AGENT_TURN_PAYLOAD,
|
||||
mockTexts: null,
|
||||
});
|
||||
|
||||
expect(res.status).toBe("ok");
|
||||
expect(res.summary).toBe("Write completed successfully.");
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps error status when run-level error accompanies post-error text", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
payloads: [
|
||||
{ text: "Model context overflow", isError: true },
|
||||
{ text: "Partial assistant text before error" },
|
||||
],
|
||||
meta: {
|
||||
durationMs: 5,
|
||||
agentMeta: { sessionId: "s", provider: "p", model: "m" },
|
||||
error: { kind: "context_overflow", message: "exceeded context window" },
|
||||
},
|
||||
});
|
||||
const { res } = await runCronTurn(home, {
|
||||
jobPayload: DEFAULT_AGENT_TURN_PAYLOAD,
|
||||
mockTexts: null,
|
||||
});
|
||||
|
||||
expect(res.status).toBe("error");
|
||||
});
|
||||
});
|
||||
|
||||
it("passes resolved agentDir to runEmbeddedPiAgent", async () => {
|
||||
await withTempHome(async (home) => {
|
||||
const { res } = await runCronTurn(home, {
|
||||
|
||||
@@ -570,17 +570,29 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
Object.keys(deliveryPayload?.channelData ?? {}).length > 0;
|
||||
const deliveryBestEffort = resolveCronDeliveryBestEffort(params.job);
|
||||
const hasErrorPayload = payloads.some((payload) => payload?.isError === true);
|
||||
const runLevelError = runResult.meta?.error;
|
||||
const lastErrorPayloadIndex = payloads.findLastIndex((payload) => payload?.isError === true);
|
||||
const hasSuccessfulPayloadAfterLastError =
|
||||
!runLevelError &&
|
||||
lastErrorPayloadIndex >= 0 &&
|
||||
payloads
|
||||
.slice(lastErrorPayloadIndex + 1)
|
||||
.some((payload) => payload?.isError !== true && Boolean(payload?.text?.trim()));
|
||||
// Tool wrappers can emit transient/false-positive error payloads before a valid final
|
||||
// assistant payload. Only treat payload errors as recoverable when (a) the run itself
|
||||
// did not report a model/context-level error and (b) a non-error payload follows.
|
||||
const hasFatalErrorPayload = hasErrorPayload && !hasSuccessfulPayloadAfterLastError;
|
||||
const lastErrorPayloadText = [...payloads]
|
||||
.toReversed()
|
||||
.find((payload) => payload?.isError === true && Boolean(payload?.text?.trim()))
|
||||
?.text?.trim();
|
||||
const embeddedRunError = hasErrorPayload
|
||||
const embeddedRunError = hasFatalErrorPayload
|
||||
? (lastErrorPayloadText ?? "cron isolated run returned an error payload")
|
||||
: undefined;
|
||||
const resolveRunOutcome = (params?: { delivered?: boolean; deliveryAttempted?: boolean }) =>
|
||||
withRunSession({
|
||||
status: hasErrorPayload ? "error" : "ok",
|
||||
...(hasErrorPayload
|
||||
status: hasFatalErrorPayload ? "error" : "ok",
|
||||
...(hasFatalErrorPayload
|
||||
? { error: embeddedRunError ?? "cron isolated run returned an error payload" }
|
||||
: {}),
|
||||
summary,
|
||||
@@ -637,7 +649,7 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
deliveryAttempted:
|
||||
deliveryResult.result.deliveryAttempted ?? deliveryResult.deliveryAttempted,
|
||||
};
|
||||
if (!hasErrorPayload || deliveryResult.result.status !== "ok") {
|
||||
if (!hasFatalErrorPayload || deliveryResult.result.status !== "ok") {
|
||||
return resultWithDeliveryMeta;
|
||||
}
|
||||
return resolveRunOutcome({
|
||||
|
||||
Reference in New Issue
Block a user