diff --git a/CHANGELOG.md b/CHANGELOG.md index 92c9785ee6e..923edf9464d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Agents/Compaction: restore embedded compaction safeguard/context-pruning extension loading in production by wiring bundled extension factories into the resource loader instead of runtime file-path resolution. (#22349) Thanks @Glucksberg. - Auto-reply/Tools: forward `senderIsOwner` through embedded queued/followup runner params so owner-only tools remain available for authorized senders. (#22296) thanks @hcoj. - Agents/Subagents: restore announce-chain delivery to agent injection, defer nested announce output until descendant follow-up content is ready, and prevent descendant deferrals from consuming announce retry budget so deep chains do not drop final completions. (#22223) Thanks @tyler6204. - Gateway/Auth: require `gateway.trustedProxies` to include a loopback proxy address when `auth.mode="trusted-proxy"` and `bind="loopback"`, preventing same-host proxy misconfiguration from silently blocking auth. (#22082, follow-up to #20097) thanks @mbelinky. diff --git a/src/agents/pi-embedded-runner/compact.ts b/src/agents/pi-embedded-runner/compact.ts index fc6548caa2d..21bb1568bbc 100644 --- a/src/agents/pi-embedded-runner/compact.ts +++ b/src/agents/pi-embedded-runner/compact.ts @@ -3,6 +3,7 @@ import os from "node:os"; import type { AgentMessage } from "@mariozechner/pi-agent-core"; import { createAgentSession, + DefaultResourceLoader, estimateTokens, SessionManager, SettingsManager, @@ -60,7 +61,7 @@ import { compactWithSafetyTimeout, EMBEDDED_COMPACTION_TIMEOUT_MS, } from "./compaction-safety-timeout.js"; -import { buildEmbeddedExtensionPaths } from "./extensions.js"; +import { buildEmbeddedExtensionFactories } from "./extensions.js"; import { logToolSchemasForGoogle, sanitizeSessionHistory, @@ -533,14 +534,27 @@ export async function compactEmbeddedPiSessionDirect( settingsManager, cfg: params.config, }); - // Call for side effects (sets compaction/pruning runtime state) - buildEmbeddedExtensionPaths({ + // Sets compaction/pruning runtime state and returns extension factories + // that must be passed to the resource loader for the safeguard to be active. + const extensionFactories = buildEmbeddedExtensionFactories({ cfg: params.config, sessionManager, provider, modelId, model, }); + // Only create an explicit resource loader when there are extension factories + // to register; otherwise let createAgentSession use its built-in default. + let resourceLoader: DefaultResourceLoader | undefined; + if (extensionFactories.length > 0) { + resourceLoader = new DefaultResourceLoader({ + cwd: resolvedWorkspace, + agentDir, + settingsManager, + extensionFactories, + }); + await resourceLoader.reload(); + } const { builtInTools, customTools } = splitSdkTools({ tools, @@ -558,6 +572,7 @@ export async function compactEmbeddedPiSessionDirect( customTools, sessionManager, settingsManager, + resourceLoader, }); applySystemPromptOverrideToSession(session, systemPromptOverride()); diff --git a/src/agents/pi-embedded-runner/extensions.ts b/src/agents/pi-embedded-runner/extensions.ts index 3fa7b90a308..cdaa47b0959 100644 --- a/src/agents/pi-embedded-runner/extensions.ts +++ b/src/agents/pi-embedded-runner/extensions.ts @@ -1,25 +1,17 @@ -import path from "node:path"; -import { fileURLToPath } from "node:url"; import type { Api, Model } from "@mariozechner/pi-ai"; -import type { SessionManager } from "@mariozechner/pi-coding-agent"; +import type { ExtensionFactory, SessionManager } from "@mariozechner/pi-coding-agent"; import type { OpenClawConfig } from "../../config/config.js"; import { resolveContextWindowInfo } from "../context-window-guard.js"; import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js"; import { setCompactionSafeguardRuntime } from "../pi-extensions/compaction-safeguard-runtime.js"; +import compactionSafeguardExtension from "../pi-extensions/compaction-safeguard.js"; +import contextPruningExtension from "../pi-extensions/context-pruning.js"; import { setContextPruningRuntime } from "../pi-extensions/context-pruning/runtime.js"; import { computeEffectiveSettings } from "../pi-extensions/context-pruning/settings.js"; import { makeToolPrunablePredicate } from "../pi-extensions/context-pruning/tools.js"; import { ensurePiCompactionReserveTokens } from "../pi-settings.js"; import { isCacheTtlEligibleProvider, readLastCacheTtlTimestamp } from "./cache-ttl.js"; -function resolvePiExtensionPath(id: string): string { - const self = fileURLToPath(import.meta.url); - const dir = path.dirname(self); - // In dev this file is `.ts` (tsx), in production it's `.js`. - const ext = path.extname(self) === ".ts" ? "ts" : "js"; - return path.join(dir, "..", "pi-extensions", `${id}.${ext}`); -} - function resolveContextWindowTokens(params: { cfg: OpenClawConfig | undefined; provider: string; @@ -35,24 +27,24 @@ function resolveContextWindowTokens(params: { }).tokens; } -function buildContextPruningExtension(params: { +function buildContextPruningFactory(params: { cfg: OpenClawConfig | undefined; sessionManager: SessionManager; provider: string; modelId: string; model: Model | undefined; -}): { additionalExtensionPaths?: string[] } { +}): ExtensionFactory | undefined { const raw = params.cfg?.agents?.defaults?.contextPruning; if (raw?.mode !== "cache-ttl") { - return {}; + return undefined; } if (!isCacheTtlEligibleProvider(params.provider, params.modelId)) { - return {}; + return undefined; } const settings = computeEffectiveSettings(raw); if (!settings) { - return {}; + return undefined; } setContextPruningRuntime(params.sessionManager, { @@ -62,23 +54,21 @@ function buildContextPruningExtension(params: { lastCacheTouchAt: readLastCacheTtlTimestamp(params.sessionManager), }); - return { - additionalExtensionPaths: [resolvePiExtensionPath("context-pruning")], - }; + return contextPruningExtension; } function resolveCompactionMode(cfg?: OpenClawConfig): "default" | "safeguard" { return cfg?.agents?.defaults?.compaction?.mode === "safeguard" ? "safeguard" : "default"; } -export function buildEmbeddedExtensionPaths(params: { +export function buildEmbeddedExtensionFactories(params: { cfg: OpenClawConfig | undefined; sessionManager: SessionManager; provider: string; modelId: string; model: Model | undefined; -}): string[] { - const paths: string[] = []; +}): ExtensionFactory[] { + const factories: ExtensionFactory[] = []; if (resolveCompactionMode(params.cfg) === "safeguard") { const compactionCfg = params.cfg?.agents?.defaults?.compaction; const contextWindowInfo = resolveContextWindowInfo({ @@ -92,13 +82,13 @@ export function buildEmbeddedExtensionPaths(params: { maxHistoryShare: compactionCfg?.maxHistoryShare, contextWindowTokens: contextWindowInfo.tokens, }); - paths.push(resolvePiExtensionPath("compaction-safeguard")); + factories.push(compactionSafeguardExtension); } - const pruning = buildContextPruningExtension(params); - if (pruning.additionalExtensionPaths) { - paths.push(...pruning.additionalExtensionPaths); + const pruningFactory = buildContextPruningFactory(params); + if (pruningFactory) { + factories.push(pruningFactory); } - return paths; + return factories; } export { ensurePiCompactionReserveTokens }; diff --git a/src/agents/pi-embedded-runner/run/attempt.ts b/src/agents/pi-embedded-runner/run/attempt.ts index 382ce4b39ef..3ab80dd2661 100644 --- a/src/agents/pi-embedded-runner/run/attempt.ts +++ b/src/agents/pi-embedded-runner/run/attempt.ts @@ -3,7 +3,12 @@ import os from "node:os"; import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { ImageContent } from "@mariozechner/pi-ai"; import { streamSimple } from "@mariozechner/pi-ai"; -import { createAgentSession, SessionManager, SettingsManager } from "@mariozechner/pi-coding-agent"; +import { + createAgentSession, + DefaultResourceLoader, + SessionManager, + SettingsManager, +} from "@mariozechner/pi-coding-agent"; import { resolveHeartbeatPrompt } from "../../../auto-reply/heartbeat.js"; import { resolveChannelCapabilities } from "../../../config/channel-capabilities.js"; import { getMachineDisplayName } from "../../../infra/machine-name.js"; @@ -70,7 +75,7 @@ import { resolveTranscriptPolicy } from "../../transcript-policy.js"; import { DEFAULT_BOOTSTRAP_FILENAME } from "../../workspace.js"; import { isRunnerAbortError } from "../abort.js"; import { appendCacheTtlTimestamp, isCacheTtlEligibleProvider } from "../cache-ttl.js"; -import { buildEmbeddedExtensionPaths } from "../extensions.js"; +import { buildEmbeddedExtensionFactories } from "../extensions.js"; import { applyExtraParamsToAgent } from "../extra-params.js"; import { logToolSchemasForGoogle, @@ -534,14 +539,27 @@ export async function runEmbeddedAttempt( cfg: params.config, }); - // Call for side effects (sets compaction/pruning runtime state) - buildEmbeddedExtensionPaths({ + // Sets compaction/pruning runtime state and returns extension factories + // that must be passed to the resource loader for the safeguard to be active. + const extensionFactories = buildEmbeddedExtensionFactories({ cfg: params.config, sessionManager, provider: params.provider, modelId: params.modelId, model: params.model, }); + // Only create an explicit resource loader when there are extension factories + // to register; otherwise let createAgentSession use its built-in default. + let resourceLoader: DefaultResourceLoader | undefined; + if (extensionFactories.length > 0) { + resourceLoader = new DefaultResourceLoader({ + cwd: resolvedWorkspace, + agentDir, + settingsManager, + extensionFactories, + }); + await resourceLoader.reload(); + } // Get hook runner early so it's available when creating tools const hookRunner = getGlobalHookRunner(); @@ -584,6 +602,7 @@ export async function runEmbeddedAttempt( customTools: allCustomTools, sessionManager, settingsManager, + resourceLoader, })); applySystemPromptOverrideToSession(session, systemPromptText); if (!session) {