mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-23 19:08:22 -06:00
fix(agents): preserve native CLI compaction bindings (#123466)
This commit is contained in:
committed by
GitHub
parent
d60a98ec87
commit
71ea4e3838
@@ -391,6 +391,7 @@ describe("runCliTurnCompactionLifecycle", () => {
|
||||
);
|
||||
expect(recordCliCompactionInStore).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
compactionKind: "context-engine",
|
||||
newSessionId: successorSessionId,
|
||||
tokensAfter: 100,
|
||||
}),
|
||||
@@ -494,12 +495,12 @@ describe("runCliTurnCompactionLifecycle", () => {
|
||||
});
|
||||
|
||||
it.each([
|
||||
["agent", { agentId: "other" }],
|
||||
["session key", { sessionKey: "agent:main:other" }],
|
||||
["store", { storePath: "/tmp/other-openclaw-sessions.sqlite" }],
|
||||
])("rejects a CLI successor outside the active %s binding", async (_label, override) => {
|
||||
["agent", () => ({ agentId: "other" })],
|
||||
["session key", () => ({ sessionKey: "agent:main:other" })],
|
||||
["store", () => ({ storePath: path.join(tmpDir, "other-openclaw-sessions.sqlite") })],
|
||||
])("rejects a CLI successor outside the active %s binding", async (label, buildOverride) => {
|
||||
const scenario = await prepareContextSuccessorScenario({
|
||||
suffix: `outside-${_label.replace(" ", "-")}`,
|
||||
suffix: `outside-${label.replace(" ", "-")}`,
|
||||
tmpDir,
|
||||
result: ({ sessionKey, storePath }) => ({
|
||||
ok: true,
|
||||
@@ -511,7 +512,7 @@ describe("runCliTurnCompactionLifecycle", () => {
|
||||
sessionId: "outside-successor",
|
||||
sessionKey,
|
||||
storePath,
|
||||
...override,
|
||||
...buildOverride(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -672,6 +673,7 @@ describe("runCliTurnCompactionLifecycle", () => {
|
||||
expect(recordCliCompactionInStore).toHaveBeenCalledTimes(1);
|
||||
expect(recordCliCompactionInStore).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
compactionKind: "native-harness",
|
||||
provider: "openai",
|
||||
sessionKey,
|
||||
tokensAfter: 100,
|
||||
@@ -924,6 +926,7 @@ describe("runCliTurnCompactionLifecycle", () => {
|
||||
expect(maintenance).toHaveBeenCalledTimes(1);
|
||||
expect(recordCliCompactionInStore).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
compactionKind: "context-engine",
|
||||
provider: "external-harness",
|
||||
sessionKey,
|
||||
tokensAfter: 100,
|
||||
|
||||
@@ -646,7 +646,7 @@ export async function runCliTurnCompactionLifecycle(params: {
|
||||
return params.sessionEntry;
|
||||
}
|
||||
|
||||
let compacted = false;
|
||||
let compactionKind: "context-engine" | "native-harness" | undefined;
|
||||
let contextCompactionOutcome: CliTranscriptCompactionOutcome | undefined;
|
||||
let nativeCompactionResult: EmbeddedAgentCompactResult | undefined;
|
||||
let useContextEngineCompaction = true;
|
||||
@@ -695,7 +695,7 @@ export async function runCliTurnCompactionLifecycle(params: {
|
||||
extraSystemPrompt: params.extraSystemPrompt,
|
||||
});
|
||||
if (nativeOutcome.compacted) {
|
||||
compacted = true;
|
||||
compactionKind = "native-harness";
|
||||
nativeCompactionResult = nativeOutcome.result;
|
||||
useContextEngineCompaction = false;
|
||||
} else if (nativeOutcome.fallbackToContextEngine) {
|
||||
@@ -704,9 +704,7 @@ export async function runCliTurnCompactionLifecycle(params: {
|
||||
nativeFallbackNeedsBindingClear = nativeOutcome.clearCliSessionBinding === true;
|
||||
} else if (nativeOutcome.failureReason) {
|
||||
throw new Error(
|
||||
`CLI native harness compaction failed for ${params.provider}/${params.model}: ${
|
||||
nativeOutcome.failureReason ?? "compaction did not reduce context"
|
||||
}`,
|
||||
`CLI native harness compaction failed for ${params.provider}/${params.model}: ${nativeOutcome.failureReason}`,
|
||||
);
|
||||
} else {
|
||||
useContextEngineCompaction = false;
|
||||
@@ -749,17 +747,15 @@ export async function runCliTurnCompactionLifecycle(params: {
|
||||
bestEffortMaintenance: nativeFallbackToContextEngine,
|
||||
});
|
||||
contextCompactionOutcome = contextOutcome;
|
||||
compacted = contextOutcome.compacted;
|
||||
if (!compacted && contextOutcome.failureReason) {
|
||||
compactionKind = contextOutcome.compacted ? "context-engine" : undefined;
|
||||
if (!compactionKind && contextOutcome.failureReason) {
|
||||
throw new Error(
|
||||
`CLI transcript compaction failed for ${params.provider}/${params.model}: ${
|
||||
contextOutcome.failureReason ?? "compaction did not reduce context"
|
||||
}`,
|
||||
`CLI transcript compaction failed for ${params.provider}/${params.model}: ${contextOutcome.failureReason}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (nativeFallbackNeedsBindingClear && !compacted && params.sessionStore && params.storePath) {
|
||||
if (nativeFallbackNeedsBindingClear && !compactionKind && params.sessionStore) {
|
||||
return (
|
||||
(await cliCompactionDeps.clearCliSessionInStore({
|
||||
provider: params.provider,
|
||||
@@ -771,12 +767,13 @@ export async function runCliTurnCompactionLifecycle(params: {
|
||||
);
|
||||
}
|
||||
|
||||
if (!compacted || !params.sessionStore || !params.storePath) {
|
||||
if (!compactionKind || !params.sessionStore) {
|
||||
return params.sessionEntry;
|
||||
}
|
||||
|
||||
return (
|
||||
(await cliCompactionDeps.recordCliCompactionInStore({
|
||||
compactionKind,
|
||||
provider: params.provider,
|
||||
sessionKey: params.sessionKey,
|
||||
sessionStore: params.sessionStore,
|
||||
|
||||
@@ -2729,7 +2729,7 @@ describe("updateSessionStoreAfterAgentRun", () => {
|
||||
});
|
||||
|
||||
describe("recordCliCompactionInStore", () => {
|
||||
it("persists native compaction token counts and clears stale CLI usage breakdown", async () => {
|
||||
it("persists native compaction token counts without clearing its CLI binding", async () => {
|
||||
await withTempSessionStore(async ({ storePath }) => {
|
||||
const sessionKey = "agent:main:explicit:test-record-cli-compaction";
|
||||
const sessionId = "test-record-cli-compaction-session";
|
||||
@@ -2775,6 +2775,7 @@ describe("recordCliCompactionInStore", () => {
|
||||
await seedSessionStore(storePath, sessionStore);
|
||||
|
||||
await recordCliCompactionInStore({
|
||||
compactionKind: "native-harness",
|
||||
provider: "codex",
|
||||
sessionKey,
|
||||
sessionStore,
|
||||
@@ -2791,11 +2792,17 @@ describe("recordCliCompactionInStore", () => {
|
||||
expect(sessionStore[sessionKey]?.cacheRead).toBeUndefined();
|
||||
expect(sessionStore[sessionKey]?.cacheWrite).toBeUndefined();
|
||||
expect(sessionStore[sessionKey]?.contextBudgetStatus).toBeUndefined();
|
||||
expect(sessionStore[sessionKey]?.cliSessionBindings?.codex).toBeUndefined();
|
||||
expect(sessionStore[sessionKey]?.cliSessionIds?.codex).toBeUndefined();
|
||||
expect(sessionStore[sessionKey]?.cliSessionBindings?.codex).toEqual({
|
||||
sessionId: "stale-cli-session",
|
||||
});
|
||||
expect(sessionStore[sessionKey]?.cliSessionIds?.codex).toBe("stale-cli-session");
|
||||
expect(persisted[sessionKey]?.totalTokens).toBe(0);
|
||||
expect(persisted[sessionKey]?.totalTokensFresh).toBe(true);
|
||||
expect(persisted[sessionKey]?.contextBudgetStatus).toBeUndefined();
|
||||
expect(persisted[sessionKey]?.cliSessionBindings?.codex).toEqual({
|
||||
sessionId: "stale-cli-session",
|
||||
});
|
||||
expect(persisted[sessionKey]?.cliSessionIds?.codex).toBe("stale-cli-session");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2837,6 +2844,7 @@ describe("recordCliCompactionInStore", () => {
|
||||
await seedSessionStore(storePath, sessionStore);
|
||||
|
||||
await recordCliCompactionInStore({
|
||||
compactionKind: "native-harness",
|
||||
provider: "codex",
|
||||
sessionKey,
|
||||
sessionStore,
|
||||
@@ -2872,6 +2880,7 @@ describe("recordCliCompactionInStore", () => {
|
||||
await seedSessionStore(storePath, sessionStore);
|
||||
|
||||
await recordCliCompactionInStore({
|
||||
compactionKind: "native-harness",
|
||||
provider: "codex",
|
||||
sessionKey,
|
||||
sessionStore,
|
||||
@@ -2890,7 +2899,7 @@ describe("recordCliCompactionInStore", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("recreates a complete persisted row when the caller snapshot survived a missing store row", async () => {
|
||||
it("recreates a complete persisted row and clears its context-engine CLI binding", async () => {
|
||||
await withTempSessionStore(async ({ storePath }) => {
|
||||
const sessionKey = "agent:main:explicit:test-record-cli-compaction-missing-row";
|
||||
const sessionId = "test-record-cli-compaction-missing-row-session";
|
||||
@@ -2918,6 +2927,7 @@ describe("recordCliCompactionInStore", () => {
|
||||
};
|
||||
|
||||
await recordCliCompactionInStore({
|
||||
compactionKind: "context-engine",
|
||||
provider: "codex",
|
||||
sessionKey,
|
||||
sessionStore,
|
||||
@@ -2938,6 +2948,7 @@ describe("recordCliCompactionInStore", () => {
|
||||
expect(persisted?.compactionCount).toBe(1);
|
||||
expect(persisted?.totalTokens).toBe(42);
|
||||
expect(persisted?.cliSessionBindings?.codex).toBeUndefined();
|
||||
expect(persisted?.cliSessionIds?.codex).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2956,6 +2967,7 @@ describe("recordCliCompactionInStore", () => {
|
||||
};
|
||||
|
||||
const result = await recordCliCompactionInStore({
|
||||
compactionKind: "context-engine",
|
||||
provider: "codex",
|
||||
sessionKey,
|
||||
sessionStore,
|
||||
|
||||
@@ -494,6 +494,7 @@ export async function persistCliSessionForkSuccessorInStore(params: {
|
||||
|
||||
/** Records CLI compaction metadata on the persisted session entry. */
|
||||
export async function recordCliCompactionInStore(params: {
|
||||
compactionKind: "context-engine" | "native-harness";
|
||||
provider: string;
|
||||
sessionKey: string;
|
||||
sessionStore: Record<string, SessionEntry>;
|
||||
@@ -502,19 +503,23 @@ export async function recordCliCompactionInStore(params: {
|
||||
newSessionId?: string;
|
||||
expectedSessionId?: string;
|
||||
}): Promise<SessionEntry | undefined> {
|
||||
const { provider, sessionKey, sessionStore, storePath, expectedSessionId } = params;
|
||||
const { compactionKind, provider, sessionKey, sessionStore, storePath, expectedSessionId } =
|
||||
params;
|
||||
const entry = sessionStore[sessionKey];
|
||||
if (!entry) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const next = { ...entry };
|
||||
clearCliSession(next, provider);
|
||||
// Context-engine compaction rewrites history outside the CLI process, invalidating its native
|
||||
// session id. Native harness compaction updates that same session in place, so preserve it.
|
||||
if (compactionKind === "context-engine") {
|
||||
clearCliSession(next, provider);
|
||||
}
|
||||
next.compactionCount = (entry.compactionCount ?? 0) + 1;
|
||||
next.updatedAt = Date.now();
|
||||
const newSessionId = normalizeOptionalString(params.newSessionId);
|
||||
const sessionIdChanged = Boolean(newSessionId && newSessionId !== entry.sessionId);
|
||||
if (sessionIdChanged && newSessionId) {
|
||||
if (newSessionId && newSessionId !== entry.sessionId) {
|
||||
delete (next as { sessionFile?: unknown }).sessionFile;
|
||||
next.sessionId = newSessionId;
|
||||
next.usageFamilyKey = entry.usageFamilyKey ?? sessionKey;
|
||||
@@ -524,21 +529,17 @@ export async function recordCliCompactionInStore(params: {
|
||||
}
|
||||
const tokensAfterCompaction = asNonNegativeFiniteNumber(params.tokensAfter);
|
||||
next.contextBudgetStatus = undefined;
|
||||
next.inputTokens = undefined;
|
||||
next.outputTokens = undefined;
|
||||
next.cacheRead = undefined;
|
||||
next.cacheWrite = undefined;
|
||||
if (tokensAfterCompaction !== undefined) {
|
||||
next.totalTokens = Math.floor(tokensAfterCompaction);
|
||||
next.totalTokensFresh = true;
|
||||
next.totalTokensVersion = SESSION_TOTAL_TOKENS_VERSION;
|
||||
next.inputTokens = undefined;
|
||||
next.outputTokens = undefined;
|
||||
next.cacheRead = undefined;
|
||||
next.cacheWrite = undefined;
|
||||
} else {
|
||||
next.totalTokensFresh = false;
|
||||
next.totalTokensVersion = undefined;
|
||||
next.inputTokens = undefined;
|
||||
next.outputTokens = undefined;
|
||||
next.cacheRead = undefined;
|
||||
next.cacheWrite = undefined;
|
||||
}
|
||||
|
||||
const persisted = await patchSessionEntryCore(
|
||||
|
||||
Reference in New Issue
Block a user