openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
149 lines (148 loc) • 5.71 kB
JavaScript
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { t as mergeProcessEnv } from "./process-env-DlZFJzq6.js";
import { i as resolveExecutableFromPathEnv } from "./executable-path-BK-aU8Pd.js";
import { a as resolveExecutableFromUserShellPath } from "./shell-env-ti8g83F7.js";
import { t as spawnTerminalPty } from "./terminal-pty-D0ZyEChB.js";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
//#region src/node-host/pty-command.ts
function resolvePtyCwd(candidate, required = false) {
if (candidate && path.isAbsolute(candidate)) try {
if (fs.statSync(candidate).isDirectory()) return candidate;
} catch {}
if (required) throw new Error("INVALID_REQUEST: cwd must be an existing absolute directory on this node");
return os.homedir();
}
function decodePtyInput(payloadJSON) {
try {
const value = JSON.parse(payloadJSON);
if (!isRecord(value)) return null;
const input = value;
if (input.kind === "data" && typeof input.data === "string") return {
kind: "data",
data: input.data
};
if (input.kind === "resize" && Number.isInteger(input.cols) && Number.isInteger(input.rows) && input.cols >= 1 && input.cols <= 2e3 && input.rows >= 1 && input.rows <= 2e3) return {
kind: "resize",
cols: input.cols,
rows: input.rows
};
return null;
} catch {
return null;
}
}
function decodePtyParams(paramsJSON, action) {
let value;
try {
value = JSON.parse(paramsJSON ?? "");
} catch {
throw new Error(`INVALID_REQUEST: terminal ${action} params must be valid JSON`);
}
if (!isRecord(value)) throw new Error(`INVALID_REQUEST: terminal ${action} params must be an object`);
const allowed = /* @__PURE__ */ new Set([
action === "start" ? "initialMessage" : "threadId",
"cwd",
"cols",
"rows"
]);
const unknown = Object.keys(value).find((key) => !allowed.has(key));
if (unknown) throw new Error(`INVALID_REQUEST: unknown terminal ${action} parameter: ${unknown}`);
const dimension = (candidate, label) => {
if (typeof candidate !== "number" || !Number.isInteger(candidate) || candidate < 1 || candidate > 2e3) throw new Error(`INVALID_REQUEST: ${label} must be an integer from 1 to 2000`);
return candidate;
};
if (value.cwd !== void 0 && (typeof value.cwd !== "string" || Buffer.byteLength(value.cwd, "utf8") > 4096)) throw new Error("INVALID_REQUEST: cwd must be a bounded string");
return {
record: value,
cwd: typeof value.cwd === "string" && value.cwd ? value.cwd : void 0,
cols: dimension(value.cols, "cols"),
rows: dimension(value.rows, "rows")
};
}
function decodeNodePtyResumeParams(paramsJSON, validateThreadId) {
const { record, ...params } = decodePtyParams(paramsJSON, "resume");
return {
threadId: validateThreadId(record.threadId),
...params
};
}
function decodeNodePtyStartParams(paramsJSON) {
const { record, cwd, ...size } = decodePtyParams(paramsJSON, "start");
if (record.initialMessage !== void 0 && (typeof record.initialMessage !== "string" || record.initialMessage.length > 16384)) throw new Error("INVALID_REQUEST: initialMessage must be a string of at most 16384 characters");
return {
cwd: resolvePtyCwd(cwd, true),
...size,
...typeof record.initialMessage === "string" ? { initialMessage: record.initialMessage } : {}
};
}
/** Runs one allowlisted plugin-owned command in an interactive node PTY. */
async function runNodePtyCommand(params, io, spawn = spawnTerminalPty) {
if (io.signal.aborted) return { exitCode: 130 };
const env = mergeProcessEnv([
process.env,
params.env,
params.pathEnv ? { PATH: params.pathEnv } : void 0,
{ OPENCLAW_TERMINAL: "1" }
]);
const pty = await spawn({
file: params.file,
args: params.args,
cwd: resolvePtyCwd(params.cwd, params.requiredCwd),
env,
cols: params.cols,
rows: params.rows
});
let outputQueue = Promise.resolve();
let settled = false;
const kill = () => pty.kill();
io.signal.addEventListener("abort", kill, { once: true });
if (io.signal.aborted) kill();
io.onInput((payloadJSON) => {
if (settled || io.signal.aborted) return;
const input = decodePtyInput(payloadJSON);
try {
if (input?.kind === "data") pty.write(input.data);
else if (input?.kind === "resize") pty.resize(input.cols, input.rows);
} catch {}
});
pty.onData((chunk) => {
if (settled) return;
pty.pause();
outputQueue = outputQueue.then(() => io.emitChunk(chunk)).finally(() => pty.resume());
});
return await new Promise((resolve) => {
pty.onExit((event) => {
if (settled) return;
settled = true;
io.signal.removeEventListener("abort", kill);
outputQueue.finally(() => resolve({
exitCode: event.exitCode,
...event.signal ? { signal: event.signal } : {}
}));
});
});
}
//#endregion
//#region src/plugin-sdk/node-host.ts
/** Resolve a node-host executable using the selected PATH source policy. */
function resolveNodeHostExecutable(executable, options) {
const env = options.env ?? process.env;
const resolve = (includeExtensionless) => {
if (options.strategy === "direct") {
const resolved = resolveExecutableFromPathEnv(executable, options.pathEnv ?? env.PATH ?? env.Path ?? "", env, { includeExtensionless });
return resolved ? { executable: resolved } : void 0;
}
return resolveExecutableFromUserShellPath(executable, {
env,
pathEnv: options.pathEnv,
includeExtensionless,
strategy: options.strategy
});
};
if (options.includeExtensionless !== void 0 || process.platform !== "win32") return resolve(options.includeExtensionless ?? true);
return resolve(false) ?? resolve(true);
}
//#endregion
export { runNodePtyCommand as i, decodeNodePtyResumeParams as n, decodeNodePtyStartParams as r, resolveNodeHostExecutable as t };