openclaw / src /agents /embedded-agent-runner /compact.hooks.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
eb3f11e verified
Raw History Blame Contribute Delete
249 kB
// Hook integration coverage for direct and queued embedded compaction.
import { mkdtemp, realpath, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { Message } from "@openclaw/llm-core";
import { expectDefined } from "@openclaw/normalization-core";
import type { AgentMessage, StreamFn } from "openclaw/plugin-sdk/agent-core";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi, type Mock } from "vitest";
import { createDeferred } from "../../../test/helpers/promise.js";
import { useAutoCleanupTempDirTracker } from "../../../test/helpers/temp-dir.js";
import { makeTextToolResult } from "../../../test/helpers/text-tool-result.js";
import { createReplyOperation } from "../../auto-reply/reply/reply-run-registry.js";
import {
loadSessionEntryReadOnly,
loadTranscriptEvents,
patchSessionEntryCore,
upsertSessionEntryCore,
} from "../../config/sessions/session-accessor.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { delegateCompactionToRuntime } from "../../context-engine/delegate.js";
import type { ContextEngine } from "../../context-engine/types.js";
import { racePromiseWithAbortSignal } from "../../infra/abort-signal.js";
import { createPluginMetadataSnapshotFixture } from "../../plugins/plugin-metadata.test-support.js";
import { getModelProviderRuntimePluginHandle } from "../../plugins/provider-hook-runtime.js";
import {
requireActivePluginRegistry,
withPluginRegistrationContext,
} from "../../plugins/runtime.js";
import type { CommandQueueEnqueueOptions } from "../../process/command-queue.types.js";
import { closeOpenClawAgentDatabasesForTest } from "../../state/openclaw-agent-db.js";
import {
createApiKeyCredential,
createAuthProfileStoreFixture,
} from "../auth-profiles/credential-fixtures.test-support.js";
import { createProcessSessionFixture } from "../bash-process-registry.test-helpers.js";
import { getRegisteredAgentHarness, registerAgentHarness } from "../harness/registry.js";
import type { AgentHarness } from "../harness/types.js";
import { getModelProviderLocalServiceReconciler } from "../provider-local-service-reconcile.js";
import { createSessionMaintenanceOwner } from "../session-maintenance/coordinator.js";
import {
createAssistant,
createAssistantResultStream,
createTestSession,
registerAgentSessionLoopTestLifecycle,
testModel,
} from "../sessions/agent-session-loop-correctness.test-support.js";
import { createResourceLoader } from "../sessions/agent-session-loop-resource-loader.test-support.js";
import { generateSummary as generateRealSummary } from "../sessions/compaction/compaction.js";
import { createEventBus } from "../sessions/event-bus.js";
import { createExtensionRuntime, loadExtensionFromFactory } from "../sessions/extensions/loader.js";
import { SessionManager } from "../sessions/session-manager.js";
import { SettingsManager } from "../sessions/settings-manager.js";
import {
acquireAgentRunPreparedModelRuntimeMock,
attemptServerEndpointCompactionMock,
applyExtraParamsToAgentMock,
applyAgentCompactionSettingsFromConfigMock,
buildEmbeddedExtensionFactoriesMock,
buildAgentRuntimePlanMock,
buildEmbeddedSystemPromptMock,
resolveBootstrapContextForRunMock,
contextEngineCompactMock,
createAgentSessionMock,
createPreparedEmbeddedAgentSettingsManagerMock,
createOpenClawCodingToolsMock,
enqueueCommandInLaneMock,
ensureAuthProfileStoreMock,
estimateTokensMock,
getApiKeyForModelMock,
getHistoryLimitFromSessionKeyMock,
getMemorySearchManagerMock,
guardSessionManagerMock,
hookRunner,
listRegisteredPluginAgentPromptGuidanceMock,
limitHistoryTurnsMock,
loadCompactHooksHarness,
maybeCompactAgentHarnessSessionMock,
resolveAgentHarnessPolicyMock,
registerProviderStreamForModelMock,
resolveAgentConfigMock,
resolveProviderEntryApiKeyProfileReferenceMock,
resolveContextWindowInfoMock,
resolveCliBackendConfigMock,
resolveContextEngineMock,
resolveDefaultAgentDirMock,
resolveEffectiveCompactionModeMock,
resolveEmbeddedAgentStreamMock,
resolveMemorySearchConfigMock,
resolveModelAsyncMock,
resolveModelMock,
resolveSandboxContextMock,
resolveSkillsPromptMock,
resolveSessionAgentIdMock,
resolveSessionAgentIdsMock,
rotateTranscriptAfterCompactionMock,
runCliAgentMock,
selectAgentHarnessForPreparedModelProvidersMock,
selectAgentHarnessMock,
shouldPreferExplicitConfigApiKeyAuthMock,
resetCompactHooksHarnessMocks,
resetCompactSessionStateMocks,
sessionAbortCompactionMock,
sessionAutomaticCompactionMock,
sessionMessages,
sessionCompactImpl,
sessionManualCompactionMock,
triggerInternalHookMock,
} from "./compact.hooks.harness.js";
import {
abortEmbeddedAgentRun,
clearActiveEmbeddedRun,
isEmbeddedAgentRunActive,
isEmbeddedAgentRunHandleActive,
setActiveEmbeddedRun,
} from "./runs.js";
let compactEmbeddedAgentSessionDirect: typeof import("./compact.js").compactEmbeddedAgentSessionDirect;
let compactEmbeddedAgentSession: typeof import("./compact.queued.js").compactEmbeddedAgentSession;
let compactTesting: typeof import("./compact.js").testing;
let onSessionTranscriptUpdate: typeof import("../../sessions/transcript-events.js").onSessionTranscriptUpdate;
let onInternalSessionTranscriptUpdate: typeof import("../../sessions/transcript-events.js").onInternalSessionTranscriptUpdate;
let diagnosticEvents: typeof import("../../infra/diagnostic-events.js");
let diagnosticRunActivity: typeof import("../../logging/diagnostic-run-activity.js");
// Target resolution still reads real SQLite metadata even when compaction is mocked.
const tempDirs = useAutoCleanupTempDirTracker((cleanup) =>
afterEach(() => {
closeOpenClawAgentDatabasesForTest();
cleanup();
}),
);
const TEST_SESSION_ID = "session-1";
const TEST_SESSION_KEY = "agent:main:session-1";
let TEST_SESSION_FILE: string;
let TEST_WORKSPACE_DIR: string;
const TEST_CUSTOM_INSTRUCTIONS = "focus on decisions";
type SessionHookEvent = {
type?: string;
action?: string;
sessionKey?: string;
context?: Record<string, unknown>;
};
type PostCompactionSyncParams = {
archiveFiles?: string[];
reason: string;
sessionFiles?: string[];
sessions?: Array<{ agentId: string; sessionId: string; sessionKey?: string }>;
};
type PostCompactionSync = (params?: unknown) => Promise<void>;
function mockPendingContextEngineCompaction() {
const pending = {
signal: undefined as AbortSignal | undefined,
started: createDeferred(),
release: createDeferred(),
};
contextEngineCompactMock.mockImplementationOnce(async (...args: unknown[]) => {
const [params] = args;
pending.signal = (params as { abortSignal?: AbortSignal }).abortSignal;
pending.started.resolve(undefined);
await pending.release.promise;
return {
ok: true,
compacted: true,
reason: undefined,
result: { summary: "engine-summary", tokensBefore: 120, tokensAfter: 50 },
};
});
return pending;
}
function mockPendingNativeCompaction() {
const pending = {
signal: undefined as AbortSignal | undefined,
started: createDeferred(),
terminal: createDeferred<{ ok: false; compacted: false; reason: string }>(),
};
maybeCompactAgentHarnessSessionMock.mockImplementationOnce(async (...args: unknown[]) => {
const [params] = args;
pending.signal = (params as { abortSignal?: AbortSignal }).abortSignal;
pending.started.resolve(undefined);
return await pending.terminal.promise;
});
return pending;
}
function expectRecordFields(record: unknown, expected: Record<string, unknown>) {
if (!record || typeof record !== "object") {
throw new Error("Expected record");
}
const actual = record as Record<string, unknown>;
for (const [key, value] of Object.entries(expected)) {
expect(actual[key]).toEqual(value);
}
return actual;
}
function mockCallArg(mock: ReturnType<typeof vi.fn>, callIndex = 0, argIndex = 0) {
const call = mock.mock.calls[callIndex];
if (!call) {
throw new Error(`Expected mock call ${callIndex}`);
}
return call[argIndex];
}
function plannedCompactionPluginSelections(
config: OpenClawConfig,
metadataSnapshot = createPluginMetadataSnapshotFixture({ plugins: [] }),
) {
const derive = expectDefined(
acquireAgentRunPreparedModelRuntimeMock.mock.calls[0]?.[1]?.deriveRuntimePluginSelections,
"admitted compaction selection recipe",
);
return derive({ config, metadataSnapshot });
}
function findMockCall(mock: ReturnType<typeof vi.fn>, predicate: (arg: unknown[]) => boolean) {
const call = mock.mock.calls.find((entry) => predicate(entry));
if (!call) {
throw new Error("Expected matching mock call");
}
return call;
}
function mockResolvedModel(params?: {
supportsTools?: boolean;
input?: string[];
contextWindow?: number;
requestTimeoutMs?: number;
}) {
resolveModelMock.mockReset();
resolveModelMock.mockImplementation(
(provider = "openai", modelId = "fake", _agentDir?: string, cfg?: unknown) => {
const providerConfig = (
cfg as
| {
models?: {
providers?: Record<string, { api?: string; baseUrl?: string }>;
};
}
| undefined
)?.models?.providers?.[provider];
return {
logicalRef: { provider, model: modelId },
model: {
provider,
api: providerConfig?.api ?? "openai-responses",
baseUrl: providerConfig?.baseUrl?.trim() || "https://api.openai.com/v1",
id: modelId,
input: params?.input ?? [],
...(params?.contextWindow === undefined ? {} : { contextWindow: params.contextWindow }),
...(params?.requestTimeoutMs === undefined
? {}
: { requestTimeoutMs: params.requestTimeoutMs }),
...(params?.supportsTools === undefined
? {}
: { compat: { supportsTools: params.supportsTools } }),
},
error: null,
authStorage: { setRuntimeApiKey: vi.fn() },
modelRegistry: {},
};
},
);
}
function compactionConfig(mode: "await" | "off" | "async") {
return {
agents: {
defaults: {
compaction: {
postIndexSync: mode,
},
},
},
} as never;
}
function wrappedCompactionArgs(overrides: Record<string, unknown> = {}) {
return {
sessionId: TEST_SESSION_ID,
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
sessionTarget: {
agentId: "main",
sessionId: TEST_SESSION_ID,
sessionKey: TEST_SESSION_KEY,
storePath: join(TEST_WORKSPACE_DIR, "sessions.json"),
},
workspaceDir: TEST_WORKSPACE_DIR,
customInstructions: TEST_CUSTOM_INSTRUCTIONS,
enqueue: async <T>(task: () => Promise<T> | T) => await task(),
...overrides,
};
}
async function nativeCompactionArgs(
overrides: Record<string, unknown> & { agentHarnessId: string },
) {
const params = wrappedCompactionArgs({ ...overrides, modelSelectionLocked: true });
await upsertSessionEntryCore(params.sessionTarget, {
sessionId: params.sessionId,
updatedAt: 1,
modelSelectionLocked: true,
agentHarnessId: overrides.agentHarnessId,
});
return params;
}
function createPreparedCodexCompactionPlans(modelId = "gpt-5.5") {
const modelRoute = {
provider: "openai",
modelId,
api: "openai-responses",
baseUrl: "https://api.openai.com/v1",
authRequirement: "api-key",
requestTransportOverrides: "none",
runtimePolicy: { compatibleIds: ["codex"] },
} as const;
const runtimeAuthPlan = {
providerForAuth: "openai",
modelId,
authProfileProviderForAuth: "openai",
harnessAuthProvider: "openai",
selectedAuthMode: "api-key",
modelRoute,
} as const;
return {
modelRoute,
runtimeAuthPlan,
runtimePlan: {
resolvedRef: {
provider: "openai",
modelId,
modelApi: "openai-responses",
harnessId: "codex",
},
auth: runtimeAuthPlan,
} as never,
};
}
const sessionHook = (action: string): SessionHookEvent | undefined =>
triggerInternalHookMock.mock.calls.find((call) => {
const event = call[0] as SessionHookEvent | undefined;
return event?.type === "session" && event.action === action;
})?.[0] as SessionHookEvent | undefined;
async function runCompactionHooks(params: { sessionKey: string; messageProvider?: string }) {
// Build metrics through the production helper so hook payload assertions stay
// aligned with compaction token accounting.
const originalMessages = sessionMessages.slice(1) as AgentMessage[];
const currentMessages = sessionMessages.slice(1) as AgentMessage[];
const beforeMetrics = compactTesting.buildBeforeCompactionHookMetrics({
originalMessages,
currentMessages,
estimateTokensFn: estimateTokensMock as (message: AgentMessage) => number,
});
const hookState = await compactTesting.runBeforeCompactionHooks({
hookRunner,
sessionId: TEST_SESSION_ID,
sessionKey: params.sessionKey,
sessionAgentId: "main",
workspaceDir: TEST_WORKSPACE_DIR,
messageProvider: params.messageProvider,
metrics: beforeMetrics,
});
await compactTesting.runAfterCompactionHooks({
hookRunner,
sessionId: TEST_SESSION_ID,
sessionAgentId: "main",
hookSessionKey: hookState.hookSessionKey,
missingSessionKey: hookState.missingSessionKey,
workspaceDir: TEST_WORKSPACE_DIR,
messageProvider: params.messageProvider,
messageCountAfter: 1,
tokensAfter: 10,
compactedCount: 1,
sessionFile: TEST_SESSION_FILE,
summaryLength: "summary".length,
tokensBefore: 120,
firstKeptEntryId: "entry-1",
});
}
beforeAll(async () => {
const loaded = await loadCompactHooksHarness();
[diagnosticEvents, diagnosticRunActivity] = await Promise.all([
import("../../infra/diagnostic-events.js"),
import("../../logging/diagnostic-run-activity.js"),
]);
compactEmbeddedAgentSessionDirect = (params) =>
loaded.compactEmbeddedAgentSessionDirect({ agentId: "main", ...params });
compactEmbeddedAgentSession = loaded.compactEmbeddedAgentSession;
compactTesting = loaded.testing;
onSessionTranscriptUpdate = loaded.onSessionTranscriptUpdate;
onInternalSessionTranscriptUpdate = loaded.onInternalSessionTranscriptUpdate;
});
beforeEach(async () => {
TEST_WORKSPACE_DIR = tempDirs.make("openclaw-compact-hooks-");
TEST_SESSION_FILE = join(TEST_WORKSPACE_DIR, "session.jsonl");
resetCompactHooksHarnessMocks(TEST_WORKSPACE_DIR);
await upsertSessionEntryCore(
{
agentId: "main",
sessionKey: TEST_SESSION_KEY,
storePath: join(TEST_WORKSPACE_DIR, "sessions.json"),
},
{ sessionId: TEST_SESSION_ID, updatedAt: 1 },
);
});
describe("compactEmbeddedAgentSessionDirect hooks", () => {
beforeEach(() => {
triggerInternalHookMock.mockClear();
hookRunner.hasHooks.mockReset();
hookRunner.runBeforeCompaction.mockReset();
hookRunner.runAfterCompaction.mockReset();
mockResolvedModel();
sessionCompactImpl.mockReset();
sessionCompactImpl.mockResolvedValue({
summary: "summary",
firstKeptEntryId: "entry-1",
tokensBefore: 120,
details: { ok: true },
});
resetCompactSessionStateMocks();
});
it("returns a summaryless xAI manual endpoint result", async () => {
mockResolvedModel();
attemptServerEndpointCompactionMock.mockImplementationOnce(async (input) => {
input.onCompactionCommitted?.(1_000);
return {
item: { type: "compaction", encrypted_content: "opaque" },
usage: { input_tokens: 1_000, output_tokens: 200 },
};
});
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
provider: "xai",
model: "grok-4.5",
config: {
models: {
providers: {
xai: {
api: "openai-responses",
baseUrl: "https://api.x.ai/v1",
models: [],
},
},
},
},
trigger: "manual",
}),
);
expect(result).toMatchObject({
ok: true,
compacted: true,
compactionKind: "server-endpoint",
result: { tokensBefore: 1_000 },
});
expect(result.result).not.toHaveProperty("summary");
expect(attemptServerEndpointCompactionMock).toHaveBeenCalledWith(
expect.objectContaining({
streamFn: expect.any(Function),
requestOptions: expect.objectContaining({ apiKey: "test" }),
}),
);
expect(sessionManualCompactionMock).not.toHaveBeenCalled();
});
it.each([
{
label: "unrestricted",
toolsAllow: undefined,
expectedPromptMode: "full",
expectedSkillsPrompt: "PRIVATE_SKILL_MARKER",
expectedToolNames: ["read", "exec"],
},
{
label: "wildcard",
toolsAllow: ["*"],
expectedPromptMode: "full",
expectedSkillsPrompt: "PRIVATE_SKILL_MARKER",
expectedToolNames: ["read", "exec"],
},
{
label: "finite",
toolsAllow: ["read"],
expectedPromptMode: "minimal",
expectedSkillsPrompt: null,
expectedToolNames: ["read"],
},
])(
"projects the $label tool policy into the compact endpoint prompt",
async ({ toolsAllow, expectedPromptMode, expectedSkillsPrompt, expectedToolNames }) => {
resolveSkillsPromptMock.mockReturnValue("PRIVATE_SKILL_MARKER");
createOpenClawCodingToolsMock.mockReturnValue([
{
name: "read",
label: "Read",
description: "Read a file",
parameters: { type: "object", properties: {} },
execute: vi.fn(),
},
{
name: "exec",
label: "Exec",
description: "Run a command",
parameters: { type: "object", properties: {} },
execute: vi.fn(),
},
]);
buildEmbeddedSystemPromptMock.mockImplementation((params) =>
JSON.stringify({
promptMode: params.promptMode,
skillsPrompt: params.skillsPrompt ?? null,
toolNames: params.tools.map((tool) => tool.name),
}),
);
let endpointSystemPrompt: string | undefined;
attemptServerEndpointCompactionMock.mockImplementationOnce(async (input) => {
endpointSystemPrompt = input.context.systemPrompt;
input.onCompactionCommitted?.(1_000);
return {
item: { type: "compaction", encrypted_content: "opaque" },
usage: { input_tokens: 1_000, output_tokens: 200 },
};
});
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
provider: "xai",
model: "grok-4.5",
config: {
models: {
providers: {
xai: {
api: "openai-responses",
baseUrl: "https://api.x.ai/v1",
models: [],
},
},
},
},
customInstructions: undefined,
trigger: "manual",
toolsAllow,
}),
);
expect(result).toMatchObject({ compacted: true, compactionKind: "server-endpoint" });
expect(endpointSystemPrompt).toBeDefined();
expect(JSON.parse(endpointSystemPrompt ?? "{}")).toEqual({
promptMode: expectedPromptMode,
skillsPrompt: expectedSkillsPrompt,
toolNames: expectedToolNames,
});
},
);
it("never calls the compact endpoint during overflow recovery", async () => {
mockResolvedModel();
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
provider: "xai",
model: "grok-4.5",
config: {
models: {
providers: {
xai: {
api: "openai-responses",
baseUrl: "https://api.x.ai/v1",
models: [],
},
},
},
},
trigger: "overflow",
}),
);
expect(result.compacted).toBe(true);
expect(attemptServerEndpointCompactionMock).toHaveBeenCalledWith(
expect.objectContaining({ trigger: "overflow" }),
);
expect(sessionAutomaticCompactionMock).toHaveBeenCalledOnce();
});
it("falls back to client compaction when the endpoint fails", async () => {
mockResolvedModel();
attemptServerEndpointCompactionMock.mockResolvedValueOnce(undefined);
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
provider: "xai",
model: "grok-4.5",
config: {
models: {
providers: {
xai: {
api: "openai-responses",
baseUrl: "https://api.x.ai/v1",
models: [],
},
},
},
},
trigger: "manual",
}),
);
expect(result).toMatchObject({ ok: true, compacted: true });
expect(result.compactionKind).toBeUndefined();
expect(sessionManualCompactionMock).toHaveBeenCalledOnce();
});
it("prepares the routed peer's account window for server-endpoint compaction", async () => {
const history = await vi.importActual<typeof import("./history.js")>("./history.js");
getHistoryLimitFromSessionKeyMock.mockImplementationOnce(history.getHistoryLimitFromSessionKey);
limitHistoryTurnsMock.mockImplementationOnce(history.limitHistoryTurns);
attemptServerEndpointCompactionMock.mockImplementationOnce(async (input) => {
input.onCompactionCommitted?.(1_000);
return {
item: { type: "compaction", encrypted_content: "opaque" },
usage: { input_tokens: 1_000, output_tokens: 200 },
};
});
const sessionKey = "agent:main:telegram:direct:direct:peer";
const sessionTarget = {
...wrappedCompactionArgs().sessionTarget,
sessionId: "routed-peer-session",
sessionKey,
};
await upsertSessionEntryCore(sessionTarget, {
sessionId: sessionTarget.sessionId,
updatedAt: 1,
});
sessionMessages.splice(
0,
sessionMessages.length,
...Array.from({ length: 6 }, (_, index) => ({
role: "user",
content: `turn-${index + 1}`,
timestamp: index + 1,
})),
);
await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
sessionId: sessionTarget.sessionId,
sessionKey,
sessionTarget,
agentAccountId: "direct",
conversationRoutePeerId: "123",
chatType: "direct",
config: {
session: { identityLinks: { "direct:peer": ["telegram:123"] } },
channels: {
telegram: {
dmHistoryLimit: 20,
accounts: {
direct: {
dmHistoryLimit: 10,
dms: { "direct:peer": { historyLimit: 2 }, peer: { historyLimit: 6 } },
},
},
},
},
},
}),
);
expect(attemptServerEndpointCompactionMock).toHaveBeenCalledOnce();
expect(attemptServerEndpointCompactionMock).toHaveBeenCalledWith(
expect.objectContaining({
context: expect.objectContaining({
messages: [
{ role: "user", content: "turn-5", timestamp: 5 },
{ role: "user", content: "turn-6", timestamp: 6 },
],
}),
}),
);
});
it("cancels direct compaction while prepared runtime admission is waiting", async () => {
const controller = new AbortController();
const started = createDeferred();
const resume = createDeferred();
const acquire = expectDefined(
acquireAgentRunPreparedModelRuntimeMock.getMockImplementation(),
"prepared runtime acquisition",
);
acquireAgentRunPreparedModelRuntimeMock.mockImplementationOnce(async (input, options) => {
started.resolve();
await racePromiseWithAbortSignal(resume.promise, options?.abortSignal);
return await acquire(input, options);
});
const pending = compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({ abortSignal: controller.signal }),
);
const stopped = pending.then(
() => "completed",
(error: unknown) => error,
);
try {
await started.promise;
const reason = new Error("direct compaction cancelled during admission");
controller.abort(reason);
// Releasing admission after cancellation must never enter model/session preparation.
resume.resolve();
expect(await stopped).toMatchObject({ name: "AbortError", cause: reason });
expect(resolveModelAsyncMock).not.toHaveBeenCalled();
expect(createAgentSessionMock).not.toHaveBeenCalled();
} finally {
resume.resolve();
await stopped;
}
});
it("refreshes the delegated watchdog before post-compaction hooks", async () => {
const compactionTimeoutReset = vi.fn();
hookRunner.hasHooks.mockImplementation((name?: string) => name === "after_compaction");
hookRunner.runAfterCompaction.mockImplementationOnce(async () => {
expect(compactionTimeoutReset).toHaveBeenCalledTimes(3);
});
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({ compactionTimeoutReset }),
);
expect(result).toMatchObject({ ok: true, compacted: true });
expect(hookRunner.runAfterCompaction).toHaveBeenCalledOnce();
});
it("refreshes the delegated watchdog before delayed fallback setup", async () => {
const compactionTimeoutReset = vi.fn();
const fallbackSetupStarted = createDeferred<number>();
const fallbackSetupReleased = createDeferred();
const createAgentSession = createAgentSessionMock.getMockImplementation();
if (!createAgentSession) {
throw new Error("Expected a create-agent-session implementation");
}
createAgentSessionMock.mockImplementation(async (...args) => {
if (createAgentSessionMock.mock.calls.length === 2) {
fallbackSetupStarted.resolve(compactionTimeoutReset.mock.calls.length);
await fallbackSetupReleased.promise;
}
return await createAgentSession(...args);
});
sessionCompactImpl
.mockRejectedValueOnce(new Error("Reasoning is mandatory for this endpoint"))
.mockResolvedValueOnce({
summary: "fallback summary",
firstKeptEntryId: "entry-fallback",
tokensBefore: 120,
details: { ok: true },
});
const pending = compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({ compactionTimeoutReset, thinkLevel: "off" }),
);
try {
expect(await fallbackSetupStarted.promise).toBe(3);
} finally {
fallbackSetupReleased.resolve(undefined);
await pending;
}
await expect(pending).resolves.toMatchObject({ ok: true, compacted: true });
expect(createAgentSessionMock).toHaveBeenCalledTimes(2);
expect(compactionTimeoutReset).toHaveBeenCalledTimes(6);
});
it("fails closed before generic compaction for a model-locked native session", async () => {
const params = await nativeCompactionArgs({
provider: "openai",
model: "gpt-5.6-luna",
agentHarnessId: "codex",
});
const result = await compactEmbeddedAgentSessionDirect({
...params,
agentHarnessId: "openclaw",
sessionEntry: { sessionId: TEST_SESSION_ID, updatedAt: 1, pluginOwnerId: "stale-owner" },
});
expect(result).toMatchObject({
ok: false,
compacted: false,
failure: { reason: "model_selection_locked" },
});
expect(resolveModelMock).not.toHaveBeenCalled();
expect(sessionCompactImpl).not.toHaveBeenCalled();
});
it("preserves prepared runtime plans for the normalized primary compaction candidate", async () => {
const { modelRoute, runtimeAuthPlan, runtimePlan } = createPreparedCodexCompactionPlans();
const result = await compactEmbeddedAgentSessionDirect({
...wrappedCompactionArgs({ provider: " OpenAI ", model: "gpt-5.5" }),
modelFallbacksOverride: ["anthropic/claude-fallback"],
runtimeAuthPlan,
runtimePlan,
});
expect(result, result.reason).toMatchObject({ ok: true });
expect(selectAgentHarnessForPreparedModelProvidersMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
modelId: "gpt-5.5",
agentHarnessRuntimeOverride: "codex",
modelProviders: [
expect.objectContaining({
api: "openai-responses",
baseUrl: "https://api.openai.com/v1",
preparedAuth: expect.objectContaining({ source: "direct" }),
}),
],
}),
);
expect(selectAgentHarnessMock).not.toHaveBeenCalled();
expect(buildAgentRuntimePlanMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
modelId: "gpt-5.5",
harnessId: "codex",
modelRoute,
}),
);
});
it("rebuilds runtime plans for an actual compaction fallback candidate", async () => {
const { runtimeAuthPlan, runtimePlan } = createPreparedCodexCompactionPlans();
sessionCompactImpl
.mockRejectedValueOnce(
Object.assign(new Error("primary compaction rate limited"), {
status: 429,
code: "rate_limit_exceeded",
}),
)
.mockResolvedValueOnce({
summary: "rebuilt fallback summary",
firstKeptEntryId: "entry-fallback",
tokensBefore: 120,
details: { ok: true },
});
const result = await compactEmbeddedAgentSessionDirect({
...wrappedCompactionArgs({ provider: "openai", model: "gpt-5.5" }),
modelFallbacksOverride: ["anthropic/claude-fallback"],
runtimeAuthPlan,
runtimePlan,
});
expect(result).toMatchObject({ ok: true, result: { summary: "rebuilt fallback summary" } });
const fallbackPlanCall = findMockCall(buildAgentRuntimePlanMock, ([input]) => {
const fields = input as { provider?: string; modelId?: string } | undefined;
return fields?.provider === "anthropic" && fields.modelId === "claude-fallback";
});
expectRecordFields(fallbackPlanCall[0], {
provider: "anthropic",
modelId: "claude-fallback",
harnessId: "openclaw",
modelRoute: undefined,
});
});
it("rematerializes the downstream model for a resolved backup profile", async () => {
getApiKeyForModelMock
.mockRejectedValueOnce(new Error("missing SecretRef"))
.mockResolvedValueOnce({
apiKey: "backup-key",
mode: "api-key",
source: "profile:openai:backup",
profileId: "openai:backup",
});
await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
runtimeAuthPlan: {
providerForAuth: "openai",
modelId: "gpt-5.5",
authProfileProviderForAuth: "openai",
forwardedAuthProfileId: "openai:missing",
forwardedAuthProfileSource: "auto",
forwardedAuthProfileCandidateIds: ["openai:missing", "openai:backup"],
selectedAuthMode: "api-key",
},
}),
);
expect(
getApiKeyForModelMock.mock.calls.map(
([params]) => (params as { profileId?: string }).profileId,
),
).toEqual(["openai:missing", "openai:backup"]);
expect(
resolveModelAsyncMock.mock.calls.some((call) => {
const options = (call as unknown as readonly unknown[])[4] as
| { authProfileId?: string }
| undefined;
return options?.authProfileId === "openai:backup";
}),
).toBe(true);
expect(
resolveModelAsyncMock.mock.calls.some((call) => {
const options = (call as unknown as readonly unknown[])[4] as
| { authProfileId?: string }
| undefined;
return options?.authProfileId === "openai:missing";
}),
).toBe(true);
expect(resolveEmbeddedAgentStreamMock).toHaveBeenCalledWith(
expect.objectContaining({ authProfileId: "openai:backup" }),
);
expect(buildAgentRuntimePlanMock).toHaveBeenCalledWith(
expect.objectContaining({ sessionAuthProfileId: "openai:backup" }),
);
});
it("falls through a failed subscription auth route to the prepared Platform route", async () => {
ensureAuthProfileStoreMock.mockReturnValue({
version: 1,
profiles: {
"openai:subscription": {
type: "token",
provider: "openai",
token: "subscription-token",
expires: Date.now() + 60_000,
},
"openai:platform": createApiKeyCredential("openai", "platform-key"),
},
order: { openai: ["openai:subscription", "openai:platform"] },
});
getApiKeyForModelMock.mockImplementation(async (authParams = {}) => {
if (authParams.profileId === "openai:subscription") {
throw new Error("subscription credential resolution failed");
}
if (authParams.profileId === "openai:platform") {
return {
apiKey: "platform-key",
mode: "api-key",
source: "profile:openai:platform",
profileId: "openai:platform",
};
}
throw new Error(`unexpected profile: ${authParams.profileId ?? "none"}`);
});
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
config: {
auth: { order: { openai: ["openai:subscription", "openai:platform"] } },
agents: {
defaults: {
models: { "openai/gpt-5.5": { agentRuntime: { id: "openclaw" } } },
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(getApiKeyForModelMock.mock.calls.map(([authParams]) => authParams?.profileId)).toEqual([
"openai:subscription",
"openai:platform",
]);
expectRecordFields(mockCallArg(createAgentSessionMock), {
model: expect.objectContaining({
api: "openai-responses",
baseUrl: "https://api.openai.com/v1",
}),
});
expect(buildAgentRuntimePlanMock).toHaveBeenCalledWith(
expect.objectContaining({
sessionAuthProfileId: "openai:platform",
modelRoute: expect.objectContaining({
api: "openai-responses",
authRequirement: "api-key",
}),
}),
);
});
it("uses a prepared direct API-key fallback only after its profile tier fails", async () => {
ensureAuthProfileStoreMock.mockReturnValue({
version: 1,
profiles: {
"openai:broken": createApiKeyCredential("openai", "broken-profile-key"),
},
order: { openai: ["openai:broken"] },
});
resolveProviderEntryApiKeyProfileReferenceMock.mockReturnValue({ kind: "literal" });
shouldPreferExplicitConfigApiKeyAuthMock.mockReturnValue(false);
getApiKeyForModelMock.mockImplementation(async (authParams = {}) => {
if (authParams.profileId === "openai:broken") {
throw new Error("profile key could not be resolved");
}
if (authParams.profileId === undefined && authParams.allowAuthProfileFallback === false) {
return {
apiKey: "literal-key",
mode: "api-key",
source: "models.json",
};
}
throw new Error("unexpected auth lookup");
});
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
config: {
auth: { order: { openai: ["openai:broken"] } },
models: {
providers: {
openai: { apiKey: "literal-key", baseUrl: "", models: [] },
},
},
agents: {
defaults: {
models: { "openai/gpt-5.5": { agentRuntime: { id: "openclaw" } } },
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(
getApiKeyForModelMock.mock.calls.map(([authParams]) => ({
profileId: authParams?.profileId,
allowAuthProfileFallback: authParams?.allowAuthProfileFallback,
})),
).toEqual([
{ profileId: "openai:broken", allowAuthProfileFallback: undefined },
{ profileId: undefined, allowAuthProfileFallback: false },
]);
expect(buildAgentRuntimePlanMock).toHaveBeenCalledWith(
expect.objectContaining({
authProfileMode: "api-key",
sessionAuthProfileId: undefined,
modelRoute: expect.objectContaining({
api: "openai-responses",
authRequirement: "api-key",
}),
}),
);
});
it("replans manual compaction once when the full attempt set changes harness", async () => {
ensureAuthProfileStoreMock.mockReturnValue({
version: 1,
profiles: {
"openai:platform": createApiKeyCredential("openai", "platform-key"),
},
order: { openai: ["openai:platform"] },
});
selectAgentHarnessMock.mockReturnValueOnce({
id: "codex",
label: "Codex test harness",
supports: () => ({ supported: true }),
runAttempt: vi.fn(),
} as never);
selectAgentHarnessForPreparedModelProvidersMock.mockReturnValue({
id: "openclaw",
label: "OpenClaw test harness",
supports: () => ({ supported: true }),
runAttempt: vi.fn(),
} as never);
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({ provider: "openai", model: "gpt-5.5" }),
);
expect(result.ok).toBe(true);
expect(selectAgentHarnessForPreparedModelProvidersMock).toHaveBeenCalledTimes(2);
expect(buildAgentRuntimePlanMock).toHaveBeenCalledWith(
expect.objectContaining({ harnessId: "openclaw", harnessRuntime: "openclaw" }),
);
});
it("bootstraps runtime plugins with the resolved workspace", async () => {
// This assertion only cares about bootstrap wiring, so stop before the
// rest of the compaction pipeline can pull in unrelated runtime surfaces.
resolveModelMock.mockReturnValue({
model: undefined,
error: "stop after bootstrap",
authStorage: { setRuntimeApiKey: vi.fn() },
modelRegistry: {},
} as never);
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
});
expect(mockCallArg(acquireAgentRunPreparedModelRuntimeMock)).toEqual(
expect.objectContaining({ config: {}, workspaceDir: join(TEST_WORKSPACE_DIR, "workspace") }),
);
});
it("does not require an implicit default owner for direct compaction", async () => {
resolveDefaultAgentDirMock.mockImplementation(() => {
throw new Error("ambiguous default agent");
});
resolveSessionAgentIdsMock.mockReturnValue({
defaultAgentId: "marie-clawndo",
sessionAgentId: "marie-clawndo",
});
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
agentId: "marie-clawndo",
config: {
agents: {
ownership: "explicit",
list: [{ id: "main" }, { id: "marie-clawndo" }],
},
},
sessionKey: "agent:marie-clawndo:dashboard:session-1",
sessionTarget: {
agentId: "marie-clawndo",
sessionId: TEST_SESSION_ID,
sessionKey: "agent:marie-clawndo:dashboard:session-1",
storePath: join(TEST_WORKSPACE_DIR, "sessions.json"),
},
}),
);
expect(result.ok).toBe(true);
expect(mockCallArg(acquireAgentRunPreparedModelRuntimeMock)).toEqual(
expect.objectContaining({ agentId: "marie-clawndo" }),
);
});
it("forwards gateway subagent binding opt-in during compaction bootstrap", async () => {
// Coding-tool forwarding is covered elsewhere; this compaction test only
// owns the runtime bootstrap wiring.
resolveModelMock.mockReturnValue({
model: undefined,
error: "stop after bootstrap",
authStorage: { setRuntimeApiKey: vi.fn() },
modelRegistry: {},
} as never);
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
allowGatewaySubagentBinding: true,
});
expect(mockCallArg(acquireAgentRunPreparedModelRuntimeMock)).toEqual(
expect.objectContaining({
config: {},
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
allowGatewaySubagentBinding: true,
}),
);
});
it("uses sandboxSessionKey only for compaction sandbox resolution", async () => {
const { addSession, deleteSession } = await import("../bash-process-registry.js");
const owned = createProcessSessionFixture({ id: "compaction-owned", backgrounded: true });
owned.scopeKey = "agent:main:main";
const other = createProcessSessionFixture({ id: "policy-owned", backgrounded: true });
other.scopeKey = "agent:main:telegram:default:direct:12345";
addSession(owned);
addSession(other);
try {
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: owned.scopeKey,
sandboxSessionKey: other.scopeKey,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
});
expect(resolveSandboxContextMock).toHaveBeenCalledWith(
expect.objectContaining({
config: {},
sessionKey: other.scopeKey,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
}),
);
expect(buildEmbeddedSystemPromptMock).toHaveBeenCalledWith(
expect.objectContaining({
runtimeInfo: expect.objectContaining({
activeProcessSessions: [expect.objectContaining({ sessionId: owned.id })],
}),
}),
);
} finally {
deleteSession(owned.id);
deleteSession(other.id);
}
});
it.each([
["global", undefined, undefined],
["global", "global", undefined],
["global", "agent:main:policy", undefined],
["agent:marketing:review", "global", "marketing"],
] as const)(
"prepares compaction with the selected sandbox policy (%s / %s)",
async (sessionKey, sandboxSessionKey, sandboxAgentId) => {
const agentScope =
await vi.importActual<typeof import("../agent-scope.js")>("../agent-scope.js");
const { resolveSandboxContext } = await import("../sandbox/context.js");
const { prepareDirectCompactionAttempt } = await import("./direct-compaction-preparation.js");
resolveSessionAgentIdMock.mockImplementation(agentScope.resolveSessionAgentId);
resolveSessionAgentIdsMock.mockImplementation(agentScope.resolveSessionAgentIds);
resolveSandboxContextMock.mockImplementation(resolveSandboxContext);
const config = {
agents: {
ownership: "explicit" as const,
defaults: { sandbox: { mode: "off" as const } },
list: [{ id: "main" }, { id: "marketing" }],
},
};
const { snapshot } = await acquireAgentRunPreparedModelRuntimeMock({
config,
agentId: "marketing",
agentDir: TEST_WORKSPACE_DIR,
workspaceDir: TEST_WORKSPACE_DIR,
});
const result = await prepareDirectCompactionAttempt({
config,
agentId: "marketing",
sessionId: TEST_SESSION_ID,
sessionKey,
sandboxSessionKey,
sandboxAgentId,
sessionFile: sessionKey,
workspaceDir: TEST_WORKSPACE_DIR,
preparedModelRuntime: snapshot as never,
});
expect(result).toMatchObject({
ok: true,
value: {
earlyAgentIds: { sessionAgentId: "marketing" },
effectiveWorkspace: TEST_WORKSPACE_DIR,
sandbox: null,
},
});
},
);
it("uses subagent prompt surface and guidance for compacted subagent prompt rebuilds", async () => {
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: "agent:main:subagent:worker",
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
cwd: join(TEST_WORKSPACE_DIR, "task-repo"),
});
expect(listRegisteredPluginAgentPromptGuidanceMock).toHaveBeenCalledWith({
surface: "subagent",
});
expect(buildEmbeddedSystemPromptMock).toHaveBeenCalledWith(
expect.objectContaining({
promptMode: "minimal",
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
runtimeCwd: join(TEST_WORKSPACE_DIR, "task-repo"),
promptSurface: "subagent",
nativeCommandGuidanceLines: ["Subagent compact command guidance."],
}),
);
});
it("threads the aggregate bootstrap-truncation notice into the compaction prompt when the budget omits a later file", async () => {
// Two valid bootstrap files; only the first is admitted into context, so the
// second is fully omitted with no per-file inline marker. The aggregate
// budget chain must surface the canonical partial-context notice.
resolveBootstrapContextForRunMock.mockResolvedValueOnce({
bootstrapFiles: [
{ name: "AGENTS.md", path: "/ws/AGENTS.md", content: "a".repeat(100), missing: false },
{ name: "IDENTITY.md", path: "/ws/IDENTITY.md", content: "b".repeat(100), missing: false },
],
contextFiles: [{ path: "/ws/AGENTS.md", content: "a".repeat(100) }],
});
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: "agent:main:session-1",
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
});
expect(buildEmbeddedSystemPromptMock).toHaveBeenCalledWith(
expect.objectContaining({
bootstrapTruncationNotice: expect.stringContaining("Treat Project Context as partial"),
}),
);
});
it("omits the bootstrap-truncation notice when the budget admits every file", async () => {
resolveBootstrapContextForRunMock.mockResolvedValueOnce({
bootstrapFiles: [
{ name: "AGENTS.md", path: "/ws/AGENTS.md", content: "a".repeat(10), missing: false },
],
contextFiles: [{ path: "/ws/AGENTS.md", content: "a".repeat(10) }],
});
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: "agent:main:session-1",
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
});
expect(buildEmbeddedSystemPromptMock).toHaveBeenCalledWith(
expect.objectContaining({
bootstrapTruncationNotice: undefined,
}),
);
});
it("delivers a real render of the bootstrap-truncation notice to the compaction session when the aggregate budget omits a later file", async () => {
// End-to-end proof: drive the real prepared-compaction owner
// (buildPreparedCompactionRuntime) through compactEmbeddedAgentSessionDirect
// with the real buildEmbeddedSystemPrompt renderer (not the "" mock), inject a
// real aggregate-exhaustion bootstrap result, and assert the notice survives
// into the actual prompt text delivered to the session via
// applySystemPromptToSession -> setBaseSystemPrompt.
resolveBootstrapContextForRunMock.mockResolvedValueOnce({
bootstrapFiles: [
{ name: "AGENTS.md", path: "/ws/AGENTS.md", content: "a".repeat(100), missing: false },
{ name: "IDENTITY.md", path: "/ws/IDENTITY.md", content: "b".repeat(100), missing: false },
],
contextFiles: [{ path: "/ws/AGENTS.md", content: "a".repeat(100) }],
});
const actualSystemPrompt =
await vi.importActual<typeof import("./system-prompt.js")>("./system-prompt.js");
buildEmbeddedSystemPromptMock.mockImplementation((params) =>
actualSystemPrompt.buildEmbeddedSystemPrompt(params),
);
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: "agent:main:session-1",
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
});
const createdSession = (await createAgentSessionMock.mock.results[0]?.value) as {
session: {
agent: { state: { systemPrompt?: string } };
setBaseSystemPrompt: Mock;
};
};
const deliveredPrompt = createdSession.session.agent.state.systemPrompt ?? "";
// The real owner computed the notice and the real renderer embedded it in the
// prompt text that compaction delivered to the session.
expect(deliveredPrompt).toContain("## Bootstrap Context Notice");
expect(deliveredPrompt).toContain("Treat Project Context as partial");
expect(createdSession.session.setBaseSystemPrompt).toHaveBeenCalledWith(deliveredPrompt);
});
it("delivers a real render with no bootstrap-truncation notice to the compaction session when the budget admits every file", async () => {
resolveBootstrapContextForRunMock.mockResolvedValueOnce({
bootstrapFiles: [
{ name: "AGENTS.md", path: "/ws/AGENTS.md", content: "a".repeat(10), missing: false },
],
contextFiles: [{ path: "/ws/AGENTS.md", content: "a".repeat(10) }],
});
const actualSystemPrompt =
await vi.importActual<typeof import("./system-prompt.js")>("./system-prompt.js");
buildEmbeddedSystemPromptMock.mockImplementation((params) =>
actualSystemPrompt.buildEmbeddedSystemPrompt(params),
);
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: "agent:main:session-1",
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
});
const createdSession = (await createAgentSessionMock.mock.results[0]?.value) as {
session: {
agent: { state: { systemPrompt?: string } };
setBaseSystemPrompt: Mock;
};
};
const deliveredPrompt = createdSession.session.agent.state.systemPrompt ?? "";
expect(deliveredPrompt).not.toContain("## Bootstrap Context Notice");
expect(deliveredPrompt).not.toContain("Treat Project Context as partial");
});
it("uses ACP prompt surface and guidance for compacted ACP prompt rebuilds", async () => {
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: "agent:codex:acp:worker",
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
});
expect(listRegisteredPluginAgentPromptGuidanceMock).toHaveBeenCalledWith({
surface: "acp_backend",
});
expect(buildEmbeddedSystemPromptMock).toHaveBeenCalledWith(
expect.objectContaining({
promptMode: "full",
promptSurface: "acp_backend",
nativeCommandGuidanceLines: ["ACP compact command guidance."],
}),
);
});
it("passes resolved agent identity context to compacted system prompt rebuilds", async () => {
resolveSessionAgentIdsMock.mockReturnValue({
defaultAgentId: "main",
sessionAgentId: "marketing-agent",
});
resolveAgentConfigMock.mockReturnValue({
identity: { name: "Campaign Navigator" },
});
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: "agent:marketing-agent:session-1",
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
config: {
agents: {
list: [{ id: "marketing-agent", identity: { name: "Campaign Navigator" } }],
},
},
});
expect(buildEmbeddedSystemPromptMock).toHaveBeenCalledWith(
expect.objectContaining({
runtimeInfo: expect.objectContaining({
agentId: "marketing-agent",
agentName: "Campaign Navigator",
sessionKey: "agent:marketing-agent:session-1",
}),
}),
);
expect(buildEmbeddedSystemPromptMock).toHaveBeenCalledWith(
expect.not.objectContaining({ heartbeatPrompt: expect.anything() }),
);
});
it("keeps the compaction prompt and durable provider resources after disposal", async () => {
buildEmbeddedSystemPromptMock.mockReturnValueOnce("compaction system prompt");
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: "agent:main:session-1",
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
});
const createdSession = (await createAgentSessionMock.mock.results[0]?.value) as {
session: {
agent: { state: { systemPrompt?: string } };
setActiveToolsByName: Mock;
setBaseSystemPrompt: Mock;
};
};
expect(createdSession.session.setBaseSystemPrompt).toHaveBeenCalledWith(
"compaction system prompt",
);
expect(createdSession.session.setActiveToolsByName.mock.invocationCallOrder[0]).toBeLessThan(
expectDefined(
createdSession.session.setBaseSystemPrompt.mock.invocationCallOrder[0],
"createdSession.session.setBaseSystemPrompt.mock.invocationCallOrder[0] test invariant",
),
);
});
it.each([
{ mode: "direct", providerTimeoutMs: 420_000, expectedTimeoutMs: 420_000 },
{ mode: "queued", providerTimeoutMs: 420_000, expectedTimeoutMs: 420_000 },
{ mode: "direct", expectedTimeoutMs: 30_000 },
{ mode: "queued", expectedTimeoutMs: 30_000 },
] as const)(
"tracks the owned request allowance for $mode compaction",
async ({ mode, expectedTimeoutMs, ...scenario }) => {
const providerRequest = createDeferred<unknown>();
const ref = { sessionId: TEST_SESSION_ID, sessionKey: TEST_SESSION_KEY };
let activeSnapshot:
| ReturnType<typeof diagnosticRunActivity.getDiagnosticSessionActivitySnapshot>
| undefined;
mockResolvedModel(
"providerTimeoutMs" in scenario ? { requestTimeoutMs: scenario.providerTimeoutMs } : {},
);
resolveEmbeddedAgentStreamMock.mockReturnValue({
streamFn: vi.fn(() => providerRequest.promise),
strategy: "session-custom",
});
attemptServerEndpointCompactionMock.mockImplementationOnce(async (input) => {
const { context, model, streamFn } = input;
const messages = context.messages.filter(
(message): message is Message =>
message.role === "user" ||
message.role === "assistant" ||
message.role === "toolResult",
);
const result = Promise.resolve(
streamFn(model, { systemPrompt: context.systemPrompt, messages }, {}),
);
await diagnosticEvents.waitForDiagnosticEventsDrained();
activeSnapshot = diagnosticRunActivity.getDiagnosticSessionActivitySnapshot(ref);
providerRequest.resolve(undefined);
await result;
input.onCompactionCommitted?.(120);
return {
item: { type: "compaction", encrypted_content: "opaque" },
usage: { input_tokens: 120, output_tokens: 50, dropped_message_count: 0 },
};
});
diagnosticEvents.resetDiagnosticEventsForTest();
diagnosticRunActivity.resetDiagnosticRunActivityForTest();
diagnosticRunActivity.startDiagnosticRunActivityTracking();
try {
if (mode === "queued") {
resolveContextEngineMock.mockResolvedValueOnce({
info: { ownsCompaction: false },
compact: vi.fn(
async () =>
(await compactEmbeddedAgentSessionDirect(wrappedCompactionArgs())) as never,
),
});
}
const result =
mode === "direct"
? await compactEmbeddedAgentSessionDirect(wrappedCompactionArgs())
: await compactEmbeddedAgentSession(
wrappedCompactionArgs({ agentId: "main", trigger: "manual" }),
);
expect(result.ok).toBe(true);
expect(activeSnapshot).toMatchObject({
activeWorkKind: "model_call",
hasActiveEmbeddedRun: true,
activeModelCallRequestTimeoutMs: expectedTimeoutMs,
});
await diagnosticEvents.waitForDiagnosticEventsDrained();
expect(
diagnosticRunActivity.getDiagnosticSessionActivitySnapshot(ref).activeWorkKind,
).toBeUndefined();
} finally {
diagnosticRunActivity.stopDiagnosticRunActivityTracking();
diagnosticRunActivity.resetDiagnosticRunActivityForTest();
diagnosticEvents.resetDiagnosticEventsForTest();
}
},
);
it("routes compaction through shared stream resolution and extra params", async () => {
const resolvedStreamFn = vi.fn();
resolveEmbeddedAgentStreamMock.mockReturnValue({
streamFn: resolvedStreamFn,
strategy: "session-custom",
});
applyExtraParamsToAgentMock.mockReturnValue({
effectiveExtraParams: { transport: "websocket" },
});
const session = {
agent: {
streamFn: vi.fn(),
},
messages: [{ role: "user", content: "hello" }],
};
await compactTesting.prepareCompactionSessionAgent({
session: session as never,
llmRuntime: { streamSimple: vi.fn() } as never,
providerStreamFn: vi.fn(),
sessionId: "session-1",
signal: new AbortController().signal,
effectiveModel: { provider: "openai", id: "fake", api: "responses", input: [] } as never,
resolvedApiKey: undefined,
authStorage: { setRuntimeApiKey: vi.fn() },
config: undefined,
provider: "openai",
modelId: "gpt-5.4",
thinkLevel: "off",
sessionAgentId: "main",
effectiveWorkspace: join(TEST_WORKSPACE_DIR, "workspace"),
agentDir: join(TEST_WORKSPACE_DIR, "workspace"),
runtimePlan: {
auth: { forwardedAuthProfileId: "openai:profile-1" },
transport: { resolveExtraParams: vi.fn(() => undefined) },
} as never,
});
const streamArg = mockCallArg(resolveEmbeddedAgentStreamMock) as Record<string, unknown>;
expect(streamArg.currentStreamFn).toBeTypeOf("function");
expect(streamArg.sessionId).toBe("session-1");
expect(streamArg.authProfileId).toBe("openai:profile-1");
expect(applyExtraParamsToAgentMock).toHaveBeenCalledWith(
expectRecordFields(mockCallArg(applyExtraParamsToAgentMock), { streamFn: resolvedStreamFn }),
undefined,
"openai",
"gpt-5.4",
undefined,
"off",
"main",
join(TEST_WORKSPACE_DIR, "workspace"),
expectRecordFields(mockCallArg(applyExtraParamsToAgentMock, 0, 8), {
provider: "openai",
id: "fake",
api: "responses",
}),
join(TEST_WORKSPACE_DIR, "workspace"),
undefined,
expectRecordFields(mockCallArg(applyExtraParamsToAgentMock, 0, 11), {
nativeWebSearchPolicyContext: {
sessionKey: undefined,
webSearchEnabled: false,
runtimeToolAllowlist: [],
sandboxToolPolicy: undefined,
messageProvider: undefined,
agentAccountId: undefined,
groupId: undefined,
groupChannel: undefined,
groupSpace: undefined,
spawnedBy: undefined,
senderId: undefined,
senderName: undefined,
senderUsername: undefined,
senderE164: undefined,
},
}),
);
});
it("maps logical Ultra to max before compaction provider hooks", async () => {
const resolveExtraParams = vi.fn(() => undefined);
await compactTesting.prepareCompactionSessionAgent({
session: {
agent: { streamFn: vi.fn() },
messages: [{ role: "user", content: "hello" }],
} as never,
llmRuntime: { streamSimple: vi.fn() } as never,
providerStreamFn: vi.fn(),
sessionId: "session-1",
signal: new AbortController().signal,
effectiveModel: { provider: "openai", id: "fake", api: "responses", input: [] } as never,
resolvedApiKey: undefined,
authStorage: { setRuntimeApiKey: vi.fn() },
config: undefined,
provider: "openai",
modelId: "gpt-5.6-sol",
thinkLevel: "ultra",
sessionAgentId: "main",
effectiveWorkspace: join(TEST_WORKSPACE_DIR, "workspace"),
agentDir: join(TEST_WORKSPACE_DIR, "workspace"),
runtimePlan: {
auth: {},
transport: { resolveExtraParams },
} as never,
});
expect(resolveExtraParams).toHaveBeenCalledWith(
expect.objectContaining({ thinkingLevel: "max" }),
);
expect(applyExtraParamsToAgentMock).toHaveBeenCalledWith(
expect.anything(),
undefined,
"openai",
"gpt-5.6-sol",
undefined,
"max",
"main",
join(TEST_WORKSPACE_DIR, "workspace"),
expect.anything(),
join(TEST_WORKSPACE_DIR, "workspace"),
undefined,
expect.anything(),
);
});
it("preserves full sender identity when building compaction tools", async () => {
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
senderId: "sender-1",
senderName: "Alice",
senderUsername: "alice_u",
senderE164: "+15551234567",
});
expectRecordFields(mockCallArg(createOpenClawCodingToolsMock), {
senderId: "sender-1",
senderName: "Alice",
senderUsername: "alice_u",
senderE164: "+15551234567",
});
});
it.each([
{ execMode: "deny", permissionMode: "read-only", expectedExecMode: "deny" },
{ execMode: "allowlist", permissionMode: "guarded", expectedExecMode: "ask" },
{ execMode: "ask", permissionMode: "guarded", expectedExecMode: "ask" },
{ execMode: "auto", permissionMode: "workspace", expectedExecMode: "auto" },
{ execMode: "full", permissionMode: "full", expectedExecMode: "full" },
] as const)(
"uses the final $permissionMode permission policy for compaction tools",
async ({ execMode, permissionMode, expectedExecMode }) => {
await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
permissionMode: "full",
sessionRoot: join(TEST_WORKSPACE_DIR, "workspace"),
execOverrides: { mode: execMode },
sessionEntry: {
sessionId: "session-1",
permissionMode: "full",
sessionRoot: join(TEST_WORKSPACE_DIR, "workspace"),
},
}),
);
const toolOptions = expectRecordFields(mockCallArg(createOpenClawCodingToolsMock), {
sessionPermissionPolicy: {
mode: permissionMode,
root: join(TEST_WORKSPACE_DIR, "workspace"),
},
});
expect(toolOptions.exec).toEqual(expect.objectContaining({ mode: expectedExecMode }));
},
);
it("defaults rootless compaction permissions to the canonical agent workspace", async () => {
const workspaceDir = tempDirs.make("openclaw-rootless-compaction-permission-");
const canonicalWorkspace = await realpath(workspaceDir);
await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
workspaceDir,
permissionMode: "workspace",
sessionEntry: { sessionId: "session-1", permissionMode: "workspace" },
}),
);
const toolOptions = expectRecordFields(mockCallArg(createOpenClawCodingToolsMock), {
sessionPermissionPolicy: { mode: "workspace", root: canonicalWorkspace },
});
expect(toolOptions.exec).toEqual(expect.objectContaining({ mode: "auto" }));
});
it("retains a host-required file boundary while rebuilding compaction tools", async () => {
await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
requireWorkspaceOnly: true,
}),
);
expectRecordFields(mockCallArg(createOpenClawCodingToolsMock), { requireWorkspaceOnly: true });
});
it("keeps manifest-profiled plugin tools executable during compaction", async () => {
const toolName = "profiled_plugin_tool";
const metadataSnapshot = {
...createPluginMetadataSnapshotFixture({
plugins: [
{
id: "profiled-plugin",
origin: "workspace",
rootDir: join(TEST_WORKSPACE_DIR, "workspace/profiled-plugin"),
source: join(TEST_WORKSPACE_DIR, "workspace/profiled-plugin/index.js"),
manifestPath: join(
TEST_WORKSPACE_DIR,
"workspace/profiled-plugin/openclaw.plugin.json",
),
contracts: { tools: [toolName] },
toolMetadata: { [toolName]: { profiles: ["coding"] } },
},
],
}),
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
};
const preparedModelRuntime = {
agentId: "main",
agentDir: join(TEST_WORKSPACE_DIR, "agents/main/agent"),
config: { tools: { profile: "coding" } },
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
metadataSnapshot,
configuredRuntimeModels: [],
inlineProviderModels: [],
createStores: () => ({ authStorage: {}, modelRegistry: {} }),
} as never;
acquireAgentRunPreparedModelRuntimeMock.mockResolvedValueOnce({
snapshot: preparedModelRuntime,
[Symbol.asyncDispose]: vi.fn(async () => {}),
});
createOpenClawCodingToolsMock.mockReturnValueOnce([
{
name: toolName,
label: "Profiled plugin tool",
description: "Profiled plugin tool test fixture",
parameters: { type: "object", properties: {}, additionalProperties: false },
execute: async () => ({ content: [{ type: "text", text: "ok" }], details: {} }),
},
] as never);
const result = await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
config: { tools: { profile: "coding" } },
});
expect(result.ok).toBe(true);
const toolOptions = expectRecordFields(mockCallArg(createOpenClawCodingToolsMock), {});
expect(
(toolOptions.preparedModelRuntime as { metadataSnapshot?: unknown }).metadataSnapshot,
).toBe(metadataSnapshot);
expect(
(
toolOptions.conversationCapabilityProfile as {
policy?: { explicitToolAllowlist?: string[] };
}
).policy?.explicitToolAllowlist,
).toContain(toolName);
const sessionOptions = expectRecordFields(mockCallArg(createAgentSessionMock), {});
expect(sessionOptions.tools).toContain(toolName);
expect(
(sessionOptions.customTools as Array<{ name: string }>).map((tool) => tool.name),
).toContain(toolName);
});
it.each([
{ input: ["text"], modelHasVision: false },
{ input: ["text", "image"], modelHasVision: true },
])(
"propagates modelHasVision=$modelHasVision when rebuilding compaction tools",
async ({ input, modelHasVision }) => {
mockResolvedModel({ input });
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
});
expectRecordFields(mockCallArg(createOpenClawCodingToolsMock), { modelHasVision });
},
);
it("uses cwd for compaction runtime tools while preserving workspace bootstrap root", async () => {
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
cwd: join(TEST_WORKSPACE_DIR, "task-repo"),
});
expectRecordFields(mockCallArg(createOpenClawCodingToolsMock), {
cwd: join(TEST_WORKSPACE_DIR, "task-repo"),
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
spawnWorkspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
});
expectRecordFields(mockCallArg(createPreparedEmbeddedAgentSettingsManagerMock), {
cwd: join(TEST_WORKSPACE_DIR, "task-repo"),
agentDir: join(TEST_WORKSPACE_DIR, "agents/main/agent"),
});
});
it("uses the caller context token budget during runtime compaction", async () => {
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
runId: "manual-compaction-operation",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
contextTokenBudget: 64_000,
});
expectRecordFields(mockCallArg(createOpenClawCodingToolsMock), {
modelContextWindowTokens: 64_000,
});
expectRecordFields(mockCallArg(guardSessionManagerMock, 0, 1), {
contextWindowTokens: 64_000,
runId: "manual-compaction-operation",
});
expectRecordFields(mockCallArg(createPreparedEmbeddedAgentSettingsManagerMock), {
contextTokenBudget: 64_000,
});
expectRecordFields(mockCallArg(applyAgentCompactionSettingsFromConfigMock), {
contextTokenBudget: 64_000,
});
});
it("creates a distinct skill-instruction delivery cache for each compaction attempt", async () => {
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
});
const firstCache = expectRecordFields(
mockCallArg(createOpenClawCodingToolsMock),
{},
).skillInstructionDeliveryCache;
await compactEmbeddedAgentSessionDirect({
sessionId: "session-2",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
});
const secondCache = expectRecordFields(
mockCallArg(createOpenClawCodingToolsMock, 1),
{},
).skillInstructionDeliveryCache;
expect(firstCache).toBeInstanceOf(Map);
expect(secondCache).toBeInstanceOf(Map);
expect(secondCache).not.toBe(firstCache);
});
it("skips runtime tool construction when the compaction model does not support tools", async () => {
mockResolvedModel({ supportsTools: false });
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
});
expect(createOpenClawCodingToolsMock).not.toHaveBeenCalled();
});
it("quarantines unsupported tool schemas before creating the compaction model session", async () => {
resolveContextEngineMock.mockResolvedValueOnce({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
});
resolveModelMock.mockImplementationOnce((provider = "openai", modelId = "fake") => ({
logicalRef: { provider, model: modelId },
model: { provider: "openai", api: "openai-responses", id: "fake", input: [] },
error: null,
authStorage: { setRuntimeApiKey: vi.fn() },
modelRegistry: {},
}));
createOpenClawCodingToolsMock.mockReturnValueOnce([
{
name: "healthy_lookup",
label: "Healthy Lookup",
description: "Look up safe data.",
parameters: { type: "object", properties: {} },
execute: async () => ({ text: "ok" }),
},
{
name: "fuzzplugin_move_angles",
label: "Fuzzplugin Move Angles",
description: "Move robot joints.",
parameters: { type: "array", items: { type: "number" } },
execute: async () => ({ text: "bad" }),
},
] as never);
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: "agent:main:session-1",
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
runId: "run-tool-schema-quarantine",
});
const sessionOptions = expectRecordFields(mockCallArg(createAgentSessionMock), {});
expect(
(sessionOptions.customTools as Array<{ name: string }>).map((tool) => tool.name),
).toEqual(["healthy_lookup"]);
expect(sessionOptions.tools).toEqual(["healthy_lookup"]);
});
it("clamps the caller context token budget to the compaction model", async () => {
resolveContextWindowInfoMock.mockReturnValueOnce({ tokens: 32_000 });
await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
contextTokenBudget: 64_000,
});
expectRecordFields(mockCallArg(createOpenClawCodingToolsMock), {
modelContextWindowTokens: 32_000,
});
});
it.each([false, true])(
"preserves configured fallback identity through compaction (credential refresh=%s)",
async (credentialRefresh) => {
const provider = "compaction-fallback-fixture";
const metadataSnapshot = createPluginMetadataSnapshotFixture({
plugins: [
{
id: provider,
providers: [provider],
modelIdNormalization: {
providers: { [provider]: { aliases: { entry: "middle", middle: "final" } } },
},
},
],
});
const { normalizeStaticProviderModelId } =
await vi.importActual<typeof import("../model-ref-shared.js")>("../model-ref-shared.js");
const { shouldPreferProviderRuntimeResolvedModel } =
await import("../../plugins/provider-runtime.js");
const acquire = expectDefined(
acquireAgentRunPreparedModelRuntimeMock.getMockImplementation(),
"prepared runtime fixture",
);
const resolveModel = expectDefined(
resolveModelAsyncMock.getMockImplementation(),
"model resolver fixture",
);
const preferRuntime = expectDefined(
vi.mocked(shouldPreferProviderRuntimeResolvedModel).getMockImplementation(),
"runtime metadata fixture",
);
const materializedModels: string[] = [];
acquireAgentRunPreparedModelRuntimeMock.mockImplementation(async (...args) => {
const lease = await acquire(...args);
return {
...lease,
snapshot: {
...lease.snapshot,
metadataSnapshot: { ...metadataSnapshot, workspaceDir: lease.snapshot.workspaceDir },
},
};
});
// Model the resolver's raw-input contract with the real captured alias policy.
resolveModelAsyncMock.mockImplementation(
async (providerId, modelId, agentDir, config, options) => {
const resolvedId =
options?.modelIdSource === "selected"
? modelId
: normalizeStaticProviderModelId(providerId, modelId, {
manifestPlugins: metadataSnapshot,
});
materializedModels.push(resolvedId);
return resolveModel(providerId, resolvedId, agentDir, config, options);
},
);
vi.mocked(shouldPreferProviderRuntimeResolvedModel).mockImplementation(
({ provider: candidateProvider }) => credentialRefresh && candidateProvider === provider,
);
resolveProviderEntryApiKeyProfileReferenceMock.mockReturnValue({ kind: "literal" });
sessionCompactImpl
.mockRejectedValueOnce(
Object.assign(new Error("primary compaction rate limited"), {
status: 429,
code: "rate_limit_exceeded",
}),
)
.mockResolvedValueOnce({
summary: "overflow fallback summary",
firstKeptEntryId: "entry-fallback",
tokensBefore: 120,
details: { ok: true },
});
try {
const result = await compactEmbeddedAgentSessionDirect({
...wrappedCompactionArgs({ provider, model: "primary" }),
trigger: "overflow",
modelFallbacksOverride: [`${provider}/entry`],
config: {
agents: { defaults: { model: { primary: `${provider}/primary`, fallbacks: [] } } },
models: {
providers: {
[provider]: {
auth: "api-key",
apiKey: "synthetic-fixture",
baseUrl: "http://127.0.0.1:1/v1",
models: [],
},
},
},
},
});
expect(result, result.reason).toMatchObject({
ok: true,
result: { summary: "overflow fallback summary" },
});
expect(createAgentSessionMock.mock.calls.map(([input]) => input)).toEqual([
expect.objectContaining({ model: expect.objectContaining({ id: "primary" }) }),
expect.objectContaining({ model: expect.objectContaining({ id: "middle" }) }),
]);
expect(materializedModels).not.toContain("final");
if (credentialRefresh) {
expect(
materializedModels.filter((model) => model === "middle").length,
).toBeGreaterThanOrEqual(2);
}
} finally {
acquireAgentRunPreparedModelRuntimeMock.mockImplementation(acquire);
resolveModelAsyncMock.mockImplementation(resolveModel);
vi.mocked(shouldPreferProviderRuntimeResolvedModel).mockImplementation(preferRuntime);
}
},
);
describe("safeguard failure provenance", () => {
registerAgentSessionLoopTestLifecycle();
const originalHistoryLimit = expectDefined(
limitHistoryTurnsMock.getMockImplementation(),
"history-limit fixture implementation",
);
let safeguard: typeof import("../agent-hooks/compaction-safeguard.js").default;
let setSafeguardRuntime: typeof import("../agent-hooks/compaction-safeguard-runtime.js").setCompactionSafeguardRuntime;
let summaryBridge: typeof import("../sessions/index.js").generateSummary;
beforeAll(async () => {
// The outer harness resets modules and mocks the session SDK. Retain the real
// disposable session fixture above, but share the safeguard registry with the runner.
safeguard = (await import("../agent-hooks/compaction-safeguard.js")).default;
setSafeguardRuntime = (await import("../agent-hooks/compaction-safeguard-runtime.js"))
.setCompactionSafeguardRuntime;
summaryBridge = (await import("../sessions/index.js")).generateSummary;
});
afterEach(() => {
vi.mocked(summaryBridge).mockReset().mockResolvedValue("summary");
limitHistoryTurnsMock.mockImplementation(originalHistoryLimit);
});
it("returns a structured automatic retention skip without reporting compaction failure", async () => {
const { isBenignCompactionSkipResult } = await import("./compact-reasons.js");
const { createAgentSessionForEmbeddedRunner } = await import("../sessions/sdk.js");
const { guardSessionManager } = await import("../session-tool-result-guard-wrapper.js");
const { resolveEmbeddedAgentStream } = await import("./stream-resolution.js");
const { attachCompactionAccountingRecorder } =
await import("./run/compaction-accounting-bridge.js");
const sessionManager = SessionManager.inMemory(TEST_WORKSPACE_DIR);
sessionManager.appendMessage({ role: "user", content: "a".repeat(46_191), timestamp: 1 });
const assistant = createAssistant(testModel, [{ type: "text", text: "ACK" }]);
sessionManager.appendMessage({
...assistant,
usage: { ...assistant.usage, input: 19_140, output: 2, totalTokens: 19_142 },
});
const pendingUserEntryId = sessionManager.appendMessage({
role: "user",
content: "b".repeat(52_602),
timestamp: 3,
});
const contextEngineRuntimeContext = {};
attachCompactionAccountingRecorder(contextEngineRuntimeContext, { pendingUserEntryId });
const conversation = () =>
sessionManager.getBranch().filter((entry) => entry.type === "message");
const before = structuredClone(conversation());
const stream = vi.fn<StreamFn>();
vi.mocked(guardSessionManager).mockReturnValue(sessionManager);
limitHistoryTurnsMock.mockImplementation((messages) => messages);
vi.mocked(resolveEmbeddedAgentStream).mockReturnValue({
streamFn: stream,
strategy: "session-custom",
});
vi.mocked(createAgentSessionForEmbeddedRunner).mockImplementation(async ({ model }) => {
if (!model) {
throw new Error("Expected prepared compaction model");
}
return await createTestSession({
model: { ...testModel, ...model },
sessionManager,
settingsManager: SettingsManager.inMemory({
compaction: { keepRecentTokens: 20_000 },
retry: { enabled: false },
}),
resourceLoader: createResourceLoader(),
});
});
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({ trigger: "budget", contextEngineRuntimeContext }),
);
expect(result).toMatchObject({
ok: true,
compacted: false,
reason: "Nothing to compact (session too small)",
});
expect(isBenignCompactionSkipResult(result)).toBe(true);
expect(conversation()).toEqual(before);
expect(
sessionManager.getBranch().filter((entry) => entry.type === "compaction"),
).toHaveLength(0);
expect(stream).not.toHaveBeenCalled();
expect(hookRunner.runAfterCompaction).not.toHaveBeenCalled();
});
it.each([
["provider timeout", "request timed out", "fallback"],
["provider rate limit", "429 rate limit exceeded", "fallback"],
["intentional quality rejection", undefined, "cancel"],
["explicit model timeout", "request timed out", "cancel"],
[
"reasoning-mandatory rejection",
"400 Reasoning is mandatory for this endpoint and cannot be disabled.",
"thinking",
],
] as const)(
"keeps model fallback boundaries for %s",
async (scenario, errorMessage, outcome) => {
const [
{ createAgentSessionForEmbeddedRunner },
{ guardSessionManager },
{ resolveEmbeddedAgentStream },
{ buildEmbeddedExtensionFactories },
] = await Promise.all([
import("../sessions/sdk.js"),
import("../session-tool-result-guard-wrapper.js"),
import("./stream-resolution.js"),
import("./extensions.js"),
]);
const fallback = outcome === "fallback";
const primary = "summary-primary";
const backup = "summary-backup";
const explicitModel = scenario === "explicit model timeout";
const fallbackSummary = [
"## Decisions",
"Review the deployment checklist before rollout.",
"## Open TODOs",
"Compare the remaining options.",
"## Constraints/Rules",
"None.",
"## Pending user asks",
"Compare the remaining options.",
"## Exact identifiers",
"None.",
].join("\n");
const expectedSummaryRequest = `Latest user request context: ${JSON.stringify("Keep the rollout notes.")}`;
const sessionManager = SessionManager.inMemory(TEST_WORKSPACE_DIR);
for (const content of [
"Review the deployment checklist.",
"Compare the remaining options.",
"Keep the rollout notes.",
]) {
sessionManager.appendMessage({ role: "user", content, timestamp: 1 });
}
const originalMessages = sessionManager.buildSessionContext().messages;
const settingsManager = SettingsManager.inMemory({
compaction: { enabled: false, reserveTokens: 1_024, keepRecentTokens: 1 },
retry: { enabled: false },
});
const extension = await loadExtensionFromFactory(
safeguard,
TEST_WORKSPACE_DIR,
createEventBus(),
createExtensionRuntime(),
);
const requestedModels: string[] = [];
const requestedThinking: Array<string | undefined> = [];
const stream = vi.fn<StreamFn>((activeModel, _context, options) => {
requestedModels.push(activeModel.id);
requestedThinking.push(options?.reasoning);
const rejected =
activeModel.id === primary &&
errorMessage &&
!(outcome === "thinking" && options?.reasoning === "minimal");
return createAssistantResultStream(
rejected
? { ...createAssistant(activeModel, [], "error"), errorMessage }
: createAssistant(activeModel, [
{
type: "text",
text: outcome === "cancel" ? "Missing required sections." : fallbackSummary,
},
]),
);
});
vi.mocked(summaryBridge).mockImplementation(generateRealSummary);
vi.mocked(guardSessionManager).mockReturnValue(sessionManager);
limitHistoryTurnsMock.mockImplementation((messages) => messages);
resolveEffectiveCompactionModeMock.mockReturnValue("safeguard");
vi.mocked(resolveEmbeddedAgentStream).mockReturnValue({
streamFn: stream,
strategy: "session-custom",
});
vi.mocked(buildEmbeddedExtensionFactories).mockImplementation(({ model }) => {
setSafeguardRuntime(sessionManager, {
model,
contextWindowTokens: 128_000,
recentTurnsPreserve: 0,
qualityGuardEnabled: true,
qualityGuardMaxRetries: 0,
});
return [];
});
vi.mocked(createAgentSessionForEmbeddedRunner).mockImplementation(
async ({ model, thinkingLevel }) => {
if (!model) {
throw new Error("Expected the prepared compaction model");
}
const created = await createTestSession({
model: {
...testModel,
...model,
reasoning: outcome === "thinking",
maxTokens: 1_024,
},
sessionManager,
settingsManager,
resourceLoader: createResourceLoader(extension.handlers),
});
created.session.setThinkingLevel(thinkingLevel ?? "off");
return created;
},
);
const config = {
agents: {
defaults: {
model: { primary: `openai/${primary}`, fallbacks: [`openai/${backup}`] },
compaction: {
mode: "safeguard" as const,
thinkingLevel: "off" as const,
...(explicitModel ? { model: `openai/${primary}` } : {}),
recentTurnsPreserve: 0,
qualityGuard: { enabled: true, maxRetries: 0 },
},
},
},
};
const configBefore = structuredClone(config);
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
provider: "openai",
model: primary,
trigger: "overflow",
config,
}),
);
expect([...new Set(requestedModels)], JSON.stringify(result)).toEqual(
fallback ? [primary, backup] : [primary],
);
expect(config).toEqual(configBefore);
if (outcome !== "cancel") {
if (outcome === "thinking") {
expect([...new Set(requestedThinking)]).toEqual(["off", "minimal"]);
}
expect(result).toMatchObject({
ok: true,
compacted: true,
result: { summary: expect.stringContaining(expectedSummaryRequest) },
});
expect(result.result?.summary).toContain(
"Review the deployment checklist before rollout.",
);
expect(
sessionManager.getBranch().findLast((entry) => entry.type === "compaction"),
).toMatchObject({
summary: expect.stringContaining(expectedSummaryRequest),
details: {
latestUnresolvedUserRequest: "Keep the rollout notes.",
},
});
} else {
expect(result).toMatchObject({ ok: false, compacted: false });
expect(result.reason).toMatch(explicitModel ? /timed out/i : /quality/i);
expect(sessionManager.getEntries().some((entry) => entry.type === "compaction")).toBe(
false,
);
expect(sessionManager.buildSessionContext().messages).toEqual(originalMessages);
}
},
);
});
it("plans runtime plugins for the canonical model behind a fallback alias", async () => {
const config = {
agents: {
defaults: { models: { "anthropic/claude-fallback": { alias: "summary-backup" } } },
},
};
const result = await compactEmbeddedAgentSessionDirect({
...wrappedCompactionArgs({ provider: "openai", model: "gpt-primary" }),
agentHarnessId: "codex",
modelFallbacksOverride: ["summary-backup"],
config,
});
expect(result.ok).toBe(true);
expect(plannedCompactionPluginSelections(config)).toEqual(
expect.arrayContaining([
expect.objectContaining({
provider: "anthropic",
modelId: "claude-fallback",
runtime: "codex",
}),
]),
);
const admittedConfig = {
agents: {
defaults: {
...config.agents.defaults,
compaction: { model: "anthropic/reloaded-summary" },
},
},
};
expect(plannedCompactionPluginSelections(admittedConfig)).toContainEqual(
expect.objectContaining({
provider: "anthropic",
modelId: "reloaded-summary",
runtime: "codex",
}),
);
});
it("plans direct compaction with the admitted workspace manifest policy", async () => {
const metadataSnapshot = {
...createPluginMetadataSnapshotFixture({
plugins: [
{
id: "compaction-normalizer",
providers: ["anthropic"],
origin: "workspace",
rootDir: TEST_WORKSPACE_DIR,
source: `${TEST_WORKSPACE_DIR}/index.js`,
manifestPath: `${TEST_WORKSPACE_DIR}/openclaw.plugin.json`,
modelIdNormalization: {
providers: {
anthropic: {
aliases: { legacy: "claude-modern" },
},
},
},
},
],
}),
configFingerprint: "workspace-compaction-normalization",
};
const result = await compactEmbeddedAgentSessionDirect({
...wrappedCompactionArgs({ provider: "openai", model: "gpt-primary" }),
agentHarnessId: "codex",
modelFallbacksOverride: ["anthropic/legacy"],
config: {} as never,
});
expect(result.ok, JSON.stringify(result)).toBe(true);
expect(plannedCompactionPluginSelections({}, metadataSnapshot)).toEqual(
expect.arrayContaining([
expect.objectContaining({
provider: "anthropic",
modelId: "claude-modern",
runtime: "codex",
}),
]),
);
});
it.each([undefined, "openclaw", "codex"])(
"keeps concrete locked compaction on its exact model after observing %s",
async (agentHarnessId) => {
sessionCompactImpl.mockRejectedValueOnce(
Object.assign(new Error("primary compaction rate limited"), { status: 429 }),
);
const params = wrappedCompactionArgs({
provider: "openai",
model: "gpt-primary",
agentHarnessId,
modelSelectionLocked: true,
modelFallbacksOverride: ["anthropic/claude-fallback"],
config: {
agents: {
defaults: {
compaction: { model: "azure/compact-primary" },
model: {
primary: "openai/gpt-primary",
fallbacks: ["anthropic/claude-fallback"],
},
},
},
},
});
await upsertSessionEntryCore(params.sessionTarget, {
sessionId: params.sessionId,
updatedAt: 1,
pluginOwnerId: "model-owner",
modelSelectionLocked: true,
agentHarnessId,
});
const result = await compactEmbeddedAgentSessionDirect(params);
expect(result.ok).toBe(false);
expect(resolveModelMock).toHaveBeenCalledTimes(1);
expect(mockCallArg(resolveModelMock)).toBe("openai");
expect(mockCallArg(resolveModelMock, 0, 1)).toBe("gpt-primary");
expect(sessionCompactImpl).toHaveBeenCalledOnce();
},
);
it("revalidates immutable Ultra for each compaction fallback candidate", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "openclaw" });
sessionCompactImpl
.mockRejectedValueOnce(
Object.assign(new Error("primary compaction rate limited"), {
status: 429,
code: "rate_limit_exceeded",
}),
)
.mockResolvedValueOnce({
summary: "fallback summary",
firstKeptEntryId: "entry-fallback",
tokensBefore: 120,
details: { ok: true },
});
const params = {
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
provider: "openai",
model: "gpt-5.6-sol",
thinkLevel: "ultra" as const,
trigger: "overflow" as const,
modelFallbacksOverride: ["demo/basic"],
config: {
agents: {
defaults: {
compaction: { thinkingLevel: "inherit" as const },
models: {
"openai/gpt-5.6-sol": { agentRuntime: { id: "openclaw" } },
},
},
},
},
};
const result = await compactEmbeddedAgentSessionDirect(params);
expect(result.ok).toBe(true);
expect(
createAgentSessionMock.mock.calls.map(
(call) => (call[0] as { thinkingLevel?: string }).thinkingLevel,
),
).toEqual(["ultra", "high"]);
expect(params.thinkLevel).toBe("ultra");
});
it("preserves Codex OAuth across same-provider OpenAI compaction fallbacks", async () => {
mockResolvedModel();
ensureAuthProfileStoreMock.mockReturnValue({
version: 1,
profiles: {
"openai:default": {
type: "oauth",
provider: "openai",
access: "test-access",
refresh: "test-refresh",
expires: Date.now() + 60_000,
},
},
order: { openai: ["openai:default"] },
});
getApiKeyForModelMock.mockImplementation(async (params?: { profileId?: string }) => ({
apiKey: "test-oauth",
mode: "oauth",
source: `profile:${params?.profileId ?? "openai:default"}`,
profileId: params?.profileId ?? "openai:default",
}));
sessionCompactImpl
.mockRejectedValueOnce(
Object.assign(new Error("primary compaction rate limited"), {
status: 429,
code: "rate_limit_exceeded",
}),
)
.mockResolvedValueOnce({
summary: "oauth fallback summary",
firstKeptEntryId: "entry-fallback",
tokensBefore: 120,
details: { ok: true },
});
const result = await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
provider: "openai",
model: "gpt-5.5",
authProfileId: "openai:default",
trigger: "overflow",
modelFallbacksOverride: ["openai/gpt-5.4-mini"],
config: {
agents: {
defaults: {
model: {
primary: "openai/gpt-5.5",
fallbacks: [],
},
},
},
} as never,
});
expect(result.ok).toBe(true);
expect(result.result?.summary).toBe("oauth fallback summary");
findMockCall(
resolveModelMock,
([provider, modelId]) => provider === "openai" && modelId === "gpt-5.5",
);
findMockCall(
resolveModelMock,
([provider, modelId]) => provider === "openai" && modelId === "gpt-5.4-mini",
);
expectRecordFields(mockCallArg(resolveEmbeddedAgentStreamMock, 1), {
authProfileId: "openai:default",
});
});
it("routes unbound ChatGPT OAuth direct compaction through auth-aware codex selection", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "codex",
runtimeSource: "implicit",
} as never);
// Only ChatGPT OAuth is available — no API-key profile. Auth-aware
// selection must pick codex (harness-owned) instead of forced openclaw.
ensureAuthProfileStoreMock.mockReturnValue({
version: 1,
profiles: {
"openai:chatgpt": {
type: "oauth",
provider: "openai",
access: "test-auth-token",
refresh: "test-auth-token",
expires: Date.now() + 10 * 60_000,
},
},
order: { openai: ["openai:chatgpt"] },
});
getApiKeyForModelMock.mockImplementation(async (params?: { profileId?: string }) => ({
apiKey: "test-auth-token",
mode: "oauth",
source: `profile:${params?.profileId ?? "openai:chatgpt"}`,
profileId: params?.profileId ?? "openai:chatgpt",
}));
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
// Do not pin provider-level api to openai-responses: that collapses
// route resolution to api-key-only and hides ChatGPT OAuth.
config: {
models: {
providers: {
openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
expect(selectAgentHarnessMock).toHaveBeenCalledWith(
expect.objectContaining({
agentHarnessId: undefined,
agentHarnessRuntimeOverride: undefined,
}),
);
expect(selectAgentHarnessMock.mock.results[0]?.value).toEqual(
expect.objectContaining({ id: "codex", authBootstrap: "harness" }),
);
expect(selectAgentHarnessForPreparedModelProvidersMock).toHaveBeenCalledWith(
expect.objectContaining({
agentHarnessRuntimeOverride: undefined,
modelProviders: expect.arrayContaining([
expect.objectContaining({
preparedAuth: expect.objectContaining({
source: "profile",
mode: "oauth",
requirement: "subscription",
}),
}),
]),
}),
);
});
it("keeps custom OpenAI-compatible compaction on OpenAI logical context", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "codex" });
const result = await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
config: {
models: {
providers: {
openai: {
api: "openai-responses",
baseUrl: "https://example.test/v1",
models: [{ id: "gpt-5.5", contextWindow: 350_000 }],
},
},
},
} as never,
});
expect(result.ok).toBe(true);
expect(mockCallArg(resolveModelMock)).toBe("openai");
expect(mockCallArg(resolveModelMock, 0, 1)).toBe("gpt-5.5");
const sessionOptions = expectRecordFields(mockCallArg(createAgentSessionMock), {});
expectRecordFields(sessionOptions.model, {
provider: "openai",
id: "gpt-5.5",
api: "openai-responses",
baseUrl: "https://example.test/v1",
});
expect(buildAgentRuntimePlanMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
modelId: "gpt-5.5",
modelApi: "openai-responses",
model: expect.objectContaining({
api: "openai-responses",
baseUrl: "https://example.test/v1",
}),
modelRoute: expect.objectContaining({
api: "openai-responses",
baseUrl: "https://example.test/v1",
authRequirement: "api-key",
}),
}),
);
expectRecordFields(mockCallArg(resolveContextWindowInfoMock), {
provider: "openai",
modelId: "gpt-5.5",
});
});
it("uses explicit Codex runtime policy for direct OpenAI compaction", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "codex",
runtimeSource: "model",
} as never);
const result = await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
config: {
models: {
providers: {
openai: { models: [{ id: "fake-model", contextWindow: 350_000 }] },
},
},
} as never,
});
expect(result.ok).toBe(true);
expect(resolveAgentHarnessPolicyMock).toHaveBeenCalledWith(
expect.objectContaining({ provider: "openai", modelId: "fake-model" }),
);
expect(selectAgentHarnessForPreparedModelProvidersMock).toHaveBeenCalledWith(
expect.objectContaining({
modelProviders: expect.arrayContaining([
expect.objectContaining({
preparedAuth: expect.objectContaining({ source: "profile" }),
runtimePolicy: expect.objectContaining({ compatibleIds: ["openclaw", "codex"] }),
}),
]),
}),
);
expect(mockCallArg(resolveModelMock)).toBe("openai");
expectRecordFields(mockCallArg(resolveContextWindowInfoMock), {
provider: "openai",
modelId: "fake-model",
});
});
it("preserves direct OpenAI API-key compaction when OpenClaw runtime is active", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "openclaw" });
const result = await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
provider: "openai",
model: "gpt-5.5",
runtimeAuthPlan: {
providerForAuth: "openai",
modelId: "gpt-5.5",
authProfileProviderForAuth: "openai",
selectedAuthMode: "api-key",
},
config: {
models: {
providers: {
openai: { models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }] },
},
},
} as never,
});
expect(result.ok).toBe(true);
expect(mockCallArg(resolveModelMock)).toBe("openai");
expect(mockCallArg(resolveModelMock, 0, 1)).toBe("gpt-5.5");
expect(mockCallArg(resolveModelAsyncMock, 0, 4)).toMatchObject({
authProfileMode: "api_key",
preparedModelRuntime: expect.objectContaining({
configuredRuntimeModels: [],
inlineProviderModels: [],
}),
});
});
it("uses the compaction model override with a pinned Codex harness", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "codex" });
const result = await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
config: {
models: {
providers: {
openai: {
models: [{ id: "gpt-5.5" }, { id: "gpt-5.4-mini", contextWindow: 350_000 }],
},
},
},
agents: {
defaults: { compaction: { model: "openai/gpt-5.4-mini" } },
},
} as never,
});
expect(result.ok).toBe(true);
expect(mockCallArg(resolveModelMock)).toBe("openai");
expect(mockCallArg(resolveModelMock, 0, 1)).toBe("gpt-5.4-mini");
expectRecordFields(mockCallArg(resolveContextWindowInfoMock), {
provider: "openai",
modelId: "gpt-5.4-mini",
});
});
it.each(["override", "fallback"] as const)(
"does not reuse a source-provider profile for cross-provider compaction (%s)",
async (targetSource) => {
const targetModel = "github-copilot/gpt-5.6-sol";
if (targetSource === "fallback") {
sessionCompactImpl.mockRejectedValueOnce(
Object.assign(new Error("primary compaction rate limited"), {
status: 429,
code: "rate_limit_exceeded",
}),
);
}
const result = await compactEmbeddedAgentSessionDirect({
...wrappedCompactionArgs(),
provider: "openai",
model: "gpt-5.5",
authProfileId: "openai:test",
runtimeAuthPlan: {
providerForAuth: "openai",
modelId: "gpt-5.5",
authProfileProviderForAuth: "openai",
forwardedAuthProfileId: "openai:test",
},
config: {
agents: {
defaults:
targetSource === "override"
? { compaction: { model: targetModel } }
: { model: { primary: "openai/gpt-5.5", fallbacks: [targetModel] } },
},
},
});
expect(result, result.reason).toMatchObject({ ok: true, compacted: true });
const targetResolveCall = resolveModelAsyncMock.mock.calls.find(
([provider]) => provider === "github-copilot",
);
expect(targetResolveCall).toBeDefined();
expect(targetResolveCall?.[4]?.authProfileId).toBeUndefined();
expectRecordFields(resolveEmbeddedAgentStreamMock.mock.lastCall?.[0], {
authProfileId: undefined,
});
},
);
it("materializes subscription-auth OpenAI compaction while preserving logical context", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "openclaw" });
mockResolvedModel({ contextWindow: 1_000_000 });
ensureAuthProfileStoreMock.mockReturnValue(
createAuthProfileStoreFixture({
"openai:work": {
type: "oauth",
provider: "openai",
access: "test-access",
refresh: "test-refresh",
expires: Date.now() + 60_000,
},
}),
);
getApiKeyForModelMock.mockImplementation(async (params?: { profileId?: string }) => ({
apiKey: "test-oauth",
mode: "oauth",
source: `profile:${params?.profileId ?? "openai:work"}`,
profileId: params?.profileId ?? "openai:work",
}));
const result = await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
provider: "openai",
model: "gpt-5.5",
authProfileId: "openai:work",
authProfileIdSource: "user",
config: {
models: {
providers: {
openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
},
},
} as never,
});
expect(result.ok).toBe(true);
expect(mockCallArg(resolveModelMock)).toBe("openai");
const sessionOptions = expectRecordFields(mockCallArg(createAgentSessionMock), {});
expectRecordFields(sessionOptions.model, {
provider: "openai",
id: "gpt-5.5",
api: "openai-chatgpt-responses",
baseUrl: "https://chatgpt.com/backend-api/codex",
});
expect(buildAgentRuntimePlanMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
modelId: "gpt-5.5",
modelApi: "openai-chatgpt-responses",
sessionAuthProfileId: "openai:work",
sessionAuthProfileSource: "user",
modelRoute: expect.objectContaining({
api: "openai-chatgpt-responses",
baseUrl: "https://chatgpt.com/backend-api/codex",
authRequirement: "subscription",
}),
}),
);
expectRecordFields(mockCallArg(resolveContextWindowInfoMock), {
provider: "openai",
modelId: "gpt-5.5",
});
});
it("keeps compaction fallback selection ephemeral", async () => {
sessionCompactImpl
.mockRejectedValueOnce(Object.assign(new Error("400 invalid request body"), { status: 400 }))
.mockResolvedValueOnce({
summary: "fallback summary",
firstKeptEntryId: "entry-fallback",
tokensBefore: 120,
details: { ok: true },
});
const config = {
agents: {
defaults: {
model: {
primary: "openai/gpt-primary",
fallbacks: ["anthropic/claude-fallback"],
},
},
},
sessions: {
entries: {
[TEST_SESSION_KEY]: {
modelProvider: "openai",
model: "gpt-primary",
},
},
},
};
const configBefore = structuredClone(config);
const result = await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
provider: "openai",
model: "gpt-primary",
config: config as never,
});
expect(result.ok).toBe(true);
expect(result.result?.summary).toBe("fallback summary");
const primaryCall = findMockCall(
resolveModelMock,
([provider, modelId]) => provider === "openai" && modelId === "gpt-primary",
);
expect(primaryCall[2]).toBeTypeOf("string");
if (primaryCall[3] === undefined) {
throw new Error("Expected primary resolve-model options");
}
const fallbackCall = findMockCall(
resolveModelMock,
([provider, modelId]) => provider === "anthropic" && modelId === "claude-fallback",
);
expect(fallbackCall[2]).toBeTypeOf("string");
if (fallbackCall[3] === undefined) {
throw new Error("Expected fallback resolve-model options");
}
expect(config).toEqual(configBefore);
});
it("preserves explicit compaction.model behavior without session fallback", async () => {
sessionCompactImpl.mockRejectedValueOnce(
Object.assign(new Error("400 invalid request body"), { status: 400 }),
);
const result = await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
provider: "openai",
model: "gpt-primary",
config: {
agents: {
defaults: {
model: {
primary: "openai/gpt-primary",
fallbacks: ["anthropic/claude-fallback"],
},
compaction: {
model: "azure/compact-primary",
},
},
},
} as never,
});
expect(result.ok).toBe(false);
expect(resolveModelMock).toHaveBeenCalledTimes(1);
expect(mockCallArg(resolveModelMock)).toBe("azure");
expect(mockCallArg(resolveModelMock, 0, 1)).toBe("compact-primary");
expect(mockCallArg(resolveModelMock, 0, 2)).toBeTypeOf("string");
if (mockCallArg(resolveModelMock, 0, 3) === undefined) {
throw new Error("Expected resolve-model options");
}
});
it("preserves compaction failure status and code metadata", async () => {
sessionCompactImpl.mockRejectedValueOnce(
Object.assign(new Error("primary compaction rate limited"), {
status: 429,
code: "rate_limit_exceeded",
}),
);
const result = await compactEmbeddedAgentSessionDirect({
sessionId: "session-1",
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
workspaceDir: join(TEST_WORKSPACE_DIR, "workspace"),
provider: "openai",
model: "gpt-primary",
config: {
agents: {
defaults: {
compaction: {
model: "openai/gpt-primary",
},
},
},
} as never,
});
expectRecordFields(result, {
ok: false,
compacted: false,
});
expect(result.failure).toEqual({
reason: "rate_limit",
status: 429,
code: "rate_limit_exceeded",
rawError: "primary compaction rate limited",
});
});
it("emits internal + plugin compaction hooks with counts", async () => {
hookRunner.hasHooks.mockReturnValue(true);
await runCompactionHooks({
sessionKey: TEST_SESSION_KEY,
messageProvider: "telegram",
});
expectRecordFields(sessionHook("compact:before"), {
type: "session",
action: "compact:before",
});
const beforeContext = sessionHook("compact:before")?.context;
const afterContext = sessionHook("compact:after")?.context;
expectRecordFields(beforeContext, {
messageCount: 2,
tokenCount: 20,
messageCountOriginal: 2,
tokenCountOriginal: 20,
});
expectRecordFields(afterContext, {
messageCount: 1,
compactedCount: 1,
});
expect(afterContext?.compactedCount).toBe(
(beforeContext?.messageCountOriginal as number) - (afterContext?.messageCount as number),
);
expect(hookRunner.runBeforeCompaction).toHaveBeenCalledWith(
expectRecordFields(mockCallArg(hookRunner.runBeforeCompaction), {
messageCount: 2,
tokenCount: 20,
}),
expectRecordFields(mockCallArg(hookRunner.runBeforeCompaction, 0, 1), {
sessionKey: "agent:main:session-1",
messageProvider: "telegram",
}),
);
expect(hookRunner.runAfterCompaction).toHaveBeenCalledWith(
{
messageCount: 1,
tokenCount: 10,
compactedCount: 1,
sessionFile: TEST_SESSION_FILE,
},
expectRecordFields(mockCallArg(hookRunner.runAfterCompaction, 0, 1), {
sessionKey: "agent:main:session-1",
messageProvider: "telegram",
}),
);
});
it("applies validated transcript before hooks even when it becomes empty", async () => {
hookRunner.hasHooks.mockReturnValue(true);
const { sanitizeSessionHistory } = await import("./replay-history.js");
vi.mocked(sanitizeSessionHistory).mockResolvedValueOnce([]);
const result = await compactEmbeddedAgentSessionDirect(wrappedCompactionArgs());
expect(result.ok).toBe(true);
const beforeContext = sessionHook("compact:before")?.context;
expectRecordFields(beforeContext, {
messageCountOriginal: 0,
tokenCountOriginal: 0,
messageCount: 0,
tokenCount: 0,
});
});
it("forwards internal compaction hook messages to the caller", async () => {
const onHookMessages = vi.fn();
triggerInternalHookMock.mockImplementation((event: unknown) => {
const hookEvent = event as { action?: string; messages?: string[] };
hookEvent.messages?.push(`${hookEvent.action} notice`);
});
const beforeMetrics = compactTesting.buildBeforeCompactionHookMetrics({
originalMessages: sessionMessages.slice(1) as AgentMessage[],
currentMessages: sessionMessages.slice(1) as AgentMessage[],
estimateTokensFn: estimateTokensMock as (message: AgentMessage) => number,
});
const hookState = await compactTesting.runBeforeCompactionHooks({
hookRunner,
sessionId: "session-1",
sessionKey: "agent:main:session-1",
sessionAgentId: "main",
workspaceDir: TEST_WORKSPACE_DIR,
metrics: beforeMetrics,
onHookMessages,
});
await compactTesting.runAfterCompactionHooks({
hookRunner,
sessionId: "session-1",
sessionAgentId: "main",
hookSessionKey: hookState.hookSessionKey,
missingSessionKey: hookState.missingSessionKey,
workspaceDir: TEST_WORKSPACE_DIR,
messageCountAfter: 1,
tokensAfter: 10,
compactedCount: 1,
sessionFile: TEST_SESSION_KEY,
onHookMessages,
});
expect(onHookMessages).toHaveBeenNthCalledWith(1, {
phase: "before",
messages: ["compact:before notice"],
sessionId: "session-1",
sessionKey: "agent:main:session-1",
});
expect(onHookMessages).toHaveBeenNthCalledWith(2, {
phase: "after",
messages: ["compact:after notice"],
sessionId: "session-1",
sessionKey: "agent:main:session-1",
});
});
it("emits a transcript update after successful compaction", async () => {
const listener = vi.fn();
const cleanup = onInternalSessionTranscriptUpdate(listener);
try {
await compactTesting.runPostCompactionSideEffects({
sessionKey: TEST_SESSION_KEY,
sessionFile: ` ${TEST_SESSION_KEY} `,
});
expect(listener).toHaveBeenCalledTimes(1);
expect(listener).toHaveBeenCalledWith({
sessionFile: TEST_SESSION_KEY,
sessionKey: TEST_SESSION_KEY,
});
} finally {
cleanup();
}
});
it("preserves tokensAfter when full-session context exceeds result.tokensBefore", () => {
estimateTokensMock.mockImplementation((message: unknown) => {
const role = (message as { role?: string }).role;
if (role === "user") {
return 30;
}
if (role === "assistant") {
return 20;
}
return 5;
});
const tokensAfter = compactTesting.estimateTokensAfterCompaction({
messagesAfter: [{ role: "user", content: "kept ask" }] as AgentMessage[],
fullSessionTokensBefore: 55,
estimateTokensFn: estimateTokensMock as (message: AgentMessage) => number,
});
expect(tokensAfter).toBe(30);
});
it("treats pre-compaction token estimation failures as a no-op sanity check", () => {
estimateTokensMock.mockImplementation((message: unknown) => {
const role = (message as { role?: string }).role;
if (role === "assistant") {
throw new Error("legacy message");
}
if (role === "user") {
return 30;
}
return 5;
});
const beforeMetrics = compactTesting.buildBeforeCompactionHookMetrics({
originalMessages: sessionMessages as AgentMessage[],
currentMessages: sessionMessages as AgentMessage[],
estimateTokensFn: estimateTokensMock as (message: AgentMessage) => number,
});
const tokensAfter = compactTesting.estimateTokensAfterCompaction({
messagesAfter: [{ role: "user", content: "kept ask" }] as AgentMessage[],
fullSessionTokensBefore: 0,
estimateTokensFn: estimateTokensMock as (message: AgentMessage) => number,
});
expect(beforeMetrics.tokenCountOriginal).toBeUndefined();
expect(beforeMetrics.tokenCountBefore).toBeUndefined();
expect(tokensAfter).toBe(30);
});
it("skips sync in await mode when postCompactionForce is false", async () => {
const sync = vi.fn(async () => {});
getMemorySearchManagerMock.mockResolvedValue({ manager: { sync } });
resolveMemorySearchConfigMock.mockReturnValue({
sources: ["sessions"],
sync: {
sessions: {
postCompactionForce: false,
},
},
});
await compactTesting.runPostCompactionSideEffects({
config: compactionConfig("await"),
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_FILE,
});
const resolveAgentArg = mockCallArg(resolveSessionAgentIdMock) as Record<string, unknown>;
expectRecordFields(resolveAgentArg, { sessionKey: TEST_SESSION_KEY });
expect(resolveAgentArg.config).toBeTypeOf("object");
expect(getMemorySearchManagerMock).not.toHaveBeenCalled();
expect(sync).not.toHaveBeenCalled();
});
it("awaits post-compaction memory sync in await mode when postCompactionForce is true", async () => {
const syncStarted = createDeferred<PostCompactionSyncParams>();
const syncRelease = createDeferred();
const sync = vi.fn<PostCompactionSync>(async (params) => {
syncStarted.resolve(params as PostCompactionSyncParams);
await syncRelease.promise;
});
getMemorySearchManagerMock.mockResolvedValue({ manager: { sync } });
let settled = false;
const resultPromise = compactTesting.runPostCompactionSideEffects({
config: compactionConfig("await"),
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_FILE,
});
void resultPromise.then(() => {
settled = true;
});
await expect(syncStarted.promise).resolves.toEqual({
archiveFiles: [TEST_SESSION_FILE],
reason: "post-compaction",
});
expect(settled).toBe(false);
syncRelease.resolve(undefined);
await resultPromise;
expect(settled).toBe(true);
});
it("skips post-compaction memory sync when the mode is off", async () => {
const sync = vi.fn(async () => {});
getMemorySearchManagerMock.mockResolvedValue({ manager: { sync } });
await compactTesting.runPostCompactionSideEffects({
config: compactionConfig("off"),
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_FILE,
});
expect(resolveSessionAgentIdMock).not.toHaveBeenCalled();
expect(getMemorySearchManagerMock).not.toHaveBeenCalled();
expect(sync).not.toHaveBeenCalled();
});
it("fires post-compaction memory sync without awaiting it in async mode", async () => {
const sync = vi.fn<PostCompactionSync>(async () => {});
const managerRequested = createDeferred();
const managerGate = createDeferred<{ manager: { sync: PostCompactionSync } }>();
const syncStarted = createDeferred<PostCompactionSyncParams>();
sync.mockImplementation(async (params) => {
syncStarted.resolve(params as PostCompactionSyncParams);
});
getMemorySearchManagerMock.mockImplementation(async () => {
managerRequested.resolve(undefined);
return await managerGate.promise;
});
let settled = false;
const resultPromise = compactTesting.runPostCompactionSideEffects({
config: compactionConfig("async"),
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_FILE,
});
await managerRequested.promise;
void resultPromise.then(() => {
settled = true;
});
await resultPromise;
expect(getMemorySearchManagerMock).toHaveBeenCalledTimes(1);
expect(settled).toBe(true);
expect(sync).not.toHaveBeenCalled();
managerGate.resolve({ manager: { sync } });
await expect(syncStarted.promise).resolves.toEqual({
archiveFiles: [TEST_SESSION_FILE],
reason: "post-compaction",
});
});
it("compacts an overflow transcript anchored by a compaction summary", async () => {
sessionMessages.splice(
0,
sessionMessages.length,
{
role: "compactionSummary",
summary: "The user asked for a long-running repository audit.",
timestamp: 1,
},
{
role: "assistant",
content: [{ type: "toolCall", id: "call-1", name: "exec", arguments: {} }],
timestamp: 2,
},
makeTextToolResult("call-1", "exec", "audit output", false, 3),
);
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({ trigger: "overflow" }),
);
expect(result).toMatchObject({ ok: true, compacted: true });
expect(sessionCompactImpl).toHaveBeenCalledOnce();
});
it.each(["overflow", "budget", "timeout_recovery"] as const)(
"uses caller-owned automatic recovery once for default-mode %s compaction",
async (trigger) => {
hookRunner.hasHooks.mockReturnValue(true);
resolveEffectiveCompactionModeMock.mockReturnValue("default");
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({
trigger,
config: { agents: { defaults: { compaction: { mode: "default" } } } },
}),
);
expect(result).toMatchObject({ ok: true, compacted: true });
expect(sessionAutomaticCompactionMock).toHaveBeenCalledWith(
TEST_CUSTOM_INSTRUCTIONS,
trigger === "overflow" ? "unresolved" : undefined,
undefined,
);
expect(sessionManualCompactionMock).not.toHaveBeenCalled();
expect(buildEmbeddedExtensionFactoriesMock).toHaveBeenCalledOnce();
expect(hookRunner.runBeforeCompaction).toHaveBeenCalledOnce();
expect(hookRunner.runAfterCompaction).toHaveBeenCalledOnce();
},
);
it.each(["overflow", "budget"] as const)(
"carries the pending request into safeguard %s recovery after endpoint fallback",
async (trigger) => {
const { attachCompactionAccountingRecorder } =
await import("./run/compaction-accounting-bridge.js");
const contextEngineRuntimeContext = {};
if (trigger === "budget") {
attachCompactionAccountingRecorder(contextEngineRuntimeContext, {
pendingRequestState: "unresolved",
});
}
resolveEffectiveCompactionModeMock.mockReturnValue("safeguard");
const result = await compactEmbeddedAgentSessionDirect(
wrappedCompactionArgs({ trigger, contextEngineRuntimeContext }),
);
expect(result).toMatchObject({ ok: true, compacted: true });
expect(sessionAutomaticCompactionMock).toHaveBeenCalledWith(
TEST_CUSTOM_INSTRUCTIONS,
"unresolved",
"none",
);
expect(sessionManualCompactionMock).not.toHaveBeenCalled();
},
);
it("skips compaction when the transcript only contains boilerplate replies and tool output", () => {
const messages = [
{ role: "user", content: "<b>HEARTBEAT_OK</b>", timestamp: 1 },
makeTextToolResult("t1", "exec", "checked", false, 2),
] as AgentMessage[];
expect(compactTesting.containsRealConversationMessages(messages)).toBe(false);
});
it("skips compaction when the transcript only contains heartbeat boilerplate and reasoning blocks", () => {
const messages = [
{ role: "user", content: "<b>HEARTBEAT_OK</b>", timestamp: 1 },
{
role: "assistant",
content: [{ type: "thinking", thinking: "checking" }],
timestamp: 2,
},
] as AgentMessage[];
expect(compactTesting.containsRealConversationMessages(messages)).toBe(false);
});
it("carries the prepared provider reconciler into direct compaction", async () => {
mockResolvedModel();
const reconcile = vi.fn(async () => undefined);
const { resolvePreparedProviderRuntimeHandle } = await import("../runtime-plan/build.js");
vi.mocked(resolvePreparedProviderRuntimeHandle).mockImplementationOnce((params) => ({
provider: params.provider,
modelId: params.modelId,
workspaceDir: params.workspaceDir,
prepared: true,
plugin: { id: params.provider, label: "Fixture", auth: [], reconcileLocalService: reconcile },
}));
await expect(compactEmbeddedAgentSessionDirect(wrappedCompactionArgs())).resolves.toMatchObject(
{ ok: true },
);
const streamRegistration = mockCallArg(registerProviderStreamForModelMock) as {
model: object;
};
expect(getModelProviderLocalServiceReconciler(streamRegistration.model)).toBe(reconcile);
expect(getModelProviderRuntimePluginHandle(streamRegistration.model)).toBe(
buildAgentRuntimePlanMock.mock.calls[0]?.[0].providerRuntimeHandle,
);
});
it("aborts in-flight compaction when the caller abort signal fires", async () => {
const { compactWithSafetyTimeout } = await vi.importActual<
typeof import("./compaction-safety-timeout.js")
>("./compaction-safety-timeout.js");
const controller = new AbortController();
const compactStarted = createDeferred();
const resultPromise = compactWithSafetyTimeout(
async () => {
compactStarted.resolve(undefined);
return await new Promise<never>(() => {});
},
30_000,
{
abortSignal: controller.signal,
onCancel: () => {
sessionAbortCompactionMock();
},
},
);
await compactStarted.promise;
controller.abort(new Error("request timed out"));
await expect(resultPromise).rejects.toThrow("request timed out");
expect(sessionAbortCompactionMock).toHaveBeenCalledTimes(1);
});
});
describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => {
async function acquiredPreparedModelRuntime() {
const pendingLease = acquireAgentRunPreparedModelRuntimeMock.mock.results[0]?.value;
if (!pendingLease) {
throw new Error("expected prepared model runtime acquisition");
}
return (await pendingLease).snapshot;
}
function expectedNativeCompactionOptions(
nativeCompactionRequest: "after_context_engine" | "required_preflight",
) {
return { nativeCompactionRequest, preparedModelRuntime: expect.any(Object) };
}
function mockQueuedRouteAwareModel(
defaultApi: "openai-responses" | "openai-chatgpt-responses" = "openai-responses",
) {
resolveModelMock.mockImplementation(
(provider = "openai", modelId = "gpt-5.5", _agentDir?: string, cfg?: unknown) => {
const providerConfig = (
cfg as
| {
models?: {
providers?: Record<string, { api?: string; baseUrl?: string }>;
};
}
| undefined
)?.models?.providers?.[provider];
const api = providerConfig?.api ?? defaultApi;
const subscription = api === "openai-chatgpt-responses";
return {
logicalRef: { provider, model: modelId },
model: {
provider,
id: modelId,
api,
baseUrl:
providerConfig?.baseUrl ??
(subscription
? "https://chatgpt.com/backend-api/codex"
: "https://api.openai.com/v1"),
contextWindow: subscription ? 272_000 : 1_050_000,
input: [],
},
error: null,
authStorage: { setRuntimeApiKey: vi.fn() },
modelRegistry: {},
};
},
);
}
beforeEach(() => {
hookRunner.hasHooks.mockReset();
hookRunner.runBeforeCompaction.mockReset();
hookRunner.runAfterCompaction.mockReset();
resolveContextEngineMock.mockReset();
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: true },
compact: contextEngineCompactMock,
});
contextEngineCompactMock.mockReset();
contextEngineCompactMock.mockResolvedValue({
ok: true,
compacted: true,
reason: undefined,
result: { summary: "engine-summary", tokensBefore: 120, tokensAfter: 50 },
});
mockResolvedModel();
mockQueuedRouteAwareModel();
});
it.each([
["native", "session"],
["native", "global"],
["native", "injected"],
["context-engine", "session"],
["context-engine", "global"],
["context-engine", "injected"],
] as const)("cancels %s compaction before the %s queue admits it", async (route, blocked) => {
const queue = await vi.importActual<typeof import("../../process/command-queue.js")>(
"../../process/command-queue.js",
);
const controller = new AbortController();
const queued = createDeferred();
const release = createDeferred();
const globalLane = `compaction-test:${route}:${blocked}`;
const blockedLane =
blocked === "session"
? "test-session-lane"
: blocked === "global"
? "test-global-lane"
: globalLane;
const blocker = queue.enqueueCommandInLane(blockedLane, () => release.promise);
const enqueue = <T>(
lane: string,
task: () => T | Promise<T>,
options?: CommandQueueEnqueueOptions,
) =>
queue.enqueueCommandInLane(lane, async () => task(), {
...options,
onQueued: () => {
options?.onQueued?.();
if (lane === blockedLane) {
queued.resolve();
}
},
});
enqueueCommandInLaneMock.mockImplementation(
(lane, task, ...[options]: [CommandQueueEnqueueOptions?]) =>
enqueue(String(lane), task, options),
);
const overrides = {
abortSignal: controller.signal,
lane: globalLane,
enqueue:
blocked === "injected"
? <T>(task: () => Promise<T>, options?: CommandQueueEnqueueOptions) =>
enqueue(globalLane, task, options)
: undefined,
};
if (route === "native") {
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
});
maybeCompactAgentHarnessSessionMock.mockResolvedValue({ ok: true, compacted: false });
}
const params =
route === "native"
? await nativeCompactionArgs({
...overrides,
agentHarnessId: "codex",
provider: "openai",
model: "gpt-5.5",
})
: wrappedCompactionArgs(overrides);
const pending = compactEmbeddedAgentSession(params);
try {
await Promise.race([
queued.promise,
pending.then((result) => {
throw new Error(
`Compaction did not reach its blocked queue: ${JSON.stringify({ result, lanes: enqueueCommandInLaneMock.mock.calls.map((call) => call[0]) })}`,
);
}),
]);
expect(contextEngineCompactMock).not.toHaveBeenCalled();
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
controller.abort(new Error("Foreground turn preempted queued maintenance"));
expect(queue.getCommandLaneSnapshot(blockedLane).queuedCount).toBe(0);
await expect(pending).resolves.toMatchObject({
ok: false,
compacted: false,
reason: "compaction aborted",
});
} finally {
release.resolve();
await Promise.allSettled([blocker, pending]);
}
expect(contextEngineCompactMock).not.toHaveBeenCalled();
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
});
it("settles optional session maintenance before manual compaction", async () => {
const owner = createSessionMaintenanceOwner({
sessionKey: TEST_SESSION_KEY,
preemptible: true,
});
const started = createDeferred();
const interrupted = createDeferred();
const release = createDeferred();
const maintenance = owner.track(
owner.run(async () => {
owner.signal.addEventListener("abort", () => interrupted.resolve(), { once: true });
started.resolve();
await release.promise;
}),
);
await started.promise;
const pending = compactEmbeddedAgentSession(wrappedCompactionArgs({ trigger: "manual" }));
try {
await expect(
Promise.race([
interrupted.promise.then(() => "preempted"),
pending.then(() => "compacted before maintenance settled"),
]),
).resolves.toBe("preempted");
expect(contextEngineCompactMock).not.toHaveBeenCalled();
release.resolve();
await expect(pending).resolves.toMatchObject({ ok: true, compacted: true });
expect(contextEngineCompactMock).toHaveBeenCalledOnce();
} finally {
release.resolve();
await Promise.allSettled([maintenance, pending]);
}
});
it("drains committed maintenance before rejecting a manual request pinned to its predecessor", async () => {
const { acceptCompactionSuccessor } = await import("./compaction-successor.js");
const params = wrappedCompactionArgs({ trigger: "manual" });
const predecessor = expectDefined(
loadSessionEntryReadOnly(params.sessionTarget),
"predecessor",
);
const successorTarget = { ...params.sessionTarget, sessionId: "maintenance-successor" };
const owner = createSessionMaintenanceOwner({
sessionKey: TEST_SESSION_KEY,
preemptible: true,
});
const committed = createDeferred();
const interrupted = createDeferred();
const releaseCleanup = createDeferred();
const events: string[] = [];
owner.signal.addEventListener("abort", () => interrupted.resolve(), { once: true });
const maintenance = owner.track(
owner.run(async () => {
await acceptCompactionSuccessor({
currentTarget: params.sessionTarget,
expectedEntry: {
sessionId: predecessor.sessionId,
lifecycleRevision: predecessor.lifecycleRevision,
activeWriterRunId: predecessor.activeWriterRunId,
},
assertActive: owner.assertCurrent,
result: {
ok: true,
compacted: true,
result: { sessionTarget: successorTarget, tokensBefore: 90_000, tokensAfter: 100 },
},
});
SessionManager.open(successorTarget).appendMessage({
role: "user",
content: [{ type: "text", text: "Retain this successor history." }],
timestamp: 1,
});
committed.resolve();
await releaseCleanup.promise;
events.push("maintenance cleanup finished");
}),
);
await Promise.race([committed.promise, maintenance]);
const transcriptBefore = await loadTranscriptEvents(successorTarget);
const acceptedEntry = loadSessionEntryReadOnly(successorTarget);
const pending = compactEmbeddedAgentSession(params);
const settled = pending.then(
() => events.push("manual resolved"),
() => events.push("manual rejected"),
);
try {
await expect(
Promise.race([
interrupted.promise.then(() => "preempted"),
settled.then(() => "manual settled before cleanup"),
]),
).resolves.toBe("preempted");
expect(events).toEqual([]);
expect(contextEngineCompactMock).not.toHaveBeenCalled();
releaseCleanup.resolve();
await expect(pending).rejects.toThrow("session writer claim changed");
await settled;
expect(events).toEqual(["maintenance cleanup finished", "manual rejected"]);
expect(contextEngineCompactMock).not.toHaveBeenCalled();
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
expect(runCliAgentMock).not.toHaveBeenCalled();
expect(loadSessionEntryReadOnly(successorTarget)).toEqual(acceptedEntry);
expect(await loadTranscriptEvents(successorTarget)).toEqual(transcriptBefore);
} finally {
releaseCleanup.resolve();
await Promise.allSettled([maintenance, pending, settled]);
}
});
it("reports target-only cancellation during prepared runtime lease admission", async () => {
const sourceController = new AbortController();
const admissionStarted = createDeferred<AbortSignal | undefined>();
acquireAgentRunPreparedModelRuntimeMock.mockImplementationOnce((async (
_input: Record<string, unknown>,
options?: { abortSignal?: AbortSignal },
): Promise<never> => {
const signal = options?.abortSignal;
admissionStarted.resolve(signal);
if (!signal) {
throw new Error("prepared runtime lease admission did not receive the caller signal");
}
return await new Promise<never>((_resolve, reject) => {
signal.addEventListener(
"abort",
() => {
const error = new Error("Prepared model runtime lease admission aborted", {
cause: signal.reason,
});
error.name = "AbortError";
reject(error);
},
{ once: true },
);
});
}) as never);
const pending = compactEmbeddedAgentSession(
wrappedCompactionArgs({ abortSignal: sourceController.signal, trigger: "manual" }),
);
const admittedSignal = expectDefined(
await admissionStarted.promise,
"prepared runtime lease admission signal",
);
expect(abortEmbeddedAgentRun(TEST_SESSION_ID)).toBe(true);
await expect(pending).resolves.toMatchObject({
ok: false,
compacted: false,
reason: "compaction aborted",
});
expect(sourceController.signal.aborted).toBe(false);
expect(admittedSignal.aborted).toBe(true);
expect(isEmbeddedAgentRunHandleActive(TEST_SESSION_ID)).toBe(false);
expect(resolveContextEngineMock).not.toHaveBeenCalled();
});
it("releases the prepared runtime lease when host authority expires after admission", async () => {
const { AsyncWorkScope } = await import("../../shared/async-work-scope.js");
const parent = new AsyncWorkScope();
const admissionStarted = createDeferred();
const releaseAdmission = createDeferred();
const releaseLease = vi.fn(async () => {});
const defaultAcquire = expectDefined(
acquireAgentRunPreparedModelRuntimeMock.getMockImplementation(),
"default prepared runtime acquisition mock",
);
let hostActive = true;
acquireAgentRunPreparedModelRuntimeMock.mockImplementationOnce((async (
input: Parameters<typeof acquireAgentRunPreparedModelRuntimeMock>[0],
) => {
admissionStarted.resolve(undefined);
await releaseAdmission.promise;
const lease = await defaultAcquire(input);
return { ...lease, [Symbol.asyncDispose]: releaseLease };
}) as never);
const pending = parent.run(() =>
compactEmbeddedAgentSession(wrappedCompactionArgs(), {
assertActive: () => {
if (!hostActive) {
throw new Error("queued compaction host authority expired");
}
},
}),
);
await admissionStarted.promise;
hostActive = false;
releaseAdmission.resolve(undefined);
try {
await expect(pending).rejects.toThrow("queued compaction host authority expired");
} finally {
await AsyncWorkScope.runWhenAllIdle(
() => [parent],
() => parent.drain(),
);
}
expect(releaseLease).toHaveBeenCalledTimes(1);
expect(resolveContextEngineMock).not.toHaveBeenCalled();
});
it("stops preparation when host authority expires during context-engine resolution", async () => {
const resolutionStarted = createDeferred();
const releaseResolution = createDeferred();
let hostActive = true;
resolveContextEngineMock.mockImplementationOnce(async () => {
resolutionStarted.resolve(undefined);
await releaseResolution.promise;
return {
info: { ownsCompaction: true },
compact: contextEngineCompactMock,
};
});
const pending = compactEmbeddedAgentSession(wrappedCompactionArgs(), {
assertActive: () => {
if (!hostActive) {
throw new Error("queued compaction host authority expired");
}
},
});
await resolutionStarted.promise;
hostActive = false;
releaseResolution.resolve(undefined);
await expect(pending).rejects.toThrow("queued compaction host authority expired");
expect(resolveModelAsyncMock).not.toHaveBeenCalled();
expect(selectAgentHarnessForPreparedModelProvidersMock).not.toHaveBeenCalled();
expect(contextEngineCompactMock).not.toHaveBeenCalled();
});
it("reports budget compaction cancellation during context-engine resolution", async () => {
const sourceController = new AbortController();
const resolutionStarted = createDeferred();
const releaseResolution = createDeferred();
resolveContextEngineMock.mockImplementationOnce(async () => {
resolutionStarted.resolve(undefined);
await releaseResolution.promise;
return {
info: { ownsCompaction: true },
compact: contextEngineCompactMock,
};
});
const pending = compactEmbeddedAgentSession(
wrappedCompactionArgs({
abortSignal: sourceController.signal,
trigger: "budget",
}),
);
await resolutionStarted.promise;
sourceController.abort(new Error("request timed out"));
releaseResolution.resolve(undefined);
await expect(pending).resolves.toEqual({
ok: false,
compacted: false,
reason: "compaction aborted",
});
expect(resolveModelAsyncMock).not.toHaveBeenCalled();
expect(selectAgentHarnessForPreparedModelProvidersMock).not.toHaveBeenCalled();
expect(contextEngineCompactMock).not.toHaveBeenCalled();
expect(enqueueCommandInLaneMock).not.toHaveBeenCalled();
});
it("resolves the durable session key before invoking an owning context engine", async () => {
const dir = await realpath(await mkdtemp(join(tmpdir(), "openclaw-compaction-session-key-")));
const storePath = join(dir, "sessions.json");
const sessionId = "9d6c8436-7cb2-4bd5-a302-e33305bfc8c4";
const sessionKey = "agent:main:telegram:direct:reporter";
try {
await upsertSessionEntryCore(
{ agentId: "main", sessionKey, storePath },
{ sessionId, updatedAt: 1 },
);
hookRunner.hasHooks.mockReturnValue(true);
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
agentId: "main",
config: { session: { store: storePath } },
sessionFile: "",
sessionId,
sessionKey: undefined,
sessionTarget: undefined,
}),
);
expect(result.ok).toBe(true);
expectRecordFields(mockCallArg(contextEngineCompactMock), {
sessionId,
sessionKey,
});
expectRecordFields(
(mockCallArg(contextEngineCompactMock) as { sessionTarget?: unknown }).sessionTarget,
{ agentId: "main", sessionId, sessionKey, storePath },
);
expectRecordFields(mockCallArg(hookRunner.runBeforeCompaction, 0, 1), { sessionKey });
expectRecordFields(mockCallArg(hookRunner.runAfterCompaction, 0, 1), { sessionKey });
} finally {
closeOpenClawAgentDatabasesForTest();
await rm(dir, { force: true, recursive: true });
}
});
it("does not require an implicit default owner for queued compaction", async () => {
await upsertSessionEntryCore(
{
agentId: "marie-clawndo",
sessionKey: "agent:marie-clawndo:dashboard:session-1",
storePath: join(TEST_WORKSPACE_DIR, "sessions.json"),
},
{ sessionId: TEST_SESSION_ID, updatedAt: 1 },
);
resolveDefaultAgentDirMock.mockImplementation(() => {
throw new Error("ambiguous default agent");
});
resolveSessionAgentIdsMock.mockReturnValue({
defaultAgentId: "marie-clawndo",
sessionAgentId: "marie-clawndo",
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
agentId: "marie-clawndo",
config: {
agents: {
ownership: "explicit",
list: [{ id: "main" }, { id: "marie-clawndo" }],
},
},
sessionKey: "agent:marie-clawndo:dashboard:session-1",
sessionTarget: {
agentId: "marie-clawndo",
sessionId: TEST_SESSION_ID,
sessionKey: "agent:marie-clawndo:dashboard:session-1",
storePath: join(TEST_WORKSPACE_DIR, "sessions.json"),
},
}),
);
expect(result.ok).toBe(true);
expect(acquireAgentRunPreparedModelRuntimeMock).toHaveBeenCalledWith(
expect.objectContaining({ agentId: "marie-clawndo" }),
expect.objectContaining({ abortSignal: undefined }),
);
});
it("disposes the context engine once when route materialization rejects", async () => {
const dispose = vi.fn(async () => {});
const authStorage = { setRuntimeApiKey: vi.fn() };
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: true },
compact: contextEngineCompactMock,
dispose,
} as never);
resolveModelAsyncMock
.mockImplementationOnce(async (provider, modelId) => ({
logicalRef: { provider, model: modelId },
model: {
provider: "openai",
id: "fake",
api: "openai-chatgpt-responses",
baseUrl: "https://chatgpt.com/backend-api/codex",
input: [],
},
error: null,
authStorage,
modelRegistry: {},
}))
.mockRejectedValueOnce(new Error("route materialization failed"));
await expect(
compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "fake",
runtimeAuthPlan: {
providerForAuth: "openai",
authProfileProviderForAuth: "openai",
selectedAuthMode: "api-key",
modelRoute: {
provider: "openai",
modelId: "fake",
api: "openai-responses",
baseUrl: "https://api.openai.com/v1",
authRequirement: "api-key",
requestTransportOverrides: "none",
},
},
}),
),
).rejects.toThrow("route materialization failed");
const snapshot = await acquiredPreparedModelRuntime();
expect(
(mockCallArg(resolveModelAsyncMock, 1, 4) as { preparedModelRuntime?: unknown })
.preparedModelRuntime,
).toBe(snapshot);
expect(dispose).toHaveBeenCalledTimes(1);
expect(enqueueCommandInLaneMock).not.toHaveBeenCalled();
});
it("stops preparation when host authority expires during model resolution before route rematerialization", async () => {
const modelResolutionStarted = createDeferred();
const releaseModelResolution = createDeferred();
const authStorage = { setRuntimeApiKey: vi.fn() };
let hostActive = true;
resolveModelAsyncMock.mockImplementationOnce(async (provider, modelId) => {
modelResolutionStarted.resolve(undefined);
await releaseModelResolution.promise;
return {
logicalRef: { provider, model: modelId },
model: {
provider: "openai",
id: "fake",
api: "openai-chatgpt-responses",
baseUrl: "https://chatgpt.com/backend-api/codex",
input: [],
},
error: null,
authStorage,
modelRegistry: {},
};
});
const pending = compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "fake",
runtimeAuthPlan: {
providerForAuth: "openai",
authProfileProviderForAuth: "openai",
selectedAuthMode: "api-key",
modelRoute: {
provider: "openai",
modelId: "fake",
api: "openai-responses",
baseUrl: "https://api.openai.com/v1",
authRequirement: "api-key",
requestTransportOverrides: "none",
},
},
}),
{
assertActive: () => {
if (!hostActive) {
throw new Error("queued compaction host authority expired");
}
},
},
);
await modelResolutionStarted.promise;
hostActive = false;
releaseModelResolution.resolve(undefined);
await expect(pending).rejects.toThrow("queued compaction host authority expired");
expect(resolveModelAsyncMock).toHaveBeenCalledTimes(1);
expect(selectAgentHarnessForPreparedModelProvidersMock).not.toHaveBeenCalled();
expect(contextEngineCompactMock).not.toHaveBeenCalled();
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
expect(enqueueCommandInLaneMock).not.toHaveBeenCalled();
});
it("disposes the context engine safely when primary native compaction throws", async () => {
const dispose = vi.fn(async () => {
throw new Error("dispose failed");
});
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
dispose,
} as never);
maybeCompactAgentHarnessSessionMock.mockRejectedValueOnce(
new Error("native compaction failed"),
);
await expect(
compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
}),
),
).rejects.toThrow("native compaction failed");
expect(dispose).toHaveBeenCalledTimes(1);
expect(enqueueCommandInLaneMock).toHaveBeenCalledOnce();
});
it("reports native harness compaction ownership", async () => {
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
});
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: { summary: "harness", firstKeptEntryId: "entry-1", tokensBefore: 100 },
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({ provider: "openai", model: "gpt-5.5", agentHarnessId: "codex" }),
);
expect(result.compactionKind).toBe("native-harness");
expect(contextEngineCompactMock).not.toHaveBeenCalled();
});
it.each([
{ outcome: "waits for the active session lane", writerRunId: undefined },
{ outcome: "rejects a replaced writer claim", writerRunId: "replacement-run" },
])("shipped /compact $outcome before native compaction", async ({ writerRunId }) => {
const command = await import("../../auto-reply/reply/commands-compact.test-support.js");
vi.mocked(command.compactEmbeddedAgentSession).mockReset();
await nativeCompactionArgs({ agentHarnessId: "codex" });
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
});
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: { summary: "harness", firstKeptEntryId: "entry-1", tokensBefore: 100 },
});
const laneRelease = createDeferred();
enqueueCommandInLaneMock.mockImplementationOnce(async (_lane, task) => {
await laneRelease.promise;
return await task();
});
vi.mocked(command.compactEmbeddedAgentSession).mockImplementationOnce(
async (params, host) => await compactEmbeddedAgentSession(params, host),
);
const pending = command.handleCompactCommand(
{
...command.buildCompactParams("/compact", {
commands: { text: true },
channels: { whatsapp: { allowFrom: ["*"] } },
session: { store: join(TEST_WORKSPACE_DIR, "sessions.json") },
}),
provider: "openai",
model: "gpt-5.5",
workspaceDir: TEST_WORKSPACE_DIR,
agentDir: join(TEST_WORKSPACE_DIR, "agents/main/agent"),
sessionEntry: {
sessionId: TEST_SESSION_ID,
updatedAt: Date.now(),
agentHarnessId: "codex",
modelSelectionLocked: true,
},
},
true,
);
await vi.waitFor(() => {
expect(enqueueCommandInLaneMock).toHaveBeenCalledOnce();
});
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
if (writerRunId) {
await patchSessionEntryCore(
{
agentId: "main",
sessionKey: TEST_SESSION_KEY,
storePath: join(TEST_WORKSPACE_DIR, "sessions.json"),
},
(entry) => ({ ...entry, activeWriterRunId: writerRunId }),
);
}
laneRelease.resolve();
if (writerRunId) {
await expect(pending).rejects.toThrow("session writer claim changed");
} else {
await expect(pending).resolves.toMatchObject({ shouldContinue: false });
}
expect(command.compactEmbeddedAgentSession).toHaveBeenCalledOnce();
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(writerRunId ? 0 : 1);
});
it("preserves a summaryless server-endpoint result through the legacy engine delegate", async () => {
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
});
contextEngineCompactMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: {
firstKeptEntryId: "assistant-entry",
tokensBefore: 1_000,
tokensAfter: 200,
details: { compactionKind: "server-endpoint" },
},
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({ provider: "xai", model: "grok-4.5" }),
);
expect(result.compactionKind).toBe("server-endpoint");
expect(result.result).toMatchObject({ kind: "server-endpoint", tokensAfter: 200 });
expect(result.result).not.toHaveProperty("summary");
});
it("does not impose a second aggregate timeout on delegated native compaction", async () => {
const { markRuntimeCompactionDelegate } =
await import("../../context-engine/compaction-watchdog.js");
const started = createDeferred<() => void>();
const terminal = createDeferred<Awaited<ReturnType<ContextEngine["compact"]>>>();
// Mark only this invocation's delegate; shared mockReset does not clear WeakSet identity.
const compact = markRuntimeCompactionDelegate(
vi.fn<ContextEngine["compact"]>(async ({ runtimeContext }) => {
const resetTimeout = runtimeContext?.compactionTimeoutReset;
if (typeof resetTimeout !== "function") {
throw new Error("Delegated compaction must receive its progress reset callback");
}
started.resolve(() => {
resetTimeout();
});
return await terminal.promise;
}),
);
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact,
});
vi.useFakeTimers();
let settled = false;
const pending = compactEmbeddedAgentSession(wrappedCompactionArgs()).finally(() => {
settled = true;
});
void pending.catch(() => undefined);
try {
const resetTimeout = await Promise.race([
started.promise,
pending.then(() => {
throw new Error("Compaction settled before the delegate started");
}),
]);
await vi.advanceTimersByTimeAsync(20_000);
expect(settled).toBe(false);
resetTimeout();
await vi.advanceTimersByTimeAsync(20_000);
expect(settled).toBe(false);
terminal.resolve({
ok: true,
compacted: true,
result: { summary: "engine-summary", tokensBefore: 120, tokensAfter: 50 },
});
await expect(pending).resolves.toMatchObject({ ok: true, compacted: true });
expect(compact).toHaveBeenCalledOnce();
} finally {
terminal.resolve({ ok: false, compacted: false });
await pending.catch(() => undefined);
vi.useRealTimers();
}
});
it("fails closed for a fallback-owned legacy compaction target", async () => {
const legacySessionId = "legacy-session-47";
const legacyStorePath = join(tempDirs.make("openclaw-legacy-compaction-"), "openclaw.sqlite");
await upsertSessionEntryCore(
{ agentId: "lossless-agent", sessionKey: "legacy-topic-47", storePath: legacyStorePath },
{ sessionId: legacySessionId, updatedAt: 1 },
);
resolveSessionAgentIdsMock.mockReturnValueOnce({
defaultAgentId: "main",
sessionAgentId: "lossless-agent",
});
await compactEmbeddedAgentSession(
wrappedCompactionArgs({
config: {
agents: {
defaults: {
model: "openai/gpt-5.5",
},
},
},
sessionId: legacySessionId,
sessionKey: "legacy-topic-47",
sessionTarget: {
agentId: "lossless-agent",
sessionId: legacySessionId,
sessionKey: "legacy-topic-47",
storePath: legacyStorePath,
},
}),
);
const contextEngineCompactCalls = contextEngineCompactMock.mock.calls as unknown as Array<
[
{
runtimeContext?: {
llm?: {
complete?: (params: {
messages: Array<{ role: "user"; content: string }>;
agentId?: string;
}) => Promise<unknown>;
};
};
},
]
>;
const runtimeContext = contextEngineCompactCalls[0]?.[0]?.runtimeContext;
if (!runtimeContext) {
throw new Error("expected compaction runtime context");
}
expect(runtimeContext.llm?.complete).toBeTypeOf("function");
await expect(
runtimeContext.llm?.complete?.({
messages: [{ role: "user", content: "summarize" }],
}),
).rejects.toThrow("not bound to an active session agent");
});
it("binds a queued legacy compaction from its explicit owner field", async () => {
const legacySessionId = "explicit-legacy-session-48";
const storePath = join(
tempDirs.make("openclaw-explicit-legacy-compaction-"),
"openclaw.sqlite",
);
await upsertSessionEntryCore(
{ agentId: "lossless-agent", sessionKey: "legacy-topic-48", storePath },
{ sessionId: legacySessionId, updatedAt: 1 },
);
await compactEmbeddedAgentSession(
wrappedCompactionArgs({
config: { agents: { defaults: { model: "openai/gpt-5.5" } } },
contextEngineAgentId: "lossless-agent",
sessionId: legacySessionId,
sessionKey: "legacy-topic-48",
sessionTarget: {
agentId: "lossless-agent",
sessionId: legacySessionId,
sessionKey: "legacy-topic-48",
storePath,
},
}),
);
const compactInput = (
contextEngineCompactMock.mock.calls as unknown as Array<
[
{
runtimeContext?: {
llm?: {
complete?: (params: {
messages: Array<{ role: "user"; content: string }>;
agentId?: string;
}) => Promise<unknown>;
};
};
},
]
>
)[0]?.[0];
await expect(
compactInput?.runtimeContext?.llm?.complete?.({
messages: [{ role: "user", content: "summarize" }],
agentId: "other-agent",
}),
).rejects.toThrow("cannot override the active session agent");
});
it.each([true, false])("pairs successful engine hooks (compacted=%s)", async (compacted) => {
hookRunner.hasHooks.mockReturnValue(true);
if (!compacted) {
contextEngineCompactMock.mockResolvedValueOnce({ ok: true, compacted: false });
}
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
messageChannel: "telegram",
}),
);
expect(result.ok).toBe(true);
expect(result.compacted).toBe(compacted);
expect(result.compactionKind).toBe("context-engine");
expect(mockCallArg(hookRunner.runBeforeCompaction)).toEqual({
messageCount: -1,
sessionFile: TEST_SESSION_KEY,
});
expectRecordFields(mockCallArg(hookRunner.runBeforeCompaction, 0, 1), {
sessionKey: TEST_SESSION_KEY,
messageProvider: "telegram",
});
expect(hookRunner.runAfterCompaction).toHaveBeenCalledTimes(1);
expect(mockCallArg(hookRunner.runAfterCompaction)).toEqual({
messageCount: -1,
compactedCount: compacted ? -1 : 0,
tokenCount: compacted ? 50 : undefined,
sessionFile: TEST_SESSION_KEY,
});
expectRecordFields(mockCallArg(hookRunner.runAfterCompaction, 0, 1), {
sessionKey: TEST_SESSION_KEY,
messageProvider: "telegram",
});
});
it("passes the rotated session id to engine-owned after_compaction hooks", async () => {
hookRunner.hasHooks.mockReturnValue(true);
const rotatedSessionId = "rotated-session";
contextEngineCompactMock.mockResolvedValue({
ok: true,
compacted: true,
reason: undefined,
result: {
summary: "engine-summary",
firstKeptEntryId: "entry-1",
tokensBefore: 120,
tokensAfter: 50,
sessionId: rotatedSessionId,
},
} as never);
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({ cwd: join(TEST_WORKSPACE_DIR, "task-repo") }),
);
expect(result.ok).toBe(true);
expectRecordFields(mockCallArg(hookRunner.runAfterCompaction), {
sessionFile: TEST_SESSION_KEY,
previousSessionId: TEST_SESSION_ID,
});
expectRecordFields(mockCallArg(hookRunner.runAfterCompaction, 0, 1), {
sessionId: rotatedSessionId,
sessionKey: TEST_SESSION_KEY,
});
});
it("emits a transcript update and post-compaction memory sync on the engine-owned path", async () => {
const listener = vi.fn();
const cleanup = onSessionTranscriptUpdate(listener);
const sync = vi.fn(async () => {});
getMemorySearchManagerMock.mockResolvedValue({ manager: { sync } });
try {
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
sessionFile: ` ${TEST_SESSION_FILE} `,
config: compactionConfig("await"),
}),
);
expect(result.ok).toBe(true);
expect(listener).toHaveBeenCalledTimes(1);
expect(listener).toHaveBeenCalledWith({
agentId: "main",
sessionKey: TEST_SESSION_KEY,
sessionId: TEST_SESSION_ID,
target: {
agentId: "main",
sessionId: TEST_SESSION_ID,
sessionKey: TEST_SESSION_KEY,
},
});
expect(sync).toHaveBeenCalledWith({
reason: "post-compaction",
sessions: [
{
agentId: "main",
sessionId: TEST_SESSION_ID,
sessionKey: TEST_SESSION_KEY,
},
],
});
} finally {
cleanup();
}
});
it("runs maintain after successful compaction with a transcript rewrite helper", async () => {
const maintain = vi.fn(async (_params?: unknown) => ({
changed: false,
bytesFreed: 0,
rewrittenEntries: 0,
}));
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: true },
compact: contextEngineCompactMock,
maintain,
} as never);
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({ cwd: join(TEST_WORKSPACE_DIR, "task-repo") }),
);
expect(result.ok).toBe(true);
const runtimeContext = (
maintain.mock.calls.at(0)?.[0] as { runtimeContext?: Record<string, unknown> } | undefined
)?.runtimeContext;
expectRecordFields(mockCallArg(maintain), {
sessionKey: TEST_SESSION_KEY,
sessionFile: TEST_SESSION_KEY,
});
expect(runtimeContext?.workspaceDir).toBe(TEST_WORKSPACE_DIR);
expect(runtimeContext?.cwd).toBe(join(TEST_WORKSPACE_DIR, "task-repo"));
expect(runtimeContext?.rewriteTranscriptEntries).toBeTypeOf("function");
});
it("resolves the effective compaction model before manual engine-owned compaction", async () => {
await compactEmbeddedAgentSession(
wrappedCompactionArgs({
config: {
agents: {
defaults: {
compaction: {
model: "anthropic/claude-opus-4-6",
},
},
},
},
provider: "openai",
model: "gpt-5.4",
authProfileId: "openai:p1",
}),
);
expect(mockCallArg(resolveModelMock)).toBe("anthropic");
expect(mockCallArg(resolveModelMock, 0, 1)).toBe("claude-opus-4-6");
expect(mockCallArg(resolveModelMock, 0, 2)).toBeTypeOf("string");
if (mockCallArg(resolveModelMock, 0, 3) === undefined) {
throw new Error("Expected resolve-model options");
}
const compactArg = mockCallArg(contextEngineCompactMock) as {
runtimeContext?: Record<string, unknown>;
};
expectRecordFields(compactArg.runtimeContext, {
provider: "anthropic",
model: "claude-opus-4-6",
authProfileId: undefined,
});
});
it("clamps caller context token budget before queued engine-owned compaction", async () => {
resolveContextWindowInfoMock.mockReturnValueOnce({ tokens: 32_000 });
await compactEmbeddedAgentSession(
wrappedCompactionArgs({
contextTokenBudget: 64_000,
config: {
agents: {
defaults: {
compaction: {
model: "anthropic/claude-opus-4-6",
},
},
},
},
}),
);
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
const compactArg = mockCallArg(contextEngineCompactMock) as {
tokenBudget?: number;
runtimeContext?: Record<string, unknown>;
};
expect(compactArg.tokenBudget).toBe(32_000);
expectRecordFields(compactArg.runtimeContext, {
provider: "anthropic",
model: "claude-opus-4-6",
});
});
it("passes resolved OpenAI runtime context to context-engine compaction", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "codex" });
ensureAuthProfileStoreMock.mockReturnValue(
createAuthProfileStoreFixture({
"openai:p1": createApiKeyCredential("openai", "platform-key"),
}),
);
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: {
summary: "harness",
firstKeptEntryId: "entry-1",
tokensBefore: 100,
},
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.4",
authProfileId: "openai:p1",
authProfileIdSource: "user",
currentTokenCount: 333,
}),
);
expect(result.ok).toBe(true);
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
const compactArg = mockCallArg(contextEngineCompactMock) as {
runtimeContext?: Record<string, unknown>;
};
expectRecordFields(compactArg.runtimeContext, {
sessionKey: TEST_SESSION_KEY,
workspaceDir: TEST_WORKSPACE_DIR,
provider: "openai",
model: "gpt-5.4",
authProfileId: "openai:p1",
currentTokenCount: 333,
});
});
it("runs selected Codex harness queued compaction on canonical OpenAI context", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "codex" });
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: {
summary: "harness",
firstKeptEntryId: "entry-1",
tokensBefore: 100,
},
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
config: {
models: {
providers: {
openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(contextEngineCompactMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(1);
const snapshot = await acquiredPreparedModelRuntime();
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
}),
{ nativeCompactionRequest: "after_context_engine", preparedModelRuntime: snapshot },
);
const compactArg = mockCallArg(contextEngineCompactMock) as {
runtimeContext?: Record<string, unknown>;
};
expectRecordFields(compactArg.runtimeContext, {
provider: "openai",
runtimeProvider: undefined,
model: "gpt-5.5",
});
});
it("keeps authorized host byte compaction successful when secondary Codex sync fails", async () => {
const order: string[] = [];
const registry = requireActivePluginRegistry();
const harness: AgentHarness = {
id: "codex",
label: "Codex",
supports: () => ({ supported: true }),
runAttempt: async () => {
throw new Error("not used");
},
};
withPluginRegistrationContext(registry, "codex", () => {
registerAgentHarness(harness, {
nativeCompaction: vi.fn(async () => ({ ok: true, compacted: true })),
});
});
const registeredHarness = expectDefined(
getRegisteredAgentHarness("codex")?.harness,
"registered Codex harness",
);
const acquirePreparedRuntime = expectDefined(
acquireAgentRunPreparedModelRuntimeMock.getMockImplementation(),
"prepared runtime acquisition",
);
acquireAgentRunPreparedModelRuntimeMock.mockImplementationOnce(async (input) => {
const lease = await acquirePreparedRuntime(input);
return {
...lease,
snapshot: { ...lease.snapshot, pluginRegistry: registry },
};
});
selectAgentHarnessMock.mockReturnValue(registeredHarness);
selectAgentHarnessForPreparedModelProvidersMock.mockReturnValue(registeredHarness);
resolveContextEngineMock.mockResolvedValue({
info: { id: "legacy", name: "Legacy", version: "1.0.0" },
compact: (params: Parameters<NonNullable<ContextEngine["compact"]>>[0]) =>
delegateCompactionToRuntime(params),
} as never);
sessionCompactImpl.mockImplementationOnce(async () => {
order.push("host");
return {
summary: "host-summary",
firstKeptEntryId: "entry-1",
tokensBefore: 120,
tokensAfter: 50,
details: { ok: true },
};
});
maybeCompactAgentHarnessSessionMock.mockImplementationOnce(async () => {
order.push("native");
return {
ok: false,
compacted: false,
reason: "provider_error_4xx",
failure: {
reason: "provider_error_4xx",
status: 400,
rawError: "provider_error_4xx",
},
};
});
const result = await compactEmbeddedAgentSession(
await nativeCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
trigger: "budget",
forcePreflight: true,
preflightRequired: true,
preflightCompactionTrigger: "transcript_bytes",
}),
{ transcriptBytePreflightHarness: "codex" },
);
expect(result.reason).toBeUndefined();
expect(result).toMatchObject({
ok: true,
compacted: true,
result: {
summary: "host-summary",
details: {
codexNativeCompaction: {
ok: false,
compacted: false,
reason: "provider_error_4xx",
failure: { reason: "provider_error_4xx", status: 400 },
},
},
},
});
expect(order).toEqual(["host", "native"]);
expect(attemptServerEndpointCompactionMock).not.toHaveBeenCalled();
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({ agentHarnessId: "codex" }),
expectedNativeCompactionOptions("after_context_engine"),
);
});
it("falls back to the context engine when required-preflight native compaction reports a binding change", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "codex" });
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
} as never);
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: false,
compacted: false,
reason: "codex app-server binding changed before native compaction",
failure: {
reason: "stale_thread_binding",
rawError: "codex app-server binding changed before native compaction",
},
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
trigger: "budget",
preflightRequired: true,
config: {
models: {
providers: {
openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(result.compacted).toBe(true);
expect(result.result?.summary).toBe("engine-summary");
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(1);
const snapshot = await acquiredPreparedModelRuntime();
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
preflightRequired: true,
}),
expect.objectContaining({
nativeCompactionRequest: "required_preflight",
preparedModelRuntime: snapshot,
}),
);
expect(contextEngineCompactMock).toHaveBeenCalledTimes(1);
});
it("keeps queued native auth candidates uncollapsed until native resolution", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "native",
runtimeSource: "model",
} as never);
ensureAuthProfileStoreMock.mockReturnValue({
version: 1,
profiles: {
"openai:subscription": {
type: "token",
provider: "openai",
token: "subscription-token",
expires: Date.now() + 60_000,
},
},
order: { openai: ["openai:subscription"] },
});
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: {
summary: "harness",
firstKeptEntryId: "entry-1",
tokensBefore: 100,
},
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "native",
config: {
models: {
providers: {
openai: {
apiKey: "literal-key",
models: [{ id: "gpt-5.5", contextWindow: 350_000 }],
},
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(selectAgentHarnessForPreparedModelProvidersMock).toHaveBeenCalledWith(
expect.objectContaining({
modelProviders: expect.arrayContaining([
expect.objectContaining({
api: "openai-chatgpt-responses",
preparedAuth: expect.objectContaining({ source: "profile" }),
}),
expect.objectContaining({
api: "openai-responses",
preparedAuth: expect.objectContaining({ source: "direct" }),
}),
]),
}),
);
const nativeParams = mockCallArg(maybeCompactAgentHarnessSessionMock) as {
runtimeAuthPlan?: unknown;
runtimePlan?: unknown;
};
expect(nativeParams.runtimeAuthPlan).toBeUndefined();
expect(nativeParams.runtimePlan).toBeUndefined();
});
it("keeps cross-route direct fallback available through queued legacy compaction", async () => {
const authStore = {
version: 1 as const,
profiles: {
"openai:subscription": {
type: "token" as const,
provider: "openai",
token: "subscription-token",
expires: Date.now() + 60_000,
},
},
order: { openai: ["openai:subscription"] },
};
ensureAuthProfileStoreMock.mockReturnValue(authStore);
getApiKeyForModelMock.mockImplementation(async (authParams = {}) => {
if (authParams.profileId === "openai:subscription") {
throw new Error("subscription credential resolution failed");
}
if (authParams.allowAuthProfileFallback === false) {
return { apiKey: "literal-key", mode: "api-key", source: "models.json" };
}
throw new Error("unexpected auth lookup");
});
const legacyCompact = vi.fn(
async (compactParams: {
sessionId: string;
sessionKey?: string;
sessionFile: string;
tokenBudget?: number;
force?: boolean;
customInstructions?: string;
runtimeContext?: Record<string, unknown>;
}) => {
const directParams = {
...compactParams.runtimeContext,
sessionId: compactParams.sessionId,
sessionKey: compactParams.sessionKey,
sessionFile: compactParams.sessionFile,
tokenBudget: compactParams.tokenBudget,
force: compactParams.force,
customInstructions: compactParams.customInstructions,
workspaceDir: TEST_WORKSPACE_DIR,
} as Parameters<typeof compactEmbeddedAgentSessionDirect>[0];
return await compactEmbeddedAgentSessionDirect(directParams);
},
);
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: legacyCompact,
} as never);
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
config: {
models: {
providers: {
openai: {
apiKey: "literal-key",
models: [{ id: "gpt-5.5" }],
},
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(
getApiKeyForModelMock.mock.calls.map(([authParams]) => ({
profileId: authParams?.profileId,
allowAuthProfileFallback: authParams?.allowAuthProfileFallback,
})),
).toEqual([
{ profileId: "openai:subscription", allowAuthProfileFallback: undefined },
{ profileId: undefined, allowAuthProfileFallback: false },
]);
});
it("uses explicit Codex runtime policy for queued native compaction", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "codex",
runtimeSource: "model",
} as never);
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: {
summary: "harness",
firstKeptEntryId: "entry-1",
tokensBefore: 100,
},
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
config: {
models: {
providers: {
openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(contextEngineCompactMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
model: "gpt-5.5",
}),
expectedNativeCompactionOptions("after_context_engine"),
);
const compactArg = mockCallArg(contextEngineCompactMock) as {
runtimeContext?: Record<string, unknown>;
};
expectRecordFields(compactArg.runtimeContext, {
provider: "openai",
runtimeProvider: undefined,
model: "gpt-5.5",
});
});
it("routes a queued manual CLI session to backend-owned compaction", async () => {
const agentDir = await realpath(
await mkdtemp(join(tmpdir(), "openclaw-native-compaction-queued-")),
);
try {
await upsertSessionEntryCore(
{
agentId: "main",
sessionKey: TEST_SESSION_KEY,
storePath: join(agentDir, "sessions.json"),
},
{ sessionId: TEST_SESSION_ID, updatedAt: 1 },
);
resolveCliBackendConfigMock.mockReturnValue({
id: "claude-cli",
ownsNativeCompaction: true,
manualCompaction: {
buildPrompt: () => "/compact",
input: "arg",
validateOutput: () => ({ ok: true }),
},
});
runCliAgentMock.mockClear();
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
agentDir,
sessionTarget: {
agentId: "main",
sessionId: TEST_SESSION_ID,
sessionKey: TEST_SESSION_KEY,
storePath: join(agentDir, "sessions.json"),
},
trigger: "manual",
provider: "anthropic",
model: "opus",
agentHarnessId: "claude-cli",
cliSessionId: "native-session",
}),
);
expect(result).toMatchObject({ ok: true, compacted: true });
expect(enqueueCommandInLaneMock).toHaveBeenCalledOnce();
expect(runCliAgentMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "claude-cli",
cliSessionId: "native-session",
prompt: "/compact",
controlOperation: "compact",
}),
);
expect(resolveContextEngineMock).not.toHaveBeenCalled();
} finally {
closeOpenClawAgentDatabasesForTest();
await rm(agentDir, { force: true, recursive: true });
}
});
it("reports cancellation while queued native CLI compaction is in flight", async () => {
const agentDir = await realpath(tempDirs.make("openclaw-native-compaction-queued-abort-"));
const controller = new AbortController();
const cliStarted = createDeferred<AbortSignal>();
try {
await upsertSessionEntryCore(
{
agentId: "main",
sessionKey: TEST_SESSION_KEY,
storePath: join(agentDir, "sessions.json"),
},
{ sessionId: TEST_SESSION_ID, updatedAt: 1 },
);
resolveCliBackendConfigMock.mockReturnValue({
id: "claude-cli",
ownsNativeCompaction: true,
manualCompaction: {
buildPrompt: () => "/compact",
input: "arg",
validateOutput: () => ({ ok: true }),
},
});
runCliAgentMock.mockImplementationOnce((async (params: { abortSignal?: AbortSignal }) => {
const signal = expectDefined(params.abortSignal, "native CLI compaction abort signal");
cliStarted.resolve(signal);
return await new Promise<never>((_resolve, reject) => {
signal.addEventListener(
"abort",
() => {
const reason = signal.reason;
reject(reason instanceof Error ? reason : new Error("native CLI compaction aborted"));
},
{ once: true },
);
});
}) as never);
const pending = compactEmbeddedAgentSession(
wrappedCompactionArgs({
abortSignal: controller.signal,
agentDir,
sessionTarget: {
agentId: "main",
sessionId: TEST_SESSION_ID,
sessionKey: TEST_SESSION_KEY,
storePath: join(agentDir, "sessions.json"),
},
trigger: "manual",
provider: "anthropic",
model: "opus",
agentHarnessId: "claude-cli",
cliSessionId: "native-session",
}),
);
const nativeSignal = await cliStarted.promise;
controller.abort(new Error("request timed out"));
await expect(pending).resolves.toEqual({
ok: false,
compacted: false,
reason: "compaction aborted",
});
expect(nativeSignal.aborted).toBe(true);
expect(resolveContextEngineMock).not.toHaveBeenCalled();
expect(isEmbeddedAgentRunHandleActive(TEST_SESSION_ID)).toBe(false);
} finally {
closeOpenClawAgentDatabasesForTest();
}
});
it("rejects a replaced writer claim before queued native CLI compaction", async () => {
const agentDir = await realpath(
await mkdtemp(join(tmpdir(), "openclaw-native-compaction-queued-replaced-")),
);
const storePath = join(agentDir, "sessions.json");
try {
await upsertSessionEntryCore(
{ agentId: "main", sessionKey: TEST_SESSION_KEY, storePath },
{ sessionId: TEST_SESSION_ID, updatedAt: 1 },
);
resolveCliBackendConfigMock.mockReturnValue({
id: "claude-cli",
ownsNativeCompaction: true,
manualCompaction: {
buildPrompt: () => "/compact",
input: "arg",
validateOutput: () => ({ ok: true }),
},
});
const laneRelease = createDeferred();
enqueueCommandInLaneMock.mockImplementationOnce(async (_lane, task) => {
await laneRelease.promise;
return await task();
});
const pending = compactEmbeddedAgentSession(
wrappedCompactionArgs({
agentDir,
sessionTarget: {
agentId: "main",
sessionId: TEST_SESSION_ID,
sessionKey: TEST_SESSION_KEY,
storePath,
},
trigger: "manual",
provider: "anthropic",
model: "opus",
agentHarnessId: "claude-cli",
cliSessionId: "native-session",
}),
);
await vi.waitFor(() => {
expect(enqueueCommandInLaneMock).toHaveBeenCalledOnce();
});
await patchSessionEntryCore(
{ agentId: "main", sessionKey: TEST_SESSION_KEY, storePath },
(entry) => ({ ...entry, activeWriterRunId: "replacement-run" }),
);
laneRelease.resolve();
await expect(pending).resolves.toMatchObject({
ok: false,
reason: expect.stringContaining("session writer claim changed"),
});
expect(runCliAgentMock).not.toHaveBeenCalled();
} finally {
closeOpenClawAgentDatabasesForTest();
await rm(agentDir, { force: true, recursive: true });
}
});
it("normalizes an omitted manual target before native harness compaction", async () => {
const agentDir = await realpath(
await mkdtemp(join(tmpdir(), "openclaw-native-compaction-target-")),
);
try {
await upsertSessionEntryCore(
{
agentId: "main",
sessionKey: TEST_SESSION_KEY,
storePath: join(agentDir, "sessions.json"),
},
{ sessionId: TEST_SESSION_ID, updatedAt: 1 },
);
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "codex",
runtimeSource: "model",
} as never);
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: { summary: "harness", firstKeptEntryId: "entry-1", tokensBefore: 100 },
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
agentDir,
sessionTarget: {
agentId: "main",
sessionId: TEST_SESSION_ID,
sessionKey: TEST_SESSION_KEY,
storePath: join(agentDir, "sessions.json"),
},
config: {
agents: {
defaults: {
compaction: { model: "openai/gpt-5.5" },
models: {
"openai/gpt-5.5": { agentRuntime: { id: "codex" } },
},
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
model: "gpt-5.5",
runtimeModel: expect.objectContaining({
api: "openai-responses",
baseUrl: "https://api.openai.com/v1",
}),
}),
{
nativeCompactionRequest: "after_context_engine",
preparedModelRuntime: expect.any(Object),
},
);
} finally {
closeOpenClawAgentDatabasesForTest();
await rm(agentDir, { force: true, recursive: true });
}
});
it("preserves concrete OpenClaw pins over explicit Codex policy for queued compaction", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "codex",
runtimeSource: "model",
} as never);
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: {
summary: "harness",
firstKeptEntryId: "entry-1",
tokensBefore: 100,
},
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "openclaw",
config: {
models: {
providers: {
openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
const compactArg = mockCallArg(contextEngineCompactMock) as {
runtimeContext?: Record<string, unknown>;
};
expectRecordFields(compactArg.runtimeContext, {
provider: "openai",
runtimeProvider: undefined,
model: "gpt-5.5",
});
});
it("uses concrete Codex pins on canonical OpenAI for queued compaction", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "auto",
runtimeSource: "model",
} as never);
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: {
summary: "harness",
firstKeptEntryId: "entry-1",
tokensBefore: 100,
},
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
config: {
models: {
providers: {
openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(contextEngineCompactMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
}),
expectedNativeCompactionOptions("after_context_engine"),
);
const compactArg = mockCallArg(contextEngineCompactMock) as {
runtimeContext?: Record<string, unknown>;
};
expectRecordFields(compactArg.runtimeContext, {
provider: "openai",
runtimeProvider: undefined,
model: "gpt-5.5",
});
});
it("materializes the selected route before deriving compaction context budget", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "codex",
runtimeSource: "model",
} as never);
resolveContextWindowInfoMock.mockImplementation((input?: { modelContextWindow?: number }) => ({
tokens: input?.modelContextWindow ?? 128_000,
}));
ensureAuthProfileStoreMock.mockReturnValue({
version: 1,
profiles: {
"openai:token": {
type: "token",
provider: "openai",
token: "subscription-token",
},
},
order: { openai: ["openai:token"] },
});
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: { summary: "harness", firstKeptEntryId: "entry-1", tokensBefore: 100 },
});
await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
authProfileId: "openai:token",
authProfileIdSource: "auto",
agentHarnessId: "codex",
}),
);
expect(mockCallArg(resolveModelAsyncMock, 0, 4)).toEqual(
expect.objectContaining({ authProfileId: "openai:token" }),
);
expect(resolveModelAsyncMock).toHaveBeenLastCalledWith(
"openai",
"gpt-5.5",
expect.any(String),
expect.objectContaining({
models: {
providers: {
openai: expect.objectContaining({
api: "openai-chatgpt-responses",
baseUrl: "https://chatgpt.com/backend-api/codex",
}),
},
},
}),
expect.objectContaining({ authProfileMode: "token" }),
);
expect(contextEngineCompactMock).toHaveBeenCalledWith(
expect.objectContaining({ tokenBudget: 272_000 }),
);
const compactArg = mockCallArg(contextEngineCompactMock) as {
runtimeContext?: Record<string, unknown>;
};
expectRecordFields(compactArg.runtimeContext, {
provider: "openai",
runtimeProvider: undefined,
model: "gpt-5.5",
});
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
authProfileId: "openai:token",
authProfileIdSource: "auto",
contextTokenBudget: 272_000,
runtimeModel: expect.objectContaining({
api: "openai-chatgpt-responses",
baseUrl: "https://chatgpt.com/backend-api/codex",
contextWindow: 272_000,
}),
runtimeAuthPlan: undefined,
}),
expectedNativeCompactionOptions("after_context_engine"),
);
});
it("prepares queued native harness auth without a host profile", async () => {
vi.stubEnv("CODEX_API_KEY", "");
vi.stubEnv("OPENAI_API_KEY", "");
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "codex",
runtimeSource: "model",
} as never);
ensureAuthProfileStoreMock.mockReturnValue({ version: 1, profiles: {} });
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: { summary: "harness", firstKeptEntryId: "entry-1", tokensBefore: 100 },
});
await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
}),
);
expect(selectAgentHarnessForPreparedModelProvidersMock).toHaveBeenCalledWith(
expect.objectContaining({
modelProviders: expect.arrayContaining([
expect.objectContaining({
preparedAuth: expect.objectContaining({ source: "harness" }),
runtimePolicy: expect.objectContaining({ compatibleIds: ["openclaw", "codex"] }),
}),
]),
}),
);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({ runtimeAuthPlan: undefined }),
expectedNativeCompactionOptions("after_context_engine"),
);
});
it("does not route queued compaction through implicit Codex policy alone", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "codex" });
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: {
summary: "harness",
firstKeptEntryId: "entry-1",
tokensBefore: 100,
},
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
config: {
models: {
providers: {
openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
const compactArg = mockCallArg(contextEngineCompactMock) as {
runtimeContext?: Record<string, unknown>;
};
expectRecordFields(compactArg.runtimeContext, {
provider: "openai",
runtimeProvider: undefined,
model: "gpt-5.5",
});
});
it("routes unbound ChatGPT OAuth queued compaction through auth-aware codex selection", async () => {
// Implicit policy may prefer codex, but with no bound/planned harness the
// override must stay undefined so prepared OAuth can select the harness.
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "codex",
runtimeSource: "implicit",
} as never);
ensureAuthProfileStoreMock.mockReturnValue({
version: 1,
profiles: {
"openai:chatgpt": {
type: "oauth",
provider: "openai",
access: "test-auth-token",
refresh: "test-auth-token",
expires: Date.now() + 10 * 60_000,
},
},
order: { openai: ["openai:chatgpt"] },
});
getApiKeyForModelMock.mockImplementation(async (params?: { profileId?: string }) => ({
apiKey: "test-auth-token",
mode: "oauth",
source: `profile:${params?.profileId ?? "openai:chatgpt"}`,
profileId: params?.profileId ?? "openai:chatgpt",
}));
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
// Do not pin provider-level api to openai-responses: that collapses
// route resolution to api-key-only and hides ChatGPT OAuth.
config: {
models: {
providers: {
openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
expect(selectAgentHarnessMock).toHaveBeenCalledWith(
expect.objectContaining({
agentHarnessId: undefined,
agentHarnessRuntimeOverride: undefined,
}),
);
expect(selectAgentHarnessMock.mock.results[0]?.value).toEqual(
expect.objectContaining({ id: "codex", authBootstrap: "harness" }),
);
expect(selectAgentHarnessForPreparedModelProvidersMock).toHaveBeenCalledWith(
expect.objectContaining({
agentHarnessRuntimeOverride: undefined,
modelProviders: expect.arrayContaining([
expect.objectContaining({
preparedAuth: expect.objectContaining({
source: "profile",
mode: "oauth",
requirement: "subscription",
}),
}),
]),
}),
);
});
it("keeps unbound api-key queued compaction on openclaw without native harness compaction", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "openclaw",
runtimeSource: "implicit",
} as never);
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
config: {
models: {
providers: {
openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
expect(selectAgentHarnessMock).toHaveBeenCalledWith(
expect.objectContaining({
agentHarnessId: undefined,
agentHarnessRuntimeOverride: undefined,
}),
);
expect(selectAgentHarnessMock.mock.results[0]?.value).toEqual(
expect.objectContaining({ id: "openclaw" }),
);
});
it("resolves reusable queued direct auth without a stored profile", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "openclaw",
runtimeSource: "implicit",
} as never);
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
runtimeAuthPlan: {
providerForAuth: "openai",
modelId: "gpt-5.5",
authProfileProviderForAuth: "openai",
selectedAuthMode: "api-key",
},
config: {
models: {
providers: {
openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(mockCallArg(resolveModelAsyncMock, 0, 4)).toMatchObject({
authProfileMode: "api_key",
});
});
it("uses a prepared harness binding for queued custom OpenAI Responses compaction", async () => {
const modelRoute = {
provider: "openai",
modelId: "gpt-5.5",
api: "openai-responses",
baseUrl: "https://example.test/v1",
authRequirement: "api-key",
requestTransportOverrides: "none",
} as const;
const runtimeAuthPlan = {
providerForAuth: "openai",
modelId: "gpt-5.5",
authProfileProviderForAuth: "openai",
harnessAuthProvider: "openai",
selectedAuthMode: "api-key",
modelRoute,
} as const;
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "codex" });
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: {
summary: "harness",
firstKeptEntryId: "entry-1",
tokensBefore: 100,
},
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
runtimeAuthPlan,
config: {
models: {
providers: {
openai: {
api: "openai-responses",
baseUrl: "https://example.test/v1",
models: [{ id: "gpt-5.5", contextWindow: 350_000 }],
},
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(mockCallArg(resolveModelMock)).toBe("openai");
expectRecordFields(mockCallArg(resolveContextWindowInfoMock), {
provider: "openai",
modelId: "gpt-5.5",
});
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
runtimeModel: expect.objectContaining({
api: "openai-responses",
baseUrl: "https://example.test/v1",
}),
runtimeAuthPlan: expect.objectContaining({ modelRoute }),
}),
expectedNativeCompactionOptions("after_context_engine"),
);
const compactArg = mockCallArg(contextEngineCompactMock) as {
runtimeContext?: Record<string, unknown>;
};
expectRecordFields(compactArg.runtimeContext, {
provider: "openai",
runtimeProvider: undefined,
model: "gpt-5.5",
});
});
it("keeps queued custom OpenAI Responses compaction embedded without a harness binding", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "openclaw",
runtimeSource: "implicit",
} as never);
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.5",
config: {
models: {
providers: {
openai: {
api: "openai-responses",
baseUrl: "https://example.test/v1",
models: [{ id: "gpt-5.5", contextWindow: 350_000 }],
},
},
},
},
}),
);
expect(result.ok).toBe(true);
expect(contextEngineCompactMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
const compactArg = mockCallArg(contextEngineCompactMock) as {
runtimeContext?: Record<string, unknown>;
};
expectRecordFields(compactArg.runtimeContext, {
provider: "openai",
runtimeProvider: undefined,
model: "gpt-5.5",
});
});
it("fails deferred budget compaction when background maintenance is not scheduled", async () => {
const dispose = vi.fn(async () => {});
const maintain = vi.fn(async () => ({
changed: false,
bytesFreed: 0,
rewrittenEntries: 0,
}));
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: true, turnMaintenanceMode: "background" },
compact: contextEngineCompactMock,
dispose,
maintain,
} as never);
enqueueCommandInLaneMock.mockImplementationOnce(() => {
throw new Error("scheduler offline");
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
trigger: "budget",
deferOwningContextEngineCompaction: true,
}),
);
expect(result.ok).toBe(false);
expect(result.compacted).toBe(false);
expect(result.reason).toBe("failed to schedule background context-engine maintenance");
expect(result.failure?.reason).toBe("deferred_compaction_not_scheduled");
expect(dispose).toHaveBeenCalledTimes(1);
expect(maintain).not.toHaveBeenCalled();
expect(contextEngineCompactMock).not.toHaveBeenCalled();
});
it("keeps context-engine compaction successful when Codex native binding is missing", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "codex" });
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: false,
compacted: false,
reason: "no codex app-server thread binding",
failure: { reason: "missing_thread_binding" },
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.4",
agentHarnessId: "codex",
currentTokenCount: 333,
}),
);
expect(result.ok).toBe(true);
expect(result.compacted).toBe(true);
expect(result.result?.summary).toBe("engine-summary");
expect(contextEngineCompactMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
model: "gpt-5.4",
agentHarnessId: "codex",
}),
expectedNativeCompactionOptions("after_context_engine"),
);
const details = result.result?.details as
| { codexNativeCompaction?: Record<string, unknown> }
| undefined;
expect(details?.codexNativeCompaction).toMatchObject({
ok: false,
compacted: false,
reason: "no codex app-server thread binding",
failure: { reason: "missing_thread_binding" },
});
});
it.each([
["missing_thread_binding", "no codex app-server thread binding"],
["stale_thread_binding", "thread not found"],
])(
"fails model-locked Codex compaction on %s without a context-engine fallback",
async (failureReason, reason) => {
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "openclaw" });
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: false,
compacted: false,
reason,
failure: { reason: failureReason },
});
const result = await compactEmbeddedAgentSession(
await nativeCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
currentTokenCount: 333,
}),
);
expect(result).toMatchObject({
ok: false,
compacted: false,
failure: { reason: failureReason },
});
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(1);
expect(contextEngineCompactMock).not.toHaveBeenCalled();
},
);
it.each([
["missing_thread_binding", "no codex app-server thread binding"],
["stale_thread_binding", "codex app-server binding changed before native compaction"],
])(
"recovers model-locked Codex compaction on %s during required preflight via context-engine fallback",
async (failureReason, reason) => {
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
} as never);
maybeCompactAgentHarnessSessionMock.mockImplementationOnce(async (...args: unknown[]) => {
const options = args[1] as { onNativeCompactionCapabilityUsed?: () => void } | undefined;
options?.onNativeCompactionCapabilityUsed?.();
return {
ok: false,
compacted: false,
reason,
failure: { reason: failureReason },
};
});
const result = await compactEmbeddedAgentSession(
await nativeCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
trigger: "budget",
preflightRequired: true,
config: {
models: {
providers: {
openai: { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
},
},
},
}),
);
expect(result).toMatchObject({
ok: true,
compacted: true,
result: { summary: "engine-summary" },
});
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
preflightRequired: true,
}),
expect.objectContaining({ nativeCompactionRequest: "required_preflight" }),
);
expect(contextEngineCompactMock).toHaveBeenCalledTimes(1);
},
);
it("keeps model-locked required preflight terminal without harness fallback authorization", async () => {
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: false,
compacted: false,
reason: "no codex app-server thread binding",
failure: { reason: "missing_thread_binding" },
});
const result = await compactEmbeddedAgentSession(
await nativeCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
trigger: "budget",
preflightRequired: true,
}),
);
expect(result).toMatchObject({
ok: false,
compacted: false,
failure: { reason: "missing_thread_binding" },
});
expect(contextEngineCompactMock).not.toHaveBeenCalled();
});
it("keeps model-locked required preflight terminal when the native failure is not recoverable", async () => {
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: false,
compacted: false,
reason: "native compaction unavailable",
failure: { reason: "native_unavailable" },
});
const result = await compactEmbeddedAgentSession(
await nativeCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
trigger: "budget",
preflightRequired: true,
}),
);
expect(result).toMatchObject({
ok: false,
compacted: false,
reason: "native compaction unavailable",
failure: { reason: "native_unavailable" },
});
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(1);
expect(contextEngineCompactMock).not.toHaveBeenCalled();
});
it.each([
["missing_thread_binding", "no copilot app-server thread binding"],
["stale_thread_binding", "copilot app-server binding changed before native compaction"],
])(
"keeps model-locked Copilot required preflight terminal on %s even with a forged fallback marker",
async (failureReason, reason) => {
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: false,
compacted: false,
reason,
failure: { reason: failureReason, fallback: "context-engine" },
});
const result = await compactEmbeddedAgentSession(
await nativeCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "copilot",
trigger: "budget",
preflightRequired: true,
}),
);
expect(result).toMatchObject({
ok: false,
compacted: false,
failure: { reason: failureReason },
});
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "copilot",
preflightRequired: true,
}),
expect.objectContaining({ nativeCompactionRequest: "required_preflight" }),
);
expect(contextEngineCompactMock).not.toHaveBeenCalled();
},
);
it("fails a native lock with a non-concrete persisted harness", async () => {
const result = await compactEmbeddedAgentSession(
await nativeCompactionArgs({
provider: "openai",
model: "gpt-5.6-luna",
agentHarnessId: "auto",
currentTokenCount: 333,
}),
);
expect(result).toMatchObject({
ok: false,
compacted: false,
failure: { reason: "model_selection_locked" },
});
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
expect(contextEngineCompactMock).not.toHaveBeenCalled();
});
it("reports a locked CLI backend's missing native binding from the public entry point", async () => {
resolveCliBackendConfigMock.mockReturnValue({
id: "claude-cli",
ownsNativeCompaction: true,
manualCompaction: {
buildPrompt: () => "/compact",
input: "arg",
validateOutput: () => ({ ok: true }),
},
config: {
command: "claude",
args: ["-p"],
resumeArgs: ["-p", "--resume", "{sessionId}"],
input: "arg",
output: "jsonl",
sessionMode: "existing",
},
});
const result = await compactEmbeddedAgentSessionDirect(
await nativeCompactionArgs({
provider: "anthropic",
model: "opus",
trigger: "manual",
agentHarnessId: "claude-cli",
cliSessionId: undefined,
currentTokenCount: 333,
}),
);
expect(result).toMatchObject({
ok: false,
compacted: false,
reason: expect.stringContaining("without a resumable native session"),
});
expect(runCliAgentMock).not.toHaveBeenCalled();
expect(contextEngineCompactMock).not.toHaveBeenCalled();
});
it("runs native manual compaction before generic model auth preparation", async () => {
const agentDir = await realpath(
await mkdtemp(join(tmpdir(), "openclaw-native-compaction-authless-")),
);
try {
await upsertSessionEntryCore(
{
agentId: "main",
sessionKey: TEST_SESSION_KEY,
storePath: join(agentDir, "sessions.json"),
},
{ sessionId: TEST_SESSION_ID, updatedAt: 1 },
);
resolveCliBackendConfigMock.mockReturnValue({
id: "claude-cli",
ownsNativeCompaction: true,
manualCompaction: {
buildPrompt: () => "/compact",
input: "arg",
validateOutput: () => ({ ok: true }),
},
config: {
command: "claude",
args: ["-p"],
resumeArgs: ["-p", "--resume", "{sessionId}"],
input: "arg",
output: "jsonl",
sessionMode: "existing",
},
});
const result = await compactEmbeddedAgentSession(
await nativeCompactionArgs({
agentDir,
sessionTarget: {
agentId: "main",
sessionId: TEST_SESSION_ID,
sessionKey: TEST_SESSION_KEY,
storePath: join(agentDir, "sessions.json"),
},
provider: "anthropic",
model: "opus",
trigger: "manual",
agentHarnessId: "claude-cli",
cliSessionId: "native-session",
currentTokenCount: 333,
}),
);
expect(result).toMatchObject({ ok: true, compacted: true });
expect(runCliAgentMock).toHaveBeenCalledWith(
expect.objectContaining({
cliSessionId: "native-session",
controlOperation: "compact",
}),
);
expect(acquireAgentRunPreparedModelRuntimeMock).not.toHaveBeenCalled();
expect(contextEngineCompactMock).not.toHaveBeenCalled();
} finally {
closeOpenClawAgentDatabasesForTest();
await rm(agentDir, { force: true, recursive: true });
}
});
it("fails a model-locked native session when its harness returns no result", async () => {
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce(undefined);
const result = await compactEmbeddedAgentSession(
await nativeCompactionArgs({
provider: "openai",
model: "gpt-5.5",
agentHarnessId: "codex",
currentTokenCount: 333,
}),
);
expect(result).toMatchObject({
ok: false,
compacted: false,
failure: { reason: "model_selection_locked" },
});
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(1);
expect(contextEngineCompactMock).not.toHaveBeenCalled();
});
it("keeps owning context-engine compaction primary for legacy Codex native sessions", async () => {
const successorSessionId = "engine-successor-session";
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "codex",
runtimeSource: "model",
} as never);
contextEngineCompactMock.mockResolvedValue({
ok: true,
compacted: true,
reason: undefined,
result: {
summary: "engine-summary",
firstKeptEntryId: "entry-1",
tokensBefore: 333,
tokensAfter: 50,
sessionId: successorSessionId,
},
} as never);
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: true,
compacted: true,
result: {
summary: "",
firstKeptEntryId: "",
tokensBefore: 333,
details: {
backend: "codex-app-server",
signal: "thread/compact/start",
pending: false,
completed: true,
},
},
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "codex",
model: "gpt-5.4",
agentHarnessId: "codex",
trigger: "budget",
currentTokenCount: 333,
}),
);
expect(result.ok).toBe(true);
expect(result.compacted).toBe(true);
expect(result.result?.summary).toBe("engine-summary");
expect(contextEngineCompactMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
sessionId: successorSessionId,
sessionFile: TEST_SESSION_KEY,
trigger: "budget",
}),
expectedNativeCompactionOptions("after_context_engine"),
);
expect(contextEngineCompactMock.mock.invocationCallOrder[0]).toBeLessThan(
expectDefined(
maybeCompactAgentHarnessSessionMock.mock.invocationCallOrder[0],
"maybeCompactAgentHarnessSessionMock.mock.invocationCallOrder[0] test invariant",
),
);
const details = result.result?.details as
| { codexNativeCompaction?: Record<string, unknown> }
| undefined;
expect(details?.codexNativeCompaction).toMatchObject({
ok: true,
compacted: true,
result: {
tokensBefore: 333,
details: {
backend: "codex-app-server",
signal: "thread/compact/start",
pending: false,
completed: true,
},
},
});
});
it("holds the queued lane until secondary Codex compaction reaches its terminal event", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "codex",
runtimeSource: "model",
} as never);
const nativeStarted = createDeferred();
const nativeResult = {
ok: true as const,
compacted: true as const,
result: { summary: "", firstKeptEntryId: "", tokensBefore: 333 },
};
const nativeTerminal = createDeferred<typeof nativeResult>();
maybeCompactAgentHarnessSessionMock.mockImplementationOnce(async () => {
nativeStarted.resolve();
return await nativeTerminal.promise;
});
vi.useFakeTimers();
let settled = false;
const resultPromise = compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "codex",
model: "gpt-5.4",
agentHarnessId: "codex",
trigger: "budget",
}),
).finally(() => {
settled = true;
});
void resultPromise.catch(() => undefined);
try {
await Promise.race([nativeStarted.promise, resultPromise]);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledOnce();
expect(settled).toBe(false);
// The native terminal owner, not another host aggregate window, holds this lane.
await vi.advanceTimersByTimeAsync(30_001);
expect(settled).toBe(false);
nativeTerminal.resolve(nativeResult);
await expect(resultPromise).resolves.toMatchObject({ ok: true, compacted: true });
} finally {
nativeTerminal.resolve(nativeResult);
await resultPromise.catch(() => undefined);
vi.useRealTimers();
}
});
it("keeps context-engine compaction successful when the secondary Codex bridge gets a provider 4xx", async () => {
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "codex",
runtimeSource: "model",
} as never);
maybeCompactAgentHarnessSessionMock.mockResolvedValueOnce({
ok: false,
compacted: false,
reason: "provider_error_4xx",
failure: {
reason: "provider_error_4xx",
status: 400,
rawError: "provider_error_4xx",
},
});
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "codex",
model: "gpt-5.4",
agentHarnessId: "codex",
trigger: "budget",
currentTokenCount: 333,
}),
);
expect(result.ok).toBe(true);
expect(result.compacted).toBe(true);
expect(result.result?.summary).toBe("engine-summary");
expect(contextEngineCompactMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledTimes(1);
expect(maybeCompactAgentHarnessSessionMock).toHaveBeenCalledWith(
expect.objectContaining({
trigger: "budget",
}),
expectedNativeCompactionOptions("after_context_engine"),
);
const details = result.result?.details as
| { codexNativeCompaction?: Record<string, unknown> }
| undefined;
expect(details?.codexNativeCompaction).toMatchObject({
ok: false,
compacted: false,
reason: "provider_error_4xx",
failure: {
reason: "provider_error_4xx",
status: 400,
},
});
});
it("does not fire after_compaction when the session is already compacted", async () => {
hookRunner.hasHooks.mockReturnValue(true);
const sync = vi.fn(async () => {});
getMemorySearchManagerMock.mockResolvedValue({ manager: { sync } });
contextEngineCompactMock.mockResolvedValue({
ok: false,
compacted: false,
reason: "already_compacted",
result: undefined,
});
const result = await compactEmbeddedAgentSession(wrappedCompactionArgs());
expect(result.ok).toBe(false);
expect(result.reason).toBe("already_compacted");
expect(hookRunner.runBeforeCompaction).toHaveBeenCalledTimes(1);
expect(hookRunner.runAfterCompaction).not.toHaveBeenCalled();
expect(sync).not.toHaveBeenCalled();
});
it("surfaces a hung/throwing engine compact() as a clean ok:false result", async () => {
hookRunner.hasHooks.mockReturnValue(true);
// The safety-timeout wrapper rejects on timeout; a thrown rejection here
// simulates that path. The queued lane must convert it to a result object
// instead of throwing a raw rejection at callers that only read result.ok.
contextEngineCompactMock.mockRejectedValue(new Error("Compaction timed out after 900000ms"));
const result = await compactEmbeddedAgentSession(wrappedCompactionArgs());
expect(result.ok).toBe(false);
expect(result.compacted).toBe(false);
expect(result.reason).toContain("timed out");
expect(hookRunner.runAfterCompaction).not.toHaveBeenCalled();
});
it("forces engine-owned compaction for preflight-required budget compaction", async () => {
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
trigger: "budget",
forcePreflight: true,
preflightRequired: true,
preflightCompactionTrigger: "transcript_bytes",
}),
);
expect(result.ok).toBe(true);
const compactArg = mockCallArg(contextEngineCompactMock) as {
runtimeContext?: Record<string, unknown>;
};
expectRecordFields(compactArg, {
compactionTarget: "budget",
force: true,
});
expectRecordFields(compactArg.runtimeContext, {
forceReason: "preflight_required",
preflightCompactionTrigger: "transcript_bytes",
});
});
it("continues forcing engine-owned manual compaction with manual force reason", async () => {
const result = await compactEmbeddedAgentSession(wrappedCompactionArgs({ trigger: "manual" }));
expect(result.ok).toBe(true);
const compactArg = mockCallArg(contextEngineCompactMock) as {
runtimeContext?: Record<string, unknown>;
};
expectRecordFields(compactArg, {
compactionTarget: "threshold",
force: true,
});
expectRecordFields(compactArg.runtimeContext, {
forceReason: "manual",
});
});
it("aborts manual compaction before its queued global task starts", async () => {
let startQueuedTask: (() => void) | undefined;
const enqueue = <T>(task: () => Promise<T> | T): Promise<T> =>
new Promise<T>((resolve, reject) => {
startQueuedTask = () => {
void Promise.resolve().then(task).then(resolve, reject);
};
});
const resultPromise = compactEmbeddedAgentSession(
wrappedCompactionArgs({ enqueue, trigger: "manual" }),
);
await vi.waitFor(() => {
expect(startQueuedTask).toBeTypeOf("function");
});
expect(isEmbeddedAgentRunHandleActive(TEST_SESSION_ID)).toBe(true);
expect(abortEmbeddedAgentRun(undefined, { mode: "compacting", reason: "restart" })).toBe(true);
const start = startQueuedTask;
if (!start) {
throw new Error("Expected queued compaction task");
}
start();
await expect(resultPromise).resolves.toMatchObject({
ok: false,
compacted: false,
reason: expect.stringContaining("aborted"),
});
expect(contextEngineCompactMock).not.toHaveBeenCalled();
expect(hookRunner.runBeforeCompaction).not.toHaveBeenCalled();
expect(hookRunner.runAfterCompaction).not.toHaveBeenCalled();
expect(isEmbeddedAgentRunHandleActive(TEST_SESSION_ID)).toBe(false);
});
it.each([
{ position: "primary", ownsCompaction: false, abortReason: "user_abort", resultOk: false },
{ position: "secondary", ownsCompaction: true, abortReason: "restart", resultOk: true },
] as const)(
"aborts $position native harness compaction through the registered handle",
async ({ ownsCompaction, abortReason, resultOk }) => {
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction },
compact: contextEngineCompactMock,
});
resolveAgentHarnessPolicyMock.mockReturnValue({
runtime: "codex",
runtimeSource: "model",
} as never);
const pending = mockPendingNativeCompaction();
const resultPromise = compactEmbeddedAgentSession(
wrappedCompactionArgs({
provider: "openai",
model: "gpt-5.4",
agentHarnessId: "codex",
trigger: "manual",
}),
);
await pending.started.promise;
const aborted =
abortReason === "restart"
? abortEmbeddedAgentRun(undefined, { mode: "compacting", reason: "restart" })
: abortEmbeddedAgentRun(TEST_SESSION_ID);
expect(aborted).toBe(true);
expect(pending.signal?.reason).toBe(abortReason);
pending.terminal.resolve({ ok: false, compacted: false, reason: "aborted" });
await expect(resultPromise).resolves.toMatchObject({ ok: resultOk });
expect(contextEngineCompactMock).toHaveBeenCalledTimes(ownsCompaction ? 1 : 0);
expect(isEmbeddedAgentRunHandleActive(TEST_SESSION_ID)).toBe(false);
},
);
it("registers manual compaction alongside its active reply operation", async () => {
const replyOperation = createReplyOperation({
sessionKey: TEST_SESSION_KEY,
sessionId: TEST_SESSION_ID,
resetTriggered: false,
});
replyOperation.setPhase("preflight_compacting");
expect(isEmbeddedAgentRunActive(TEST_SESSION_ID)).toBe(true);
expect(isEmbeddedAgentRunHandleActive(TEST_SESSION_ID)).toBe(false);
const pending = mockPendingContextEngineCompaction();
try {
const resultPromise = compactEmbeddedAgentSession(
wrappedCompactionArgs({
abortSignal: replyOperation.abortSignal,
trigger: "manual",
}),
);
await pending.started.promise;
expect(isEmbeddedAgentRunActive(TEST_SESSION_ID)).toBe(true);
expect(isEmbeddedAgentRunHandleActive(TEST_SESSION_ID)).toBe(true);
expect(replyOperation.abortByUser()).toBe(true);
expect(pending.signal?.aborted).toBe(true);
expect(pending.signal?.reason).toBe(replyOperation.abortSignal.reason);
pending.release.resolve(undefined);
await expect(resultPromise).resolves.toMatchObject({ ok: false, compacted: false });
expect(isEmbeddedAgentRunHandleActive(TEST_SESSION_ID)).toBe(false);
expect(isEmbeddedAgentRunActive(TEST_SESSION_ID)).toBe(true);
} finally {
replyOperation.complete();
}
});
it("clears the manual handle when setup rejects", async () => {
resolveModelMock.mockImplementationOnce(() => {
throw new Error("model failed");
});
await expect(
compactEmbeddedAgentSession(wrappedCompactionArgs({ trigger: "manual" })),
).rejects.toThrow("model failed");
expect(isEmbeddedAgentRunHandleActive(TEST_SESSION_ID)).toBe(false);
});
it.each([
{
identity: "session key",
activeSessionKey: TEST_SESSION_KEY,
activeSessionFile: "other-session.jsonl",
},
{
identity: "session file",
activeSessionKey: "agent:main:other-session",
activeSessionFile: TEST_SESSION_KEY,
},
])("rejects manual compaction matching an active $identity", async (active) => {
const activeSessionId = "other-session";
const activeSessionFile =
active.activeSessionFile === TEST_SESSION_KEY
? TEST_SESSION_KEY
: join(TEST_WORKSPACE_DIR, active.activeSessionFile);
const existingHandle = {
kind: "embedded" as const,
queueMessage: async () => {},
isStreaming: () => true,
isCompacting: () => false,
abort: vi.fn(),
};
setActiveEmbeddedRun(
activeSessionId,
existingHandle,
active.activeSessionKey,
activeSessionFile,
);
try {
await expect(
compactEmbeddedAgentSession(wrappedCompactionArgs({ trigger: "manual" })),
).resolves.toMatchObject({
ok: false,
compacted: false,
failure: { reason: "active_run" },
});
expect(contextEngineCompactMock).not.toHaveBeenCalled();
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
expect(isEmbeddedAgentRunHandleActive(activeSessionId)).toBe(true);
} finally {
clearActiveEmbeddedRun(
activeSessionId,
existingHandle,
active.activeSessionKey,
activeSessionFile,
);
}
});
it("does not duplicate transcript updates or sync in the wrapper when the engine delegates compaction", async () => {
const listener = vi.fn();
const cleanup = onSessionTranscriptUpdate(listener);
const sync = vi.fn(async () => {});
getMemorySearchManagerMock.mockResolvedValue({ manager: { sync } });
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
});
try {
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
config: compactionConfig("await"),
}),
);
expect(result.ok).toBe(true);
expect(listener).not.toHaveBeenCalled();
expect(sync).not.toHaveBeenCalled();
} finally {
cleanup();
}
});
it("reuses a delegated compaction successor session identity", async () => {
const maintain = vi.fn(async (_params?: unknown) => ({
changed: false,
bytesFreed: 0,
rewrittenEntries: 0,
}));
const delegatedSessionId = "delegated-session";
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
maintain,
} as never);
contextEngineCompactMock.mockResolvedValue({
ok: true,
compacted: true,
reason: undefined,
result: {
summary: "engine-summary",
firstKeptEntryId: "entry-1",
tokensBefore: 120,
tokensAfter: 50,
sessionId: delegatedSessionId,
},
} as never);
const result = await compactEmbeddedAgentSession(wrappedCompactionArgs());
expect(result.ok).toBe(true);
expect(result.result?.sessionId).toBe(delegatedSessionId);
expect(result.result?.sessionFile).toBeUndefined();
expectRecordFields(mockCallArg(maintain), {
sessionId: delegatedSessionId,
sessionFile: TEST_SESSION_KEY,
sessionTarget: expect.objectContaining({ sessionId: delegatedSessionId }),
});
});
it("keeps a partial structured successor in the active transcript store", async () => {
const maintain = vi.fn(async (_params?: unknown) => ({
changed: false,
bytesFreed: 0,
rewrittenEntries: 0,
}));
const delegatedSessionId = "delegated-session";
const storePath = join(TEST_WORKSPACE_DIR, "custom-active-sessions.json");
await upsertSessionEntryCore(
{ agentId: "main", sessionKey: TEST_SESSION_KEY, storePath },
{ sessionId: TEST_SESSION_ID, updatedAt: 1 },
);
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
maintain,
} as never);
contextEngineCompactMock.mockResolvedValue({
ok: true,
compacted: true,
result: {
sessionTarget: { sessionId: delegatedSessionId },
},
} as never);
const result = await compactEmbeddedAgentSession(
wrappedCompactionArgs({
sessionTarget: {
agentId: "main",
sessionId: TEST_SESSION_ID,
sessionKey: TEST_SESSION_KEY,
storePath,
},
}),
);
expect(result.ok).toBe(true);
expectRecordFields(mockCallArg(maintain), {
sessionId: delegatedSessionId,
sessionTarget: expect.objectContaining({
agentId: "main",
sessionId: delegatedSessionId,
sessionKey: TEST_SESSION_KEY,
storePath,
}),
});
});
it.each([
["session key", "agent:main:other", "active-sessions.json"],
["store path", TEST_SESSION_KEY, "other-sessions.json"],
])(
"rejects a structured successor outside the active %s",
async (_label, sessionKey, storeName) => {
const storePath = join(TEST_WORKSPACE_DIR, storeName);
const activeTarget = {
...wrappedCompactionArgs().sessionTarget,
storePath: join(TEST_WORKSPACE_DIR, "active-sessions.json"),
};
await upsertSessionEntryCore(activeTarget, { sessionId: TEST_SESSION_ID, updatedAt: 1 });
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
} as never);
contextEngineCompactMock.mockResolvedValue({
ok: true,
compacted: true,
result: {
sessionTarget: {
agentId: "main",
sessionId: "delegated-session",
sessionKey,
storePath,
},
},
} as never);
await expect(
compactEmbeddedAgentSession(wrappedCompactionArgs({ sessionTarget: activeTarget })),
).rejects.toThrow("successor target changed the active session binding");
expect(contextEngineCompactMock).toHaveBeenCalledOnce();
},
);
it("rejects a deprecated session-key successor outside the active binding", async () => {
const delegatedSessionId = "delegated-key-session";
const delegatedSessionKey = "agent:main:delegated-key-session";
const dir = await realpath(await mkdtemp(join(tmpdir(), "openclaw-compaction-successor-")));
const storePath = join(dir, "sessions.json");
const activeTarget = { ...wrappedCompactionArgs().sessionTarget, storePath };
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
} as never);
contextEngineCompactMock.mockResolvedValue({
ok: true,
compacted: true,
result: {
sessionFile: delegatedSessionKey,
},
} as never);
try {
await upsertSessionEntryCore(activeTarget, { sessionId: TEST_SESSION_ID, updatedAt: 1 });
await upsertSessionEntryCore(
{
agentId: "main",
sessionKey: delegatedSessionKey,
storePath,
},
{ sessionId: delegatedSessionId, updatedAt: 1 },
);
await expect(
compactEmbeddedAgentSession(wrappedCompactionArgs({ sessionTarget: activeTarget })),
).rejects.toThrow("successor target changed the active session binding");
expect(contextEngineCompactMock).toHaveBeenCalledOnce();
} finally {
closeOpenClawAgentDatabasesForTest();
await rm(dir, { force: true, recursive: true });
}
});
it("rejects a deprecated session-key successor with a mismatched stored id", async () => {
const dir = await realpath(
await mkdtemp(join(tmpdir(), "openclaw-compaction-successor-mismatch-")),
);
const storePath = join(dir, "sessions.json");
const activeTarget = { ...wrappedCompactionArgs().sessionTarget, storePath };
const delegatedSessionKey = "agent:main:delegated-key-mismatch";
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
} as never);
contextEngineCompactMock.mockResolvedValue({
ok: true,
compacted: true,
result: {
sessionFile: delegatedSessionKey,
sessionId: "reported-session",
},
} as never);
try {
await upsertSessionEntryCore(activeTarget, { sessionId: TEST_SESSION_ID, updatedAt: 1 });
await upsertSessionEntryCore(
{ agentId: "main", sessionKey: delegatedSessionKey, storePath },
{ sessionId: "stored-session", updatedAt: 1 },
);
await expect(
compactEmbeddedAgentSession(wrappedCompactionArgs({ sessionTarget: activeTarget })),
).rejects.toThrow("successor identity is inconsistent");
expect(contextEngineCompactMock).toHaveBeenCalledOnce();
} finally {
closeOpenClawAgentDatabasesForTest();
await rm(dir, { force: true, recursive: true });
}
});
it("preserves a deprecated SQLite marker successor for legacy maintenance", async () => {
const maintain = vi.fn(async (_params?: unknown) => ({
changed: false,
bytesFreed: 0,
rewrittenEntries: 0,
}));
const delegatedSessionId = "delegated-marker-session";
const storePath = join(TEST_WORKSPACE_DIR, "sessions.json");
const marker = `sqlite:main:${delegatedSessionId}:${storePath}`;
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
maintain,
} as never);
contextEngineCompactMock.mockResolvedValue({
ok: true,
compacted: true,
result: {
sessionFile: marker,
sessionId: delegatedSessionId,
},
} as never);
await compactEmbeddedAgentSession(wrappedCompactionArgs());
expectRecordFields(mockCallArg(maintain), {
sessionFile: marker,
sessionId: delegatedSessionId,
sessionTarget: expect.objectContaining({
sessionId: delegatedSessionId,
storePath,
}),
});
});
it("rejects a marker successor that contradicts the reported session id", async () => {
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
} as never);
contextEngineCompactMock.mockResolvedValue({
ok: true,
compacted: true,
result: {
sessionFile: `sqlite:main:marker-session:${join(TEST_WORKSPACE_DIR, "sessions.json")}`,
sessionId: TEST_SESSION_ID,
},
} as never);
await expect(compactEmbeddedAgentSession(wrappedCompactionArgs())).rejects.toThrow(
"successor identity is inconsistent",
);
});
it("rebinds a deprecated SQLite marker successor over the retained active entry", async () => {
const maintain = vi.fn(async (_params?: unknown) => ({
changed: false,
bytesFreed: 0,
rewrittenEntries: 0,
}));
const delegatedSessionId = "delegated-marker-session";
const dir = await realpath(
await mkdtemp(join(tmpdir(), "openclaw-compaction-marker-successor-")),
);
const storePath = join(dir, "sessions.json");
const marker = `sqlite:main:${delegatedSessionId}:${storePath}`;
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
maintain,
} as never);
contextEngineCompactMock.mockResolvedValue({
ok: true,
compacted: true,
result: {
sessionFile: marker,
sessionId: delegatedSessionId,
},
} as never);
try {
await upsertSessionEntryCore(
{ agentId: "main", sessionKey: TEST_SESSION_KEY, storePath },
{ sessionId: TEST_SESSION_ID, updatedAt: 1 },
);
await compactEmbeddedAgentSession(
wrappedCompactionArgs({
sessionTarget: {
agentId: "main",
sessionId: TEST_SESSION_ID,
sessionKey: TEST_SESSION_KEY,
storePath,
},
}),
);
expectRecordFields(mockCallArg(maintain), {
sessionId: delegatedSessionId,
sessionTarget: expect.objectContaining({
agentId: "main",
sessionId: delegatedSessionId,
sessionKey: TEST_SESSION_KEY,
storePath,
}),
});
} finally {
closeOpenClawAgentDatabasesForTest();
await rm(dir, { force: true, recursive: true });
}
});
it("rejects conflicting structured successor session ids", async () => {
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
} as never);
contextEngineCompactMock.mockResolvedValue({
ok: true,
compacted: true,
result: {
sessionId: "top-level-session",
sessionTarget: {
agentId: "main",
sessionId: "target-session",
sessionKey: TEST_SESSION_KEY,
storePath: join(TEST_WORKSPACE_DIR, "sessions.json"),
},
},
} as never);
await expect(compactEmbeddedAgentSession(wrappedCompactionArgs())).rejects.toThrow(
"successor identity is inconsistent",
);
});
it("derives queued compaction ownership from a self-contained session target", async () => {
await upsertSessionEntryCore(
{
agentId: "other",
sessionKey: "agent:other:main",
storePath: join(TEST_WORKSPACE_DIR, "other-sessions.json"),
},
{ sessionId: "other-session", updatedAt: 1 },
);
await compactEmbeddedAgentSession(
wrappedCompactionArgs({
agentId: undefined,
sessionKey: undefined,
sessionTarget: {
agentId: "other",
sessionId: "other-session",
sessionKey: "agent:other:main",
storePath: join(TEST_WORKSPACE_DIR, "other-sessions.json"),
},
}),
);
expect(resolveSessionAgentIdsMock).toHaveBeenCalledWith(
expect.objectContaining({ agentId: "other", sessionKey: "agent:other:main" }),
);
});
it("keeps a delegated result that echoes the current transcript on the active transcript", async () => {
const maintain = vi.fn(async (_params?: unknown) => ({
changed: false,
bytesFreed: 0,
rewrittenEntries: 0,
}));
resolveContextEngineMock.mockResolvedValue({
info: { ownsCompaction: false },
compact: contextEngineCompactMock,
maintain,
} as never);
contextEngineCompactMock.mockResolvedValue({
ok: true,
compacted: true,
reason: undefined,
result: {
summary: "engine-summary",
firstKeptEntryId: "entry-1",
tokensBefore: 120,
tokensAfter: 50,
sessionId: TEST_SESSION_ID,
},
} as never);
const result = await compactEmbeddedAgentSession(wrappedCompactionArgs());
expect(result.ok).toBe(true);
expect(rotateTranscriptAfterCompactionMock).not.toHaveBeenCalled();
expect(result.result?.sessionId).toBeUndefined();
expect(result.result?.sessionFile).toBeUndefined();
expectRecordFields(mockCallArg(maintain), {
sessionId: TEST_SESSION_ID,
sessionFile: TEST_SESSION_KEY,
});
});
it("catches and logs hook exceptions without aborting compaction", async () => {
hookRunner.hasHooks.mockReturnValue(true);
hookRunner.runBeforeCompaction.mockRejectedValue(new Error("hook boom"));
const result = await compactEmbeddedAgentSession(wrappedCompactionArgs());
expect(result.ok).toBe(true);
expect(result.compacted).toBe(true);
expect(contextEngineCompactMock).toHaveBeenCalledTimes(1);
});
});
/* oxlint-disable max-lines -- TODO: split this grandfathered oversized file. */