mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 06:31:33 +00:00
test(agents): isolate prompt cooldown scheduling
This commit is contained in:
@@ -90,7 +90,6 @@ const installRunEmbeddedMocks = () => {
|
||||
};
|
||||
|
||||
let runEmbeddedAgent: typeof import("./embedded-agent-runner/run.js").runEmbeddedAgent;
|
||||
let authProfileUsageTesting: typeof import("./auth-profiles/usage.test-support.js").testing;
|
||||
let createDiagnosticLogRecordCaptureFn: typeof import("../logging/test-helpers/diagnostic-log-capture.js").createDiagnosticLogRecordCapture;
|
||||
let cleanupLogCapture: (() => void) | undefined;
|
||||
let resetLoggerFn: typeof import("../logging/logger.js").resetLogger;
|
||||
@@ -101,7 +100,6 @@ beforeAll(async () => {
|
||||
vi.resetModules();
|
||||
installRunEmbeddedMocks();
|
||||
({ runEmbeddedAgent } = await import("./embedded-agent-runner/run.js"));
|
||||
({ testing: authProfileUsageTesting } = await import("./auth-profiles/usage.test-support.js"));
|
||||
({ createDiagnosticLogRecordCapture: createDiagnosticLogRecordCaptureFn } =
|
||||
await import("../logging/test-helpers/diagnostic-log-capture.js"));
|
||||
({ resetLogger: resetLoggerFn, setLoggerOverride: setLoggerOverrideFn } =
|
||||
@@ -141,7 +139,6 @@ beforeEach(() => {
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
authProfileUsageTesting.setDepsForTest(null);
|
||||
cleanupLogCapture?.();
|
||||
cleanupLogCapture = undefined;
|
||||
setLoggerOverrideFn(null);
|
||||
@@ -937,46 +934,6 @@ describe("runEmbeddedAgent auth profile rotation", () => {
|
||||
expect(sleepWithAbortMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not wait for prompt failure cooldown marking before retrying", async () => {
|
||||
let releaseMark: (() => void) | undefined;
|
||||
const markCanFinish = new Promise<void>((resolve) => {
|
||||
releaseMark = resolve;
|
||||
});
|
||||
let markStarted = false;
|
||||
authProfileUsageTesting.setDepsForTest({
|
||||
updateAuthProfileStoreWithLock: async () => {
|
||||
markStarted = true;
|
||||
await markCanFinish;
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await withAgentWorkspace(async ({ agentDir, workspaceDir }) => {
|
||||
await writeAuthStore(agentDir);
|
||||
mockPromptErrorThenSuccessfulAttempt("rate limit exceeded");
|
||||
|
||||
const runPromise = runAutoPinnedOpenAiTurn({
|
||||
agentDir,
|
||||
workspaceDir,
|
||||
sessionKey: "agent:test:prompt-deferred-mark",
|
||||
runId: "run:prompt-deferred-mark",
|
||||
});
|
||||
|
||||
await vi.waitFor(() => expect(runEmbeddedAttemptMock).toHaveBeenCalledTimes(2));
|
||||
expect(markStarted).toBe(true);
|
||||
releaseMark?.();
|
||||
releaseMark = undefined;
|
||||
await runPromise;
|
||||
|
||||
const usageStats = await readUsageStats(agentDir);
|
||||
expect(typeof usageStats["openai:p2"]?.lastUsed).toBe("number");
|
||||
});
|
||||
} finally {
|
||||
releaseMark?.();
|
||||
}
|
||||
});
|
||||
|
||||
it("rotates on timeout without cooling down the timed-out profile", async () => {
|
||||
const { usageStats } = await runAutoPinnedRotationCase({
|
||||
errorMessage: "request ended without sending any chunks",
|
||||
|
||||
107
src/agents/embedded-agent-runner/run/prompt-failure.test.ts
Normal file
107
src/agents/embedded-agent-runner/run/prompt-failure.test.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
// Prompt failure tests cover retry composition around profile rotation.
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { handleEmbeddedPromptFailure } from "./prompt-failure.js";
|
||||
|
||||
type Params = Parameters<typeof handleEmbeddedPromptFailure>[0];
|
||||
|
||||
function makeParams(overrides: Partial<Params> = {}): Params {
|
||||
const provider = "openai";
|
||||
const modelId = "gpt-5";
|
||||
const defaults: Params = {
|
||||
runParams: {
|
||||
config: undefined,
|
||||
runId: "run:prompt-failure-test",
|
||||
} as Params["runParams"],
|
||||
attempt: {
|
||||
replayMetadata: {
|
||||
replaySafe: true,
|
||||
},
|
||||
} as Params["attempt"],
|
||||
promptError: new Error("rate limit exceeded"),
|
||||
promptErrorSource: "prompt",
|
||||
activeErrorContext: { provider, model: modelId },
|
||||
provider,
|
||||
modelId,
|
||||
authProfileId: "openai:p1",
|
||||
authProfileStore: {
|
||||
version: 1,
|
||||
profiles: {},
|
||||
},
|
||||
sessionIdUsed: "session:prompt-failure-test",
|
||||
lane: "test",
|
||||
agentDir: "/tmp/openclaw-prompt-failure-test",
|
||||
suspensionSessionId: "session:prompt-failure-test",
|
||||
runtimeAuthRetry: false,
|
||||
maybeRefreshRuntimeAuthForAuthError: vi.fn(async () => false),
|
||||
suspendForFailure: vi.fn(),
|
||||
resolveReplayInvalid: vi.fn(() => false),
|
||||
setTerminalLifecycleMeta: vi.fn(),
|
||||
buildErrorAgentMeta: vi.fn(),
|
||||
startedAtMs: 0,
|
||||
fallbackConfigured: true,
|
||||
aborted: false,
|
||||
externalAbort: false,
|
||||
pluginHarnessOwnsTransport: false,
|
||||
timedOutByRunBudget: false,
|
||||
resolveAuthProfileFailureReason: vi.fn(() => "rate_limit"),
|
||||
maybeEscalateRateLimitProfileFallback: vi.fn(),
|
||||
advanceAttemptAuthProfile: vi.fn(async () => true),
|
||||
maybeMarkAuthProfileFailure: vi.fn(async () => {}),
|
||||
maybeBackoffBeforeOverloadFailover: vi.fn(async () => {}),
|
||||
attemptedThinking: new Set(),
|
||||
thinkLevel: "low",
|
||||
getThinkLevel: () => "low",
|
||||
traceAttempts: [],
|
||||
previousRetryFailoverReason: null,
|
||||
};
|
||||
return { ...defaults, ...overrides };
|
||||
}
|
||||
|
||||
describe("handleEmbeddedPromptFailure", () => {
|
||||
it("returns the profile-rotation retry before failure marking finishes", async () => {
|
||||
const events: string[] = [];
|
||||
let releaseMark: (() => void) | undefined;
|
||||
const markCanFinish = new Promise<void>((resolve) => {
|
||||
releaseMark = resolve;
|
||||
});
|
||||
const maybeMarkAuthProfileFailure = vi.fn(async () => {
|
||||
events.push("mark-start");
|
||||
await markCanFinish;
|
||||
events.push("mark-finish");
|
||||
});
|
||||
|
||||
try {
|
||||
const outcome = await handleEmbeddedPromptFailure(
|
||||
makeParams({
|
||||
advanceAttemptAuthProfile: vi.fn(async () => {
|
||||
events.push("advance");
|
||||
return true;
|
||||
}),
|
||||
maybeMarkAuthProfileFailure,
|
||||
maybeBackoffBeforeOverloadFailover: vi.fn(async () => {
|
||||
events.push("backoff");
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(outcome).toEqual({
|
||||
action: "retry",
|
||||
thinkLevel: "low",
|
||||
authRetryPending: false,
|
||||
lastRetryFailoverReason: "rate_limit",
|
||||
});
|
||||
expect(events).toEqual(["advance", "mark-start", "backoff"]);
|
||||
expect(maybeMarkAuthProfileFailure).toHaveBeenCalledWith({
|
||||
profileId: "openai:p1",
|
||||
reason: "rate_limit",
|
||||
modelId: "gpt-5",
|
||||
});
|
||||
} finally {
|
||||
releaseMark?.();
|
||||
}
|
||||
|
||||
await vi.waitFor(() =>
|
||||
expect(events).toEqual(["advance", "mark-start", "backoff", "mark-finish"]),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user