diff --git a/CHANGELOG.md b/CHANGELOG.md index 32cb0ee7bdd..407c1e72b18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Agents/tool-result guard: use the resolved runtime context token budget for non-context-engine tool-result overflow checks, so long tool-heavy sessions no longer compact early when `contextTokens` is larger than native `contextWindow`. Fixes #74917. Thanks @kAIborg24. - Gateway/systemd: exit with sysexits 78 for supervised lock and `EADDRINUSE` conflicts so `RestartPreventExitStatus=78` stops `Restart=always` restart loops instead of repeatedly reloading plugins against an occupied port. Fixes #75115. Thanks @yhyatt. - Plugins/runtime-deps: replace stale symlinked mirror target roots before writing runtime-mirror temp files and skip rewriting already materialized hardlinks, so cross-version container upgrades no longer crash-loop on read-only image-layer paths while warm mirrors do less churn. Fixes #75108; refs #75069. Thanks @coletebou and @xiaohuaxi. - Auto-reply/group chats: fall back to automatic source delivery when a channel precomputes message-tool-only replies but the `message` tool is unavailable, so Discord/Slack-style group turns do not silently complete without a visible reply. Fixes #74868. Thanks @kagura-agent. diff --git a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-engine.test.ts b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-engine.test.ts index f0a799906a9..710a7aae4f7 100644 --- a/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-engine.test.ts +++ b/src/agents/pi-embedded-runner/run/attempt.spawn-workspace.context-engine.test.ts @@ -869,3 +869,43 @@ describe("runEmbeddedAttempt context engine mid-turn precheck integration", () = expect(result.messagesSnapshot).toEqual([seedMessage]); }); }); + +describe("runEmbeddedAttempt tool-result guard budget wiring", () => { + const sessionKey = "agent:main:guildchat:channel:tool-result-guard-budget"; + const tempPaths: string[] = []; + + beforeEach(() => { + resetEmbeddedAttemptHarness(); + clearMemoryPluginState(); + }); + + afterEach(async () => { + await cleanupTempPaths(tempPaths); + clearMemoryPluginState(); + vi.restoreAllMocks(); + }); + + it("uses the resolved contextTokenBudget before model contextWindow", async () => { + await createContextEngineAttemptRunner({ + contextEngine: createContextEngineBootstrapAndAssemble(), + sessionKey, + tempPaths, + attemptOverrides: { + contextTokenBudget: 1_000_000, + model: { + api: "openai-completions", + provider: "openai", + compat: {}, + contextWindow: 200_000, + input: ["text"], + } as never, + }, + }); + + expect(hoisted.installToolResultContextGuardMock).toHaveBeenCalledWith( + expect.objectContaining({ + contextWindowTokens: 1_000_000, + }), + ); + }); +}); diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index a0e133c3a28..b203e901e14 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -1528,7 +1528,12 @@ export async function runEmbeddedAttempt( }; const contextTokenBudgetForGuard = Math.max( 1, - Math.floor(params.contextTokenBudget ?? DEFAULT_CONTEXT_TOKENS), + Math.floor( + params.contextTokenBudget ?? + params.model.contextWindow ?? + params.model.maxTokens ?? + DEFAULT_CONTEXT_TOKENS, + ), ); const toolResultMaxCharsForGuard = resolveLiveToolResultMaxChars({ contextWindowTokens: contextTokenBudgetForGuard, @@ -1544,12 +1549,7 @@ export async function runEmbeddedAttempt( if (!activeContextEngine || activeContextEngine.info.ownsCompaction !== true) { removeToolResultContextGuard = installToolResultContextGuard({ agent: activeSession.agent, - contextWindowTokens: Math.max( - 1, - Math.floor( - params.model.contextWindow ?? params.model.maxTokens ?? DEFAULT_CONTEXT_TOKENS, - ), - ), + contextWindowTokens: contextTokenBudgetForGuard, ...(midTurnPrecheckEnabled ? { midTurnPrecheck: {