openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
278 lines (277 loc) • 10.9 kB
JavaScript
import { o as racePromiseWithAbortSignal } from "./src-D1BHGNTj.mjs";
import { t as setSafeTimeout } from "./timer-delay-D9eb6vIQ.mjs";
import { t as ADMIN_SCOPE } from "./operator-scopes-BO7_XQCg.mjs";
import { n as parseTimeoutMsWithFallback } from "./parse-timeout-DRdbYkg7.mjs";
import { a as renderNativeHookRelayUnavailableResponse, n as invokeNativeHookRelayBridge, r as isNativeHookRelayBridgeStaleRegistrationError } from "./native-hook-relay-client-CV9-hyYF.mjs";
import { constants } from "node:fs";
import { open } from "node:fs/promises";
import { request } from "node:https";
//#region src/agents/harness/native-hook-relay-remote-client.ts
const MAX_CREDENTIAL_BYTES = 16384;
const MAX_RESPONSE_BYTES = 1048576;
/** Read a dedicated credential, never a Gateway config or shared state database. */
async function invokeRemoteNativeHookRelay(credentialPath, params, signal) {
const file = await open(credentialPath, constants.O_RDONLY | constants.O_NOFOLLOW);
let credential;
try {
const stat = await file.stat();
if (!stat.isFile() || stat.size > MAX_CREDENTIAL_BYTES) throw new Error("Invalid native hook callback credential");
const buffer = Buffer.alloc(16385);
const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
if (bytesRead > MAX_CREDENTIAL_BYTES) throw new Error("Invalid native hook callback credential");
try {
credential = JSON.parse(buffer.subarray(0, bytesRead).toString("utf8"));
} catch {
throw new Error("Invalid native hook callback credential");
}
} finally {
await file.close();
}
if (!credential || typeof credential.url !== "string" || typeof credential.token !== "string" || !credential.token) throw new Error("Invalid native hook callback credential");
let url;
try {
url = new URL(credential.url);
} catch {
throw new Error("Invalid native hook callback URL");
}
if (url.protocol !== "https:" || url.username || url.password || url.search || url.hash) throw new Error("Native hook callback requires an HTTPS URL without credentials or query");
signal.throwIfAborted();
const body = JSON.stringify(params);
return await new Promise((resolve, reject) => {
const req = request(url, {
method: "POST",
signal,
headers: {
authorization: `Bearer ${credential.token}`,
"content-type": "application/json",
"content-length": Buffer.byteLength(body)
}
}, (res) => {
if (res.statusCode !== 200) {
res.destroy();
reject(/* @__PURE__ */ new Error(`Native hook callback rejected (${res.statusCode ?? 0})`));
return;
}
const chunks = [];
let size = 0;
res.on("data", (chunk) => {
size += chunk.length;
if (size > MAX_RESPONSE_BYTES) {
reject(/* @__PURE__ */ new Error("Native hook callback response too large"));
res.destroy();
return;
}
chunks.push(chunk);
});
res.on("error", () => reject(/* @__PURE__ */ new Error("Native hook callback response failed")));
res.on("end", () => {
try {
const decoded = JSON.parse(Buffer.concat(chunks).toString("utf8"));
const result = decoded?.result;
if (decoded?.ok !== true || !result || typeof result.stdout !== "string" || typeof result.stderr !== "string" || !Number.isInteger(result.exitCode)) throw new Error("Invalid response");
resolve(result);
} catch {
reject(/* @__PURE__ */ new Error("Invalid native hook callback response"));
}
});
});
req.on("error", () => reject(/* @__PURE__ */ new Error("Native hook callback connection failed")));
req.end(body);
});
}
//#endregion
//#region src/cli/native-hook-relay-cli.ts
const MAX_NATIVE_HOOK_STDIN_BYTES = 1048576;
const NATIVE_HOOK_RELAY_VALUE_FLAGS = {
"--provider": "provider",
"--relay-id": "relayId",
"--state-db": "stateDb",
"--remote-credential": "remoteCredential",
"--generation": "generation",
"--event": "event",
"--pre-tool-use-unavailable": "preToolUseUnavailable",
"--timeout": "timeout"
};
var NativeHookRelayDeadlineError = class extends Error {
constructor(timeoutMs) {
super(`native hook relay timed out after ${timeoutMs}ms`);
this.name = "NativeHookRelayDeadlineError";
}
};
/** Parse and run the internal native relay directly from the process argument vector. */
async function runNativeHookRelayCliFromArgv(argv) {
return await runNativeHookRelayCli(parseNativeHookRelayCliOptions(argv));
}
function parseNativeHookRelayCliOptions(argv) {
const relayIndex = argv.findIndex((arg, index) => arg === "relay" && argv[index - 1] === "hooks");
if (relayIndex < 0) throw new Error("native hook relay command path is required");
const opts = {};
for (let index = relayIndex + 1; index < argv.length; index += 1) {
const rawFlag = argv[index] ?? "";
const equalsIndex = rawFlag.indexOf("=");
const flag = equalsIndex > 0 ? rawFlag.slice(0, equalsIndex) : rawFlag;
const key = NATIVE_HOOK_RELAY_VALUE_FLAGS[flag];
if (!key) throw new Error(`unknown native hook relay option: ${rawFlag}`);
const value = equalsIndex > 0 ? rawFlag.slice(equalsIndex + 1) : argv[++index];
if (!value) throw new Error(`native hook relay option ${flag} requires a value`);
opts[key] = value;
}
return opts;
}
/** Run one native hook relay invocation from stdin JSON to stdout/stderr response streams. */
async function runNativeHookRelayCli(opts) {
const { stdin, stdout, stderr } = process;
const provider = readRequiredOption(opts.provider, "provider");
const relayId = readRequiredOption(opts.relayId, "relay-id");
const generation = opts.generation?.trim() || void 0;
const event = readRequiredOption(opts.event, "event");
let timeoutMs;
try {
timeoutMs = parseTimeoutMsWithFallback(opts.timeout, 5e3, { invalidType: "error" });
} catch (error) {
writeText(stderr, formatRelayCliError("invalid native hook timeout", error));
return 1;
}
const deadline = createNativeHookRelayDeadline(timeoutMs);
const writeResponse = (response) => {
writeText(stdout, response.stdout);
writeText(stderr, response.stderr);
return response.exitCode;
};
const unavailable = (message = "Native hook relay unavailable") => writeResponse(renderNativeHookRelayUnavailableResponse({
provider,
event,
preToolUseUnavailable: opts.preToolUseUnavailable,
message
}));
const timedOut = (error) => {
writeText(stderr, formatRelayCliError("native hook relay timed out", error));
return unavailable("Native hook relay timed out");
};
try {
let rawPayload;
try {
const rawInput = await readStreamText(stdin, MAX_NATIVE_HOOK_STDIN_BYTES, deadline);
rawPayload = rawInput.trim() ? JSON.parse(rawInput) : null;
} catch (error) {
if (isNativeHookRelayDeadlineError(error)) return timedOut(error);
writeText(stderr, formatRelayCliError("failed to read native hook input", error));
return 1;
}
try {
if (opts.remoteCredential) return writeResponse(await withNativeHookRelayDeadline(deadline, invokeRemoteNativeHookRelay(opts.remoteCredential, {
provider,
relayId,
generation,
event,
rawPayload
}, deadline.signal)));
try {
const remainingMs = remainingNativeHookRelayDeadlineMs(deadline);
return writeResponse(await withNativeHookRelayDeadline(deadline, invokeNativeHookRelayBridge({
provider,
relayId,
stateDbPath: opts.stateDb?.trim() || void 0,
generation,
event,
rawPayload,
registrationTimeoutMs: Math.min(100, remainingMs),
timeoutMs: remainingMs
})));
} catch (error) {
if (isNativeHookRelayDeadlineError(error) || isNativeHookRelayBridgeStaleRegistrationError(error)) throw error;
}
return writeResponse(await withNativeHookRelayDeadline(deadline, callGatewayLazy({
method: "nativeHook.invoke",
params: {
provider,
relayId,
generation,
event,
rawPayload
},
timeoutMs: remainingNativeHookRelayDeadlineMs(deadline),
signal: deadline.signal,
scopes: [ADMIN_SCOPE]
})));
} catch (error) {
if (isNativeHookRelayDeadlineError(error)) return timedOut(error);
writeText(stderr, formatRelayCliError("native hook relay unavailable", error));
return unavailable();
}
} finally {
deadline.dispose();
}
}
async function callGatewayLazy(opts) {
const { callGateway } = await import("./call-DMnDFvKM.mjs");
return await callGateway(opts);
}
function readRequiredOption(value, name) {
if (typeof value === "string" && value.trim()) return value.trim();
throw new Error(`Missing required option --${name}`);
}
async function readStreamText(stream, maxBytes, deadline) {
const chunks = [];
let total = 0;
const abortRead = () => {
stream.destroy(new NativeHookRelayDeadlineError(deadline.timeoutMs));
};
deadline.signal.addEventListener("abort", abortRead, { once: true });
try {
remainingNativeHookRelayDeadlineMs(deadline);
for await (const chunk of stream) {
remainingNativeHookRelayDeadlineMs(deadline);
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
total += buffer.byteLength;
if (total > maxBytes) throw new Error(`native hook input exceeds ${maxBytes} bytes`);
chunks.push(buffer);
}
remainingNativeHookRelayDeadlineMs(deadline);
return Buffer.concat(chunks, total).toString("utf8");
} catch (error) {
if (isNativeHookRelayDeadlineError(error) || deadline.signal.aborted) throw new NativeHookRelayDeadlineError(deadline.timeoutMs);
throw error;
} finally {
deadline.signal.removeEventListener("abort", abortRead);
}
}
function writeText(stream, value) {
if (value) stream.write(value);
}
function formatRelayCliError(prefix, error) {
return `${prefix}: ${error instanceof Error ? error.message : String(error)}\n`;
}
function createNativeHookRelayDeadline(timeoutMs) {
const controller = new AbortController();
const timer = setSafeTimeout(() => controller.abort(), timeoutMs);
timer.unref?.();
return {
expiresAtMs: performance.now() + timeoutMs,
signal: controller.signal,
timeoutMs,
dispose: () => clearTimeout(timer)
};
}
function isNativeHookRelayDeadlineError(error) {
return error instanceof Error && error.name === "NativeHookRelayDeadlineError";
}
function remainingNativeHookRelayDeadlineMs(deadline) {
const remainingMs = deadline.expiresAtMs - performance.now();
if (remainingMs <= 0 || deadline.signal.aborted) throw new NativeHookRelayDeadlineError(deadline.timeoutMs);
return Math.max(1, remainingMs);
}
async function withNativeHookRelayDeadline(deadline, promise) {
if (deadline.expiresAtMs <= performance.now()) {
promise.catch(() => void 0);
throw new NativeHookRelayDeadlineError(deadline.timeoutMs);
}
return await racePromiseWithAbortSignal(promise.then((value) => {
remainingNativeHookRelayDeadlineMs(deadline);
return value;
}, (error) => {
throw error instanceof Error ? error : new Error(String(error));
}), deadline.signal, () => new NativeHookRelayDeadlineError(deadline.timeoutMs));
}
//#endregion
export { runNativeHookRelayCliFromArgv as n, runNativeHookRelayCli as t };