mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 16:30:30 +00:00
fix(exec): make Windows exec hints accurate and dynamic
This commit is contained in:
@@ -2,7 +2,13 @@ import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { AgentTool, AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import { analyzeShellCommand } from "../infra/exec-approvals-analysis.js";
|
||||
import { type ExecHost, loadExecApprovals, maxAsk, minSecurity } from "../infra/exec-approvals.js";
|
||||
import {
|
||||
type ExecHost,
|
||||
loadExecApprovals,
|
||||
maxAsk,
|
||||
minSecurity,
|
||||
resolveExecApprovalsFromFile,
|
||||
} from "../infra/exec-approvals.js";
|
||||
import { resolveExecSafeBinRuntimePolicy } from "../infra/exec-safe-bin-runtime-policy.js";
|
||||
import { sanitizeHostExecEnvWithDiagnostics } from "../infra/host-env-security.js";
|
||||
import {
|
||||
@@ -430,6 +436,55 @@ function rejectExecApprovalShellCommand(command: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the exact approved token in hints. Absolute paths stay absolute so the
|
||||
* hint cannot imply an equivalent PATH lookup that resolves to a different binary.
|
||||
*/
|
||||
function deriveExecShortName(fullPath: string): string {
|
||||
if (path.isAbsolute(fullPath)) {
|
||||
return fullPath;
|
||||
}
|
||||
const base = path.basename(fullPath);
|
||||
return base.replace(/\.exe$/i, "") || base;
|
||||
}
|
||||
|
||||
function buildExecToolDescription(agentId?: string): string {
|
||||
const base =
|
||||
"Execute shell commands with background continuation. Use yieldMs/background to continue later via process tool. Use pty=true for TTY-required commands (terminal UIs, coding agents).";
|
||||
if (process.platform !== "win32") {
|
||||
return base;
|
||||
}
|
||||
const lines: string[] = [base];
|
||||
lines.push(
|
||||
"IMPORTANT (Windows): Run executables directly — do NOT wrap commands in `cmd /c`, `powershell -Command`, `& ` prefix, or WSL. Use backslash paths (C:\\path), not forward slashes. Use short executable names (e.g. `node`, `python3`) instead of full paths.",
|
||||
);
|
||||
try {
|
||||
const approvalsFile = loadExecApprovals();
|
||||
const approvals = resolveExecApprovalsFromFile({ file: approvalsFile, agentId });
|
||||
const allowlist = approvals.allowlist.filter((entry) => {
|
||||
const pattern = entry.pattern?.trim() ?? "";
|
||||
return (
|
||||
pattern.length > 0 &&
|
||||
pattern !== "*" &&
|
||||
!pattern.startsWith("=command:") &&
|
||||
(pattern.includes("/") || pattern.includes("\\") || pattern.includes("~"))
|
||||
);
|
||||
});
|
||||
if (allowlist.length > 0) {
|
||||
lines.push(
|
||||
"Pre-approved executables (exact arguments are enforced at runtime; no approval prompt needed when args match):",
|
||||
);
|
||||
for (const entry of allowlist.slice(0, 10)) {
|
||||
const shortName = deriveExecShortName(entry.pattern);
|
||||
const argNote = entry.argPattern ? "(restricted args)" : "(any arguments)";
|
||||
lines.push(` ${shortName} ${argNote}`);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Allowlist loading is best-effort; don't block tool creation.
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
export function createExecTool(
|
||||
defaults?: ExecToolDefaults,
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
@@ -485,8 +540,9 @@ export function createExecTool(
|
||||
return {
|
||||
name: "exec",
|
||||
label: "exec",
|
||||
description:
|
||||
"Execute shell commands with background continuation. Use yieldMs/background to continue later via process tool. Use pty=true for TTY-required commands (terminal UIs, coding agents).",
|
||||
get description() {
|
||||
return buildExecToolDescription(agentId);
|
||||
},
|
||||
parameters: execSchema,
|
||||
execute: async (_toolCallId, args, signal, onUpdate) => {
|
||||
const params = args as {
|
||||
|
||||
Reference in New Issue
Block a user