fix(agents): preserve distinct empty exec failures · openclaw/openclaw@9b2f10d
steipete
·
2026-04-27
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -560,6 +560,36 @@ describe("tool-loop-detection", () => {
|
560 | 560 | } |
561 | 561 | }); |
562 | 562 | |
| 563 | +it("keeps changing empty-output exec failures below the global no-progress breaker", () => { |
| 564 | +const state = createState(); |
| 565 | +const params = { command: "openclaw flaky-helper" }; |
| 566 | + |
| 567 | +for (let index = 0; index < GLOBAL_CIRCUIT_BREAKER_THRESHOLD; index += 1) { |
| 568 | +recordSuccessfulCall( |
| 569 | +state, |
| 570 | +"exec", |
| 571 | +params, |
| 572 | +{ |
| 573 | +content: [{ type: "text", text: `Runtime failed before spawn: attempt ${index}` }], |
| 574 | +details: { |
| 575 | +status: "failed", |
| 576 | +exitCode: null, |
| 577 | +durationMs: 100 + index, |
| 578 | +aggregated: "", |
| 579 | +}, |
| 580 | +}, |
| 581 | +index, |
| 582 | +); |
| 583 | +} |
| 584 | + |
| 585 | +const loopResult = detectToolCallLoop(state, "exec", params, enabledLoopDetectionConfig); |
| 586 | +expect(loopResult.stuck).toBe(true); |
| 587 | +if (loopResult.stuck) { |
| 588 | +expect(loopResult.level).toBe("warning"); |
| 589 | +expect(loopResult.detector).toBe("generic_repeat"); |
| 590 | +} |
| 591 | +}); |
| 592 | + |
563 | 593 | it("does not block repeated unknown-tool failures before the unknown-tool threshold", () => { |
564 | 594 | const state = createState(); |
565 | 595 | const toolName = "exec"; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -206,6 +206,14 @@ function stringField(value: unknown): string | null {
|
206 | 206 | return typeof value === "string" ? value : null; |
207 | 207 | } |
208 | 208 | |
| 209 | +function nonEmptyStringField(value: unknown): string | null { |
| 210 | +if (typeof value !== "string") { |
| 211 | +return null; |
| 212 | +} |
| 213 | +const trimmed = value.trim(); |
| 214 | +return trimmed ? trimmed : null; |
| 215 | +} |
| 216 | + |
209 | 217 | function hashExecToolOutcome(details: Record<string, unknown>, text: string): string | undefined { |
210 | 218 | const status = stringField(details.status); |
211 | 219 | if (!status) { |
@@ -224,7 +232,7 @@ function hashExecToolOutcome(details: Record<string, unknown>, text: string): st
|
224 | 232 | status, |
225 | 233 | exitCode: typeof details.exitCode === "number" ? details.exitCode : null, |
226 | 234 | timedOut: details.timedOut === true, |
227 | | -output: stringField(details.aggregated) ?? text, |
| 235 | +output: nonEmptyStringField(details.aggregated) ?? text, |
228 | 236 | }); |
229 | 237 | } |
230 | 238 | |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。