mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-08 06:54:24 +00:00
refactor: unify tools.fs workspaceOnly resolution
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||
import type { ImageContent } from "@mariozechner/pi-ai";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../../../config/config.js";
|
||||
import {
|
||||
injectHistoryImagesIntoMessages,
|
||||
resolveAttemptFsWorkspaceOnly,
|
||||
resolvePromptBuildHookResult,
|
||||
resolvePromptModeForSession,
|
||||
} from "./attempt.js";
|
||||
@@ -118,3 +120,45 @@ describe("resolvePromptModeForSession", () => {
|
||||
expect(resolvePromptModeForSession("agent:main:cron:job-1:run:run-abc")).toBe("full");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveAttemptFsWorkspaceOnly", () => {
|
||||
it("uses global tools.fs.workspaceOnly when agent has no override", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
tools: {
|
||||
fs: { workspaceOnly: true },
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
resolveAttemptFsWorkspaceOnly({
|
||||
config: cfg,
|
||||
sessionAgentId: "main",
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("prefers agent-specific tools.fs.workspaceOnly override", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
tools: {
|
||||
fs: { workspaceOnly: true },
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
tools: {
|
||||
fs: { workspaceOnly: false },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
resolveAttemptFsWorkspaceOnly({
|
||||
config: cfg,
|
||||
sessionAgentId: "main",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "@mariozechner/pi-coding-agent";
|
||||
import { resolveHeartbeatPrompt } from "../../../auto-reply/heartbeat.js";
|
||||
import { resolveChannelCapabilities } from "../../../config/channel-capabilities.js";
|
||||
import type { OpenClawConfig } from "../../../config/config.js";
|
||||
import { getMachineDisplayName } from "../../../infra/machine-name.js";
|
||||
import { MAX_IMAGE_BYTES } from "../../../media/constants.js";
|
||||
import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
|
||||
@@ -28,7 +29,7 @@ import { resolveUserPath } from "../../../utils.js";
|
||||
import { normalizeMessageChannel } from "../../../utils/message-channel.js";
|
||||
import { isReasoningTagProvider } from "../../../utils/provider-utils.js";
|
||||
import { resolveOpenClawAgentDir } from "../../agent-paths.js";
|
||||
import { resolveAgentConfig, resolveSessionAgentIds } from "../../agent-scope.js";
|
||||
import { resolveSessionAgentIds } from "../../agent-scope.js";
|
||||
import { createAnthropicPayloadLogger } from "../../anthropic-payload-log.js";
|
||||
import { makeBootstrapWarn, resolveBootstrapContextForRun } from "../../bootstrap-files.js";
|
||||
import { createCacheTrace } from "../../cache-trace.js";
|
||||
@@ -74,6 +75,7 @@ import {
|
||||
import { buildSystemPromptParams } from "../../system-prompt-params.js";
|
||||
import { buildSystemPromptReport } from "../../system-prompt-report.js";
|
||||
import { sanitizeToolCallIdsForCloudCodeAssist } from "../../tool-call-id.js";
|
||||
import { resolveEffectiveToolFsWorkspaceOnly } from "../../tool-fs-policy.js";
|
||||
import { resolveTranscriptPolicy } from "../../transcript-policy.js";
|
||||
import { DEFAULT_BOOTSTRAP_FILENAME } from "../../workspace.js";
|
||||
import { isRunnerAbortError } from "../abort.js";
|
||||
@@ -228,6 +230,16 @@ export function resolvePromptModeForSession(sessionKey?: string): "minimal" | "f
|
||||
return isSubagentSessionKey(sessionKey) ? "minimal" : "full";
|
||||
}
|
||||
|
||||
export function resolveAttemptFsWorkspaceOnly(params: {
|
||||
config?: OpenClawConfig;
|
||||
sessionAgentId: string;
|
||||
}): boolean {
|
||||
return resolveEffectiveToolFsWorkspaceOnly({
|
||||
cfg: params.config,
|
||||
agentId: params.sessionAgentId,
|
||||
});
|
||||
}
|
||||
|
||||
function summarizeMessagePayload(msg: AgentMessage): { textChars: number; imageBlocks: number } {
|
||||
const content = (msg as { content?: unknown }).content;
|
||||
if (typeof content === "string") {
|
||||
@@ -363,9 +375,10 @@ export async function runEmbeddedAttempt(
|
||||
config: params.config,
|
||||
agentId: params.agentId,
|
||||
});
|
||||
const effectiveFsWorkspaceOnly =
|
||||
(resolveAgentConfig(params.config ?? {}, sessionAgentId)?.tools?.fs?.workspaceOnly ??
|
||||
params.config?.tools?.fs?.workspaceOnly) === true;
|
||||
const effectiveFsWorkspaceOnly = resolveAttemptFsWorkspaceOnly({
|
||||
config: params.config,
|
||||
sessionAgentId,
|
||||
});
|
||||
// Check if the model supports native image input
|
||||
const modelHasVision = params.model.input?.includes("image") ?? false;
|
||||
const toolsRaw = params.disableTools
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { ImageContent } from "@mariozechner/pi-ai";
|
||||
import { resolveUserPath } from "../../../utils.js";
|
||||
import { loadWebMedia } from "../../../web/media.js";
|
||||
import type { ImageSanitizationLimits } from "../../image-sanitization.js";
|
||||
import { resolveSandboxedBridgeMediaPath } from "../../sandbox-media-paths.js";
|
||||
import { assertSandboxPath } from "../../sandbox-paths.js";
|
||||
import type { SandboxFsBridge } from "../../sandbox/fs-bridge.js";
|
||||
import { sanitizeImageBlocks } from "../../tool-images.js";
|
||||
@@ -199,11 +200,15 @@ export async function loadImageFromRef(
|
||||
if (ref.type === "path") {
|
||||
if (options?.sandbox) {
|
||||
try {
|
||||
const resolved = options.sandbox.bridge.resolvePath({
|
||||
filePath: targetPath,
|
||||
cwd: options.sandbox.root,
|
||||
const resolved = await resolveSandboxedBridgeMediaPath({
|
||||
sandbox: {
|
||||
root: options.sandbox.root,
|
||||
bridge: options.sandbox.bridge,
|
||||
workspaceOnly: options.workspaceOnly,
|
||||
},
|
||||
mediaPath: targetPath,
|
||||
});
|
||||
targetPath = resolved.hostPath;
|
||||
targetPath = resolved.resolved;
|
||||
} catch (err) {
|
||||
log.debug(
|
||||
`Native image: sandbox validation failed for ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`,
|
||||
@@ -213,7 +218,7 @@ export async function loadImageFromRef(
|
||||
} else if (!path.isAbsolute(targetPath)) {
|
||||
targetPath = path.resolve(workspaceDir, targetPath);
|
||||
}
|
||||
if (options?.workspaceOnly) {
|
||||
if (options?.workspaceOnly && !options?.sandbox) {
|
||||
const root = options?.sandbox?.root ?? workspaceDir;
|
||||
await assertSandboxPath({
|
||||
filePath: targetPath,
|
||||
|
||||
@@ -49,7 +49,7 @@ import { cleanToolSchemaForGemini, normalizeToolParameters } from "./pi-tools.sc
|
||||
import type { AnyAgentTool } from "./pi-tools.types.js";
|
||||
import type { SandboxContext } from "./sandbox.js";
|
||||
import { getSubagentDepthFromSessionStore } from "./subagent-depth.js";
|
||||
import { createToolFsPolicy } from "./tool-fs-policy.js";
|
||||
import { createToolFsPolicy, resolveToolFsConfig } from "./tool-fs-policy.js";
|
||||
import {
|
||||
applyToolPolicyPipeline,
|
||||
buildDefaultToolPolicyPipelineSteps,
|
||||
@@ -124,16 +124,6 @@ function resolveExecConfig(params: { cfg?: OpenClawConfig; agentId?: string }) {
|
||||
};
|
||||
}
|
||||
|
||||
function resolveFsConfig(params: { cfg?: OpenClawConfig; agentId?: string }) {
|
||||
const cfg = params.cfg;
|
||||
const globalFs = cfg?.tools?.fs;
|
||||
const agentFs =
|
||||
cfg && params.agentId ? resolveAgentConfig(cfg, params.agentId)?.tools?.fs : undefined;
|
||||
return {
|
||||
workspaceOnly: agentFs?.workspaceOnly ?? globalFs?.workspaceOnly,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveToolLoopDetectionConfig(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
agentId?: string;
|
||||
@@ -291,7 +281,7 @@ export function createOpenClawCodingTools(options?: {
|
||||
subagentPolicy,
|
||||
]);
|
||||
const execConfig = resolveExecConfig({ cfg: options?.config, agentId });
|
||||
const fsConfig = resolveFsConfig({ cfg: options?.config, agentId });
|
||||
const fsConfig = resolveToolFsConfig({ cfg: options?.config, agentId });
|
||||
const fsPolicy = createToolFsPolicy({
|
||||
workspaceOnly: fsConfig.workspaceOnly,
|
||||
});
|
||||
|
||||
50
src/agents/tool-fs-policy.test.ts
Normal file
50
src/agents/tool-fs-policy.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveEffectiveToolFsWorkspaceOnly } from "./tool-fs-policy.js";
|
||||
|
||||
describe("resolveEffectiveToolFsWorkspaceOnly", () => {
|
||||
it("returns false by default when tools.fs.workspaceOnly is unset", () => {
|
||||
expect(resolveEffectiveToolFsWorkspaceOnly({ cfg: {}, agentId: "main" })).toBe(false);
|
||||
});
|
||||
|
||||
it("uses global tools.fs.workspaceOnly when no agent override exists", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
tools: { fs: { workspaceOnly: true } },
|
||||
};
|
||||
expect(resolveEffectiveToolFsWorkspaceOnly({ cfg, agentId: "main" })).toBe(true);
|
||||
});
|
||||
|
||||
it("prefers agent-specific tools.fs.workspaceOnly override over global setting", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
tools: { fs: { workspaceOnly: true } },
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
tools: {
|
||||
fs: { workspaceOnly: false },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(resolveEffectiveToolFsWorkspaceOnly({ cfg, agentId: "main" })).toBe(false);
|
||||
});
|
||||
|
||||
it("supports agent-specific enablement when global workspaceOnly is off", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
tools: { fs: { workspaceOnly: false } },
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
tools: {
|
||||
fs: { workspaceOnly: true },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
expect(resolveEffectiveToolFsWorkspaceOnly({ cfg, agentId: "main" })).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveAgentConfig } from "./agent-scope.js";
|
||||
|
||||
export type ToolFsPolicy = {
|
||||
workspaceOnly: boolean;
|
||||
};
|
||||
@@ -7,3 +10,22 @@ export function createToolFsPolicy(params: { workspaceOnly?: boolean }): ToolFsP
|
||||
workspaceOnly: params.workspaceOnly === true,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveToolFsConfig(params: { cfg?: OpenClawConfig; agentId?: string }): {
|
||||
workspaceOnly?: boolean;
|
||||
} {
|
||||
const cfg = params.cfg;
|
||||
const globalFs = cfg?.tools?.fs;
|
||||
const agentFs =
|
||||
cfg && params.agentId ? resolveAgentConfig(cfg, params.agentId)?.tools?.fs : undefined;
|
||||
return {
|
||||
workspaceOnly: agentFs?.workspaceOnly ?? globalFs?.workspaceOnly,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveEffectiveToolFsWorkspaceOnly(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
agentId?: string;
|
||||
}): boolean {
|
||||
return resolveToolFsConfig(params).workspaceOnly === true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user