Skip to content

Commit aada44f

Browse files
authored
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 ac6f93d
1 parent 4365887 commit aada44f

15 files changed

Lines changed: 851 additions & 84 deletions

src/agents/embedded-agent-runner/compact.hooks.test.ts

Lines changed: 372 additions & 14 deletions
Large diffs are not rendered by default.

src/agents/embedded-agent-runner/compact.queued.ts

Lines changed: 78 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,19 @@ import { formatErrorMessage } from "../../infra/errors.js";
1616
import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
1717
import type { ProviderRuntimeModel } from "../../plugins/provider-runtime-model.types.js";
1818
import { enqueueCommandInLane } from "../../process/command-queue.js";
19+
import { parseAgentSessionKey } from "../../routing/session-key.js";
1920
import { resolveUserPath } from "../../utils.js";
21+
import { isDefaultAgentRuntimeId, normalizeOptionalAgentRuntimeId } from "../agent-runtime-id.js";
2022
import { resolveAgentDir, resolveSessionAgentIds } from "../agent-scope.js";
2123
import { resolveContextWindowInfo } from "../context-window-guard.js";
2224
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../defaults.js";
2325
import { isRecoverableNativeHarnessBindingFailure } from "../harness/compaction-recovery.js";
26+
import { ensureSelectedAgentHarnessPlugin } from "../harness/runtime-plugin.js";
2427
import {
2528
maybeCompactAgentHarnessSession,
2629
resolveAgentHarnessPolicy,
2730
} from "../harness/selection.js";
28-
import { resolveContextConfigProviderForRuntime } from "../openai-codex-routing.js";
31+
import { isOpenAICodexProvider, isOpenAIProvider } from "../openai-codex-routing.js";
2932
import { ensureRuntimePluginsLoaded } from "../runtime-plugins.js";
3033
import { DEFERRED_CONTEXT_ENGINE_COMPACTION_REASON } from "./compact-reasons.js";
3134
import type { CompactEmbeddedAgentSessionParams } from "./compact.types.js";
@@ -169,39 +172,76 @@ export async function compactEmbeddedAgentSession(
169172
agentDir,
170173
workspaceDir: resolvedWorkspaceDir,
171174
});
175+
const runtimePolicySessionKey = params.sandboxSessionKey ?? params.sessionKey;
176+
const runtimePolicyAgentId =
177+
params.sandboxSessionKey && parseAgentSessionKey(params.sandboxSessionKey)
178+
? undefined
179+
: params.agentId;
180+
const policyCompactionTarget = resolveEmbeddedCompactionTarget({
181+
config: params.config,
182+
provider: params.provider,
183+
modelId: params.model,
184+
authProfileId: params.authProfileId,
185+
defaultProvider: DEFAULT_PROVIDER,
186+
defaultModel: DEFAULT_MODEL,
187+
});
188+
const configuredHarnessPolicy = resolveAgentHarnessPolicy({
189+
provider: policyCompactionTarget.provider ?? DEFAULT_PROVIDER,
190+
modelId: policyCompactionTarget.model ?? DEFAULT_MODEL,
191+
config: params.config,
192+
agentId: runtimePolicyAgentId,
193+
sessionKey: runtimePolicySessionKey,
194+
});
195+
const configuredHarnessRuntime =
196+
configuredHarnessPolicy.runtimeSource &&
197+
configuredHarnessPolicy.runtimeSource !== "implicit" &&
198+
!isDefaultAgentRuntimeId(configuredHarnessPolicy.runtime)
199+
? configuredHarnessPolicy.runtime
200+
: undefined;
201+
// The persisted harness id is the runtime contract for this session; config
202+
// changes can supply a runtime only when the session has no concrete pin.
203+
const selectedHarnessRuntime = params.agentHarnessId ?? configuredHarnessRuntime;
172204
const resolvedCompactionTarget = resolveEmbeddedCompactionTarget({
173205
config: params.config,
174206
provider: params.provider,
175207
modelId: params.model,
176208
authProfileId: params.authProfileId,
209+
harnessRuntime: selectedHarnessRuntime,
177210
defaultProvider: DEFAULT_PROVIDER,
178211
defaultModel: DEFAULT_MODEL,
179212
});
180213
const ceProvider = resolvedCompactionTarget.provider ?? DEFAULT_PROVIDER;
181214
const ceRuntimeProvider = resolvedCompactionTarget.runtimeProvider ?? ceProvider;
215+
const ceContextConfigProvider = resolvedCompactionTarget.contextProvider ?? ceProvider;
182216
const ceModelId = resolvedCompactionTarget.model ?? DEFAULT_MODEL;
217+
const attemptNativeHarnessCompaction = shouldAttemptNativeHarnessCompaction({
218+
provider: ceProvider,
219+
contextProvider: resolvedCompactionTarget.contextProvider,
220+
selectedHarnessRuntime,
221+
});
222+
if (attemptNativeHarnessCompaction) {
223+
await ensureSelectedAgentHarnessPlugin({
224+
config: params.config,
225+
provider: ceProvider,
226+
modelId: ceModelId,
227+
agentId: runtimePolicyAgentId,
228+
sessionKey: runtimePolicySessionKey,
229+
agentHarnessRuntimeOverride: selectedHarnessRuntime,
230+
workspaceDir: resolvedWorkspaceDir,
231+
});
232+
}
183233
const { model: ceModel } = await resolveModelAsync(
184234
ceRuntimeProvider,
185235
ceModelId,
186236
agentDir,
187237
params.config,
188238
);
189239
const ceRuntimeModel = ceModel as ProviderRuntimeModel | undefined;
190-
const ceHarnessPolicy = resolveAgentHarnessPolicy({
191-
provider: ceProvider,
192-
modelId: ceModelId,
193-
config: params.config,
194-
agentId: agentIds.sessionAgentId,
195-
sessionKey: params.sessionKey,
196-
});
197240
const resolvedContextTokenBudget =
198241
normalizeContextTokenBudget(
199242
resolveContextWindowInfo({
200243
cfg: params.config,
201-
provider: resolveContextConfigProviderForRuntime({
202-
provider: ceProvider,
203-
runtimeId: params.agentHarnessId ?? ceHarnessPolicy.runtime,
204-
}),
244+
provider: ceContextConfigProvider,
205245
modelId: ceModelId,
206246
modelContextTokens: readAgentModelContextTokens(ceModel),
207247
modelContextWindow: ceRuntimeModel?.contextWindow,
@@ -216,15 +256,18 @@ export async function compactEmbeddedAgentSession(
216256
const contextEngineRuntimeContext = buildCompactionContextEngineRuntimeContext({
217257
params,
218258
agentDir,
259+
harnessRuntime: selectedHarnessRuntime,
219260
contextTokenBudget,
220261
contextEnginePluginId: resolveContextEngineOwnerPluginId(contextEngine),
221262
});
222-
const harnessResult = await maybeCompactAgentHarnessSession({
223-
...params,
224-
contextEngine,
225-
contextTokenBudget,
226-
contextEngineRuntimeContext,
227-
});
263+
const harnessResult = attemptNativeHarnessCompaction
264+
? await maybeCompactAgentHarnessSession({
265+
...params,
266+
contextEngine,
267+
contextTokenBudget,
268+
contextEngineRuntimeContext,
269+
})
270+
: undefined;
228271
if (harnessResult) {
229272
if (!shouldFallbackAfterHarnessCompaction(harnessResult)) {
230273
await contextEngine.dispose?.();
@@ -468,9 +511,25 @@ export async function compactEmbeddedAgentSession(
468511
);
469512
}
470513

514+
function shouldAttemptNativeHarnessCompaction(params: {
515+
provider: string;
516+
contextProvider?: string;
517+
selectedHarnessRuntime?: string | null;
518+
}): boolean {
519+
if (isOpenAICodexProvider(params.provider)) {
520+
return true;
521+
}
522+
const selectedRuntime = normalizeOptionalAgentRuntimeId(params.selectedHarnessRuntime);
523+
if (!selectedRuntime || selectedRuntime === "auto" || selectedRuntime === "openclaw") {
524+
return false;
525+
}
526+
return isOpenAIProvider(params.provider) ? params.contextProvider !== undefined : true;
527+
}
528+
471529
function buildCompactionContextEngineRuntimeContext(params: {
472530
params: CompactEmbeddedAgentSessionParams;
473531
agentDir: string;
532+
harnessRuntime?: string;
474533
contextEnginePluginId?: string;
475534
contextTokenBudget?: number;
476535
}): ContextEngineRuntimeContext {
@@ -499,6 +558,7 @@ function buildCompactionContextEngineRuntimeContext(params: {
499558
senderId: params.params.senderId,
500559
provider: params.params.provider,
501560
modelId: params.params.model,
561+
harnessRuntime: params.harnessRuntime,
502562
modelFallbacksOverride: params.params.modelFallbacksOverride,
503563
thinkLevel: params.params.thinkLevel,
504564
reasoningLevel: params.params.reasoningLevel,

src/agents/embedded-agent-runner/compact.ts

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@ import {
2424
resolveProviderTextTransforms,
2525
transformProviderSystemPrompt,
2626
} from "../../plugins/provider-runtime.js";
27-
import { isCronSessionKey, isSubagentSessionKey } from "../../routing/session-key.js";
27+
import {
28+
isCronSessionKey,
29+
isSubagentSessionKey,
30+
parseAgentSessionKey,
31+
} from "../../routing/session-key.js";
2832
import { resolveSkillsPromptForRun } from "../../skills/loading/workspace.js";
2933
import { resolveEmbeddedRunSkillEntries } from "../../skills/runtime/embedded-run-entries.js";
3034
import {
@@ -41,6 +45,7 @@ import {
4145
setCompactionSafeguardCancelReason,
4246
} from "../agent-hooks/compaction-safeguard-runtime.js";
4347
import { createPreparedEmbeddedAgentSettingsManager } from "../agent-project-settings.js";
48+
import { isDefaultAgentRuntimeId } from "../agent-runtime-id.js";
4449
import {
4550
resolveAgentDir,
4651
resolveRunModelFallbacksOverride,
@@ -87,7 +92,6 @@ import {
8792
import { isFallbackSummaryError, runWithModelFallback } from "../model-fallback.js";
8893
import { supportsModelTools } from "../model-tool-support.js";
8994
import { ensureOpenClawModelsJson } from "../models-config.js";
90-
import { resolveContextConfigProviderForRuntime } from "../openai-codex-routing.js";
9195
import { wrapStreamFnTextTransforms } from "../plugin-text-transforms.js";
9296
import { resolveAgentPromptSurfaceForSessionKey } from "../prompt-surface.js";
9397
import { registerProviderStreamForModel } from "../provider-stream.js";
@@ -489,20 +493,67 @@ async function compactEmbeddedAgentSessionDirectOnce(
489493
workspaceDir: resolvedWorkspace,
490494
allowGatewaySubagentBinding: params.allowGatewaySubagentBinding,
491495
});
496+
const earlyAgentIds = resolveSessionAgentIds({
497+
sessionKey: params.sessionKey,
498+
config: params.config,
499+
agentId: params.agentId,
500+
});
501+
const agentDir =
502+
params.agentDir ?? resolveAgentDir(params.config ?? {}, earlyAgentIds.sessionAgentId);
503+
const runtimePolicySessionKey = params.sandboxSessionKey ?? params.sessionKey;
504+
const runtimePolicyAgentId =
505+
params.sandboxSessionKey && parseAgentSessionKey(params.sandboxSessionKey)
506+
? undefined
507+
: params.agentId;
508+
const policyCompactionTarget = resolveEmbeddedCompactionTarget({
509+
config: params.config,
510+
provider: params.provider,
511+
modelId: params.model,
512+
authProfileId: params.authProfileId,
513+
defaultProvider: DEFAULT_PROVIDER,
514+
defaultModel: DEFAULT_MODEL,
515+
});
516+
const configuredHarnessPolicy = resolveAgentHarnessPolicy({
517+
provider: policyCompactionTarget.provider ?? DEFAULT_PROVIDER,
518+
modelId: policyCompactionTarget.model ?? DEFAULT_MODEL,
519+
config: params.config,
520+
agentId: runtimePolicyAgentId,
521+
sessionKey: runtimePolicySessionKey,
522+
});
523+
const configuredHarnessRuntime =
524+
configuredHarnessPolicy.runtimeSource &&
525+
configuredHarnessPolicy.runtimeSource !== "implicit" &&
526+
!isDefaultAgentRuntimeId(configuredHarnessPolicy.runtime)
527+
? configuredHarnessPolicy.runtime
528+
: undefined;
529+
const selectedHarnessRuntime = params.agentHarnessId ?? configuredHarnessRuntime;
492530
const resolvedCompactionTarget = resolveEmbeddedCompactionTarget({
493531
config: params.config,
494532
provider: params.provider,
495533
modelId: params.model,
496534
authProfileId: params.authProfileId,
535+
harnessRuntime: selectedHarnessRuntime,
497536
defaultProvider: DEFAULT_PROVIDER,
498537
defaultModel: DEFAULT_MODEL,
499538
});
500539
// Keep the configured provider for harness policy, while auth/model loading below can
501540
// route OpenAI compaction through Codex OAuth when that runtime owns the session credentials.
502541
const provider = resolvedCompactionTarget.provider ?? DEFAULT_PROVIDER;
503542
const runtimeProvider = resolvedCompactionTarget.runtimeProvider ?? provider;
543+
const contextConfigProvider = resolvedCompactionTarget.contextProvider ?? provider;
504544
const modelId = resolvedCompactionTarget.model ?? DEFAULT_MODEL;
505545
const authProfileId = resolvedCompactionTarget.authProfileId;
546+
if (runtimeProvider !== provider || selectedHarnessRuntime) {
547+
await ensureSelectedAgentHarnessPlugin({
548+
config: params.config,
549+
provider,
550+
modelId,
551+
agentId: runtimePolicyAgentId,
552+
sessionKey: runtimePolicySessionKey,
553+
agentHarnessRuntimeOverride: selectedHarnessRuntime,
554+
workspaceDir: resolvedWorkspace,
555+
});
556+
}
506557
let thinkLevel: ThinkLevel = params.thinkLevel ?? "off";
507558
const attemptedThinking = new Set<ThinkLevel>();
508559
const fail = (reason: string, err?: unknown): EmbeddedAgentCompactResult => {
@@ -531,13 +582,6 @@ async function compactEmbeddedAgentSessionDirectOnce(
531582
: undefined,
532583
};
533584
};
534-
const earlyAgentIds = resolveSessionAgentIds({
535-
sessionKey: params.sessionKey,
536-
config: params.config,
537-
agentId: params.agentId,
538-
});
539-
const agentDir =
540-
params.agentDir ?? resolveAgentDir(params.config ?? {}, earlyAgentIds.sessionAgentId);
541585
await ensureOpenClawModelsJson(params.config, agentDir, {
542586
workspaceDir: resolvedWorkspace,
543587
});
@@ -627,11 +671,7 @@ async function compactEmbeddedAgentSessionDirectOnce(
627671
sessionId: params.sessionId,
628672
cwd: effectiveCwd,
629673
});
630-
const { sessionAgentId: effectiveSkillAgentId } = resolveSessionAgentIds({
631-
sessionKey: params.sessionKey,
632-
config: params.config,
633-
agentId: params.agentId,
634-
});
674+
const { sessionAgentId: effectiveSkillAgentId } = earlyAgentIds;
635675

636676
let restoreSkillEnv: (() => void) | undefined;
637677
let compactionSessionManager: unknown = null;
@@ -683,19 +723,9 @@ async function compactEmbeddedAgentSessionDirectOnce(
683723
// Apply contextTokens cap to model so session runtime's auto-compaction
684724
// threshold uses the effective limit, not the native context window.
685725
const runtimeModelWithContext = runtimeModel as ProviderRuntimeModel;
686-
const runtimeHarnessPolicy = resolveAgentHarnessPolicy({
687-
provider,
688-
modelId,
689-
config: params.config,
690-
agentId: effectiveSkillAgentId,
691-
sessionKey: params.sessionKey,
692-
});
693726
const ctxInfo = resolveContextWindowInfo({
694727
cfg: params.config,
695-
provider: resolveContextConfigProviderForRuntime({
696-
provider,
697-
runtimeId: params.agentHarnessId ?? runtimeHarnessPolicy.runtime,
698-
}),
728+
provider: contextConfigProvider,
699729
modelId,
700730
modelContextTokens: readAgentModelContextTokens(runtimeModel),
701731
modelContextWindow: runtimeModelWithContext.contextWindow,
@@ -731,7 +761,7 @@ async function compactEmbeddedAgentSessionDirectOnce(
731761
model: effectiveModel,
732762
modelApi: effectiveModel.api,
733763
harnessId: params.agentHarnessId,
734-
harnessRuntime: runtimeHarnessPolicy.runtime,
764+
harnessRuntime: selectedHarnessRuntime,
735765
authProfileProvider: authProfileId?.split(":", 1)[0],
736766
sessionAuthProfileId: authProfileId,
737767
config: params.config,

0 commit comments

Comments
 (0)