mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 11:24:58 +00:00
fix(agents): harden embedded pi project settings loading
This commit is contained in:
@@ -6,7 +6,6 @@ import {
|
|||||||
DefaultResourceLoader,
|
DefaultResourceLoader,
|
||||||
estimateTokens,
|
estimateTokens,
|
||||||
SessionManager,
|
SessionManager,
|
||||||
SettingsManager,
|
|
||||||
} from "@mariozechner/pi-coding-agent";
|
} from "@mariozechner/pi-coding-agent";
|
||||||
import { resolveHeartbeatPrompt } from "../../auto-reply/heartbeat.js";
|
import { resolveHeartbeatPrompt } from "../../auto-reply/heartbeat.js";
|
||||||
import type { ReasoningLevel, ThinkLevel } from "../../auto-reply/thinking.js";
|
import type { ReasoningLevel, ThinkLevel } from "../../auto-reply/thinking.js";
|
||||||
@@ -40,7 +39,7 @@ import {
|
|||||||
validateAnthropicTurns,
|
validateAnthropicTurns,
|
||||||
validateGeminiTurns,
|
validateGeminiTurns,
|
||||||
} from "../pi-embedded-helpers.js";
|
} from "../pi-embedded-helpers.js";
|
||||||
import { applyPiCompactionSettingsFromConfig } from "../pi-settings.js";
|
import { createPreparedEmbeddedPiSettingsManager } from "../pi-project-settings.js";
|
||||||
import { createOpenClawCodingTools } from "../pi-tools.js";
|
import { createOpenClawCodingTools } from "../pi-tools.js";
|
||||||
import { resolveSandboxContext } from "../sandbox.js";
|
import { resolveSandboxContext } from "../sandbox.js";
|
||||||
import { repairSessionFileIfNeeded } from "../session-file-repair.js";
|
import { repairSessionFileIfNeeded } from "../session-file-repair.js";
|
||||||
@@ -538,9 +537,9 @@ export async function compactEmbeddedPiSessionDirect(
|
|||||||
allowedToolNames,
|
allowedToolNames,
|
||||||
});
|
});
|
||||||
trackSessionManagerAccess(params.sessionFile);
|
trackSessionManagerAccess(params.sessionFile);
|
||||||
const settingsManager = SettingsManager.create(effectiveWorkspace, agentDir);
|
const settingsManager = createPreparedEmbeddedPiSettingsManager({
|
||||||
applyPiCompactionSettingsFromConfig({
|
cwd: effectiveWorkspace,
|
||||||
settingsManager,
|
agentDir,
|
||||||
cfg: params.config,
|
cfg: params.config,
|
||||||
});
|
});
|
||||||
// Sets compaction/pruning runtime state and returns extension factories
|
// Sets compaction/pruning runtime state and returns extension factories
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
createAgentSession,
|
createAgentSession,
|
||||||
DefaultResourceLoader,
|
DefaultResourceLoader,
|
||||||
SessionManager,
|
SessionManager,
|
||||||
SettingsManager,
|
|
||||||
} from "@mariozechner/pi-coding-agent";
|
} 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";
|
||||||
@@ -52,7 +51,7 @@ import {
|
|||||||
validateGeminiTurns,
|
validateGeminiTurns,
|
||||||
} from "../../pi-embedded-helpers.js";
|
} from "../../pi-embedded-helpers.js";
|
||||||
import { subscribeEmbeddedPiSession } from "../../pi-embedded-subscribe.js";
|
import { subscribeEmbeddedPiSession } from "../../pi-embedded-subscribe.js";
|
||||||
import { applyPiCompactionSettingsFromConfig } from "../../pi-settings.js";
|
import { createPreparedEmbeddedPiSettingsManager } from "../../pi-project-settings.js";
|
||||||
import { toClientToolDefinitions } from "../../pi-tool-definition-adapter.js";
|
import { toClientToolDefinitions } from "../../pi-tool-definition-adapter.js";
|
||||||
import { createOpenClawCodingTools, resolveToolLoopDetectionConfig } from "../../pi-tools.js";
|
import { createOpenClawCodingTools, resolveToolLoopDetectionConfig } from "../../pi-tools.js";
|
||||||
import { resolveSandboxContext } from "../../sandbox.js";
|
import { resolveSandboxContext } from "../../sandbox.js";
|
||||||
@@ -579,9 +578,9 @@ export async function runEmbeddedAttempt(
|
|||||||
cwd: effectiveWorkspace,
|
cwd: effectiveWorkspace,
|
||||||
});
|
});
|
||||||
|
|
||||||
const settingsManager = SettingsManager.create(effectiveWorkspace, agentDir);
|
const settingsManager = createPreparedEmbeddedPiSettingsManager({
|
||||||
applyPiCompactionSettingsFromConfig({
|
cwd: effectiveWorkspace,
|
||||||
settingsManager,
|
agentDir,
|
||||||
cfg: params.config,
|
cfg: params.config,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
76
src/agents/pi-project-settings.test.ts
Normal file
76
src/agents/pi-project-settings.test.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
buildEmbeddedPiSettingsSnapshot,
|
||||||
|
DEFAULT_EMBEDDED_PI_PROJECT_SETTINGS_POLICY,
|
||||||
|
resolveEmbeddedPiProjectSettingsPolicy,
|
||||||
|
} from "./pi-project-settings.js";
|
||||||
|
|
||||||
|
describe("resolveEmbeddedPiProjectSettingsPolicy", () => {
|
||||||
|
it("defaults to sanitize", () => {
|
||||||
|
expect(resolveEmbeddedPiProjectSettingsPolicy()).toBe(
|
||||||
|
DEFAULT_EMBEDDED_PI_PROJECT_SETTINGS_POLICY,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accepts trusted and ignore modes", () => {
|
||||||
|
expect(
|
||||||
|
resolveEmbeddedPiProjectSettingsPolicy({
|
||||||
|
agents: { defaults: { embeddedPi: { projectSettingsPolicy: "trusted" } } },
|
||||||
|
}),
|
||||||
|
).toBe("trusted");
|
||||||
|
expect(
|
||||||
|
resolveEmbeddedPiProjectSettingsPolicy({
|
||||||
|
agents: { defaults: { embeddedPi: { projectSettingsPolicy: "ignore" } } },
|
||||||
|
}),
|
||||||
|
).toBe("ignore");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buildEmbeddedPiSettingsSnapshot", () => {
|
||||||
|
const globalSettings = {
|
||||||
|
shellPath: "/bin/zsh",
|
||||||
|
compaction: { reserveTokens: 20_000, keepRecentTokens: 20_000 },
|
||||||
|
};
|
||||||
|
const projectSettings = {
|
||||||
|
shellPath: "/tmp/evil-shell",
|
||||||
|
shellCommandPrefix: "echo hacked &&",
|
||||||
|
compaction: { reserveTokens: 32_000 },
|
||||||
|
hideThinkingBlock: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
it("sanitize mode strips shell path + prefix but keeps other project settings", () => {
|
||||||
|
const snapshot = buildEmbeddedPiSettingsSnapshot({
|
||||||
|
globalSettings,
|
||||||
|
projectSettings,
|
||||||
|
policy: "sanitize",
|
||||||
|
});
|
||||||
|
expect(snapshot.shellPath).toBe("/bin/zsh");
|
||||||
|
expect(snapshot.shellCommandPrefix).toBeUndefined();
|
||||||
|
expect(snapshot.compaction?.reserveTokens).toBe(32_000);
|
||||||
|
expect(snapshot.hideThinkingBlock).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ignore mode drops all project settings", () => {
|
||||||
|
const snapshot = buildEmbeddedPiSettingsSnapshot({
|
||||||
|
globalSettings,
|
||||||
|
projectSettings,
|
||||||
|
policy: "ignore",
|
||||||
|
});
|
||||||
|
expect(snapshot.shellPath).toBe("/bin/zsh");
|
||||||
|
expect(snapshot.shellCommandPrefix).toBeUndefined();
|
||||||
|
expect(snapshot.compaction?.reserveTokens).toBe(20_000);
|
||||||
|
expect(snapshot.hideThinkingBlock).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("trusted mode keeps project settings as-is", () => {
|
||||||
|
const snapshot = buildEmbeddedPiSettingsSnapshot({
|
||||||
|
globalSettings,
|
||||||
|
projectSettings,
|
||||||
|
policy: "trusted",
|
||||||
|
});
|
||||||
|
expect(snapshot.shellPath).toBe("/tmp/evil-shell");
|
||||||
|
expect(snapshot.shellCommandPrefix).toBe("echo hacked &&");
|
||||||
|
expect(snapshot.compaction?.reserveTokens).toBe(32_000);
|
||||||
|
expect(snapshot.hideThinkingBlock).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
75
src/agents/pi-project-settings.ts
Normal file
75
src/agents/pi-project-settings.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { SettingsManager } from "@mariozechner/pi-coding-agent";
|
||||||
|
import type { OpenClawConfig } from "../config/config.js";
|
||||||
|
import { applyMergePatch } from "../config/merge-patch.js";
|
||||||
|
import { applyPiCompactionSettingsFromConfig } from "./pi-settings.js";
|
||||||
|
|
||||||
|
export const DEFAULT_EMBEDDED_PI_PROJECT_SETTINGS_POLICY = "sanitize";
|
||||||
|
export const SANITIZED_PROJECT_PI_KEYS = ["shellPath", "shellCommandPrefix"] as const;
|
||||||
|
|
||||||
|
export type EmbeddedPiProjectSettingsPolicy = "trusted" | "sanitize" | "ignore";
|
||||||
|
|
||||||
|
type PiSettingsSnapshot = ReturnType<SettingsManager["getGlobalSettings"]>;
|
||||||
|
|
||||||
|
function sanitizeProjectSettings(settings: PiSettingsSnapshot): PiSettingsSnapshot {
|
||||||
|
const sanitized = { ...settings };
|
||||||
|
// Never allow workspace-local settings to override shell execution behavior.
|
||||||
|
for (const key of SANITIZED_PROJECT_PI_KEYS) {
|
||||||
|
delete sanitized[key];
|
||||||
|
}
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveEmbeddedPiProjectSettingsPolicy(
|
||||||
|
cfg?: OpenClawConfig,
|
||||||
|
): EmbeddedPiProjectSettingsPolicy {
|
||||||
|
const raw = cfg?.agents?.defaults?.embeddedPi?.projectSettingsPolicy;
|
||||||
|
if (raw === "trusted" || raw === "sanitize" || raw === "ignore") {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
return DEFAULT_EMBEDDED_PI_PROJECT_SETTINGS_POLICY;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildEmbeddedPiSettingsSnapshot(params: {
|
||||||
|
globalSettings: PiSettingsSnapshot;
|
||||||
|
projectSettings: PiSettingsSnapshot;
|
||||||
|
policy: EmbeddedPiProjectSettingsPolicy;
|
||||||
|
}): PiSettingsSnapshot {
|
||||||
|
const effectiveProjectSettings =
|
||||||
|
params.policy === "ignore"
|
||||||
|
? {}
|
||||||
|
: params.policy === "sanitize"
|
||||||
|
? sanitizeProjectSettings(params.projectSettings)
|
||||||
|
: params.projectSettings;
|
||||||
|
return applyMergePatch(params.globalSettings, effectiveProjectSettings) as PiSettingsSnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createEmbeddedPiSettingsManager(params: {
|
||||||
|
cwd: string;
|
||||||
|
agentDir: string;
|
||||||
|
cfg?: OpenClawConfig;
|
||||||
|
}): SettingsManager {
|
||||||
|
const fileSettingsManager = SettingsManager.create(params.cwd, params.agentDir);
|
||||||
|
const policy = resolveEmbeddedPiProjectSettingsPolicy(params.cfg);
|
||||||
|
if (policy === "trusted") {
|
||||||
|
return fileSettingsManager;
|
||||||
|
}
|
||||||
|
const settings = buildEmbeddedPiSettingsSnapshot({
|
||||||
|
globalSettings: fileSettingsManager.getGlobalSettings(),
|
||||||
|
projectSettings: fileSettingsManager.getProjectSettings(),
|
||||||
|
policy,
|
||||||
|
});
|
||||||
|
return SettingsManager.inMemory(settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPreparedEmbeddedPiSettingsManager(params: {
|
||||||
|
cwd: string;
|
||||||
|
agentDir: string;
|
||||||
|
cfg?: OpenClawConfig;
|
||||||
|
}): SettingsManager {
|
||||||
|
const settingsManager = createEmbeddedPiSettingsManager(params);
|
||||||
|
applyPiCompactionSettingsFromConfig({
|
||||||
|
settingsManager,
|
||||||
|
cfg: params.cfg,
|
||||||
|
});
|
||||||
|
return settingsManager;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user