mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 19:54:32 +00:00
fix: compaction safeguard extension not loading in production builds (openclaw#22349) thanks @Glucksberg
Verified: - pnpm build - pnpm check - pnpm test:macmini (local run had unrelated baseline failures; Tak approved proceed) Co-authored-by: Glucksberg <80581902+Glucksberg@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
### Fixes
|
### 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.
|
- 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.
|
- 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.
|
- 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.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import os from "node:os";
|
|||||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||||
import {
|
import {
|
||||||
createAgentSession,
|
createAgentSession,
|
||||||
|
DefaultResourceLoader,
|
||||||
estimateTokens,
|
estimateTokens,
|
||||||
SessionManager,
|
SessionManager,
|
||||||
SettingsManager,
|
SettingsManager,
|
||||||
@@ -60,7 +61,7 @@ import {
|
|||||||
compactWithSafetyTimeout,
|
compactWithSafetyTimeout,
|
||||||
EMBEDDED_COMPACTION_TIMEOUT_MS,
|
EMBEDDED_COMPACTION_TIMEOUT_MS,
|
||||||
} from "./compaction-safety-timeout.js";
|
} from "./compaction-safety-timeout.js";
|
||||||
import { buildEmbeddedExtensionPaths } from "./extensions.js";
|
import { buildEmbeddedExtensionFactories } from "./extensions.js";
|
||||||
import {
|
import {
|
||||||
logToolSchemasForGoogle,
|
logToolSchemasForGoogle,
|
||||||
sanitizeSessionHistory,
|
sanitizeSessionHistory,
|
||||||
@@ -533,14 +534,27 @@ export async function compactEmbeddedPiSessionDirect(
|
|||||||
settingsManager,
|
settingsManager,
|
||||||
cfg: params.config,
|
cfg: params.config,
|
||||||
});
|
});
|
||||||
// Call for side effects (sets compaction/pruning runtime state)
|
// Sets compaction/pruning runtime state and returns extension factories
|
||||||
buildEmbeddedExtensionPaths({
|
// that must be passed to the resource loader for the safeguard to be active.
|
||||||
|
const extensionFactories = buildEmbeddedExtensionFactories({
|
||||||
cfg: params.config,
|
cfg: params.config,
|
||||||
sessionManager,
|
sessionManager,
|
||||||
provider,
|
provider,
|
||||||
modelId,
|
modelId,
|
||||||
model,
|
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({
|
const { builtInTools, customTools } = splitSdkTools({
|
||||||
tools,
|
tools,
|
||||||
@@ -558,6 +572,7 @@ export async function compactEmbeddedPiSessionDirect(
|
|||||||
customTools,
|
customTools,
|
||||||
sessionManager,
|
sessionManager,
|
||||||
settingsManager,
|
settingsManager,
|
||||||
|
resourceLoader,
|
||||||
});
|
});
|
||||||
applySystemPromptOverrideToSession(session, systemPromptOverride());
|
applySystemPromptOverrideToSession(session, systemPromptOverride());
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,17 @@
|
|||||||
import path from "node:path";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
import type { Api, Model } from "@mariozechner/pi-ai";
|
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 type { OpenClawConfig } from "../../config/config.js";
|
||||||
import { resolveContextWindowInfo } from "../context-window-guard.js";
|
import { resolveContextWindowInfo } from "../context-window-guard.js";
|
||||||
import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js";
|
import { DEFAULT_CONTEXT_TOKENS } from "../defaults.js";
|
||||||
import { setCompactionSafeguardRuntime } from "../pi-extensions/compaction-safeguard-runtime.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 { setContextPruningRuntime } from "../pi-extensions/context-pruning/runtime.js";
|
||||||
import { computeEffectiveSettings } from "../pi-extensions/context-pruning/settings.js";
|
import { computeEffectiveSettings } from "../pi-extensions/context-pruning/settings.js";
|
||||||
import { makeToolPrunablePredicate } from "../pi-extensions/context-pruning/tools.js";
|
import { makeToolPrunablePredicate } from "../pi-extensions/context-pruning/tools.js";
|
||||||
import { ensurePiCompactionReserveTokens } from "../pi-settings.js";
|
import { ensurePiCompactionReserveTokens } from "../pi-settings.js";
|
||||||
import { isCacheTtlEligibleProvider, readLastCacheTtlTimestamp } from "./cache-ttl.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: {
|
function resolveContextWindowTokens(params: {
|
||||||
cfg: OpenClawConfig | undefined;
|
cfg: OpenClawConfig | undefined;
|
||||||
provider: string;
|
provider: string;
|
||||||
@@ -35,24 +27,24 @@ function resolveContextWindowTokens(params: {
|
|||||||
}).tokens;
|
}).tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildContextPruningExtension(params: {
|
function buildContextPruningFactory(params: {
|
||||||
cfg: OpenClawConfig | undefined;
|
cfg: OpenClawConfig | undefined;
|
||||||
sessionManager: SessionManager;
|
sessionManager: SessionManager;
|
||||||
provider: string;
|
provider: string;
|
||||||
modelId: string;
|
modelId: string;
|
||||||
model: Model<Api> | undefined;
|
model: Model<Api> | undefined;
|
||||||
}): { additionalExtensionPaths?: string[] } {
|
}): ExtensionFactory | undefined {
|
||||||
const raw = params.cfg?.agents?.defaults?.contextPruning;
|
const raw = params.cfg?.agents?.defaults?.contextPruning;
|
||||||
if (raw?.mode !== "cache-ttl") {
|
if (raw?.mode !== "cache-ttl") {
|
||||||
return {};
|
return undefined;
|
||||||
}
|
}
|
||||||
if (!isCacheTtlEligibleProvider(params.provider, params.modelId)) {
|
if (!isCacheTtlEligibleProvider(params.provider, params.modelId)) {
|
||||||
return {};
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = computeEffectiveSettings(raw);
|
const settings = computeEffectiveSettings(raw);
|
||||||
if (!settings) {
|
if (!settings) {
|
||||||
return {};
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
setContextPruningRuntime(params.sessionManager, {
|
setContextPruningRuntime(params.sessionManager, {
|
||||||
@@ -62,23 +54,21 @@ function buildContextPruningExtension(params: {
|
|||||||
lastCacheTouchAt: readLastCacheTtlTimestamp(params.sessionManager),
|
lastCacheTouchAt: readLastCacheTtlTimestamp(params.sessionManager),
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return contextPruningExtension;
|
||||||
additionalExtensionPaths: [resolvePiExtensionPath("context-pruning")],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveCompactionMode(cfg?: OpenClawConfig): "default" | "safeguard" {
|
function resolveCompactionMode(cfg?: OpenClawConfig): "default" | "safeguard" {
|
||||||
return cfg?.agents?.defaults?.compaction?.mode === "safeguard" ? "safeguard" : "default";
|
return cfg?.agents?.defaults?.compaction?.mode === "safeguard" ? "safeguard" : "default";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildEmbeddedExtensionPaths(params: {
|
export function buildEmbeddedExtensionFactories(params: {
|
||||||
cfg: OpenClawConfig | undefined;
|
cfg: OpenClawConfig | undefined;
|
||||||
sessionManager: SessionManager;
|
sessionManager: SessionManager;
|
||||||
provider: string;
|
provider: string;
|
||||||
modelId: string;
|
modelId: string;
|
||||||
model: Model<Api> | undefined;
|
model: Model<Api> | undefined;
|
||||||
}): string[] {
|
}): ExtensionFactory[] {
|
||||||
const paths: string[] = [];
|
const factories: ExtensionFactory[] = [];
|
||||||
if (resolveCompactionMode(params.cfg) === "safeguard") {
|
if (resolveCompactionMode(params.cfg) === "safeguard") {
|
||||||
const compactionCfg = params.cfg?.agents?.defaults?.compaction;
|
const compactionCfg = params.cfg?.agents?.defaults?.compaction;
|
||||||
const contextWindowInfo = resolveContextWindowInfo({
|
const contextWindowInfo = resolveContextWindowInfo({
|
||||||
@@ -92,13 +82,13 @@ export function buildEmbeddedExtensionPaths(params: {
|
|||||||
maxHistoryShare: compactionCfg?.maxHistoryShare,
|
maxHistoryShare: compactionCfg?.maxHistoryShare,
|
||||||
contextWindowTokens: contextWindowInfo.tokens,
|
contextWindowTokens: contextWindowInfo.tokens,
|
||||||
});
|
});
|
||||||
paths.push(resolvePiExtensionPath("compaction-safeguard"));
|
factories.push(compactionSafeguardExtension);
|
||||||
}
|
}
|
||||||
const pruning = buildContextPruningExtension(params);
|
const pruningFactory = buildContextPruningFactory(params);
|
||||||
if (pruning.additionalExtensionPaths) {
|
if (pruningFactory) {
|
||||||
paths.push(...pruning.additionalExtensionPaths);
|
factories.push(pruningFactory);
|
||||||
}
|
}
|
||||||
return paths;
|
return factories;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ensurePiCompactionReserveTokens };
|
export { ensurePiCompactionReserveTokens };
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import os from "node:os";
|
|||||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||||
import type { ImageContent } from "@mariozechner/pi-ai";
|
import type { ImageContent } from "@mariozechner/pi-ai";
|
||||||
import { streamSimple } 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 { resolveHeartbeatPrompt } from "../../../auto-reply/heartbeat.js";
|
||||||
import { resolveChannelCapabilities } from "../../../config/channel-capabilities.js";
|
import { resolveChannelCapabilities } from "../../../config/channel-capabilities.js";
|
||||||
import { getMachineDisplayName } from "../../../infra/machine-name.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 { DEFAULT_BOOTSTRAP_FILENAME } from "../../workspace.js";
|
||||||
import { isRunnerAbortError } from "../abort.js";
|
import { isRunnerAbortError } from "../abort.js";
|
||||||
import { appendCacheTtlTimestamp, isCacheTtlEligibleProvider } from "../cache-ttl.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 { applyExtraParamsToAgent } from "../extra-params.js";
|
||||||
import {
|
import {
|
||||||
logToolSchemasForGoogle,
|
logToolSchemasForGoogle,
|
||||||
@@ -534,14 +539,27 @@ export async function runEmbeddedAttempt(
|
|||||||
cfg: params.config,
|
cfg: params.config,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Call for side effects (sets compaction/pruning runtime state)
|
// Sets compaction/pruning runtime state and returns extension factories
|
||||||
buildEmbeddedExtensionPaths({
|
// that must be passed to the resource loader for the safeguard to be active.
|
||||||
|
const extensionFactories = buildEmbeddedExtensionFactories({
|
||||||
cfg: params.config,
|
cfg: params.config,
|
||||||
sessionManager,
|
sessionManager,
|
||||||
provider: params.provider,
|
provider: params.provider,
|
||||||
modelId: params.modelId,
|
modelId: params.modelId,
|
||||||
model: params.model,
|
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
|
// Get hook runner early so it's available when creating tools
|
||||||
const hookRunner = getGlobalHookRunner();
|
const hookRunner = getGlobalHookRunner();
|
||||||
@@ -584,6 +602,7 @@ export async function runEmbeddedAttempt(
|
|||||||
customTools: allCustomTools,
|
customTools: allCustomTools,
|
||||||
sessionManager,
|
sessionManager,
|
||||||
settingsManager,
|
settingsManager,
|
||||||
|
resourceLoader,
|
||||||
}));
|
}));
|
||||||
applySystemPromptOverrideToSession(session, systemPromptText);
|
applySystemPromptOverrideToSession(session, systemPromptText);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
|
|||||||
Reference in New Issue
Block a user