























@@ -110,16 +110,29 @@ describe("maybeCompactCodexAppServerSession", () => {
110110method: "thread/compacted",
111111params: { threadId: "thread-1", turnId: "turn-1" },
112112});
113+fake.emit({
114+method: "thread/tokenUsage/updated",
115+params: {
116+threadId: "thread-1",
117+tokenUsage: {
118+last_token_usage: {
119+total_tokens: 27_170,
120+},
121+},
122+},
123+});
113124const result = requireCompactResult(await pendingResult);
114125115126expect(result.ok).toBe(true);
116127expect(result.compacted).toBe(true);
117128expect(result.result?.tokensBefore).toBe(123);
129+expect(result.result?.tokensAfter).toBe(27_170);
118130const details = compactDetails(result);
119131expect(details.backend).toBe("codex-app-server");
120132expect(details.threadId).toBe("thread-1");
121133expect(details.signal).toBe("thread/compacted");
122134expect(details.turnId).toBe("turn-1");
135+expect(details.tokenUsageSource).toBe("thread/tokenUsage/updated");
123136});
124137125138it("blocks native app-server compaction when the current OpenClaw session is sandboxed", async () => {
@@ -137,7 +150,73 @@ describe("maybeCompactCodexAppServerSession", () => {
137150expect(fake.request).not.toHaveBeenCalled();
138151});
139152140-it("accepts native context-compaction item completion as success", async () => {
153+it("uses native token usage that arrives before compaction completion", async () => {
154+const fake = createFakeCodexClient();
155+setCodexAppServerClientFactoryForTest(async () => fake.client);
156+const sessionFile = await writeTestBinding();
157+158+const pendingResult = startCompaction(sessionFile, { currentTokenCount: 123 });
159+await vi.waitFor(() => {
160+expect(fake.request).toHaveBeenCalledWith("thread/compact/start", { threadId: "thread-1" });
161+});
162+163+fake.emit({
164+method: "thread/tokenUsage/updated",
165+params: {
166+threadId: "thread-1",
167+tokenUsage: {
168+last_token_usage: {
169+total_tokens: 18_004,
170+},
171+},
172+},
173+});
174+fake.emit({
175+method: "thread/compacted",
176+params: { threadId: "thread-1", turnId: "turn-1" },
177+});
178+const result = requireCompactResult(await pendingResult);
179+180+expect(result.ok).toBe(true);
181+expect(result.compacted).toBe(true);
182+expect(result.result?.tokensAfter).toBe(18_004);
183+expect(compactDetails(result).tokenUsageSource).toBe("thread/tokenUsage/updated");
184+});
185+186+it("accepts native current token usage with a total alias", async () => {
187+const fake = createFakeCodexClient();
188+setCodexAppServerClientFactoryForTest(async () => fake.client);
189+const sessionFile = await writeTestBinding();
190+191+const pendingResult = startCompaction(sessionFile, { currentTokenCount: 123 });
192+await vi.waitFor(() => {
193+expect(fake.request).toHaveBeenCalledWith("thread/compact/start", { threadId: "thread-1" });
194+});
195+196+fake.emit({
197+method: "thread/tokenUsage/updated",
198+params: {
199+threadId: "thread-1",
200+tokenUsage: {
201+last: {
202+total: 16_384,
203+},
204+},
205+},
206+});
207+fake.emit({
208+method: "thread/compacted",
209+params: { threadId: "thread-1", turnId: "turn-1" },
210+});
211+const result = requireCompactResult(await pendingResult);
212+213+expect(result.ok).toBe(true);
214+expect(result.compacted).toBe(true);
215+expect(result.result?.tokensAfter).toBe(16_384);
216+expect(compactDetails(result).tokenUsageSource).toBe("thread/tokenUsage/updated");
217+});
218+219+it("accepts native context-compaction item completion with unknown token count as success", async () => {
141220const fake = createFakeCodexClient();
142221setCodexAppServerClientFactoryForTest(async () => fake.client);
143222const sessionFile = await writeTestBinding();
@@ -158,11 +237,44 @@ describe("maybeCompactCodexAppServerSession", () => {
158237const result = requireCompactResult(await pendingResult);
159238expect(result.ok).toBe(true);
160239expect(result.compacted).toBe(true);
240+expect(result.result?.tokensAfter).toBeUndefined();
161241const details = compactDetails(result);
162242expect(details.signal).toBe("item/completed");
163243expect(details.itemId).toBe("compact-1");
164244});
165245246+it("does not treat zero native token usage as an authoritative post-compaction count", async () => {
247+const fake = createFakeCodexClient();
248+setCodexAppServerClientFactoryForTest(async () => fake.client);
249+const sessionFile = await writeTestBinding();
250+251+const pendingResult = startCompaction(sessionFile, { currentTokenCount: 123 });
252+await vi.waitFor(() => {
253+expect(fake.request).toHaveBeenCalledWith("thread/compact/start", { threadId: "thread-1" });
254+});
255+fake.emit({
256+method: "thread/compacted",
257+params: { threadId: "thread-1", turnId: "turn-1" },
258+});
259+fake.emit({
260+method: "thread/tokenUsage/updated",
261+params: {
262+threadId: "thread-1",
263+tokenUsage: {
264+last_token_usage: {
265+total_tokens: 0,
266+},
267+},
268+},
269+});
270+271+const result = requireCompactResult(await pendingResult);
272+expect(result.ok).toBe(true);
273+expect(result.compacted).toBe(true);
274+expect(result.result?.tokensAfter).toBeUndefined();
275+expect(compactDetails(result).tokenUsageSource).toBeUndefined();
276+});
277+166278it("reuses the bound auth profile for native compaction", async () => {
167279const fake = createFakeCodexClient();
168280let seenAuthProfileId: string | undefined;
@@ -185,6 +297,39 @@ describe("maybeCompactCodexAppServerSession", () => {
185297expect(seenAuthProfileId).toBe("openai-codex:work");
186298});
187299300+it("reports missing thread bindings as failed native compaction", async () => {
301+const sessionFile = path.join(tempDir, "missing-binding.jsonl");
302+303+const result = requireCompactResult(
304+await startCompaction(sessionFile, { currentTokenCount: 123 }),
305+);
306+307+expect(result.ok).toBe(false);
308+expect(result.compacted).toBe(false);
309+expect(result.reason).toBe("no codex app-server thread binding");
310+expect(result.failure?.reason).toBe("missing_thread_binding");
311+expect(result.result).toBeUndefined();
312+});
313+314+it("clears stale thread bindings and reports failed native compaction", async () => {
315+const fake = createFakeCodexClient();
316+fake.request.mockRejectedValueOnce(new Error("thread not found: thread-1"));
317+setCodexAppServerClientFactoryForTest(async () => fake.client);
318+const sessionFile = await writeTestBinding();
319+320+const result = requireCompactResult(
321+await startCompaction(sessionFile, { currentTokenCount: 456 }),
322+);
323+324+expect(fake.request).toHaveBeenCalledWith("thread/compact/start", { threadId: "thread-1" });
325+expect(await readCodexAppServerBinding(sessionFile)).toBeUndefined();
326+expect(result.ok).toBe(false);
327+expect(result.compacted).toBe(false);
328+expect(result.reason).toBe("thread not found: thread-1");
329+expect(result.failure?.reason).toBe("stale_thread_binding");
330+expect(result.result).toBeUndefined();
331+});
332+188333it("warns when stale OpenClaw compaction overrides are ignored", async () => {
189334const warn = vi.spyOn(embeddedAgentLog, "warn").mockImplementation(() => undefined);
190335const fake = createFakeCodexClient();
@@ -541,6 +686,58 @@ describe("maybeCompactCodexAppServerSession", () => {
541686);
542687});
543688689+it("honors explicit force for budget-triggered owning context-engine compaction", async () => {
690+const info = vi.spyOn(embeddedAgentLog, "info").mockImplementation(() => undefined);
691+const sessionFile = await writeTestBinding();
692+const compact = vi.fn(async () => ({
693+ok: true,
694+compacted: true,
695+result: {
696+summary: "engine summary",
697+firstKeptEntryId: "entry-1",
698+tokensBefore: 900,
699+tokensAfter: 100,
700+},
701+}));
702+const contextEngine: ContextEngine = {
703+info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
704+assemble: vi.fn() as never,
705+ingest: vi.fn() as never,
706+ compact,
707+};
708+709+const result = requireCompactResult(
710+await maybeCompactCodexAppServerSession({
711+sessionId: "session-1",
712+sessionKey: "agent:main:session-1",
713+ sessionFile,
714+workspaceDir: tempDir,
715+ contextEngine,
716+contextTokenBudget: 777,
717+currentTokenCount: 900,
718+trigger: "budget",
719+force: true,
720+}),
721+);
722+723+expect(result.ok).toBe(true);
724+expect(result.compacted).toBe(true);
725+expect(compact).toHaveBeenCalledWith(
726+expect.objectContaining({
727+compactionTarget: "budget",
728+force: true,
729+}),
730+);
731+expect(info).toHaveBeenCalledWith(
732+"starting context-engine-owned Codex app-server compaction",
733+expect.objectContaining({
734+trigger: "budget",
735+compactionTarget: "budget",
736+force: true,
737+}),
738+);
739+});
740+544741it("adopts successor transcript handles after owning context-engine compaction", async () => {
545742const sessionFile = await writeTestBinding();
546743const successorFile = path.join(tempDir, "session.compacted.jsonl");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。