diff --git a/src/agents/bash-tools.exec-runtime.ts b/src/agents/bash-tools.exec-runtime.ts index 770960dc43..57dcdb6bb9 100644 --- a/src/agents/bash-tools.exec-runtime.ts +++ b/src/agents/bash-tools.exec-runtime.ts @@ -7,7 +7,9 @@ import type { ProcessSession, SessionStdin } from "./bash-process-registry.js"; import type { ExecToolDetails } from "./bash-tools.exec.js"; import type { BashSandboxConfig } from "./bash-tools.shared.js"; import { requestHeartbeatNow } from "../infra/heartbeat-wake.js"; +import { mergePathPrepend } from "../infra/path-prepend.js"; import { enqueueSystemEvent } from "../infra/system-events.js"; +export { applyPathPrepend, normalizePathPrepend } from "../infra/path-prepend.js"; import { logWarn } from "../logger.js"; import { formatSpawnError, spawnWithFallback } from "../process/spawn-utils.js"; import { @@ -227,63 +229,6 @@ function compactNotifyOutput(value: string, maxChars = DEFAULT_NOTIFY_SNIPPET_CH return `${normalized.slice(0, safe)}…`; } -export function normalizePathPrepend(entries?: string[]) { - if (!Array.isArray(entries)) { - return []; - } - const seen = new Set(); - const normalized: string[] = []; - for (const entry of entries) { - if (typeof entry !== "string") { - continue; - } - const trimmed = entry.trim(); - if (!trimmed || seen.has(trimmed)) { - continue; - } - seen.add(trimmed); - normalized.push(trimmed); - } - return normalized; -} - -function mergePathPrepend(existing: string | undefined, prepend: string[]) { - if (prepend.length === 0) { - return existing; - } - const partsExisting = (existing ?? "") - .split(path.delimiter) - .map((part) => part.trim()) - .filter(Boolean); - const merged: string[] = []; - const seen = new Set(); - for (const part of [...prepend, ...partsExisting]) { - if (seen.has(part)) { - continue; - } - seen.add(part); - merged.push(part); - } - return merged.join(path.delimiter); -} - -export function applyPathPrepend( - env: Record, - prepend: string[], - options?: { requireExisting?: boolean }, -) { - if (prepend.length === 0) { - return; - } - if (options?.requireExisting && !env.PATH) { - return; - } - const merged = mergePathPrepend(env.PATH, prepend); - if (merged) { - env.PATH = merged; - } -} - export function applyShellPath(env: Record, shellPath?: string | null) { if (!shellPath) { return; diff --git a/src/cli/nodes-cli/register.invoke.ts b/src/cli/nodes-cli/register.invoke.ts index 45cfaed4ad..6cf004be89 100644 --- a/src/cli/nodes-cli/register.invoke.ts +++ b/src/cli/nodes-cli/register.invoke.ts @@ -1,5 +1,4 @@ import type { Command } from "commander"; -import path from "node:path"; import type { NodesRpcOpts } from "./types.js"; import { resolveAgentConfig, resolveDefaultAgentId } from "../../agents/agent-scope.js"; import { loadConfig } from "../../config/config.js"; @@ -14,6 +13,7 @@ import { resolveExecApprovalsFromFile, } from "../../infra/exec-approvals.js"; import { buildNodeShellCommand } from "../../infra/node-shell.js"; +import { applyPathPrepend } from "../../infra/path-prepend.js"; import { defaultRuntime } from "../../runtime.js"; import { parseEnvPairs, parseTimeoutMs } from "../nodes-run.js"; import { getNodesTheme, runNodesCommand } from "./cli-utils.js"; @@ -58,43 +58,6 @@ function normalizeExecAsk(value?: string | null): ExecAsk | null { return null; } -function mergePathPrepend(existing: string | undefined, prepend: string[]) { - if (prepend.length === 0) { - return existing; - } - const partsExisting = (existing ?? "") - .split(path.delimiter) - .map((part) => part.trim()) - .filter(Boolean); - const merged: string[] = []; - const seen = new Set(); - for (const part of [...prepend, ...partsExisting]) { - if (seen.has(part)) { - continue; - } - seen.add(part); - merged.push(part); - } - return merged.join(path.delimiter); -} - -function applyPathPrepend( - env: Record, - prepend: string[] | undefined, - options?: { requireExisting?: boolean }, -) { - if (!Array.isArray(prepend) || prepend.length === 0) { - return; - } - if (options?.requireExisting && !env.PATH) { - return; - } - const merged = mergePathPrepend(env.PATH, prepend); - if (merged) { - env.PATH = merged; - } -} - function resolveExecDefaults( cfg: ReturnType, agentId: string | undefined, diff --git a/src/infra/path-prepend.ts b/src/infra/path-prepend.ts new file mode 100644 index 0000000000..df3e2a5951 --- /dev/null +++ b/src/infra/path-prepend.ts @@ -0,0 +1,58 @@ +import path from "node:path"; + +export function normalizePathPrepend(entries?: string[]) { + if (!Array.isArray(entries)) { + return []; + } + const seen = new Set(); + const normalized: string[] = []; + for (const entry of entries) { + if (typeof entry !== "string") { + continue; + } + const trimmed = entry.trim(); + if (!trimmed || seen.has(trimmed)) { + continue; + } + seen.add(trimmed); + normalized.push(trimmed); + } + return normalized; +} + +export function mergePathPrepend(existing: string | undefined, prepend: string[]) { + if (prepend.length === 0) { + return existing; + } + const partsExisting = (existing ?? "") + .split(path.delimiter) + .map((part) => part.trim()) + .filter(Boolean); + const merged: string[] = []; + const seen = new Set(); + for (const part of [...prepend, ...partsExisting]) { + if (seen.has(part)) { + continue; + } + seen.add(part); + merged.push(part); + } + return merged.join(path.delimiter); +} + +export function applyPathPrepend( + env: Record, + prepend: string[] | undefined, + options?: { requireExisting?: boolean }, +) { + if (!Array.isArray(prepend) || prepend.length === 0) { + return; + } + if (options?.requireExisting && !env.PATH) { + return; + } + const merged = mergePathPrepend(env.PATH, prepend); + if (merged) { + env.PATH = merged; + } +}