mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-18 04:31:10 +00:00
perf: reduce test import overhead
This commit is contained in:
76
src/agents/bash-tools.descriptions.ts
Normal file
76
src/agents/bash-tools.descriptions.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import path from "node:path";
|
||||
import { loadExecApprovals, resolveExecApprovalsFromFile } from "../infra/exec-approvals.js";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
export function describeExecTool(params?: { agentId?: string; hasCronTool?: boolean }): string {
|
||||
const base = [
|
||||
"Execute shell commands with background continuation for work that starts now.",
|
||||
"Use yieldMs/background to continue later via process tool.",
|
||||
"For long-running work started now, rely on automatic completion wake when it is enabled and the command emits output or fails; otherwise use process to confirm completion. Use process whenever you need logs, status, input, or intervention.",
|
||||
params?.hasCronTool
|
||||
? "Do not use exec sleep or delay loops for reminders or deferred follow-ups; use cron instead."
|
||||
: undefined,
|
||||
"Use pty=true for TTY-required commands (terminal UIs, coding agents).",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
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: params?.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 describeProcessTool(params?: { hasCronTool?: boolean }): string {
|
||||
return [
|
||||
"Manage running exec sessions for commands already started: list, poll, log, write, send-keys, submit, paste, kill.",
|
||||
"Use poll/log when you need status, logs, quiet-success confirmation, or completion confirmation when automatic completion wake is unavailable. Use write/send-keys/submit/paste/kill for input or intervention.",
|
||||
params?.hasCronTool
|
||||
? "Do not use process polling to emulate timers or reminders; use cron for scheduled follow-ups."
|
||||
: undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
}
|
||||
@@ -1,13 +1,7 @@
|
||||
import path from "node:path";
|
||||
import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
import { analyzeShellCommand } from "../infra/exec-approvals-analysis.js";
|
||||
import {
|
||||
type ExecHost,
|
||||
loadExecApprovals,
|
||||
maxAsk,
|
||||
minSecurity,
|
||||
resolveExecApprovalsFromFile,
|
||||
} from "../infra/exec-approvals.js";
|
||||
import { type ExecHost, loadExecApprovals, maxAsk, minSecurity } from "../infra/exec-approvals.js";
|
||||
import { resolveExecSafeBinRuntimePolicy } from "../infra/exec-safe-bin-runtime-policy.js";
|
||||
import { SafeOpenError, readFileWithinRoot } from "../infra/fs-safe.js";
|
||||
import { sanitizeHostExecEnvWithDiagnostics } from "../infra/host-env-security.js";
|
||||
@@ -24,6 +18,7 @@ import {
|
||||
} from "../shared/string-coerce.js";
|
||||
import { splitShellArgs } from "../utils/shell-argv.js";
|
||||
import { markBackgrounded } from "./bash-process-registry.js";
|
||||
import { describeExecTool } from "./bash-tools.descriptions.js";
|
||||
import { processGatewayAllowlist } from "./bash-tools.exec-host-gateway.js";
|
||||
import { executeNodeHostCommand } from "./bash-tools.exec-host-node.js";
|
||||
import {
|
||||
@@ -1282,67 +1277,6 @@ 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;
|
||||
}
|
||||
|
||||
export function describeExecTool(params?: { agentId?: string; hasCronTool?: boolean }): string {
|
||||
const base = [
|
||||
"Execute shell commands with background continuation for work that starts now.",
|
||||
"Use yieldMs/background to continue later via process tool.",
|
||||
"For long-running work started now, rely on automatic completion wake when it is enabled and the command emits output or fails; otherwise use process to confirm completion. Use process whenever you need logs, status, input, or intervention.",
|
||||
params?.hasCronTool
|
||||
? "Do not use exec sleep or delay loops for reminders or deferred follow-ups; use cron instead."
|
||||
: undefined,
|
||||
"Use pty=true for TTY-required commands (terminal UIs, coding agents).",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
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: params?.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,
|
||||
): AgentToolWithMeta<typeof execSchema, ExecToolDetails> {
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
markExited,
|
||||
setJobTtlMs,
|
||||
} from "./bash-process-registry.js";
|
||||
import { describeProcessTool } from "./bash-tools.descriptions.js";
|
||||
import { deriveSessionName, pad, sliceLogLines, truncateMiddle } from "./bash-tools.shared.js";
|
||||
import { recordCommandPoll, resetCommandPollCount } from "./command-poll-backoff.js";
|
||||
import { encodeKeySequence, encodePaste, hasCursorModeSensitiveKeys } from "./pty-keys.js";
|
||||
@@ -119,18 +120,6 @@ function resetPollRetrySuggestion(sessionId: string): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function describeProcessTool(params?: { hasCronTool?: boolean }): string {
|
||||
return [
|
||||
"Manage running exec sessions for commands already started: list, poll, log, write, send-keys, submit, paste, kill.",
|
||||
"Use poll/log when you need status, logs, quiet-success confirmation, or completion confirmation when automatic completion wake is unavailable. Use write/send-keys/submit/paste/kill for input or intervention.",
|
||||
params?.hasCronTool
|
||||
? "Do not use process polling to emulate timers or reminders; use cron for scheduled follow-ups."
|
||||
: undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
export function createProcessTool(
|
||||
defaults?: ProcessToolDefaults,
|
||||
): AgentToolWithMeta<typeof processSchema, unknown> {
|
||||
|
||||
@@ -4,6 +4,7 @@ export type {
|
||||
ExecToolDefaults,
|
||||
ExecToolDetails,
|
||||
} from "./bash-tools.exec.js";
|
||||
export { createExecTool, describeExecTool, execTool } from "./bash-tools.exec.js";
|
||||
export { describeExecTool, describeProcessTool } from "./bash-tools.descriptions.js";
|
||||
export { createExecTool, execTool } from "./bash-tools.exec.js";
|
||||
export type { ProcessToolDefaults } from "./bash-tools.process.js";
|
||||
export { createProcessTool, describeProcessTool, processTool } from "./bash-tools.process.js";
|
||||
export { createProcessTool, processTool } from "./bash-tools.process.js";
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import "./test-helpers/fast-openclaw-tools.js";
|
||||
import { createOpenClawCodingTools } from "./pi-tools.js";
|
||||
import { applyDeferredFollowupToolDescriptions } from "./pi-tools.deferred-followup.js";
|
||||
import type { AnyAgentTool } from "./pi-tools.types.js";
|
||||
|
||||
function findToolDescription(toolName: string, senderIsOwner: boolean) {
|
||||
const tools = createOpenClawCodingTools({ senderIsOwner });
|
||||
const tools = applyDeferredFollowupToolDescriptions([
|
||||
{ name: "exec", description: "exec base" },
|
||||
{ name: "process", description: "process base" },
|
||||
...(senderIsOwner ? [{ name: "cron", description: "cron base" }] : []),
|
||||
] as AnyAgentTool[]);
|
||||
const tool = tools.find((entry) => entry.name === toolName);
|
||||
return {
|
||||
toolNames: tools.map((entry) => entry.name),
|
||||
|
||||
24
src/agents/pi-tools.deferred-followup.ts
Normal file
24
src/agents/pi-tools.deferred-followup.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { describeExecTool, describeProcessTool } from "./bash-tools.descriptions.js";
|
||||
import type { AnyAgentTool } from "./pi-tools.types.js";
|
||||
|
||||
export function applyDeferredFollowupToolDescriptions(
|
||||
tools: AnyAgentTool[],
|
||||
params?: { agentId?: string },
|
||||
): AnyAgentTool[] {
|
||||
const hasCronTool = tools.some((tool) => tool.name === "cron");
|
||||
return tools.map((tool) => {
|
||||
if (tool.name === "exec") {
|
||||
return {
|
||||
...tool,
|
||||
description: describeExecTool({ agentId: params?.agentId, hasCronTool }),
|
||||
};
|
||||
}
|
||||
if (tool.name === "process") {
|
||||
return {
|
||||
...tool,
|
||||
description: describeProcessTool({ hasCronTool }),
|
||||
};
|
||||
}
|
||||
return tool;
|
||||
});
|
||||
}
|
||||
@@ -16,8 +16,6 @@ import { createApplyPatchTool } from "./apply-patch.js";
|
||||
import {
|
||||
createExecTool,
|
||||
createProcessTool,
|
||||
describeExecTool,
|
||||
describeProcessTool,
|
||||
type ExecToolDefaults,
|
||||
type ProcessToolDefaults,
|
||||
} from "./bash-tools.js";
|
||||
@@ -28,6 +26,7 @@ import type { ModelAuthMode } from "./model-auth.js";
|
||||
import { createOpenClawTools } from "./openclaw-tools.js";
|
||||
import { wrapToolWithAbortSignal } from "./pi-tools.abort.js";
|
||||
import { wrapToolWithBeforeToolCallHook } from "./pi-tools.before-tool-call.js";
|
||||
import { applyDeferredFollowupToolDescriptions } from "./pi-tools.deferred-followup.js";
|
||||
import { filterToolsByMessageProvider } from "./pi-tools.message-provider-policy.js";
|
||||
import {
|
||||
isToolAllowedByPolicies,
|
||||
@@ -97,28 +96,6 @@ function applyModelProviderToolPolicy(
|
||||
return tools;
|
||||
}
|
||||
|
||||
function applyDeferredFollowupToolDescriptions(
|
||||
tools: AnyAgentTool[],
|
||||
params?: { agentId?: string },
|
||||
): AnyAgentTool[] {
|
||||
const hasCronTool = tools.some((tool) => tool.name === "cron");
|
||||
return tools.map((tool) => {
|
||||
if (tool.name === "exec") {
|
||||
return {
|
||||
...tool,
|
||||
description: describeExecTool({ agentId: params?.agentId, hasCronTool }),
|
||||
};
|
||||
}
|
||||
if (tool.name === "process") {
|
||||
return {
|
||||
...tool,
|
||||
description: describeProcessTool({ hasCronTool }),
|
||||
};
|
||||
}
|
||||
return tool;
|
||||
});
|
||||
}
|
||||
|
||||
function isApplyPatchAllowedForModel(params: {
|
||||
modelProvider?: string;
|
||||
modelId?: string;
|
||||
|
||||
Reference in New Issue
Block a user