mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-03 21:44:05 +00:00
fix(agents): preserve Codex auth for compaction fallback
Fixes #86820.
Preserve Codex OAuth-backed compaction by selecting and loading the Codex harness before resolving direct or queued compaction models, while keeping OpenAI-compatible custom base URLs on the OpenAI context config path. Also preserves persisted concrete harness pins so compaction does not hot-switch existing sessions just because an explicit Codex fallback exists.
Verification:
- node scripts/run-vitest.mjs src/agents/embedded-agent-runner/compact.hooks.test.ts src/agents/harness/selection.test.ts src/agents/harness/runtime-plugin.test.ts
- pnpm tsgo:prod
- pnpm check:test-types
- pnpm lint --threads=8
- git diff --check origin/main...HEAD
- git diff --check
- autoreview clean: no accepted/actionable findings reported; overall patch is correct (0.82)
- GitHub PR checks green on ac6f93de4a
This commit is contained in:
committed by
GitHub
parent
43658872d9
commit
aada44fca5
@@ -618,14 +618,6 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => {
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(result.result?.summary).toBe("oauth fallback summary");
|
||||
findMockCall(resolveAgentHarnessPolicyMock, ([arg]) => {
|
||||
const policyArg = arg as Record<string, unknown>;
|
||||
return policyArg.provider === "openai" && policyArg.modelId === "gpt-primary";
|
||||
});
|
||||
findMockCall(resolveAgentHarnessPolicyMock, ([arg]) => {
|
||||
const policyArg = arg as Record<string, unknown>;
|
||||
return policyArg.provider === "openai" && policyArg.modelId === "gpt-fallback";
|
||||
});
|
||||
findMockCall(
|
||||
resolveModelMock,
|
||||
([provider, modelId]) => provider === "openai-codex" && modelId === "gpt-primary",
|
||||
@@ -639,7 +631,7 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the selected Codex runtime provider for OpenAI compaction context windows", async () => {
|
||||
it("uses the selected Codex runtime provider for OpenAI compaction", async () => {
|
||||
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "codex" });
|
||||
resolveModelMock.mockImplementation((provider = "openai", modelId = "fake") => ({
|
||||
model: { provider, api: "responses", id: modelId, input: [] },
|
||||
@@ -655,6 +647,7 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => {
|
||||
workspaceDir: "/tmp/workspace",
|
||||
provider: "openai",
|
||||
model: "gpt-5.5",
|
||||
agentHarnessId: "codex",
|
||||
config: {
|
||||
models: {
|
||||
providers: {
|
||||
@@ -672,7 +665,7 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => {
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(mockCallArg(resolveModelMock)).toBe("openai");
|
||||
expect(mockCallArg(resolveModelMock)).toBe("openai-codex");
|
||||
expect(mockCallArg(resolveModelMock, 0, 1)).toBe("gpt-5.5");
|
||||
expectRecordFields(mockCallArg(resolveContextWindowInfoMock), {
|
||||
provider: "openai-codex",
|
||||
@@ -680,8 +673,86 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves direct OpenAI API-key compaction when no Codex auth is configured", async () => {
|
||||
it("uses explicit Codex runtime policy for direct OpenAI compaction", async () => {
|
||||
resolveAgentHarnessPolicyMock.mockReturnValue({
|
||||
runtime: "codex",
|
||||
runtimeSource: "model",
|
||||
} as never);
|
||||
resolveModelMock.mockImplementation((provider = "openai", modelId = "fake") => ({
|
||||
model: { provider, api: "responses", id: modelId, input: [] },
|
||||
error: null,
|
||||
authStorage: { setRuntimeApiKey: vi.fn() },
|
||||
modelRegistry: {},
|
||||
}));
|
||||
|
||||
const result = await compactEmbeddedAgentSessionDirect({
|
||||
sessionId: "session-1",
|
||||
sessionKey: TEST_SESSION_KEY,
|
||||
sessionFile: "/tmp/session.jsonl",
|
||||
workspaceDir: "/tmp/workspace",
|
||||
config: {
|
||||
models: {
|
||||
providers: {
|
||||
openai: { models: [{ id: "fake-model", contextWindow: 1_000_000 }] },
|
||||
"openai-codex": { 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(mockCallArg(resolveModelMock)).toBe("openai-codex");
|
||||
expectRecordFields(mockCallArg(resolveContextWindowInfoMock), {
|
||||
provider: "openai-codex",
|
||||
modelId: "fake-model",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps custom OpenAI-compatible compaction on OpenAI context config", async () => {
|
||||
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "codex" });
|
||||
resolveModelMock.mockImplementation((provider = "openai", modelId = "fake") => ({
|
||||
model: { provider, api: "responses", id: modelId, input: [], contextWindow: 1_000_000 },
|
||||
error: null,
|
||||
authStorage: { setRuntimeApiKey: vi.fn() },
|
||||
modelRegistry: {},
|
||||
}));
|
||||
|
||||
const result = await compactEmbeddedAgentSessionDirect({
|
||||
sessionId: "session-1",
|
||||
sessionKey: TEST_SESSION_KEY,
|
||||
sessionFile: "/tmp/session.jsonl",
|
||||
workspaceDir: "/tmp/workspace",
|
||||
provider: "openai",
|
||||
model: "gpt-5.5",
|
||||
agentHarnessId: "codex",
|
||||
config: {
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "https://openai-compatible.example/v1",
|
||||
models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }],
|
||||
},
|
||||
"openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
|
||||
},
|
||||
},
|
||||
agents: { defaults: { embeddedHarness: { runtime: "codex" } } },
|
||||
} as never,
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(mockCallArg(resolveModelMock)).toBe("openai");
|
||||
expect(mockCallArg(resolveModelMock, 0, 1)).toBe("gpt-5.5");
|
||||
expectRecordFields(mockCallArg(resolveContextWindowInfoMock), {
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves direct OpenAI API-key compaction when OpenClaw runtime is active", async () => {
|
||||
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "openclaw" });
|
||||
resolveModelMock.mockImplementation((provider = "openai", modelId = "fake") => ({
|
||||
model: { provider, api: "responses", id: modelId, input: [] },
|
||||
error: null,
|
||||
@@ -702,7 +773,7 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => {
|
||||
openai: { models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }] },
|
||||
},
|
||||
},
|
||||
agents: { defaults: { embeddedHarness: { runtime: "codex" } } },
|
||||
agents: { defaults: { embeddedHarness: { runtime: "openclaw" } } },
|
||||
} as never,
|
||||
});
|
||||
|
||||
@@ -711,6 +782,52 @@ describe("compactEmbeddedAgentSessionDirect hooks", () => {
|
||||
expect(mockCallArg(resolveModelMock, 0, 1)).toBe("gpt-5.5");
|
||||
});
|
||||
|
||||
it("routes OpenAI compaction model overrides through Codex OAuth when Codex runtime is active", async () => {
|
||||
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "codex" });
|
||||
resolveModelMock.mockImplementation((provider = "openai", modelId = "fake") => ({
|
||||
model: { provider, api: "responses", id: modelId, input: [] },
|
||||
error: null,
|
||||
authStorage: { setRuntimeApiKey: vi.fn() },
|
||||
modelRegistry: {},
|
||||
}));
|
||||
|
||||
const result = await compactEmbeddedAgentSessionDirect({
|
||||
sessionId: "session-1",
|
||||
sessionKey: TEST_SESSION_KEY,
|
||||
sessionFile: "/tmp/session.jsonl",
|
||||
workspaceDir: "/tmp/workspace",
|
||||
provider: "openai",
|
||||
model: "gpt-5.5",
|
||||
agentHarnessId: "codex",
|
||||
config: {
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }, { id: "gpt-5.4-mini" }],
|
||||
},
|
||||
"openai-codex": {
|
||||
models: [{ id: "gpt-5.5" }, { id: "gpt-5.4-mini", contextWindow: 350_000 }],
|
||||
},
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
defaults: {
|
||||
embeddedHarness: { runtime: "codex" },
|
||||
compaction: { model: "openai/gpt-5.4-mini" },
|
||||
},
|
||||
},
|
||||
} as never,
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
expect(mockCallArg(resolveModelMock)).toBe("openai-codex");
|
||||
expect(mockCallArg(resolveModelMock, 0, 1)).toBe("gpt-5.4-mini");
|
||||
expectRecordFields(mockCallArg(resolveContextWindowInfoMock), {
|
||||
provider: "openai-codex",
|
||||
modelId: "gpt-5.4-mini",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses Codex auth for runtime model loading while preserving OpenAI context config", async () => {
|
||||
resolveAgentHarnessPolicyMock.mockReturnValue({ runtime: "openclaw" });
|
||||
resolveModelMock.mockImplementation((provider = "openai", modelId = "fake") => ({
|
||||
@@ -1671,8 +1788,7 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const harnessArg = mockCallArg(maybeCompactAgentHarnessSessionMock) as Record<string, unknown>;
|
||||
expect(harnessArg.contextTokenBudget).toBe(32_000);
|
||||
expect(maybeCompactAgentHarnessSessionMock).not.toHaveBeenCalled();
|
||||
const compactArg = mockCallArg(contextEngineCompactMock) as {
|
||||
tokenBudget?: number;
|
||||
runtimeContext?: Record<string, unknown>;
|
||||
@@ -1720,6 +1836,248 @@ describe("compactEmbeddedAgentSession hooks (ownsCompaction engine)", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("passes selected Codex runtime to queued context-engine runtime 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-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
const harnessArg = mockCallArg(maybeCompactAgentHarnessSessionMock) as Record<string, unknown>;
|
||||
expectRecordFields(harnessArg.contextEngineRuntimeContext, {
|
||||
provider: "openai",
|
||||
runtimeProvider: "openai-codex",
|
||||
model: "gpt-5.5",
|
||||
});
|
||||
});
|
||||
|
||||
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-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
const harnessArg = mockCallArg(maybeCompactAgentHarnessSessionMock) as Record<string, unknown>;
|
||||
expectRecordFields(harnessArg.contextEngineRuntimeContext, {
|
||||
provider: "openai",
|
||||
runtimeProvider: "openai-codex",
|
||||
model: "gpt-5.5",
|
||||
});
|
||||
});
|
||||
|
||||
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-codex": { 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("keeps concrete Codex pins when explicit policy is auto 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-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.ok).toBe(true);
|
||||
const harnessArg = mockCallArg(maybeCompactAgentHarnessSessionMock) as Record<string, unknown>;
|
||||
expectRecordFields(harnessArg.contextEngineRuntimeContext, {
|
||||
provider: "openai",
|
||||
runtimeProvider: "openai-codex",
|
||||
model: "gpt-5.5",
|
||||
});
|
||||
});
|
||||
|
||||
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: 1_000_000 }] },
|
||||
"openai-codex": { 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("keeps queued custom OpenAI-compatible compaction on OpenAI context config", 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: {
|
||||
baseUrl: "https://openai-compatible.example/v1",
|
||||
models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }],
|
||||
},
|
||||
"openai-codex": { 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).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 () => ({
|
||||
|
||||
@@ -16,16 +16,19 @@ import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
|
||||
import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js";
|
||||
import { enqueueCommandInLane } from "../../process/command-queue.js";
|
||||
import { parseAgentSessionKey } from "../../routing/session-key.js";
|
||||
import { resolveUserPath } from "../../utils.js";
|
||||
import { isDefaultAgentRuntimeId, normalizeOptionalAgentRuntimeId } from "../agent-runtime-id.js";
|
||||
import { resolveAgentDir, resolveSessionAgentIds } from "../agent-scope.js";
|
||||
import { resolveContextWindowInfo } from "../context-window-guard.js";
|
||||
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../defaults.js";
|
||||
import { isRecoverableNativeHarnessBindingFailure } from "../harness/compaction-recovery.js";
|
||||
import { ensureSelectedAgentHarnessPlugin } from "../harness/runtime-plugin.js";
|
||||
import {
|
||||
maybeCompactAgentHarnessSession,
|
||||
resolveAgentHarnessPolicy,
|
||||
} from "../harness/selection.js";
|
||||
import { resolveContextConfigProviderForRuntime } from "../openai-codex-routing.js";
|
||||
import { isOpenAICodexProvider, isOpenAIProvider } from "../openai-codex-routing.js";
|
||||
import { ensureRuntimePluginsLoaded } from "../runtime-plugins.js";
|
||||
import { DEFERRED_CONTEXT_ENGINE_COMPACTION_REASON } from "./compact-reasons.js";
|
||||
import type { CompactEmbeddedAgentSessionParams } from "./compact.types.js";
|
||||
@@ -169,7 +172,12 @@ export async function compactEmbeddedAgentSession(
|
||||
agentDir,
|
||||
workspaceDir: resolvedWorkspaceDir,
|
||||
});
|
||||
const resolvedCompactionTarget = resolveEmbeddedCompactionTarget({
|
||||
const runtimePolicySessionKey = params.sandboxSessionKey ?? params.sessionKey;
|
||||
const runtimePolicyAgentId =
|
||||
params.sandboxSessionKey && parseAgentSessionKey(params.sandboxSessionKey)
|
||||
? undefined
|
||||
: params.agentId;
|
||||
const policyCompactionTarget = resolveEmbeddedCompactionTarget({
|
||||
config: params.config,
|
||||
provider: params.provider,
|
||||
modelId: params.model,
|
||||
@@ -177,9 +185,51 @@ export async function compactEmbeddedAgentSession(
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
const configuredHarnessPolicy = resolveAgentHarnessPolicy({
|
||||
provider: policyCompactionTarget.provider ?? DEFAULT_PROVIDER,
|
||||
modelId: policyCompactionTarget.model ?? DEFAULT_MODEL,
|
||||
config: params.config,
|
||||
agentId: runtimePolicyAgentId,
|
||||
sessionKey: runtimePolicySessionKey,
|
||||
});
|
||||
const configuredHarnessRuntime =
|
||||
configuredHarnessPolicy.runtimeSource &&
|
||||
configuredHarnessPolicy.runtimeSource !== "implicit" &&
|
||||
!isDefaultAgentRuntimeId(configuredHarnessPolicy.runtime)
|
||||
? configuredHarnessPolicy.runtime
|
||||
: undefined;
|
||||
// The persisted harness id is the runtime contract for this session; config
|
||||
// changes can supply a runtime only when the session has no concrete pin.
|
||||
const selectedHarnessRuntime = params.agentHarnessId ?? configuredHarnessRuntime;
|
||||
const resolvedCompactionTarget = resolveEmbeddedCompactionTarget({
|
||||
config: params.config,
|
||||
provider: params.provider,
|
||||
modelId: params.model,
|
||||
authProfileId: params.authProfileId,
|
||||
harnessRuntime: selectedHarnessRuntime,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
const ceProvider = resolvedCompactionTarget.provider ?? DEFAULT_PROVIDER;
|
||||
const ceRuntimeProvider = resolvedCompactionTarget.runtimeProvider ?? ceProvider;
|
||||
const ceContextConfigProvider = resolvedCompactionTarget.contextProvider ?? ceProvider;
|
||||
const ceModelId = resolvedCompactionTarget.model ?? DEFAULT_MODEL;
|
||||
const attemptNativeHarnessCompaction = shouldAttemptNativeHarnessCompaction({
|
||||
provider: ceProvider,
|
||||
contextProvider: resolvedCompactionTarget.contextProvider,
|
||||
selectedHarnessRuntime,
|
||||
});
|
||||
if (attemptNativeHarnessCompaction) {
|
||||
await ensureSelectedAgentHarnessPlugin({
|
||||
config: params.config,
|
||||
provider: ceProvider,
|
||||
modelId: ceModelId,
|
||||
agentId: runtimePolicyAgentId,
|
||||
sessionKey: runtimePolicySessionKey,
|
||||
agentHarnessRuntimeOverride: selectedHarnessRuntime,
|
||||
workspaceDir: resolvedWorkspaceDir,
|
||||
});
|
||||
}
|
||||
const { model: ceModel } = await resolveModelAsync(
|
||||
ceRuntimeProvider,
|
||||
ceModelId,
|
||||
@@ -187,21 +237,11 @@ export async function compactEmbeddedAgentSession(
|
||||
params.config,
|
||||
);
|
||||
const ceRuntimeModel = ceModel as ProviderRuntimeModel | undefined;
|
||||
const ceHarnessPolicy = resolveAgentHarnessPolicy({
|
||||
provider: ceProvider,
|
||||
modelId: ceModelId,
|
||||
config: params.config,
|
||||
agentId: agentIds.sessionAgentId,
|
||||
sessionKey: params.sessionKey,
|
||||
});
|
||||
const resolvedContextTokenBudget =
|
||||
normalizeContextTokenBudget(
|
||||
resolveContextWindowInfo({
|
||||
cfg: params.config,
|
||||
provider: resolveContextConfigProviderForRuntime({
|
||||
provider: ceProvider,
|
||||
runtimeId: params.agentHarnessId ?? ceHarnessPolicy.runtime,
|
||||
}),
|
||||
provider: ceContextConfigProvider,
|
||||
modelId: ceModelId,
|
||||
modelContextTokens: readAgentModelContextTokens(ceModel),
|
||||
modelContextWindow: ceRuntimeModel?.contextWindow,
|
||||
@@ -216,15 +256,18 @@ export async function compactEmbeddedAgentSession(
|
||||
const contextEngineRuntimeContext = buildCompactionContextEngineRuntimeContext({
|
||||
params,
|
||||
agentDir,
|
||||
harnessRuntime: selectedHarnessRuntime,
|
||||
contextTokenBudget,
|
||||
contextEnginePluginId: resolveContextEngineOwnerPluginId(contextEngine),
|
||||
});
|
||||
const harnessResult = await maybeCompactAgentHarnessSession({
|
||||
...params,
|
||||
contextEngine,
|
||||
contextTokenBudget,
|
||||
contextEngineRuntimeContext,
|
||||
});
|
||||
const harnessResult = attemptNativeHarnessCompaction
|
||||
? await maybeCompactAgentHarnessSession({
|
||||
...params,
|
||||
contextEngine,
|
||||
contextTokenBudget,
|
||||
contextEngineRuntimeContext,
|
||||
})
|
||||
: undefined;
|
||||
if (harnessResult) {
|
||||
if (!shouldFallbackAfterHarnessCompaction(harnessResult)) {
|
||||
await contextEngine.dispose?.();
|
||||
@@ -468,9 +511,25 @@ export async function compactEmbeddedAgentSession(
|
||||
);
|
||||
}
|
||||
|
||||
function shouldAttemptNativeHarnessCompaction(params: {
|
||||
provider: string;
|
||||
contextProvider?: string;
|
||||
selectedHarnessRuntime?: string | null;
|
||||
}): boolean {
|
||||
if (isOpenAICodexProvider(params.provider)) {
|
||||
return true;
|
||||
}
|
||||
const selectedRuntime = normalizeOptionalAgentRuntimeId(params.selectedHarnessRuntime);
|
||||
if (!selectedRuntime || selectedRuntime === "auto" || selectedRuntime === "openclaw") {
|
||||
return false;
|
||||
}
|
||||
return isOpenAIProvider(params.provider) ? params.contextProvider !== undefined : true;
|
||||
}
|
||||
|
||||
function buildCompactionContextEngineRuntimeContext(params: {
|
||||
params: CompactEmbeddedAgentSessionParams;
|
||||
agentDir: string;
|
||||
harnessRuntime?: string;
|
||||
contextEnginePluginId?: string;
|
||||
contextTokenBudget?: number;
|
||||
}): ContextEngineRuntimeContext {
|
||||
@@ -499,6 +558,7 @@ function buildCompactionContextEngineRuntimeContext(params: {
|
||||
senderId: params.params.senderId,
|
||||
provider: params.params.provider,
|
||||
modelId: params.params.model,
|
||||
harnessRuntime: params.harnessRuntime,
|
||||
modelFallbacksOverride: params.params.modelFallbacksOverride,
|
||||
thinkLevel: params.params.thinkLevel,
|
||||
reasoningLevel: params.params.reasoningLevel,
|
||||
|
||||
@@ -24,7 +24,11 @@ import {
|
||||
resolveProviderTextTransforms,
|
||||
transformProviderSystemPrompt,
|
||||
} from "../../plugins/provider-runtime.js";
|
||||
import { isCronSessionKey, isSubagentSessionKey } from "../../routing/session-key.js";
|
||||
import {
|
||||
isCronSessionKey,
|
||||
isSubagentSessionKey,
|
||||
parseAgentSessionKey,
|
||||
} from "../../routing/session-key.js";
|
||||
import { resolveSkillsPromptForRun } from "../../skills/loading/workspace.js";
|
||||
import { resolveEmbeddedRunSkillEntries } from "../../skills/runtime/embedded-run-entries.js";
|
||||
import {
|
||||
@@ -41,6 +45,7 @@ import {
|
||||
setCompactionSafeguardCancelReason,
|
||||
} from "../agent-hooks/compaction-safeguard-runtime.js";
|
||||
import { createPreparedEmbeddedAgentSettingsManager } from "../agent-project-settings.js";
|
||||
import { isDefaultAgentRuntimeId } from "../agent-runtime-id.js";
|
||||
import {
|
||||
resolveAgentDir,
|
||||
resolveRunModelFallbacksOverride,
|
||||
@@ -87,7 +92,6 @@ import {
|
||||
import { isFallbackSummaryError, runWithModelFallback } from "../model-fallback.js";
|
||||
import { supportsModelTools } from "../model-tool-support.js";
|
||||
import { ensureOpenClawModelsJson } from "../models-config.js";
|
||||
import { resolveContextConfigProviderForRuntime } from "../openai-codex-routing.js";
|
||||
import { wrapStreamFnTextTransforms } from "../plugin-text-transforms.js";
|
||||
import { resolveAgentPromptSurfaceForSessionKey } from "../prompt-surface.js";
|
||||
import { registerProviderStreamForModel } from "../provider-stream.js";
|
||||
@@ -489,7 +493,19 @@ async function compactEmbeddedAgentSessionDirectOnce(
|
||||
workspaceDir: resolvedWorkspace,
|
||||
allowGatewaySubagentBinding: params.allowGatewaySubagentBinding,
|
||||
});
|
||||
const resolvedCompactionTarget = resolveEmbeddedCompactionTarget({
|
||||
const earlyAgentIds = resolveSessionAgentIds({
|
||||
sessionKey: params.sessionKey,
|
||||
config: params.config,
|
||||
agentId: params.agentId,
|
||||
});
|
||||
const agentDir =
|
||||
params.agentDir ?? resolveAgentDir(params.config ?? {}, earlyAgentIds.sessionAgentId);
|
||||
const runtimePolicySessionKey = params.sandboxSessionKey ?? params.sessionKey;
|
||||
const runtimePolicyAgentId =
|
||||
params.sandboxSessionKey && parseAgentSessionKey(params.sandboxSessionKey)
|
||||
? undefined
|
||||
: params.agentId;
|
||||
const policyCompactionTarget = resolveEmbeddedCompactionTarget({
|
||||
config: params.config,
|
||||
provider: params.provider,
|
||||
modelId: params.model,
|
||||
@@ -497,12 +513,47 @@ async function compactEmbeddedAgentSessionDirectOnce(
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
const configuredHarnessPolicy = resolveAgentHarnessPolicy({
|
||||
provider: policyCompactionTarget.provider ?? DEFAULT_PROVIDER,
|
||||
modelId: policyCompactionTarget.model ?? DEFAULT_MODEL,
|
||||
config: params.config,
|
||||
agentId: runtimePolicyAgentId,
|
||||
sessionKey: runtimePolicySessionKey,
|
||||
});
|
||||
const configuredHarnessRuntime =
|
||||
configuredHarnessPolicy.runtimeSource &&
|
||||
configuredHarnessPolicy.runtimeSource !== "implicit" &&
|
||||
!isDefaultAgentRuntimeId(configuredHarnessPolicy.runtime)
|
||||
? configuredHarnessPolicy.runtime
|
||||
: undefined;
|
||||
const selectedHarnessRuntime = params.agentHarnessId ?? configuredHarnessRuntime;
|
||||
const resolvedCompactionTarget = resolveEmbeddedCompactionTarget({
|
||||
config: params.config,
|
||||
provider: params.provider,
|
||||
modelId: params.model,
|
||||
authProfileId: params.authProfileId,
|
||||
harnessRuntime: selectedHarnessRuntime,
|
||||
defaultProvider: DEFAULT_PROVIDER,
|
||||
defaultModel: DEFAULT_MODEL,
|
||||
});
|
||||
// Keep the configured provider for harness policy, while auth/model loading below can
|
||||
// route OpenAI compaction through Codex OAuth when that runtime owns the session credentials.
|
||||
const provider = resolvedCompactionTarget.provider ?? DEFAULT_PROVIDER;
|
||||
const runtimeProvider = resolvedCompactionTarget.runtimeProvider ?? provider;
|
||||
const contextConfigProvider = resolvedCompactionTarget.contextProvider ?? provider;
|
||||
const modelId = resolvedCompactionTarget.model ?? DEFAULT_MODEL;
|
||||
const authProfileId = resolvedCompactionTarget.authProfileId;
|
||||
if (runtimeProvider !== provider || selectedHarnessRuntime) {
|
||||
await ensureSelectedAgentHarnessPlugin({
|
||||
config: params.config,
|
||||
provider,
|
||||
modelId,
|
||||
agentId: runtimePolicyAgentId,
|
||||
sessionKey: runtimePolicySessionKey,
|
||||
agentHarnessRuntimeOverride: selectedHarnessRuntime,
|
||||
workspaceDir: resolvedWorkspace,
|
||||
});
|
||||
}
|
||||
let thinkLevel: ThinkLevel = params.thinkLevel ?? "off";
|
||||
const attemptedThinking = new Set<ThinkLevel>();
|
||||
const fail = (reason: string, err?: unknown): EmbeddedAgentCompactResult => {
|
||||
@@ -531,13 +582,6 @@ async function compactEmbeddedAgentSessionDirectOnce(
|
||||
: undefined,
|
||||
};
|
||||
};
|
||||
const earlyAgentIds = resolveSessionAgentIds({
|
||||
sessionKey: params.sessionKey,
|
||||
config: params.config,
|
||||
agentId: params.agentId,
|
||||
});
|
||||
const agentDir =
|
||||
params.agentDir ?? resolveAgentDir(params.config ?? {}, earlyAgentIds.sessionAgentId);
|
||||
await ensureOpenClawModelsJson(params.config, agentDir, {
|
||||
workspaceDir: resolvedWorkspace,
|
||||
});
|
||||
@@ -627,11 +671,7 @@ async function compactEmbeddedAgentSessionDirectOnce(
|
||||
sessionId: params.sessionId,
|
||||
cwd: effectiveCwd,
|
||||
});
|
||||
const { sessionAgentId: effectiveSkillAgentId } = resolveSessionAgentIds({
|
||||
sessionKey: params.sessionKey,
|
||||
config: params.config,
|
||||
agentId: params.agentId,
|
||||
});
|
||||
const { sessionAgentId: effectiveSkillAgentId } = earlyAgentIds;
|
||||
|
||||
let restoreSkillEnv: (() => void) | undefined;
|
||||
let compactionSessionManager: unknown = null;
|
||||
@@ -683,19 +723,9 @@ async function compactEmbeddedAgentSessionDirectOnce(
|
||||
// Apply contextTokens cap to model so session runtime's auto-compaction
|
||||
// threshold uses the effective limit, not the native context window.
|
||||
const runtimeModelWithContext = runtimeModel as ProviderRuntimeModel;
|
||||
const runtimeHarnessPolicy = resolveAgentHarnessPolicy({
|
||||
provider,
|
||||
modelId,
|
||||
config: params.config,
|
||||
agentId: effectiveSkillAgentId,
|
||||
sessionKey: params.sessionKey,
|
||||
});
|
||||
const ctxInfo = resolveContextWindowInfo({
|
||||
cfg: params.config,
|
||||
provider: resolveContextConfigProviderForRuntime({
|
||||
provider,
|
||||
runtimeId: params.agentHarnessId ?? runtimeHarnessPolicy.runtime,
|
||||
}),
|
||||
provider: contextConfigProvider,
|
||||
modelId,
|
||||
modelContextTokens: readAgentModelContextTokens(runtimeModel),
|
||||
modelContextWindow: runtimeModelWithContext.contextWindow,
|
||||
@@ -731,7 +761,7 @@ async function compactEmbeddedAgentSessionDirectOnce(
|
||||
model: effectiveModel,
|
||||
modelApi: effectiveModel.api,
|
||||
harnessId: params.agentHarnessId,
|
||||
harnessRuntime: runtimeHarnessPolicy.runtime,
|
||||
harnessRuntime: selectedHarnessRuntime,
|
||||
authProfileProvider: authProfileId?.split(":", 1)[0],
|
||||
sessionAuthProfileId: authProfileId,
|
||||
config: params.config,
|
||||
|
||||
@@ -25,7 +25,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
workspaceDir: "/tmp/workspace",
|
||||
cwd: "/tmp/task-repo",
|
||||
agentDir: "/tmp/agent",
|
||||
config: {} as OpenClawConfig,
|
||||
config: {} as unknown as OpenClawConfig,
|
||||
senderIsOwner: true,
|
||||
senderId: "user-123",
|
||||
provider: "openai-codex",
|
||||
@@ -87,7 +87,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
agentDir: "/tmp/agent",
|
||||
config: {
|
||||
agents: { defaults: { compaction: { model: "anthropic/claude-opus-4-6" } } },
|
||||
} as OpenClawConfig,
|
||||
} as unknown as OpenClawConfig,
|
||||
provider: "ollama",
|
||||
modelId: "minimax-m2.7:cloud",
|
||||
authProfileId: "ollama:default",
|
||||
@@ -104,7 +104,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
agentDir: "/tmp/agent",
|
||||
config: {
|
||||
agents: { defaults: { compaction: { model: "gpt-4o" } } },
|
||||
} as OpenClawConfig,
|
||||
} as unknown as OpenClawConfig,
|
||||
provider: "openai",
|
||||
modelId: "gpt-3.5-turbo",
|
||||
authProfileId: "openai:p1",
|
||||
@@ -119,7 +119,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
const result = buildEmbeddedCompactionRuntimeContext({
|
||||
workspaceDir: "/tmp/workspace",
|
||||
agentDir: "/tmp/agent",
|
||||
config: {} as OpenClawConfig,
|
||||
config: {} as unknown as OpenClawConfig,
|
||||
provider: "ollama",
|
||||
modelId: "minimax-m2.7:cloud",
|
||||
authProfileId: "ollama:default",
|
||||
@@ -153,7 +153,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
sessionKey: "agent:main:thread:1",
|
||||
workspaceDir: "/tmp/workspace",
|
||||
agentDir: "/tmp/agent",
|
||||
config: {} as OpenClawConfig,
|
||||
config: {} as unknown as OpenClawConfig,
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -188,7 +188,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
const result = buildEmbeddedCompactionRuntimeContext({
|
||||
workspaceDir: "/tmp/workspace",
|
||||
agentDir: "/tmp/agent",
|
||||
config: {} as OpenClawConfig,
|
||||
config: {} as unknown as OpenClawConfig,
|
||||
});
|
||||
|
||||
expect(result.activeProcessSessions).toBeUndefined();
|
||||
@@ -199,7 +199,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
resolveEmbeddedCompactionTarget({
|
||||
config: {
|
||||
agents: { defaults: { compaction: { model: "anthropic/" } } },
|
||||
} as OpenClawConfig,
|
||||
} as unknown as OpenClawConfig,
|
||||
provider: "openai-codex",
|
||||
modelId: "gpt-5.4",
|
||||
authProfileId: "openai:p1",
|
||||
@@ -223,6 +223,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
});
|
||||
expect(result.provider).toBe("openai");
|
||||
expect(result.runtimeProvider).toBe("openai-codex");
|
||||
expect(result.contextProvider).toBeUndefined();
|
||||
expect(result.model).toBe("gpt-5.4");
|
||||
expect(result.authProfileId).toBe("openai-codex:default");
|
||||
});
|
||||
@@ -231,7 +232,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
const result = resolveEmbeddedCompactionTarget({
|
||||
config: {
|
||||
auth: { order: { openai: ["openai-codex:default"] } },
|
||||
} as OpenClawConfig,
|
||||
} as unknown as OpenClawConfig,
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
defaultProvider: "openai",
|
||||
@@ -239,6 +240,90 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
});
|
||||
expect(result.provider).toBe("openai");
|
||||
expect(result.runtimeProvider).toBe("openai-codex");
|
||||
expect(result.contextProvider).toBeUndefined();
|
||||
expect(result.model).toBe("gpt-5.5");
|
||||
expect(result.authProfileId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("routes Codex-runtime OpenAI compaction through the plugin-backed Codex provider", () => {
|
||||
const result = resolveEmbeddedCompactionTarget({
|
||||
config: {
|
||||
models: {
|
||||
providers: {
|
||||
openai: { models: [{ id: "gpt-5.5" }] },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig,
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
harnessRuntime: "codex",
|
||||
defaultProvider: "openai",
|
||||
defaultModel: "gpt-5.5",
|
||||
});
|
||||
expect(result.provider).toBe("openai");
|
||||
expect(result.runtimeProvider).toBe("openai-codex");
|
||||
expect(result.contextProvider).toBe("openai-codex");
|
||||
expect(result.model).toBe("gpt-5.5");
|
||||
expect(result.authProfileId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("carries the selected harness id for delegated runtime compaction", () => {
|
||||
const result = buildEmbeddedCompactionRuntimeContext({
|
||||
workspaceDir: "/tmp/workspace",
|
||||
agentDir: "/tmp/agent",
|
||||
config: {} as unknown as OpenClawConfig,
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
harnessRuntime: "codex",
|
||||
});
|
||||
expect(result.agentHarnessId).toBe("codex");
|
||||
expect(result.runtimeProvider).toBe("openai-codex");
|
||||
});
|
||||
|
||||
it("preserves direct OpenAI compaction for the OpenClaw runtime", () => {
|
||||
const result = resolveEmbeddedCompactionTarget({
|
||||
config: {
|
||||
models: {
|
||||
providers: {
|
||||
openai: { models: [{ id: "gpt-5.5" }] },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig,
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
harnessRuntime: "openclaw",
|
||||
defaultProvider: "openai",
|
||||
defaultModel: "gpt-5.5",
|
||||
});
|
||||
expect(result.provider).toBe("openai");
|
||||
expect(result.runtimeProvider).toBeUndefined();
|
||||
expect(result.contextProvider).toBeUndefined();
|
||||
expect(result.model).toBe("gpt-5.5");
|
||||
expect(result.authProfileId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("preserves custom OpenAI-compatible compaction providers", () => {
|
||||
const result = resolveEmbeddedCompactionTarget({
|
||||
config: {
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "https://openai-compatible.example/v1",
|
||||
models: [{ id: "gpt-5.5" }],
|
||||
},
|
||||
"openai-codex": { models: [{ id: "gpt-5.5" }] },
|
||||
},
|
||||
},
|
||||
} as unknown as OpenClawConfig,
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
harnessRuntime: "codex",
|
||||
defaultProvider: "openai",
|
||||
defaultModel: "gpt-5.5",
|
||||
});
|
||||
expect(result.provider).toBe("openai");
|
||||
expect(result.runtimeProvider).toBeUndefined();
|
||||
expect(result.contextProvider).toBeUndefined();
|
||||
expect(result.model).toBe("gpt-5.5");
|
||||
expect(result.authProfileId).toBeUndefined();
|
||||
});
|
||||
@@ -247,7 +332,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
const result = resolveEmbeddedCompactionTarget({
|
||||
config: {
|
||||
agents: { defaults: { compaction: { model: "gpt-5.4" } } },
|
||||
} as OpenClawConfig,
|
||||
} as unknown as OpenClawConfig,
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
authProfileId: "openai-codex:default",
|
||||
@@ -256,6 +341,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
});
|
||||
expect(result.provider).toBe("openai");
|
||||
expect(result.runtimeProvider).toBe("openai-codex");
|
||||
expect(result.contextProvider).toBeUndefined();
|
||||
expect(result.model).toBe("gpt-5.4");
|
||||
expect(result.authProfileId).toBe("openai-codex:default");
|
||||
});
|
||||
@@ -264,7 +350,7 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
const result = resolveEmbeddedCompactionTarget({
|
||||
config: {
|
||||
agents: { defaults: { compaction: { model: "openai/gpt-5.4" } } },
|
||||
} as OpenClawConfig,
|
||||
} as unknown as OpenClawConfig,
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
authProfileId: "openai-codex:default",
|
||||
@@ -273,10 +359,35 @@ describe("buildEmbeddedCompactionRuntimeContext", () => {
|
||||
});
|
||||
expect(result.provider).toBe("openai");
|
||||
expect(result.runtimeProvider).toBe("openai-codex");
|
||||
expect(result.contextProvider).toBeUndefined();
|
||||
expect(result.model).toBe("gpt-5.4");
|
||||
expect(result.authProfileId).toBe("openai-codex:default");
|
||||
});
|
||||
|
||||
it("routes OpenAI compaction model overrides through Codex runtime auth", () => {
|
||||
const result = resolveEmbeddedCompactionTarget({
|
||||
config: {
|
||||
models: {
|
||||
providers: {
|
||||
openai: { models: [{ id: "gpt-5.5" }, { id: "gpt-5.4-mini" }] },
|
||||
"openai-codex": { models: [{ id: "gpt-5.5" }, { id: "gpt-5.4-mini" }] },
|
||||
},
|
||||
},
|
||||
agents: { defaults: { compaction: { model: "openai/gpt-5.4-mini" } } },
|
||||
} as unknown as OpenClawConfig,
|
||||
provider: "openai",
|
||||
modelId: "gpt-5.5",
|
||||
harnessRuntime: "codex",
|
||||
defaultProvider: "openai",
|
||||
defaultModel: "gpt-5.5",
|
||||
});
|
||||
expect(result.provider).toBe("openai");
|
||||
expect(result.runtimeProvider).toBe("openai-codex");
|
||||
expect(result.contextProvider).toBe("openai-codex");
|
||||
expect(result.model).toBe("gpt-5.4-mini");
|
||||
expect(result.authProfileId).toBeUndefined();
|
||||
});
|
||||
|
||||
it("leaves non-openai providers unchanged", () => {
|
||||
const result = resolveEmbeddedCompactionTarget({
|
||||
provider: "anthropic",
|
||||
|
||||
@@ -2,12 +2,16 @@ import type { SourceReplyDeliveryMode } from "../../auto-reply/get-reply-options
|
||||
import type { ReasoningLevel, ThinkLevel } from "../../auto-reply/thinking.js";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import type { SkillSnapshot } from "../../skills/types.js";
|
||||
import { normalizeOptionalAgentRuntimeId } from "../agent-runtime-id.js";
|
||||
import {
|
||||
listActiveProcessSessionReferences,
|
||||
type ActiveProcessSessionReference,
|
||||
} from "../bash-process-references.js";
|
||||
import type { ExecElevatedDefaults } from "../bash-tools.js";
|
||||
import { resolveSelectedOpenAIRuntimeProvider } from "../openai-codex-routing.js";
|
||||
import {
|
||||
openAIProviderUsesCodexRuntimeByDefault,
|
||||
resolveSelectedOpenAIRuntimeProvider,
|
||||
} from "../openai-codex-routing.js";
|
||||
|
||||
export type EmbeddedCompactionRuntimeContext = {
|
||||
sessionKey?: string;
|
||||
@@ -18,6 +22,7 @@ export type EmbeddedCompactionRuntimeContext = {
|
||||
currentThreadTs?: string;
|
||||
currentMessageId?: string | number;
|
||||
authProfileId?: string;
|
||||
agentHarnessId?: string;
|
||||
workspaceDir: string;
|
||||
cwd?: string;
|
||||
agentDir: string;
|
||||
@@ -47,37 +52,49 @@ export function resolveEmbeddedCompactionTarget(params: {
|
||||
provider?: string | null;
|
||||
modelId?: string | null;
|
||||
authProfileId?: string | null;
|
||||
harnessRuntime?: string | null;
|
||||
defaultProvider?: string;
|
||||
defaultModel?: string;
|
||||
}): {
|
||||
provider: string | undefined;
|
||||
runtimeProvider?: string;
|
||||
contextProvider?: string;
|
||||
model: string | undefined;
|
||||
authProfileId: string | undefined;
|
||||
} {
|
||||
const provider = params.provider?.trim() || params.defaultProvider;
|
||||
const model = params.modelId?.trim() || params.defaultModel;
|
||||
const override = params.config?.agents?.defaults?.compaction?.model?.trim();
|
||||
const resolveRuntimeProvider = (
|
||||
const resolveTargetProviders = (
|
||||
targetProvider: string | undefined,
|
||||
authProfileId: string | undefined,
|
||||
) => {
|
||||
if (!targetProvider) {
|
||||
return undefined;
|
||||
return {};
|
||||
}
|
||||
const useCodexHarnessRuntime = shouldUseCodexRuntimeProviderForCompaction({
|
||||
config: params.config,
|
||||
provider: targetProvider,
|
||||
harnessRuntime: params.harnessRuntime,
|
||||
});
|
||||
const harnessRuntime = useCodexHarnessRuntime ? params.harnessRuntime : "openclaw";
|
||||
const runtimeProvider = resolveSelectedOpenAIRuntimeProvider({
|
||||
provider: targetProvider,
|
||||
harnessRuntime: "openclaw",
|
||||
harnessRuntime: harnessRuntime ?? undefined,
|
||||
authProfileId,
|
||||
config: params.config,
|
||||
});
|
||||
return runtimeProvider === targetProvider ? undefined : runtimeProvider;
|
||||
const routedRuntimeProvider = runtimeProvider === targetProvider ? undefined : runtimeProvider;
|
||||
return {
|
||||
runtimeProvider: routedRuntimeProvider,
|
||||
contextProvider: useCodexHarnessRuntime ? routedRuntimeProvider : undefined,
|
||||
};
|
||||
};
|
||||
if (!override) {
|
||||
const authProfileId = params.authProfileId ?? undefined;
|
||||
return {
|
||||
provider,
|
||||
runtimeProvider: resolveRuntimeProvider(provider, authProfileId),
|
||||
...resolveTargetProviders(provider, authProfileId),
|
||||
model,
|
||||
authProfileId,
|
||||
};
|
||||
@@ -94,7 +111,7 @@ export function resolveEmbeddedCompactionTarget(params: {
|
||||
: (params.authProfileId ?? undefined);
|
||||
return {
|
||||
provider: overrideProvider,
|
||||
runtimeProvider: resolveRuntimeProvider(overrideProvider, authProfileId),
|
||||
...resolveTargetProviders(overrideProvider, authProfileId),
|
||||
model: overrideModel,
|
||||
authProfileId,
|
||||
};
|
||||
@@ -102,12 +119,26 @@ export function resolveEmbeddedCompactionTarget(params: {
|
||||
const authProfileId = params.authProfileId ?? undefined;
|
||||
return {
|
||||
provider,
|
||||
runtimeProvider: resolveRuntimeProvider(provider, authProfileId),
|
||||
...resolveTargetProviders(provider, authProfileId),
|
||||
model: override,
|
||||
authProfileId,
|
||||
};
|
||||
}
|
||||
|
||||
function shouldUseCodexRuntimeProviderForCompaction(params: {
|
||||
config?: OpenClawConfig;
|
||||
provider: string;
|
||||
harnessRuntime?: string | null;
|
||||
}): boolean {
|
||||
if (normalizeOptionalAgentRuntimeId(params.harnessRuntime) !== "codex") {
|
||||
return false;
|
||||
}
|
||||
if (!openAIProviderUsesCodexRuntimeByDefault(params)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function buildEmbeddedCompactionRuntimeContext(params: {
|
||||
sessionKey?: string | null;
|
||||
messageChannel?: string | null;
|
||||
@@ -126,6 +157,7 @@ export function buildEmbeddedCompactionRuntimeContext(params: {
|
||||
senderId?: string | null;
|
||||
provider?: string | null;
|
||||
modelId?: string | null;
|
||||
harnessRuntime?: string | null;
|
||||
modelFallbacksOverride?: string[];
|
||||
thinkLevel?: ThinkLevel;
|
||||
reasoningLevel?: ReasoningLevel;
|
||||
@@ -140,7 +172,9 @@ export function buildEmbeddedCompactionRuntimeContext(params: {
|
||||
provider: params.provider,
|
||||
modelId: params.modelId,
|
||||
authProfileId: params.authProfileId,
|
||||
harnessRuntime: params.harnessRuntime,
|
||||
});
|
||||
const agentHarnessId = params.harnessRuntime?.trim() || undefined;
|
||||
const processScopeKey = params.sessionKey?.trim();
|
||||
const activeProcessSessions =
|
||||
params.activeProcessSessions ??
|
||||
@@ -156,6 +190,7 @@ export function buildEmbeddedCompactionRuntimeContext(params: {
|
||||
currentThreadTs: params.currentThreadTs ?? undefined,
|
||||
currentMessageId: params.currentMessageId ?? undefined,
|
||||
authProfileId: resolved.authProfileId,
|
||||
agentHarnessId,
|
||||
workspaceDir: params.workspaceDir,
|
||||
cwd: params.cwd ?? undefined,
|
||||
agentDir: params.agentDir,
|
||||
|
||||
@@ -768,6 +768,7 @@ export async function runEmbeddedAgent(
|
||||
contextConfigProvider: resolveContextConfigProviderForRuntime({
|
||||
provider: modelConfigProvider,
|
||||
runtimeId: agentHarness.id,
|
||||
config: params.config,
|
||||
}),
|
||||
modelId,
|
||||
runtimeModel,
|
||||
@@ -1905,6 +1906,7 @@ export async function runEmbeddedAgent(
|
||||
senderId: params.senderId,
|
||||
provider,
|
||||
modelId,
|
||||
harnessRuntime: agentHarness.id,
|
||||
modelFallbacksOverride: params.modelFallbacksOverride,
|
||||
thinkLevel,
|
||||
reasoningLevel: params.reasoningLevel,
|
||||
@@ -2096,6 +2098,7 @@ export async function runEmbeddedAgent(
|
||||
senderId: params.senderId,
|
||||
provider,
|
||||
modelId,
|
||||
harnessRuntime: agentHarness.id,
|
||||
thinkLevel,
|
||||
reasoningLevel: params.reasoningLevel,
|
||||
bashElevated: params.bashElevated,
|
||||
|
||||
@@ -534,6 +534,7 @@ type AfterTurnRuntimeContextAttempt = Pick<
|
||||
| "senderId"
|
||||
| "provider"
|
||||
| "modelId"
|
||||
| "agentHarnessId"
|
||||
| "thinkLevel"
|
||||
| "reasoningLevel"
|
||||
| "bashElevated"
|
||||
@@ -574,6 +575,7 @@ export function buildAfterTurnRuntimeContext(params: {
|
||||
senderId: params.attempt.senderId,
|
||||
provider: params.attempt.provider,
|
||||
modelId: params.attempt.modelId,
|
||||
harnessRuntime: params.attempt.agentHarnessId,
|
||||
thinkLevel: params.attempt.thinkLevel,
|
||||
reasoningLevel: params.attempt.reasoningLevel,
|
||||
bashElevated: params.attempt.bashElevated,
|
||||
|
||||
@@ -756,6 +756,35 @@ describe("selectAgentHarness", () => {
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it("honors selected plugin harness pins during compaction preflight", async () => {
|
||||
const compact = vi.fn(async () => ({ ok: true, compacted: false }));
|
||||
registerAgentHarness(
|
||||
{
|
||||
id: "codex",
|
||||
label: "Codex",
|
||||
supports: (ctx) =>
|
||||
ctx.provider === "openai" ? { supported: true, priority: 100 } : { supported: false },
|
||||
runAttempt: vi.fn(async () => createAttemptResult("codex")),
|
||||
compact,
|
||||
},
|
||||
{ ownerPluginId: "codex" },
|
||||
);
|
||||
|
||||
await expect(
|
||||
maybeCompactAgentHarnessSession({
|
||||
sessionId: "session-1",
|
||||
sessionKey: "agent:main:main",
|
||||
sessionFile: "/tmp/session.jsonl",
|
||||
workspaceDir: "/tmp/workspace",
|
||||
provider: "openai",
|
||||
model: "gpt-5.5",
|
||||
agentHarnessId: "codex",
|
||||
config: agentModelRuntimeConfig("openai/gpt-5.5", "openclaw"),
|
||||
}),
|
||||
).resolves.toEqual({ ok: true, compacted: false });
|
||||
expect(compact).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("does not compact a selected plugin harness through OpenClaw when the plugin has no compactor", async () => {
|
||||
registerFailingCodexHarness();
|
||||
|
||||
@@ -777,6 +806,95 @@ describe("selectAgentHarness", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses agent-scoped runtime policy during compaction preflight", async () => {
|
||||
const compact = vi.fn(async () => ({ ok: true, compacted: false }));
|
||||
registerAgentHarness(
|
||||
{
|
||||
id: "codex",
|
||||
label: "Codex",
|
||||
supports: (ctx) =>
|
||||
ctx.provider === "openai" ? { supported: true, priority: 100 } : { supported: false },
|
||||
runAttempt: vi.fn(async () => createAttemptResult("codex")),
|
||||
compact,
|
||||
},
|
||||
{ ownerPluginId: "codex" },
|
||||
);
|
||||
|
||||
await expect(
|
||||
maybeCompactAgentHarnessSession({
|
||||
sessionId: "session-1",
|
||||
sessionKey: "agent:strict:main",
|
||||
sessionFile: "/tmp/session.jsonl",
|
||||
workspaceDir: "/tmp/workspace",
|
||||
provider: "openai",
|
||||
model: "gpt-5.5",
|
||||
agentId: "strict",
|
||||
config: agentModelRuntimeConfig("openai/gpt-5.5", "codex", "strict"),
|
||||
}),
|
||||
).resolves.toEqual({ ok: true, compacted: false });
|
||||
expect(compact).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("uses sandbox session key for compaction preflight runtime policy", async () => {
|
||||
const compact = vi.fn(async () => ({ ok: true, compacted: false }));
|
||||
registerAgentHarness(
|
||||
{
|
||||
id: "codex",
|
||||
label: "Codex",
|
||||
supports: (ctx) =>
|
||||
ctx.provider === "openai" ? { supported: true, priority: 100 } : { supported: false },
|
||||
runAttempt: vi.fn(async () => createAttemptResult("codex")),
|
||||
compact,
|
||||
},
|
||||
{ ownerPluginId: "codex" },
|
||||
);
|
||||
|
||||
await expect(
|
||||
maybeCompactAgentHarnessSession({
|
||||
sessionId: "session-1",
|
||||
sessionKey: "agent:main:main",
|
||||
sandboxSessionKey: "agent:strict:main",
|
||||
sessionFile: "/tmp/session.jsonl",
|
||||
workspaceDir: "/tmp/workspace",
|
||||
provider: "openai",
|
||||
model: "gpt-5.5",
|
||||
agentId: "main",
|
||||
config: agentModelRuntimeConfig("openai/gpt-5.5", "codex", "strict"),
|
||||
}),
|
||||
).resolves.toEqual({ ok: true, compacted: false });
|
||||
expect(compact).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("keeps explicit agent id for non-agent sandbox policy keys during compaction preflight", async () => {
|
||||
const compact = vi.fn(async () => ({ ok: true, compacted: false }));
|
||||
registerAgentHarness(
|
||||
{
|
||||
id: "codex",
|
||||
label: "Codex",
|
||||
supports: (ctx) =>
|
||||
ctx.provider === "openai" ? { supported: true, priority: 100 } : { supported: false },
|
||||
runAttempt: vi.fn(async () => createAttemptResult("codex")),
|
||||
compact,
|
||||
},
|
||||
{ ownerPluginId: "codex" },
|
||||
);
|
||||
|
||||
await expect(
|
||||
maybeCompactAgentHarnessSession({
|
||||
sessionId: "session-1",
|
||||
sessionKey: "agent:main:main",
|
||||
sandboxSessionKey: "global",
|
||||
sessionFile: "/tmp/session.jsonl",
|
||||
workspaceDir: "/tmp/workspace",
|
||||
provider: "openai",
|
||||
model: "gpt-5.5",
|
||||
agentId: "strict",
|
||||
config: agentModelRuntimeConfig("openai/gpt-5.5", "codex", "strict"),
|
||||
}),
|
||||
).resolves.toEqual({ ok: true, compacted: false });
|
||||
expect(compact).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ provider: "anthropic", modelId: "sonnet-4.6", alias: "claude-cli" },
|
||||
{ provider: "google", modelId: "gemini-3-pro-preview", alias: "google-gemini-cli" },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
import { parseAgentSessionKey } from "../../routing/session-key.js";
|
||||
import { isDefaultAgentRuntimeId, normalizeOptionalAgentRuntimeId } from "../agent-runtime-id.js";
|
||||
import {
|
||||
resolveEffectiveToolPolicy,
|
||||
@@ -490,21 +491,43 @@ export async function maybeCompactAgentHarnessSession(
|
||||
if (params.provider && isCliRuntimeProvider(params.provider, { config: params.config })) {
|
||||
return undefined;
|
||||
}
|
||||
const runtimePolicySessionKey = params.sandboxSessionKey ?? params.sessionKey;
|
||||
const runtimePolicyAgentId =
|
||||
params.sandboxSessionKey && parseAgentSessionKey(params.sandboxSessionKey)
|
||||
? undefined
|
||||
: params.agentId;
|
||||
const runtime = resolveConfiguredAgentHarnessPolicy({
|
||||
provider: params.provider,
|
||||
modelId: params.model,
|
||||
config: params.config,
|
||||
sessionKey: params.sessionKey,
|
||||
agentId: runtimePolicyAgentId,
|
||||
sessionKey: runtimePolicySessionKey,
|
||||
}).runtime;
|
||||
if (isCliRuntimeAliasForProvider({ runtime, provider: params.provider, cfg: params.config })) {
|
||||
return undefined;
|
||||
}
|
||||
const harness = selectAgentHarness({
|
||||
provider: params.provider ?? "",
|
||||
modelId: params.model,
|
||||
config: params.config,
|
||||
sessionKey: params.sessionKey,
|
||||
});
|
||||
const selectedRuntime = normalizeOptionalAgentRuntimeId(params.agentHarnessId);
|
||||
const agentHarnessRuntimeOverride =
|
||||
selectedRuntime && !isDefaultAgentRuntimeId(selectedRuntime) ? selectedRuntime : undefined;
|
||||
let harness: AgentHarness;
|
||||
try {
|
||||
harness = selectAgentHarness({
|
||||
provider: params.provider ?? "",
|
||||
modelId: params.model,
|
||||
config: params.config,
|
||||
agentId: runtimePolicyAgentId,
|
||||
sessionKey: runtimePolicySessionKey,
|
||||
agentHarnessRuntimeOverride,
|
||||
});
|
||||
} catch (err) {
|
||||
if (agentHarnessRuntimeOverride) {
|
||||
const message = formatErrorMessage(err);
|
||||
if (message.includes("does not support")) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
if (!harness.compact) {
|
||||
if (harness.id !== "openclaw") {
|
||||
return {
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
listOpenAIAuthProfileProvidersForAgentRuntime,
|
||||
modelSelectionShouldEnsureCodexPlugin,
|
||||
openAIProviderUsesCodexRuntimeByDefault,
|
||||
resolveContextConfigProviderForRuntime,
|
||||
resolveOpenAIRuntimeProvider,
|
||||
resolveSelectedOpenAIRuntimeProvider,
|
||||
} from "./openai-codex-routing.js";
|
||||
@@ -33,6 +34,22 @@ describe("OpenAI Codex routing policy", () => {
|
||||
|
||||
expect(openAIProviderUsesCodexRuntimeByDefault({ provider: "openai", config })).toBe(false);
|
||||
expect(modelSelectionShouldEnsureCodexPlugin({ model: "openai/gpt-5.5", config })).toBe(false);
|
||||
expect(
|
||||
resolveContextConfigProviderForRuntime({
|
||||
provider: "openai",
|
||||
runtimeId: "codex",
|
||||
config,
|
||||
}),
|
||||
).toBe("openai");
|
||||
});
|
||||
|
||||
it("uses Codex context config for official OpenAI under the Codex runtime", () => {
|
||||
expect(
|
||||
resolveContextConfigProviderForRuntime({
|
||||
provider: "openai",
|
||||
runtimeId: "codex",
|
||||
}),
|
||||
).toBe("openai-codex");
|
||||
});
|
||||
|
||||
it("maps explicit OpenClaw plus Codex auth profile to the OpenClaw Codex-auth transport", () => {
|
||||
|
||||
@@ -200,10 +200,15 @@ export function resolveSelectedOpenAIRuntimeProvider(params: {
|
||||
export function resolveContextConfigProviderForRuntime(params: {
|
||||
provider: string;
|
||||
runtimeId?: string;
|
||||
config?: OpenClawConfig;
|
||||
}): string {
|
||||
const provider = normalizeProviderId(params.provider);
|
||||
const runtimeId = normalizeOptionalAgentRuntimeId(params.runtimeId) ?? OPENCLAW_AGENT_RUNTIME_ID;
|
||||
if (provider === OPENAI_PROVIDER_ID && runtimeId === "codex") {
|
||||
if (
|
||||
provider === OPENAI_PROVIDER_ID &&
|
||||
runtimeId === "codex" &&
|
||||
openAIProviderUsesCodexRuntimeByDefault({ provider, config: params.config })
|
||||
) {
|
||||
return OPENAI_CODEX_PROVIDER_ID;
|
||||
}
|
||||
return params.provider;
|
||||
|
||||
@@ -247,6 +247,7 @@ function resolveFollowupContextConfigProvider(params: {
|
||||
return resolveContextConfigProviderForRuntime({
|
||||
provider,
|
||||
runtimeId: resolveFollowupAgentRuntimeId(params),
|
||||
config: params.cfg,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ function resolveManualCompactContextTokenBudget(params: {
|
||||
const contextConfigProvider = resolveContextConfigProviderForRuntime({
|
||||
provider,
|
||||
runtimeId: harnessPolicy.runtime,
|
||||
config: params.cfg,
|
||||
});
|
||||
const configuredContextTokens = resolveContextTokensForModel({
|
||||
cfg: params.cfg,
|
||||
|
||||
@@ -396,6 +396,7 @@ export async function persistInlineDirectives(params: {
|
||||
agentId: activeAgentId,
|
||||
sessionKey,
|
||||
}).runtime,
|
||||
config: cfg,
|
||||
}),
|
||||
model,
|
||||
}),
|
||||
|
||||
@@ -205,6 +205,8 @@ export type ContextEngineRuntimeContext = Record<string, unknown> & {
|
||||
allowDeferredCompactionExecution?: boolean;
|
||||
/** Runtime-resolved context window budget for the active model call. */
|
||||
tokenBudget?: number;
|
||||
/** Selected agent harness id when compaction delegates back to the runtime. */
|
||||
agentHarnessId?: string;
|
||||
/** Best-effort current prompt/context token estimate for this turn. */
|
||||
currentTokenCount?: number;
|
||||
/** Optional prompt-cache telemetry for cache-aware engines. */
|
||||
|
||||
Reference in New Issue
Block a user