mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 18:50:42 +00:00
Agents: stabilize overflow runner test harness
This commit is contained in:
406
src/agents/pi-embedded-runner/run.overflow-compaction.harness.ts
Normal file
406
src/agents/pi-embedded-runner/run.overflow-compaction.harness.ts
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
import { vi, type Mock } from "vitest";
|
||||||
|
import type { ThinkLevel } from "../../auto-reply/thinking.js";
|
||||||
|
import type {
|
||||||
|
PluginHookAgentContext,
|
||||||
|
PluginHookBeforeAgentStartResult,
|
||||||
|
PluginHookBeforeModelResolveResult,
|
||||||
|
PluginHookBeforePromptBuildResult,
|
||||||
|
} from "../../plugins/types.js";
|
||||||
|
import type { EmbeddedRunAttemptResult } from "./run/types.js";
|
||||||
|
|
||||||
|
type MockCompactionResult =
|
||||||
|
| {
|
||||||
|
ok: true;
|
||||||
|
compacted: true;
|
||||||
|
result: {
|
||||||
|
summary: string;
|
||||||
|
firstKeptEntryId?: string;
|
||||||
|
tokensBefore?: number;
|
||||||
|
tokensAfter?: number;
|
||||||
|
};
|
||||||
|
reason?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
ok: false;
|
||||||
|
compacted: false;
|
||||||
|
reason: string;
|
||||||
|
result?: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockedGlobalHookRunner = {
|
||||||
|
hasHooks: vi.fn((_hookName: string) => false),
|
||||||
|
runBeforeAgentStart: vi.fn(
|
||||||
|
async (
|
||||||
|
_event: { prompt: string; messages?: unknown[] },
|
||||||
|
_ctx: PluginHookAgentContext,
|
||||||
|
): Promise<PluginHookBeforeAgentStartResult | undefined> => undefined,
|
||||||
|
),
|
||||||
|
runBeforePromptBuild: vi.fn(
|
||||||
|
async (
|
||||||
|
_event: { prompt: string; messages: unknown[] },
|
||||||
|
_ctx: PluginHookAgentContext,
|
||||||
|
): Promise<PluginHookBeforePromptBuildResult | undefined> => undefined,
|
||||||
|
),
|
||||||
|
runBeforeModelResolve: vi.fn(
|
||||||
|
async (
|
||||||
|
_event: { prompt: string },
|
||||||
|
_ctx: PluginHookAgentContext,
|
||||||
|
): Promise<PluginHookBeforeModelResolveResult | undefined> => undefined,
|
||||||
|
),
|
||||||
|
runBeforeCompaction: vi.fn(async () => undefined),
|
||||||
|
runAfterCompaction: vi.fn(async () => undefined),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockedContextEngine = {
|
||||||
|
info: { ownsCompaction: false as boolean },
|
||||||
|
compact: vi.fn<(params: unknown) => Promise<MockCompactionResult>>(async () => ({
|
||||||
|
ok: false as const,
|
||||||
|
compacted: false as const,
|
||||||
|
reason: "nothing to compact",
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockedContextEngineCompact = mockedContextEngine.compact;
|
||||||
|
export const mockedCompactDirect = mockedContextEngine.compact;
|
||||||
|
export const mockedEnsureRuntimePluginsLoaded = vi.fn<(params?: unknown) => void>();
|
||||||
|
export const mockedPrepareProviderRuntimeAuth = vi.fn(async () => undefined);
|
||||||
|
export const mockedRunEmbeddedAttempt =
|
||||||
|
vi.fn<(params: unknown) => Promise<EmbeddedRunAttemptResult>>();
|
||||||
|
export const mockedSessionLikelyHasOversizedToolResults = vi.fn(() => false);
|
||||||
|
export const mockedTruncateOversizedToolResultsInSession = vi.fn<
|
||||||
|
() => Promise<MockTruncateOversizedToolResultsResult>
|
||||||
|
>(async () => ({
|
||||||
|
truncated: false,
|
||||||
|
truncatedCount: 0,
|
||||||
|
reason: "no oversized tool results",
|
||||||
|
}));
|
||||||
|
|
||||||
|
type MockFailoverErrorDescription = {
|
||||||
|
message: string;
|
||||||
|
reason: string | undefined;
|
||||||
|
status: number | undefined;
|
||||||
|
code: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
type MockCoerceToFailoverError = (
|
||||||
|
err: unknown,
|
||||||
|
params?: { provider?: string; model?: string; profileId?: string },
|
||||||
|
) => unknown;
|
||||||
|
type MockDescribeFailoverError = (err: unknown) => MockFailoverErrorDescription;
|
||||||
|
type MockResolveFailoverStatus = (reason: string) => number | undefined;
|
||||||
|
type MockTruncateOversizedToolResultsResult = {
|
||||||
|
truncated: boolean;
|
||||||
|
truncatedCount: number;
|
||||||
|
reason?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockedCoerceToFailoverError = vi.fn<MockCoerceToFailoverError>();
|
||||||
|
export const mockedDescribeFailoverError = vi.fn<MockDescribeFailoverError>(
|
||||||
|
(err: unknown): MockFailoverErrorDescription => ({
|
||||||
|
message: err instanceof Error ? err.message : String(err),
|
||||||
|
reason: undefined,
|
||||||
|
status: undefined,
|
||||||
|
code: undefined,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
export const mockedResolveFailoverStatus = vi.fn<MockResolveFailoverStatus>();
|
||||||
|
|
||||||
|
export const mockedLog: {
|
||||||
|
debug: Mock<(...args: unknown[]) => void>;
|
||||||
|
info: Mock<(...args: unknown[]) => void>;
|
||||||
|
warn: Mock<(...args: unknown[]) => void>;
|
||||||
|
error: Mock<(...args: unknown[]) => void>;
|
||||||
|
isEnabled: Mock<(level?: string) => boolean>;
|
||||||
|
} = {
|
||||||
|
debug: vi.fn(),
|
||||||
|
info: vi.fn(),
|
||||||
|
warn: vi.fn(),
|
||||||
|
error: vi.fn(),
|
||||||
|
isEnabled: vi.fn(() => false),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mockedClassifyFailoverReason = vi.fn(() => null);
|
||||||
|
export const mockedExtractObservedOverflowTokenCount = vi.fn((msg?: string) => {
|
||||||
|
const match = msg?.match(/prompt is too long:\s*([\d,]+)\s+tokens\s*>\s*[\d,]+\s+maximum/i);
|
||||||
|
return match?.[1] ? Number(match[1].replaceAll(",", "")) : undefined;
|
||||||
|
});
|
||||||
|
export const mockedIsCompactionFailureError = vi.fn(() => false);
|
||||||
|
export const mockedIsLikelyContextOverflowError = vi.fn((msg?: string) => {
|
||||||
|
const lower = (msg ?? "").toLowerCase();
|
||||||
|
return (
|
||||||
|
lower.includes("request_too_large") ||
|
||||||
|
lower.includes("context window exceeded") ||
|
||||||
|
lower.includes("prompt is too long")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
export const mockedPickFallbackThinkingLevel = vi.fn<(params?: unknown) => ThinkLevel | null>(
|
||||||
|
() => null,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const overflowBaseRunParams = {
|
||||||
|
sessionId: "test-session",
|
||||||
|
sessionKey: "test-key",
|
||||||
|
sessionFile: "/tmp/session.json",
|
||||||
|
workspaceDir: "/tmp/workspace",
|
||||||
|
prompt: "hello",
|
||||||
|
timeoutMs: 30000,
|
||||||
|
runId: "run-1",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export function resetRunOverflowCompactionHarnessMocks(): void {
|
||||||
|
mockedGlobalHookRunner.hasHooks.mockReset();
|
||||||
|
mockedGlobalHookRunner.hasHooks.mockReturnValue(false);
|
||||||
|
mockedGlobalHookRunner.runBeforeAgentStart.mockReset();
|
||||||
|
mockedGlobalHookRunner.runBeforeAgentStart.mockResolvedValue(undefined);
|
||||||
|
mockedGlobalHookRunner.runBeforePromptBuild.mockReset();
|
||||||
|
mockedGlobalHookRunner.runBeforePromptBuild.mockResolvedValue(undefined);
|
||||||
|
mockedGlobalHookRunner.runBeforeModelResolve.mockReset();
|
||||||
|
mockedGlobalHookRunner.runBeforeModelResolve.mockResolvedValue(undefined);
|
||||||
|
mockedGlobalHookRunner.runBeforeCompaction.mockReset();
|
||||||
|
mockedGlobalHookRunner.runBeforeCompaction.mockResolvedValue(undefined);
|
||||||
|
mockedGlobalHookRunner.runAfterCompaction.mockReset();
|
||||||
|
mockedGlobalHookRunner.runAfterCompaction.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
mockedContextEngine.info.ownsCompaction = false;
|
||||||
|
mockedContextEngineCompact.mockReset();
|
||||||
|
mockedContextEngineCompact.mockResolvedValue({
|
||||||
|
ok: false,
|
||||||
|
compacted: false,
|
||||||
|
reason: "nothing to compact",
|
||||||
|
});
|
||||||
|
|
||||||
|
mockedEnsureRuntimePluginsLoaded.mockReset();
|
||||||
|
mockedPrepareProviderRuntimeAuth.mockReset();
|
||||||
|
mockedPrepareProviderRuntimeAuth.mockResolvedValue(undefined);
|
||||||
|
mockedRunEmbeddedAttempt.mockReset();
|
||||||
|
mockedSessionLikelyHasOversizedToolResults.mockReset();
|
||||||
|
mockedSessionLikelyHasOversizedToolResults.mockReturnValue(false);
|
||||||
|
mockedTruncateOversizedToolResultsInSession.mockReset();
|
||||||
|
mockedTruncateOversizedToolResultsInSession.mockResolvedValue({
|
||||||
|
truncated: false,
|
||||||
|
truncatedCount: 0,
|
||||||
|
reason: "no oversized tool results",
|
||||||
|
});
|
||||||
|
|
||||||
|
mockedCoerceToFailoverError.mockReset();
|
||||||
|
mockedCoerceToFailoverError.mockReturnValue(null);
|
||||||
|
mockedDescribeFailoverError.mockReset();
|
||||||
|
mockedDescribeFailoverError.mockImplementation(
|
||||||
|
(err: unknown): MockFailoverErrorDescription => ({
|
||||||
|
message: err instanceof Error ? err.message : String(err),
|
||||||
|
reason: undefined,
|
||||||
|
status: undefined,
|
||||||
|
code: undefined,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
mockedResolveFailoverStatus.mockReset();
|
||||||
|
mockedResolveFailoverStatus.mockReturnValue(undefined);
|
||||||
|
|
||||||
|
mockedLog.debug.mockReset();
|
||||||
|
mockedLog.info.mockReset();
|
||||||
|
mockedLog.warn.mockReset();
|
||||||
|
mockedLog.error.mockReset();
|
||||||
|
mockedLog.isEnabled.mockReset();
|
||||||
|
mockedLog.isEnabled.mockReturnValue(false);
|
||||||
|
|
||||||
|
mockedClassifyFailoverReason.mockReset();
|
||||||
|
mockedClassifyFailoverReason.mockReturnValue(null);
|
||||||
|
mockedExtractObservedOverflowTokenCount.mockReset();
|
||||||
|
mockedExtractObservedOverflowTokenCount.mockImplementation((msg?: string) => {
|
||||||
|
const match = msg?.match(/prompt is too long:\s*([\d,]+)\s+tokens\s*>\s*[\d,]+\s+maximum/i);
|
||||||
|
return match?.[1] ? Number(match[1].replaceAll(",", "")) : undefined;
|
||||||
|
});
|
||||||
|
mockedIsCompactionFailureError.mockReset();
|
||||||
|
mockedIsCompactionFailureError.mockReturnValue(false);
|
||||||
|
mockedIsLikelyContextOverflowError.mockReset();
|
||||||
|
mockedIsLikelyContextOverflowError.mockImplementation((msg?: string) => {
|
||||||
|
const lower = (msg ?? "").toLowerCase();
|
||||||
|
return (
|
||||||
|
lower.includes("request_too_large") ||
|
||||||
|
lower.includes("context window exceeded") ||
|
||||||
|
lower.includes("prompt is too long")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
mockedPickFallbackThinkingLevel.mockReset();
|
||||||
|
mockedPickFallbackThinkingLevel.mockReturnValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadRunOverflowCompactionHarness(): Promise<{
|
||||||
|
runEmbeddedPiAgent: typeof import("./run.js").runEmbeddedPiAgent;
|
||||||
|
}> {
|
||||||
|
resetRunOverflowCompactionHarnessMocks();
|
||||||
|
vi.resetModules();
|
||||||
|
|
||||||
|
vi.doMock("../../plugins/hook-runner-global.js", () => ({
|
||||||
|
getGlobalHookRunner: vi.fn(() => mockedGlobalHookRunner),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../../context-engine/index.js", () => ({
|
||||||
|
ensureContextEnginesInitialized: vi.fn(),
|
||||||
|
resolveContextEngine: vi.fn(async () => mockedContextEngine),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../runtime-plugins.js", () => ({
|
||||||
|
ensureRuntimePluginsLoaded: mockedEnsureRuntimePluginsLoaded,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../../plugins/provider-runtime.js", () => ({
|
||||||
|
prepareProviderRuntimeAuth: mockedPrepareProviderRuntimeAuth,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../auth-profiles.js", () => ({
|
||||||
|
isProfileInCooldown: vi.fn(() => false),
|
||||||
|
markAuthProfileFailure: vi.fn(async () => {}),
|
||||||
|
markAuthProfileGood: vi.fn(async () => {}),
|
||||||
|
markAuthProfileUsed: vi.fn(async () => {}),
|
||||||
|
resolveProfilesUnavailableReason: vi.fn(() => undefined),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../usage.js", () => ({
|
||||||
|
normalizeUsage: vi.fn((usage?: unknown) =>
|
||||||
|
usage && typeof usage === "object" ? usage : undefined,
|
||||||
|
),
|
||||||
|
derivePromptTokens: vi.fn(
|
||||||
|
(usage?: { input?: number; cacheRead?: number; cacheWrite?: number }) =>
|
||||||
|
usage
|
||||||
|
? (() => {
|
||||||
|
const sum = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
||||||
|
return sum > 0 ? sum : undefined;
|
||||||
|
})()
|
||||||
|
: undefined,
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../workspace-run.js", () => ({
|
||||||
|
resolveRunWorkspaceDir: vi.fn((params: { workspaceDir: string }) => ({
|
||||||
|
workspaceDir: params.workspaceDir,
|
||||||
|
usedFallback: false,
|
||||||
|
fallbackReason: undefined,
|
||||||
|
agentId: "main",
|
||||||
|
})),
|
||||||
|
redactRunIdentifier: vi.fn((value?: string) => value ?? ""),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../pi-embedded-helpers.js", () => ({
|
||||||
|
formatBillingErrorMessage: vi.fn(() => ""),
|
||||||
|
classifyFailoverReason: mockedClassifyFailoverReason,
|
||||||
|
extractObservedOverflowTokenCount: mockedExtractObservedOverflowTokenCount,
|
||||||
|
formatAssistantErrorText: vi.fn(() => ""),
|
||||||
|
isAuthAssistantError: vi.fn(() => false),
|
||||||
|
isBillingAssistantError: vi.fn(() => false),
|
||||||
|
isCompactionFailureError: mockedIsCompactionFailureError,
|
||||||
|
isLikelyContextOverflowError: mockedIsLikelyContextOverflowError,
|
||||||
|
isFailoverAssistantError: vi.fn(() => false),
|
||||||
|
isFailoverErrorMessage: vi.fn(() => false),
|
||||||
|
parseImageSizeError: vi.fn(() => null),
|
||||||
|
parseImageDimensionError: vi.fn(() => null),
|
||||||
|
isRateLimitAssistantError: vi.fn(() => false),
|
||||||
|
isTimeoutErrorMessage: vi.fn(() => false),
|
||||||
|
pickFallbackThinkingLevel: mockedPickFallbackThinkingLevel,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("./run/attempt.js", () => ({
|
||||||
|
runEmbeddedAttempt: mockedRunEmbeddedAttempt,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("./model.js", () => ({
|
||||||
|
resolveModelAsync: vi.fn(async () => ({
|
||||||
|
model: {
|
||||||
|
id: "test-model",
|
||||||
|
provider: "anthropic",
|
||||||
|
contextWindow: 200000,
|
||||||
|
api: "messages",
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
authStorage: {
|
||||||
|
setRuntimeApiKey: vi.fn(),
|
||||||
|
},
|
||||||
|
modelRegistry: {},
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../model-auth.js", () => ({
|
||||||
|
applyLocalNoAuthHeaderOverride: vi.fn((model: unknown) => model),
|
||||||
|
ensureAuthProfileStore: vi.fn(() => ({})),
|
||||||
|
getApiKeyForModel: vi.fn(async () => ({
|
||||||
|
apiKey: "test-key",
|
||||||
|
profileId: "test-profile",
|
||||||
|
source: "test",
|
||||||
|
})),
|
||||||
|
resolveAuthProfileOrder: vi.fn(() => []),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../models-config.js", () => ({
|
||||||
|
ensureOpenClawModelsJson: vi.fn(async () => {}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../context-window-guard.js", () => ({
|
||||||
|
CONTEXT_WINDOW_HARD_MIN_TOKENS: 1000,
|
||||||
|
CONTEXT_WINDOW_WARN_BELOW_TOKENS: 5000,
|
||||||
|
evaluateContextWindowGuard: vi.fn(() => ({
|
||||||
|
shouldWarn: false,
|
||||||
|
shouldBlock: false,
|
||||||
|
tokens: 200000,
|
||||||
|
source: "model",
|
||||||
|
})),
|
||||||
|
resolveContextWindowInfo: vi.fn(() => ({
|
||||||
|
tokens: 200000,
|
||||||
|
source: "model",
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../../process/command-queue.js", () => ({
|
||||||
|
enqueueCommandInLane: vi.fn((_lane: string, task: () => unknown) => task()),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../../utils/message-channel.js", () => ({
|
||||||
|
isMarkdownCapableMessageChannel: vi.fn(() => true),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../agent-paths.js", () => ({
|
||||||
|
resolveOpenClawAgentDir: vi.fn(() => "/tmp/agent-dir"),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../defaults.js", () => ({
|
||||||
|
DEFAULT_CONTEXT_TOKENS: 200000,
|
||||||
|
DEFAULT_MODEL: "test-model",
|
||||||
|
DEFAULT_PROVIDER: "anthropic",
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("../failover-error.js", () => ({
|
||||||
|
FailoverError: class extends Error {},
|
||||||
|
coerceToFailoverError: mockedCoerceToFailoverError,
|
||||||
|
describeFailoverError: mockedDescribeFailoverError,
|
||||||
|
resolveFailoverStatus: mockedResolveFailoverStatus,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("./lanes.js", () => ({
|
||||||
|
resolveSessionLane: vi.fn(() => "session-lane"),
|
||||||
|
resolveGlobalLane: vi.fn(() => "global-lane"),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("./logger.js", () => ({
|
||||||
|
log: mockedLog,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("./run/payloads.js", () => ({
|
||||||
|
buildEmbeddedRunPayloads: vi.fn(() => []),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("./tool-result-truncation.js", () => ({
|
||||||
|
truncateOversizedToolResultsInSession: mockedTruncateOversizedToolResultsInSession,
|
||||||
|
sessionLikelyHasOversizedToolResults: mockedSessionLikelyHasOversizedToolResults,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.doMock("./utils.js", () => ({
|
||||||
|
describeUnknownError: vi.fn((err: unknown) => {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
return err.message;
|
||||||
|
}
|
||||||
|
return String(err);
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { runEmbeddedPiAgent } = await import("./run.js");
|
||||||
|
return { runEmbeddedPiAgent };
|
||||||
|
}
|
||||||
@@ -1,17 +1,4 @@
|
|||||||
import "./run.overflow-compaction.mocks.shared.js";
|
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
||||||
import { isCompactionFailureError, isLikelyContextOverflowError } from "../pi-embedded-helpers.js";
|
|
||||||
|
|
||||||
vi.mock(import("../../utils.js"), async (importOriginal) => {
|
|
||||||
const actual = await importOriginal();
|
|
||||||
return {
|
|
||||||
...actual,
|
|
||||||
resolveUserPath: vi.fn((p: string) => p),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
import { log } from "./logger.js";
|
|
||||||
import { runEmbeddedPiAgent } from "./run.js";
|
|
||||||
import {
|
import {
|
||||||
makeAttemptResult,
|
makeAttemptResult,
|
||||||
makeCompactionSuccess,
|
makeCompactionSuccess,
|
||||||
@@ -20,26 +7,38 @@ import {
|
|||||||
queueOverflowAttemptWithOversizedToolOutput,
|
queueOverflowAttemptWithOversizedToolOutput,
|
||||||
} from "./run.overflow-compaction.fixture.js";
|
} from "./run.overflow-compaction.fixture.js";
|
||||||
import {
|
import {
|
||||||
|
loadRunOverflowCompactionHarness,
|
||||||
mockedContextEngine,
|
mockedContextEngine,
|
||||||
mockedCompactDirect,
|
mockedCompactDirect,
|
||||||
|
mockedIsCompactionFailureError,
|
||||||
|
mockedIsLikelyContextOverflowError,
|
||||||
|
mockedLog,
|
||||||
mockedRunEmbeddedAttempt,
|
mockedRunEmbeddedAttempt,
|
||||||
mockedSessionLikelyHasOversizedToolResults,
|
mockedSessionLikelyHasOversizedToolResults,
|
||||||
mockedTruncateOversizedToolResultsInSession,
|
mockedTruncateOversizedToolResultsInSession,
|
||||||
overflowBaseRunParams as baseParams,
|
overflowBaseRunParams as baseParams,
|
||||||
} from "./run.overflow-compaction.shared-test.js";
|
} from "./run.overflow-compaction.harness.js";
|
||||||
import type { EmbeddedRunAttemptResult } from "./run/types.js";
|
import type { EmbeddedRunAttemptResult } from "./run/types.js";
|
||||||
|
|
||||||
const mockedIsCompactionFailureError = vi.mocked(isCompactionFailureError);
|
let runEmbeddedPiAgent: typeof import("./run.js").runEmbeddedPiAgent;
|
||||||
const mockedIsLikelyContextOverflowError = vi.mocked(isLikelyContextOverflowError);
|
|
||||||
|
|
||||||
describe("overflow compaction in run loop", () => {
|
describe("overflow compaction in run loop", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
({ runEmbeddedPiAgent } = await loadRunOverflowCompactionHarness());
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
|
||||||
mockedRunEmbeddedAttempt.mockReset();
|
mockedRunEmbeddedAttempt.mockReset();
|
||||||
mockedCompactDirect.mockReset();
|
mockedCompactDirect.mockReset();
|
||||||
mockedSessionLikelyHasOversizedToolResults.mockReset();
|
mockedSessionLikelyHasOversizedToolResults.mockReset();
|
||||||
mockedTruncateOversizedToolResultsInSession.mockReset();
|
mockedTruncateOversizedToolResultsInSession.mockReset();
|
||||||
mockedContextEngine.info.ownsCompaction = false;
|
mockedContextEngine.info.ownsCompaction = false;
|
||||||
|
mockedLog.debug.mockReset();
|
||||||
|
mockedLog.info.mockReset();
|
||||||
|
mockedLog.warn.mockReset();
|
||||||
|
mockedLog.error.mockReset();
|
||||||
|
mockedLog.isEnabled.mockReset();
|
||||||
|
mockedLog.isEnabled.mockReturnValue(false);
|
||||||
mockedIsCompactionFailureError.mockImplementation((msg?: string) => {
|
mockedIsCompactionFailureError.mockImplementation((msg?: string) => {
|
||||||
if (!msg) {
|
if (!msg) {
|
||||||
return false;
|
return false;
|
||||||
@@ -87,12 +86,14 @@ describe("overflow compaction in run loop", () => {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(2);
|
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(2);
|
||||||
expect(log.warn).toHaveBeenCalledWith(
|
expect(mockedLog.warn).toHaveBeenCalledWith(
|
||||||
expect.stringContaining(
|
expect.stringContaining(
|
||||||
"context overflow detected (attempt 1/3); attempting auto-compaction",
|
"context overflow detected (attempt 1/3); attempting auto-compaction",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
expect(log.info).toHaveBeenCalledWith(expect.stringContaining("auto-compaction succeeded"));
|
expect(mockedLog.info).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("auto-compaction succeeded"),
|
||||||
|
);
|
||||||
// Should not be an error result
|
// Should not be an error result
|
||||||
expect(result.meta.error).toBeUndefined();
|
expect(result.meta.error).toBeUndefined();
|
||||||
});
|
});
|
||||||
@@ -116,7 +117,7 @@ describe("overflow compaction in run loop", () => {
|
|||||||
|
|
||||||
expect(mockedCompactDirect).toHaveBeenCalledTimes(1);
|
expect(mockedCompactDirect).toHaveBeenCalledTimes(1);
|
||||||
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(2);
|
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(2);
|
||||||
expect(log.warn).toHaveBeenCalledWith(expect.stringContaining("source=promptError"));
|
expect(mockedLog.warn).toHaveBeenCalledWith(expect.stringContaining("source=promptError"));
|
||||||
expect(result.meta.error).toBeUndefined();
|
expect(result.meta.error).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -137,7 +138,7 @@ describe("overflow compaction in run loop", () => {
|
|||||||
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(1);
|
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(1);
|
||||||
expect(result.meta.error?.kind).toBe("context_overflow");
|
expect(result.meta.error?.kind).toBe("context_overflow");
|
||||||
expect(result.payloads?.[0]?.isError).toBe(true);
|
expect(result.payloads?.[0]?.isError).toBe(true);
|
||||||
expect(log.warn).toHaveBeenCalledWith(expect.stringContaining("auto-compaction failed"));
|
expect(mockedLog.warn).toHaveBeenCalledWith(expect.stringContaining("auto-compaction failed"));
|
||||||
});
|
});
|
||||||
|
|
||||||
it("falls back to tool-result truncation and retries when oversized results are detected", async () => {
|
it("falls back to tool-result truncation and retries when oversized results are detected", async () => {
|
||||||
@@ -165,7 +166,9 @@ describe("overflow compaction in run loop", () => {
|
|||||||
expect.objectContaining({ sessionFile: "/tmp/session.json" }),
|
expect.objectContaining({ sessionFile: "/tmp/session.json" }),
|
||||||
);
|
);
|
||||||
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(2);
|
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(2);
|
||||||
expect(log.info).toHaveBeenCalledWith(expect.stringContaining("Truncated 1 tool result(s)"));
|
expect(mockedLog.info).toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("Truncated 1 tool result(s)"),
|
||||||
|
);
|
||||||
expect(result.meta.error).toBeUndefined();
|
expect(result.meta.error).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -284,7 +287,7 @@ describe("overflow compaction in run loop", () => {
|
|||||||
|
|
||||||
expect(mockedCompactDirect).toHaveBeenCalledTimes(1);
|
expect(mockedCompactDirect).toHaveBeenCalledTimes(1);
|
||||||
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(2);
|
expect(mockedRunEmbeddedAttempt).toHaveBeenCalledTimes(2);
|
||||||
expect(log.warn).toHaveBeenCalledWith(expect.stringContaining("source=assistantError"));
|
expect(mockedLog.warn).toHaveBeenCalledWith(expect.stringContaining("source=assistantError"));
|
||||||
expect(result.meta.error).toBeUndefined();
|
expect(result.meta.error).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -302,7 +305,9 @@ describe("overflow compaction in run loop", () => {
|
|||||||
await expect(runEmbeddedPiAgent(baseParams)).rejects.toThrow("transport disconnected");
|
await expect(runEmbeddedPiAgent(baseParams)).rejects.toThrow("transport disconnected");
|
||||||
|
|
||||||
expect(mockedCompactDirect).not.toHaveBeenCalled();
|
expect(mockedCompactDirect).not.toHaveBeenCalled();
|
||||||
expect(log.warn).not.toHaveBeenCalledWith(expect.stringContaining("source=assistantError"));
|
expect(mockedLog.warn).not.toHaveBeenCalledWith(
|
||||||
|
expect.stringContaining("source=assistantError"),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns an explicit timeout payload when the run times out before producing any reply", async () => {
|
it("returns an explicit timeout payload when the run times out before producing any reply", async () => {
|
||||||
|
|||||||
@@ -1,292 +0,0 @@
|
|||||||
import { vi } from "vitest";
|
|
||||||
import type {
|
|
||||||
PluginHookAgentContext,
|
|
||||||
PluginHookBeforeAgentStartResult,
|
|
||||||
PluginHookBeforeModelResolveResult,
|
|
||||||
PluginHookBeforePromptBuildResult,
|
|
||||||
} from "../../plugins/types.js";
|
|
||||||
|
|
||||||
type MockCompactionResult =
|
|
||||||
| {
|
|
||||||
ok: true;
|
|
||||||
compacted: true;
|
|
||||||
result: {
|
|
||||||
summary: string;
|
|
||||||
firstKeptEntryId?: string;
|
|
||||||
tokensBefore?: number;
|
|
||||||
tokensAfter?: number;
|
|
||||||
};
|
|
||||||
reason?: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
ok: false;
|
|
||||||
compacted: false;
|
|
||||||
reason: string;
|
|
||||||
result?: undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const mockedGlobalHookRunner = {
|
|
||||||
hasHooks: vi.fn((_hookName: string) => false),
|
|
||||||
runBeforeAgentStart: vi.fn(
|
|
||||||
async (
|
|
||||||
_event: { prompt: string; messages?: unknown[] },
|
|
||||||
_ctx: PluginHookAgentContext,
|
|
||||||
): Promise<PluginHookBeforeAgentStartResult | undefined> => undefined,
|
|
||||||
),
|
|
||||||
runBeforePromptBuild: vi.fn(
|
|
||||||
async (
|
|
||||||
_event: { prompt: string; messages: unknown[] },
|
|
||||||
_ctx: PluginHookAgentContext,
|
|
||||||
): Promise<PluginHookBeforePromptBuildResult | undefined> => undefined,
|
|
||||||
),
|
|
||||||
runBeforeModelResolve: vi.fn(
|
|
||||||
async (
|
|
||||||
_event: { prompt: string },
|
|
||||||
_ctx: PluginHookAgentContext,
|
|
||||||
): Promise<PluginHookBeforeModelResolveResult | undefined> => undefined,
|
|
||||||
),
|
|
||||||
runBeforeCompaction: vi.fn(async () => undefined),
|
|
||||||
runAfterCompaction: vi.fn(async () => undefined),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const mockedContextEngine = {
|
|
||||||
info: { ownsCompaction: false as boolean },
|
|
||||||
compact: vi.fn<(params: unknown) => Promise<MockCompactionResult>>(async () => ({
|
|
||||||
ok: false as const,
|
|
||||||
compacted: false as const,
|
|
||||||
reason: "nothing to compact",
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const mockedContextEngineCompact = vi.mocked(mockedContextEngine.compact);
|
|
||||||
export const mockedEnsureRuntimePluginsLoaded: (...args: unknown[]) => void = vi.fn();
|
|
||||||
|
|
||||||
vi.mock("../../plugins/hook-runner-global.js", () => ({
|
|
||||||
getGlobalHookRunner: vi.fn(() => mockedGlobalHookRunner),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../../context-engine/index.js", () => ({
|
|
||||||
ensureContextEnginesInitialized: vi.fn(),
|
|
||||||
resolveContextEngine: vi.fn(async () => mockedContextEngine),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../runtime-plugins.js", () => ({
|
|
||||||
ensureRuntimePluginsLoaded: mockedEnsureRuntimePluginsLoaded,
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../auth-profiles.js", () => ({
|
|
||||||
isProfileInCooldown: vi.fn(() => false),
|
|
||||||
markAuthProfileFailure: vi.fn(async () => {}),
|
|
||||||
markAuthProfileGood: vi.fn(async () => {}),
|
|
||||||
markAuthProfileUsed: vi.fn(async () => {}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../usage.js", () => ({
|
|
||||||
normalizeUsage: vi.fn((usage?: unknown) =>
|
|
||||||
usage && typeof usage === "object" ? usage : undefined,
|
|
||||||
),
|
|
||||||
derivePromptTokens: vi.fn((usage?: { input?: number; cacheRead?: number; cacheWrite?: number }) =>
|
|
||||||
usage
|
|
||||||
? (() => {
|
|
||||||
const sum = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
|
|
||||||
return sum > 0 ? sum : undefined;
|
|
||||||
})()
|
|
||||||
: undefined,
|
|
||||||
),
|
|
||||||
hasNonzeroUsage: vi.fn(() => false),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../workspace-run.js", () => ({
|
|
||||||
resolveRunWorkspaceDir: vi.fn((params: { workspaceDir: string }) => ({
|
|
||||||
workspaceDir: params.workspaceDir,
|
|
||||||
usedFallback: false,
|
|
||||||
fallbackReason: undefined,
|
|
||||||
agentId: "main",
|
|
||||||
})),
|
|
||||||
redactRunIdentifier: vi.fn((value?: string) => value ?? ""),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../pi-embedded-helpers.js", () => ({
|
|
||||||
formatBillingErrorMessage: vi.fn(() => ""),
|
|
||||||
classifyFailoverReason: vi.fn(() => null),
|
|
||||||
extractObservedOverflowTokenCount: vi.fn((msg?: string) => {
|
|
||||||
const match = msg?.match(/prompt is too long:\s*([\d,]+)\s+tokens\s*>\s*[\d,]+\s+maximum/i);
|
|
||||||
return match?.[1] ? Number(match[1].replaceAll(",", "")) : undefined;
|
|
||||||
}),
|
|
||||||
formatAssistantErrorText: vi.fn(() => ""),
|
|
||||||
isAuthAssistantError: vi.fn(() => false),
|
|
||||||
isBillingAssistantError: vi.fn(() => false),
|
|
||||||
isCompactionFailureError: vi.fn(() => false),
|
|
||||||
isLikelyContextOverflowError: vi.fn((msg?: string) => {
|
|
||||||
const lower = (msg ?? "").toLowerCase();
|
|
||||||
return (
|
|
||||||
lower.includes("request_too_large") ||
|
|
||||||
lower.includes("context window exceeded") ||
|
|
||||||
lower.includes("prompt is too long")
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
isFailoverAssistantError: vi.fn(() => false),
|
|
||||||
isFailoverErrorMessage: vi.fn(() => false),
|
|
||||||
parseImageSizeError: vi.fn(() => null),
|
|
||||||
parseImageDimensionError: vi.fn(() => null),
|
|
||||||
isRateLimitAssistantError: vi.fn(() => false),
|
|
||||||
isTimeoutErrorMessage: vi.fn(() => false),
|
|
||||||
pickFallbackThinkingLevel: vi.fn(() => null),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("./run/attempt.js", () => ({
|
|
||||||
runEmbeddedAttempt: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("./compact.js", () => ({
|
|
||||||
compactEmbeddedPiSessionDirect: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("./model.js", () => ({
|
|
||||||
resolveModel: vi.fn(() => ({
|
|
||||||
model: {
|
|
||||||
id: "test-model",
|
|
||||||
provider: "anthropic",
|
|
||||||
contextWindow: 200000,
|
|
||||||
api: "messages",
|
|
||||||
},
|
|
||||||
error: null,
|
|
||||||
authStorage: {
|
|
||||||
setRuntimeApiKey: vi.fn(),
|
|
||||||
},
|
|
||||||
modelRegistry: {},
|
|
||||||
})),
|
|
||||||
resolveModelAsync: vi.fn(async () => ({
|
|
||||||
model: {
|
|
||||||
id: "test-model",
|
|
||||||
provider: "anthropic",
|
|
||||||
contextWindow: 200000,
|
|
||||||
api: "messages",
|
|
||||||
},
|
|
||||||
error: null,
|
|
||||||
authStorage: {
|
|
||||||
setRuntimeApiKey: vi.fn(),
|
|
||||||
},
|
|
||||||
modelRegistry: {},
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../model-auth.js", () => ({
|
|
||||||
ensureAuthProfileStore: vi.fn(() => ({})),
|
|
||||||
getApiKeyForModel: vi.fn(async () => ({
|
|
||||||
apiKey: "test-key",
|
|
||||||
profileId: "test-profile",
|
|
||||||
source: "test",
|
|
||||||
})),
|
|
||||||
resolveAuthProfileOrder: vi.fn(() => []),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../models-config.js", () => ({
|
|
||||||
ensureOpenClawModelsJson: vi.fn(async () => {}),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../context-window-guard.js", () => ({
|
|
||||||
CONTEXT_WINDOW_HARD_MIN_TOKENS: 1000,
|
|
||||||
CONTEXT_WINDOW_WARN_BELOW_TOKENS: 5000,
|
|
||||||
evaluateContextWindowGuard: vi.fn(() => ({
|
|
||||||
shouldWarn: false,
|
|
||||||
shouldBlock: false,
|
|
||||||
tokens: 200000,
|
|
||||||
source: "model",
|
|
||||||
})),
|
|
||||||
resolveContextWindowInfo: vi.fn(() => ({
|
|
||||||
tokens: 200000,
|
|
||||||
source: "model",
|
|
||||||
})),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../../process/command-queue.js", () => ({
|
|
||||||
enqueueCommandInLane: vi.fn((_lane: string, task: () => unknown) => task()),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock(import("../../utils/message-channel.js"), async (importOriginal) => {
|
|
||||||
const actual = await importOriginal();
|
|
||||||
return {
|
|
||||||
...actual,
|
|
||||||
isMarkdownCapableMessageChannel: vi.fn(() => true),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
vi.mock("../agent-paths.js", () => ({
|
|
||||||
resolveOpenClawAgentDir: vi.fn(() => "/tmp/agent-dir"),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../defaults.js", () => ({
|
|
||||||
DEFAULT_CONTEXT_TOKENS: 200000,
|
|
||||||
DEFAULT_MODEL: "test-model",
|
|
||||||
DEFAULT_PROVIDER: "anthropic",
|
|
||||||
}));
|
|
||||||
|
|
||||||
type MockFailoverErrorDescription = {
|
|
||||||
message: string;
|
|
||||||
reason: string | undefined;
|
|
||||||
status: number | undefined;
|
|
||||||
code: string | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
type MockCoerceToFailoverError = (
|
|
||||||
err: unknown,
|
|
||||||
params?: { provider?: string; model?: string; profileId?: string },
|
|
||||||
) => unknown;
|
|
||||||
type MockDescribeFailoverError = (err: unknown) => MockFailoverErrorDescription;
|
|
||||||
type MockResolveFailoverStatus = (reason: string) => number | undefined;
|
|
||||||
|
|
||||||
export const mockedCoerceToFailoverError = vi.fn<MockCoerceToFailoverError>();
|
|
||||||
export const mockedDescribeFailoverError = vi.fn<MockDescribeFailoverError>(
|
|
||||||
(err: unknown): MockFailoverErrorDescription => ({
|
|
||||||
message: err instanceof Error ? err.message : String(err),
|
|
||||||
reason: undefined,
|
|
||||||
status: undefined,
|
|
||||||
code: undefined,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
export const mockedResolveFailoverStatus = vi.fn<MockResolveFailoverStatus>();
|
|
||||||
|
|
||||||
vi.mock("../failover-error.js", () => ({
|
|
||||||
FailoverError: class extends Error {},
|
|
||||||
coerceToFailoverError: mockedCoerceToFailoverError,
|
|
||||||
describeFailoverError: mockedDescribeFailoverError,
|
|
||||||
resolveFailoverStatus: mockedResolveFailoverStatus,
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("./lanes.js", () => ({
|
|
||||||
resolveSessionLane: vi.fn(() => "session-lane"),
|
|
||||||
resolveGlobalLane: vi.fn(() => "global-lane"),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("./logger.js", () => ({
|
|
||||||
log: {
|
|
||||||
debug: vi.fn(),
|
|
||||||
info: vi.fn(),
|
|
||||||
warn: vi.fn(),
|
|
||||||
error: vi.fn(),
|
|
||||||
isEnabled: vi.fn(() => false),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("./run/payloads.js", () => ({
|
|
||||||
buildEmbeddedRunPayloads: vi.fn(() => []),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("./tool-result-truncation.js", () => ({
|
|
||||||
truncateOversizedToolResultsInSession: vi.fn(async () => ({
|
|
||||||
truncated: false,
|
|
||||||
truncatedCount: 0,
|
|
||||||
reason: "no oversized tool results",
|
|
||||||
})),
|
|
||||||
sessionLikelyHasOversizedToolResults: vi.fn(() => false),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("./utils.js", () => ({
|
|
||||||
describeUnknownError: vi.fn((err: unknown) => {
|
|
||||||
if (err instanceof Error) {
|
|
||||||
return err.message;
|
|
||||||
}
|
|
||||||
return String(err);
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { vi } from "vitest";
|
|
||||||
import {
|
|
||||||
mockedContextEngine,
|
|
||||||
mockedContextEngineCompact,
|
|
||||||
} from "./run.overflow-compaction.mocks.shared.js";
|
|
||||||
import { runEmbeddedAttempt } from "./run/attempt.js";
|
|
||||||
import {
|
|
||||||
sessionLikelyHasOversizedToolResults,
|
|
||||||
truncateOversizedToolResultsInSession,
|
|
||||||
} from "./tool-result-truncation.js";
|
|
||||||
|
|
||||||
export const mockedRunEmbeddedAttempt = vi.mocked(runEmbeddedAttempt);
|
|
||||||
export const mockedCompactDirect = mockedContextEngineCompact;
|
|
||||||
export const mockedSessionLikelyHasOversizedToolResults = vi.mocked(
|
|
||||||
sessionLikelyHasOversizedToolResults,
|
|
||||||
);
|
|
||||||
export const mockedTruncateOversizedToolResultsInSession = vi.mocked(
|
|
||||||
truncateOversizedToolResultsInSession,
|
|
||||||
);
|
|
||||||
export { mockedContextEngine };
|
|
||||||
|
|
||||||
export const overflowBaseRunParams = {
|
|
||||||
sessionId: "test-session",
|
|
||||||
sessionKey: "test-key",
|
|
||||||
sessionFile: "/tmp/session.json",
|
|
||||||
workspaceDir: "/tmp/workspace",
|
|
||||||
prompt: "hello",
|
|
||||||
timeoutMs: 30000,
|
|
||||||
runId: "run-1",
|
|
||||||
} as const;
|
|
||||||
@@ -1,7 +1,4 @@
|
|||||||
import "./run.overflow-compaction.mocks.shared.js";
|
import { beforeEach, describe, expect, it } from "vitest";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
||||||
import { pickFallbackThinkingLevel } from "../pi-embedded-helpers.js";
|
|
||||||
import { runEmbeddedPiAgent } from "./run.js";
|
|
||||||
import {
|
import {
|
||||||
makeAttemptResult,
|
makeAttemptResult,
|
||||||
makeCompactionSuccess,
|
makeCompactionSuccess,
|
||||||
@@ -10,24 +7,30 @@ import {
|
|||||||
queueOverflowAttemptWithOversizedToolOutput,
|
queueOverflowAttemptWithOversizedToolOutput,
|
||||||
} from "./run.overflow-compaction.fixture.js";
|
} from "./run.overflow-compaction.fixture.js";
|
||||||
import {
|
import {
|
||||||
|
loadRunOverflowCompactionHarness,
|
||||||
mockedCoerceToFailoverError,
|
mockedCoerceToFailoverError,
|
||||||
mockedDescribeFailoverError,
|
mockedDescribeFailoverError,
|
||||||
mockedGlobalHookRunner,
|
mockedGlobalHookRunner,
|
||||||
|
mockedPickFallbackThinkingLevel,
|
||||||
mockedResolveFailoverStatus,
|
mockedResolveFailoverStatus,
|
||||||
} from "./run.overflow-compaction.mocks.shared.js";
|
|
||||||
import {
|
|
||||||
mockedContextEngine,
|
mockedContextEngine,
|
||||||
mockedCompactDirect,
|
mockedCompactDirect,
|
||||||
mockedRunEmbeddedAttempt,
|
mockedRunEmbeddedAttempt,
|
||||||
mockedSessionLikelyHasOversizedToolResults,
|
mockedSessionLikelyHasOversizedToolResults,
|
||||||
mockedTruncateOversizedToolResultsInSession,
|
mockedTruncateOversizedToolResultsInSession,
|
||||||
overflowBaseRunParams,
|
overflowBaseRunParams,
|
||||||
} from "./run.overflow-compaction.shared-test.js";
|
} from "./run.overflow-compaction.harness.js";
|
||||||
const mockedPickFallbackThinkingLevel = vi.mocked(pickFallbackThinkingLevel);
|
|
||||||
|
let runEmbeddedPiAgent: typeof import("./run.js").runEmbeddedPiAgent;
|
||||||
|
|
||||||
describe("runEmbeddedPiAgent overflow compaction trigger routing", () => {
|
describe("runEmbeddedPiAgent overflow compaction trigger routing", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
return loadRunOverflowCompactionHarness().then((loaded) => {
|
||||||
|
runEmbeddedPiAgent = loaded.runEmbeddedPiAgent;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
mockedRunEmbeddedAttempt.mockReset();
|
mockedRunEmbeddedAttempt.mockReset();
|
||||||
mockedCompactDirect.mockReset();
|
mockedCompactDirect.mockReset();
|
||||||
mockedCoerceToFailoverError.mockReset();
|
mockedCoerceToFailoverError.mockReset();
|
||||||
@@ -257,7 +260,8 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => {
|
|||||||
it("returns retry_limit when repeated retries never converge", async () => {
|
it("returns retry_limit when repeated retries never converge", async () => {
|
||||||
mockedRunEmbeddedAttempt.mockClear();
|
mockedRunEmbeddedAttempt.mockClear();
|
||||||
mockedCompactDirect.mockClear();
|
mockedCompactDirect.mockClear();
|
||||||
mockedPickFallbackThinkingLevel.mockClear();
|
mockedPickFallbackThinkingLevel.mockReset();
|
||||||
|
mockedPickFallbackThinkingLevel.mockReturnValue(null);
|
||||||
mockedRunEmbeddedAttempt.mockResolvedValue(
|
mockedRunEmbeddedAttempt.mockResolvedValue(
|
||||||
makeAttemptResult({ promptError: new Error("unsupported reasoning mode") }),
|
makeAttemptResult({ promptError: new Error("unsupported reasoning mode") }),
|
||||||
);
|
);
|
||||||
@@ -288,15 +292,15 @@ describe("runEmbeddedPiAgent overflow compaction trigger routing", () => {
|
|||||||
status: 429,
|
status: 429,
|
||||||
});
|
});
|
||||||
|
|
||||||
mockedRunEmbeddedAttempt.mockResolvedValueOnce(makeAttemptResult({ promptError }));
|
mockedRunEmbeddedAttempt.mockResolvedValue(makeAttemptResult({ promptError }));
|
||||||
mockedCoerceToFailoverError.mockReturnValueOnce(normalized);
|
mockedCoerceToFailoverError.mockReturnValue(normalized);
|
||||||
mockedDescribeFailoverError.mockImplementation((err: unknown) => ({
|
mockedDescribeFailoverError.mockImplementation((err: unknown) => ({
|
||||||
message: err instanceof Error ? err.message : String(err),
|
message: err instanceof Error ? err.message : String(err),
|
||||||
reason: err === normalized ? "rate_limit" : undefined,
|
reason: err === normalized ? "rate_limit" : undefined,
|
||||||
status: err === normalized ? 429 : undefined,
|
status: err === normalized ? 429 : undefined,
|
||||||
code: undefined,
|
code: undefined,
|
||||||
}));
|
}));
|
||||||
mockedResolveFailoverStatus.mockReturnValueOnce(429);
|
mockedResolveFailoverStatus.mockReturnValue(429);
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
runEmbeddedPiAgent({
|
runEmbeddedPiAgent({
|
||||||
|
|||||||
@@ -3,20 +3,25 @@
|
|||||||
* with no pending tool calls, so the parent session is idle when subagent
|
* with no pending tool calls, so the parent session is idle when subagent
|
||||||
* results arrive.
|
* results arrive.
|
||||||
*/
|
*/
|
||||||
import "./run.overflow-compaction.mocks.shared.js";
|
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
||||||
import { runEmbeddedPiAgent } from "./run.js";
|
|
||||||
import { makeAttemptResult } from "./run.overflow-compaction.fixture.js";
|
import { makeAttemptResult } from "./run.overflow-compaction.fixture.js";
|
||||||
import { mockedGlobalHookRunner } from "./run.overflow-compaction.mocks.shared.js";
|
|
||||||
import {
|
import {
|
||||||
|
loadRunOverflowCompactionHarness,
|
||||||
|
mockedGlobalHookRunner,
|
||||||
mockedRunEmbeddedAttempt,
|
mockedRunEmbeddedAttempt,
|
||||||
overflowBaseRunParams,
|
overflowBaseRunParams,
|
||||||
} from "./run.overflow-compaction.shared-test.js";
|
} from "./run.overflow-compaction.harness.js";
|
||||||
import { isEmbeddedPiRunActive, queueEmbeddedPiMessage } from "./runs.js";
|
import { isEmbeddedPiRunActive, queueEmbeddedPiMessage } from "./runs.js";
|
||||||
|
|
||||||
|
let runEmbeddedPiAgent: typeof import("./run.js").runEmbeddedPiAgent;
|
||||||
|
|
||||||
describe("sessions_yield orchestration", () => {
|
describe("sessions_yield orchestration", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
({ runEmbeddedPiAgent } = await loadRunOverflowCompactionHarness());
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
mockedRunEmbeddedAttempt.mockReset();
|
||||||
mockedGlobalHookRunner.hasHooks.mockImplementation(() => false);
|
mockedGlobalHookRunner.hasHooks.mockImplementation(() => false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,20 @@
|
|||||||
import "./run.overflow-compaction.mocks.shared.js";
|
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import {
|
||||||
|
loadRunOverflowCompactionHarness,
|
||||||
|
mockedEnsureRuntimePluginsLoaded,
|
||||||
|
mockedRunEmbeddedAttempt,
|
||||||
|
} from "./run.overflow-compaction.harness.js";
|
||||||
|
|
||||||
const runtimePluginMocks = vi.hoisted(() => ({
|
let runEmbeddedPiAgent: typeof import("./run.js").runEmbeddedPiAgent;
|
||||||
ensureRuntimePluginsLoaded: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
vi.mock("../runtime-plugins.js", () => ({
|
|
||||||
ensureRuntimePluginsLoaded: runtimePluginMocks.ensureRuntimePluginsLoaded,
|
|
||||||
}));
|
|
||||||
|
|
||||||
import { runEmbeddedPiAgent } from "./run.js";
|
|
||||||
import { runEmbeddedAttempt } from "./run/attempt.js";
|
|
||||||
|
|
||||||
const mockedRunEmbeddedAttempt = vi.mocked(runEmbeddedAttempt);
|
|
||||||
|
|
||||||
describe("runEmbeddedPiAgent usage reporting", () => {
|
describe("runEmbeddedPiAgent usage reporting", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
({ runEmbeddedPiAgent } = await loadRunOverflowCompactionHarness());
|
||||||
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
mockedEnsureRuntimePluginsLoaded.mockReset();
|
||||||
|
mockedRunEmbeddedAttempt.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("bootstraps runtime plugins with the resolved workspace before running", async () => {
|
it("bootstraps runtime plugins with the resolved workspace before running", async () => {
|
||||||
@@ -39,7 +37,7 @@ describe("runEmbeddedPiAgent usage reporting", () => {
|
|||||||
runId: "run-plugin-bootstrap",
|
runId: "run-plugin-bootstrap",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(runtimePluginMocks.ensureRuntimePluginsLoaded).toHaveBeenCalledWith({
|
expect(mockedEnsureRuntimePluginsLoaded).toHaveBeenCalledWith({
|
||||||
config: undefined,
|
config: undefined,
|
||||||
workspaceDir: "/tmp/workspace",
|
workspaceDir: "/tmp/workspace",
|
||||||
});
|
});
|
||||||
@@ -66,7 +64,7 @@ describe("runEmbeddedPiAgent usage reporting", () => {
|
|||||||
allowGatewaySubagentBinding: true,
|
allowGatewaySubagentBinding: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(runtimePluginMocks.ensureRuntimePluginsLoaded).toHaveBeenCalledWith({
|
expect(mockedEnsureRuntimePluginsLoaded).toHaveBeenCalledWith({
|
||||||
config: undefined,
|
config: undefined,
|
||||||
workspaceDir: "/tmp/workspace",
|
workspaceDir: "/tmp/workspace",
|
||||||
allowGatewaySubagentBinding: true,
|
allowGatewaySubagentBinding: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user