mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 05:51:24 +00:00
feat: route macOS node exec via app IPC
This commit is contained in:
109
src/infra/exec-host.ts
Normal file
109
src/infra/exec-host.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import crypto from "node:crypto";
|
||||
import net from "node:net";
|
||||
|
||||
export type ExecHostRequest = {
|
||||
command: string[];
|
||||
rawCommand?: string | null;
|
||||
cwd?: string | null;
|
||||
env?: Record<string, string> | null;
|
||||
timeoutMs?: number | null;
|
||||
needsScreenRecording?: boolean | null;
|
||||
agentId?: string | null;
|
||||
sessionKey?: string | null;
|
||||
};
|
||||
|
||||
export type ExecHostRunResult = {
|
||||
exitCode?: number;
|
||||
timedOut: boolean;
|
||||
success: boolean;
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
error?: string | null;
|
||||
};
|
||||
|
||||
export type ExecHostError = {
|
||||
code: string;
|
||||
message: string;
|
||||
reason?: string;
|
||||
};
|
||||
|
||||
export type ExecHostResponse =
|
||||
| { ok: true; payload: ExecHostRunResult }
|
||||
| { ok: false; error: ExecHostError };
|
||||
|
||||
export async function requestExecHostViaSocket(params: {
|
||||
socketPath: string;
|
||||
token: string;
|
||||
request: ExecHostRequest;
|
||||
timeoutMs?: number;
|
||||
}): Promise<ExecHostResponse | null> {
|
||||
const { socketPath, token, request } = params;
|
||||
if (!socketPath || !token) return null;
|
||||
const timeoutMs = params.timeoutMs ?? 20_000;
|
||||
return await new Promise((resolve) => {
|
||||
const client = new net.Socket();
|
||||
let settled = false;
|
||||
let buffer = "";
|
||||
const finish = (value: ExecHostResponse | null) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
try {
|
||||
client.destroy();
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
resolve(value);
|
||||
};
|
||||
|
||||
const requestJson = JSON.stringify(request);
|
||||
const nonce = crypto.randomBytes(16).toString("hex");
|
||||
const ts = Date.now();
|
||||
const hmac = crypto
|
||||
.createHmac("sha256", token)
|
||||
.update(`${nonce}:${ts}:${requestJson}`)
|
||||
.digest("hex");
|
||||
const payload = JSON.stringify({
|
||||
type: "exec",
|
||||
id: crypto.randomUUID(),
|
||||
nonce,
|
||||
ts,
|
||||
hmac,
|
||||
requestJson,
|
||||
});
|
||||
|
||||
const timer = setTimeout(() => finish(null), timeoutMs);
|
||||
|
||||
client.on("error", () => finish(null));
|
||||
client.connect(socketPath, () => {
|
||||
client.write(`${payload}\n`);
|
||||
});
|
||||
client.on("data", (data) => {
|
||||
buffer += data.toString("utf8");
|
||||
let idx = buffer.indexOf("\n");
|
||||
while (idx !== -1) {
|
||||
const line = buffer.slice(0, idx).trim();
|
||||
buffer = buffer.slice(idx + 1);
|
||||
idx = buffer.indexOf("\n");
|
||||
if (!line) continue;
|
||||
try {
|
||||
const msg = JSON.parse(line) as { type?: string; ok?: boolean; payload?: unknown; error?: unknown };
|
||||
if (msg?.type === "exec-res") {
|
||||
clearTimeout(timer);
|
||||
if (msg.ok === true && msg.payload) {
|
||||
finish({ ok: true, payload: msg.payload as ExecHostRunResult });
|
||||
return;
|
||||
}
|
||||
if (msg.ok === false && msg.error) {
|
||||
finish({ ok: false, error: msg.error as ExecHostError });
|
||||
return;
|
||||
}
|
||||
finish(null);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user