openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
909 lines (885 loc) • 41.1 kB
JavaScript
import { c as isRecord } from "../record-coerce-DItp3I4t.js";
import { r as createLazyPromiseLoader } from "../lazy-promise-DGqyc4Y4.js";
import { r as serveWorkerTasks } from "../worker-task-pool-BNbf5LmH.js";
import { n as ToolInputError } from "../tool-input-error-mjW74R8m.js";
import { a as captureCodeModeOutput, n as EMPTY_CODE_MODE_OUTPUT, o as captureCodeModeValue, r as boundCodeModeError } from "../code-mode-json-DT3j3vDO.js";
import { n as parseCodeModeScriptSyntax, t as buildCodeModeScriptParseSource } from "../code-mode-script-syntax-DZwdESO8.js";
import { t as _usingCtx } from "../usingCtx-CTfBxUtw.js";
import { parse, tokenizer } from "acorn";
import { EvalFlags, JSException, QuickJS } from "quickjs-wasi";
import { Script } from "node:vm";
//#region src/agents/code-mode-swarm-controller-source.ts
/** Guest-side Swarm helpers injected into the isolated QuickJS controller. */
const CODE_MODE_SWARM_CONTROLLER_SOURCE = String.raw`
class SwarmAgentError extends Error {
constructor(runId, status, detail) {
super("Swarm agent " + runId + " " + status + ": " + detail);
this.name = "SwarmAgentError";
this.runId = runId;
this.status = status;
}
}
function swarmNote(kind, value) {
if (typeof value !== "string" || !value.trim()) {
throw new TypeError(kind + " note must be a non-empty string");
}
void request("swarmNote", [{ kind, text: value }], { queue: true }).catch(() => {});
}
async function runAgent(prompt, options = {}) {
if (typeof prompt !== "string" || !prompt.trim()) {
throw new TypeError("agents.run prompt must be a non-empty string");
}
if (options === null || typeof options !== "object" || Array.isArray(options)) {
throw new TypeError("agents.run options must be an object");
}
if (options.phase !== undefined && (typeof options.phase !== "string" || !options.phase.trim())) {
throw new TypeError("agents.run phase must be a non-empty string");
}
// Match the submitted contract even when callers reuse options while the child runs.
const structured = options.schema !== undefined;
if (options.phase !== undefined) swarmNote("phase", options.phase);
const spawned = await request("agentSpawn", [prompt, options], { queue: true });
const completion = await request("agentWait", [spawned.runId], { queue: true });
if (!completion || completion.status !== "done") {
const runId = completion?.runId ?? spawned.runId ?? "unknown";
const status = completion?.status ?? "failed";
const detail = [completion?.error, completion?.schemaError, completion?.result].find(
(value) => typeof value === "string" && value.trim()
) || "collector returned no result";
throw new SwarmAgentError(runId, status, detail);
}
return structured ? completion.structured : completion.result;
}
`;
//#endregion
//#region src/agents/code-mode-controller-source.ts
/** Sandboxed guest globals and host bridge for Code Mode QuickJS cells. */
const CODE_MODE_CONTROLLER_SOURCE = String.raw`
(() => {
const output = [];
const pending = new Map();
const queued = [];
const maxPending = globalThis.__openclawMaxPendingToolCalls;
delete globalThis.__openclawMaxPendingToolCalls;
const catalogBindings = Array.isArray(globalThis.__openclawCatalog) ? globalThis.__openclawCatalog : [];
const apiFiles = Array.isArray(globalThis.__openclawApiFiles) ? globalThis.__openclawApiFiles : [];
const namespaceDescriptors = Array.isArray(globalThis.__openclawNamespaces) ? globalThis.__openclawNamespaces : [];
const hostRequest = globalThis.__openclawHostRequest;
const hostCancelRequest = globalThis.__openclawHostCancelRequest;
delete globalThis.__openclawHostRequest;
delete globalThis.__openclawHostCancelRequest;
delete globalThis.__openclawCatalog;
delete globalThis.__openclawApiFiles;
delete globalThis.__openclawNamespaces;
const bridgeSequences = new Map();
const timers = new Map();
// Keep rejection ownership in the snapshot so a handler attached after wait
// can clear it; an unawaited failure must not become a successful cell.
const unhandledRejections = new Map();
let nextTimerId = 0;
function safe(value) {
if (value === undefined) return null;
try {
return JSON.parse(JSON.stringify(value));
} catch {
if (value instanceof Error) {
return { name: value.name, message: value.message };
}
if (value === null) return null;
const type = typeof value;
if (type === "string" || type === "number" || type === "boolean") return value;
return String(value);
}
}
function asText(value) {
if (typeof value === "string") return value;
const encoded = JSON.stringify(safe(value));
return typeof encoded === "string" ? encoded : String(value);
}
function beginRequest(method, args, { queue = false } = {}) {
const methodName = String(method);
const sequence = (bridgeSequences.get(methodName) ?? 0) + 1;
bridgeSequences.set(methodName, sequence);
const id = "bridge:" + methodName + ":" + String(sequence);
const argsJson = JSON.stringify(safe(args ?? []));
let callbacks;
const promise = new Promise((resolve, reject) => { callbacks = { resolve, reject }; });
const admit = () => {
hostRequest(methodName, argsJson, id);
pending.set(id, callbacks);
};
if (queue && pending.size >= maxPending) queued.push(admit);
else admit();
return { id, promise };
}
// Swarm queues before admission; raw tools retain all-or-nothing overflow rejection.
// Closures and stable IDs live in the bounded VM snapshot, never a second host queue.
function drainQueuedRequests() {
while (queued.length > 0 && pending.size < maxPending) queued.shift()();
}
function request(method, args, options) {
return beginRequest(method, args, options).promise;
}
function scheduleTimer(callback, delay, args) {
if (typeof callback !== "function") {
throw new TypeError("setTimeout callback must be a function");
}
const numericDelay = Number(delay);
const delayMs = Number.isFinite(numericDelay) ? Math.max(0, Math.floor(numericDelay)) : 0;
const timerId = ++nextTimerId;
const timerRequest = beginRequest("sleep", [delayMs]);
timers.set(timerId, timerRequest.id);
void timerRequest.promise.then(() => {
if (!timers.delete(timerId)) return;
callback(...args);
});
return timerId;
}
function cancelTimer(timerId) {
const requestId = timers.get(Number(timerId));
if (!requestId) return;
timers.delete(Number(timerId));
hostCancelRequest(requestId);
const entry = pending.get(requestId);
if (!entry) return;
pending.delete(requestId);
entry.resolve(null);
drainQueuedRequests();
}
${CODE_MODE_SWARM_CONTROLLER_SOURCE}
function namespaceFunction(namespaceId, path) {
const callablePath = Object.freeze((Array.isArray(path) ? path : []).map((entry) => String(entry)));
return (...args) => request("namespace", [namespaceId, callablePath, args]);
}
function deserializeNamespaceValue(namespaceId, value) {
if (!value || typeof value !== "object") return null;
if (value.kind === "function") {
return namespaceFunction(namespaceId, Array.isArray(value.path) ? value.path.slice() : []);
}
if (value.kind === "array") {
return Object.freeze((Array.isArray(value.items) ? value.items : []).map((item) => deserializeNamespaceValue(namespaceId, item)));
}
if (value.kind === "object") {
const object = Object.create(null);
for (const entry of Array.isArray(value.entries) ? value.entries : []) {
const key = Array.isArray(entry) && typeof entry[0] === "string" ? entry[0] : "";
if (!key) continue;
Object.defineProperty(object, key, {
value: deserializeNamespaceValue(namespaceId, entry[1]),
enumerable: true,
});
}
return Object.freeze(object);
}
return safe(value.value);
}
function settle(id, ok, payload) {
const entry = pending.get(String(id));
if (!entry) return false;
pending.delete(String(id));
let parsed = null;
try {
parsed = JSON.parse(String(payload));
} catch {
parsed = String(payload);
}
if (ok) {
entry.resolve(parsed);
} else {
const error = new Error(typeof parsed === "string" ? parsed : parsed?.message ?? "nested tool failed");
entry.reject(error);
}
drainQueuedRequests();
return true;
}
function nodeHandle(descriptor) {
const handle = Object.create(null);
Object.defineProperties(handle, {
id: { value: descriptor.id, enumerable: true },
name: { value: descriptor.name, enumerable: true },
invoke: {
value: (command, params) => request("nodes", ["invoke", descriptor.id, command, params]),
enumerable: true,
},
});
if (typeof descriptor.listDirCommand === "string") {
Object.defineProperty(handle, "listDir", {
value: (path) => request("nodes", ["invoke", descriptor.id, descriptor.listDirCommand, { path }]),
enumerable: true,
});
}
return Object.freeze(handle);
}
const nodes = Object.freeze({
list: () => request("nodes", ["list"]),
get: async (idOrName) => nodeHandle(await request("nodes", ["get", idOrName])),
});
const skills = Object.freeze({
list: () => request("skillsList", []),
read: (name) => request("skillsRead", [name]),
});
if (globalThis.__openclawSwarmEnabled === true) {
Object.defineProperties(globalThis, {
agents: {
value: Object.freeze({ run: runAgent }),
enumerable: true,
},
phase: { value: (title) => swarmNote("phase", title), enumerable: true },
log: { value: (message) => swarmNote("log", message), enumerable: true },
});
}
function normalizeApiPath(value) {
const text = String(value ?? "").trim().replace(/^\/+/, "");
if (!text || text.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
throw new Error("invalid API file path");
}
return text;
}
const apiFileMap = new Map();
for (const file of apiFiles) {
if (!file || typeof file !== "object") continue;
const path = typeof file.path === "string" ? file.path : "";
const content = typeof file.content === "string" ? file.content : "";
if (!path || !content) continue;
apiFileMap.set(path, Object.freeze({
path,
content,
description: typeof file.description === "string" ? file.description : undefined,
bytes: file.bytes,
}));
}
const api = Object.freeze({
list: async (prefix = "") => {
// list takes a directory prefix, so tolerate a trailing slash (API.list("mcp/"))
// that read's exact-path normalizer would otherwise reject as an empty segment.
const rawPrefix = prefix == null ? "" : String(prefix).trim().replace(/\/+$/, "");
const normalizedPrefix = rawPrefix === "" ? "" : normalizeApiPath(rawPrefix);
const files = [...apiFileMap.values()]
.filter((file) => !normalizedPrefix || file.path === normalizedPrefix || file.path.startsWith(normalizedPrefix.replace(/\/?$/, "/")))
.map((file) => Object.freeze({
path: file.path,
description: file.description,
bytes: file.bytes,
}));
return { files };
},
read: async (path) => {
const normalizedPath = normalizeApiPath(path);
const file = apiFileMap.get(normalizedPath);
if (!file) throw new Error("Unknown API file: " + normalizedPath);
return file;
},
});
const callableHandles = new Map();
const callableMetadata = new WeakMap();
function callableHandle(binding) {
const callableName = typeof binding?.callableName === "string" ? binding.callableName : "";
if (!callableName) return null;
const existing = callableHandles.get(callableName);
if (existing) return existing;
const handle = (input) => request("callValue", [callableName, input]);
const metadata = Object.freeze({
callableName,
toolName: typeof binding.name === "string" ? binding.name : callableName,
label: typeof binding.label === "string" ? binding.label : undefined,
description: typeof binding.description === "string" ? binding.description : "",
source: binding.source,
input: binding.input,
output: binding.output,
});
for (const [key, value] of Object.entries(metadata)) {
Object.defineProperty(handle, key, { value, enumerable: true });
}
Object.defineProperties(handle, {
name: { value: callableName },
describe: { value: () => request("describe", [callableName]), enumerable: true },
toJSON: { value: () => metadata },
});
const frozen = Object.freeze(handle);
callableHandles.set(callableName, frozen);
callableMetadata.set(frozen, metadata);
return frozen;
}
// Final values may nest handles (Promise.all of searches, keyed maps); an
// unserialized handle dumps as null and the model never learns the tool name.
function serializeCatalogHandles(value, seen = new Set()) {
const metadata = callableMetadata.get(value);
if (metadata) return metadata;
if (value === null || typeof value !== "object" || seen.has(value)) return value;
const proto = Object.getPrototypeOf(value);
if (!Array.isArray(value) && proto !== Object.prototype && proto !== null) return value;
seen.add(value);
try {
if (Array.isArray(value)) return value.map((entry) => serializeCatalogHandles(entry, seen));
const plain = {};
for (const [key, entry] of Object.entries(value)) {
plain[key] = serializeCatalogHandles(entry, seen);
}
return plain;
} finally {
seen.delete(value);
}
}
const catalog = Object.freeze({
search: async (query, options) => {
const matches = await request("search", [query, options]);
return Object.freeze(matches.map((name) =>
callableHandles.get(String(name))
).filter(Boolean));
},
all: () => Object.freeze([...callableHandles.values()]),
});
const namespaceGlobals = Object.create(null);
for (const descriptor of namespaceDescriptors) {
const id = typeof descriptor?.id === "string" ? descriptor.id : "";
const globalName = typeof descriptor?.globalName === "string" ? descriptor.globalName : "";
if (!id || !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(globalName)) continue;
const scope = deserializeNamespaceValue(id, descriptor.scope);
Object.defineProperty(namespaceGlobals, globalName, {
value: scope,
enumerable: true,
});
const existingGlobal = Object.getOwnPropertyDescriptor(globalThis, globalName);
if (existingGlobal && existingGlobal.configurable === false) continue;
Object.defineProperty(globalThis, globalName, {
value: scope,
enumerable: true,
configurable: true,
});
}
for (const binding of catalogBindings) {
const handle = callableHandle(binding);
const callableName = typeof binding?.callableName === "string" ? binding.callableName : "";
if (!handle || !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(callableName)) continue;
Object.defineProperty(globalThis, callableName, {
value: handle,
enumerable: true,
configurable: true,
});
}
Object.defineProperties(globalThis, {
API: { value: api, enumerable: true },
catalog: { value: catalog, enumerable: true },
nodes: { value: nodes, enumerable: true },
namespaces: { value: Object.freeze(namespaceGlobals), enumerable: true },
skills: { value: skills, enumerable: true },
setTimeout: { value: (callback, delay, ...args) => scheduleTimer(callback, delay, args), enumerable: true },
clearTimeout: { value: cancelTimer, enumerable: true },
text: { value: (value) => output.push({ type: "text", text: asText(value) }), enumerable: true },
json: { value: (value) => output.push({ type: "json", value: safe(value) }), enumerable: true },
yield_control: { value: (reason) => request("yield", [reason]), enumerable: true },
__openclawSettleBridge: { value: settle },
__openclawSerializeCatalogHandles: { value: serializeCatalogHandles },
__openclawTakeOutput: { value: () => output.splice(0) },
__openclawTrackRejection: {
value: (promise, reason, handled) => {
if (handled) unhandledRejections.delete(promise);
else unhandledRejections.set(promise, reason);
},
},
__openclawUnhandledRejection: { value: () => unhandledRejections.keys().next().value },
});
})();
`;
//#endregion
//#region src/agents/code-mode-shell-source.ts
const JAVASCRIPT_EXPORT = /^export\s+(?:(?:abstract|as|async|class|const|declare|default|enum|function|import|interface|let|namespace|type|var)\b|[={*])/u;
const JAVASCRIPT_KEYWORD = /^(?:abstract|as|async|await|break|case|catch|class|const|continue|debugger|declare|default|delete|do|else|enum|export|extends|false|finally|for|function|if|implements|import|in|instanceof|interface|let|namespace|new|null|of|private|protected|public|return|satisfies|static|super|switch|this|throw|true|try|typeof|undefined|var|void|while|with|yield)$/u;
const JAVASCRIPT_GLOBAL = /^(?:API|MCP|AggregateError|Array|ArrayBuffer|BigInt|BigInt64Array|BigUint64Array|Boolean|DataView|Date|Error|EvalError|Float32Array|Float64Array|Function|Infinity|Int16Array|Int32Array|Int8Array|Intl|JSON|Map|Math|NaN|Number|Object|Promise|Proxy|RangeError|ReferenceError|Reflect|RegExp|Set|String|Symbol|SyntaxError|TypeError|URIError|Uint16Array|Uint32Array|Uint8Array|Uint8ClampedArray|WeakMap|WeakSet|catalog|clearTimeout|console|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|eval|globalThis|isFinite|isNaN|json|nodes|parseFloat|parseInt|setTimeout|skills|text|yield_control)$/u;
const SHELL_COMMAND = /^(?:\/(?:usr\/(?:local\/)?)?bin\/)?(alias|apt|awk|bash|bg|brew|builtin|bun|cargo|cat|cd|chmod|cmd|command|cp|curl|cut|date|declare|df|dir|docker|dotnet|du|echo|env|exec|exit|export|fg|file|find|getopts|git|go|gradle|grep|hash|head|help|hostname|id|java|javac|jobs|jq|kill|kubectl|ln|local|logout|ls|make|mkdir|mvn|mv|node|npm|npx|perl|php|pip|pip3|pnpm|poetry|popd|powershell|printf|ps|pushd|pwd|pwsh|pytest|python|python3|read|readonly|rg|rm|ruby|rustc|rustup|sed|set|sh|shift|sleep|sort|source|stat|sudo|swift|systemctl|tail|tar|tee|test|touch|trap|tree|type|ulimit|umask|uname|uniq|unset|unzip|uv|uvx|vitest|wait|wc|wget|which|whoami|xargs|yarn|zip|zsh)(?=$|[\s;&|<>])/u;
const SHELL_IDENTIFIER = /^([A-Za-z_][\w-]*)(?=$|[\s;&|<>])/u;
const SHELL_EXECUTABLE_PATH = /^(?:(?:\.{1,2}|~)[\\/]|\/|[A-Za-z]:[\\/])[^\s;|&()]+(?=$|[\t \r\n;&|])/u;
const SHELL_ARGUMENT = /^(?:-{1,2}[a-z\d][\w-]*(?:[\t =;&|]|$)|(?:(?:\.{1,2}|~)[\\/]|\/|[A-Za-z]:[\\/])[^\s]+)/iu;
const SHELL_ENV_ASSIGNMENT = /^[A-Za-z_]\w*=(?:"(?:\\.|[^"])*"|'[^']*'|\\.|[^\s;&|])*(?:[\t ]+|[\t ]*\r?\n[\t ]*)(?=\S)/u;
const SHELL_CONTROL = /^(?:(?:if|elif|while|until)[\t ]+(?:\[{1,2}(?=[\t ]|$)|test\b|[A-Za-z_][\w-]*(?=[\t ;]))|for[\t ]+(?:[A-Za-z_]\w*[\t ]+in\b|\(\([^\r\n]*\)\)[\t ]*;[\t ]*do\b)|case[\t ]+\S+[\t ]+in\b|function[\t ]+[A-Za-z_][\w-]*[\t ]*\{)/u;
const SHELL_REDIRECTION = /^(?:\d*(?:>>?|<<?)|&>>?)/u;
const LEADING_SOURCE_COMMENTS = /^(?:(?:\/\/[^\r\n]*(?:\r?\n|$)|\/\*[\s\S]*?\*\/|#[^\r\n]*(?:\r?\n|$))[\t \r\n]*)+/u;
function parsesAsGuestJavaScript(source, declaration = "") {
try {
return new Script(`(async () => {\n${declaration}${source}\n})`) instanceof Script;
} catch {
return false;
}
}
function hasHoistedGuestBinding(source, name) {
return !parsesAsGuestJavaScript(source, `let ${name};\n`) && parsesAsGuestJavaScript(source, `var ${name};\n`);
}
/** Reject recognizable shell commands without guessing at JavaScript expressions. */
function isShellLikeCodeModeSource(source, preparedSource = source) {
const trimmed = source.trim();
if (trimmed.startsWith("#!")) return true;
const uncommented = trimmed.replace(LEADING_SOURCE_COMMENTS, "");
if (!uncommented || JAVASCRIPT_EXPORT.test(uncommented)) return false;
if (SHELL_CONTROL.test(uncommented)) return true;
let commandSource = uncommented;
for (;;) {
const assignment = SHELL_ENV_ASSIGNMENT.exec(commandSource);
if (!assignment) break;
commandSource = commandSource.slice(assignment[0].length);
}
const knownCommand = SHELL_COMMAND.exec(commandSource);
const unknownCommand = SHELL_IDENTIFIER.exec(commandSource);
const command = knownCommand ?? (unknownCommand && !JAVASCRIPT_KEYWORD.test(unknownCommand[1] ?? "") && !JAVASCRIPT_GLOBAL.test(unknownCommand[1] ?? "") ? unknownCommand : null);
if (!command && !SHELL_EXECUTABLE_PATH.test(commandSource)) return false;
const commandTail = command ? commandSource.slice(command[0].length) : "";
const remainder = commandTail.trimStart();
if (command && !remainder) return knownCommand !== null;
if (!parsesAsGuestJavaScript(preparedSource)) return true;
const commandName = command?.[1];
if (commandName && hasHoistedGuestBinding(preparedSource, commandName)) return false;
if (/^[\t ]*(?:[;\r\n]|&&?|\|{1,2})/u.test(commandTail)) return knownCommand !== null;
if (!command || !SHELL_ARGUMENT.test(remainder) && !(knownCommand !== null && SHELL_REDIRECTION.test(remainder)) && !(commandName === "jq" && remainder.startsWith("."))) return false;
return true;
}
const CODE_MODE_SHELL_SOURCE_ERROR = "code-mode exec runs JavaScript or TypeScript, not shell commands. Call an enabled async tool global from guest JavaScript; use catalog.search(query) when the bounded quick index omits it. Do not retry the same shell command as code.";
//#endregion
//#region src/agents/code-mode-typescript-runtime.ts
const typescriptRuntimeLoader = createLazyPromiseLoader(() => import("typescript"), { cacheRejections: true });
function loadCodeModeTypeScriptRuntime() {
return typescriptRuntimeLoader.load();
}
//#endregion
//#region src/agents/code-mode-source.ts
/** Validate and transpile guest source in the execution worker. */
function maskCodeLiteralsAndComments(code, typescriptRuntime) {
let masked = code.split("");
const maskRange = (start, end, offset = 0) => {
for (let index = Math.max(start - offset, 0); index < Math.min(end - offset, masked.length); index += 1) if (masked[index] !== "\n" && masked[index] !== "\r") masked[index] = " ";
};
try {
const wrapped = buildCodeModeScriptParseSource(code);
parse(wrapped.source, {
ecmaVersion: "latest",
onComment: (_isBlock, _text, start, end) => maskRange(start, end, wrapped.codeOffset),
onToken: (token) => {
if (token.type.label === "string" || token.type.label === "regexp" || token.type.label === "template") maskRange(token.start, token.end, wrapped.codeOffset);
}
});
return masked.join("");
} catch {
masked = code.split("");
if (typescriptRuntime) try {
const sourceFile = typescriptRuntime.createSourceFile("code-mode.ts", code, typescriptRuntime.ScriptTarget.ES2022, true, typescriptRuntime.ScriptKind.TS);
const visit = (node) => {
typescriptRuntime.forEachLeadingCommentRange(code, node.getFullStart(), (start, end) => maskRange(start, end));
typescriptRuntime.forEachTrailingCommentRange(code, node.getEnd(), (start, end) => maskRange(start, end));
if (typescriptRuntime.isStringLiteralLike(node) || typescriptRuntime.isRegularExpressionLiteral(node) || typescriptRuntime.isTemplateHead(node) || typescriptRuntime.isTemplateMiddle(node) || typescriptRuntime.isTemplateTail(node)) maskRange(node.getStart(sourceFile), node.getEnd());
typescriptRuntime.forEachChild(node, visit);
};
visit(sourceFile);
return masked.join("");
} catch {
return code;
}
try {
for (const token of tokenizer(code, {
ecmaVersion: "latest",
onComment: (_isBlock, _text, start, end) => maskRange(start, end)
})) if (token.type.label === "string" || token.type.label === "template") maskRange(token.start, token.end);
return masked.join("");
} catch {
return code;
}
}
}
function isModuleLoaderCallee(callee) {
if (callee.type === "ParenthesizedExpression") return isModuleLoaderCallee(callee.expression);
if (callee.type === "ChainExpression") return isModuleLoaderCallee(callee.expression);
if (callee.type === "SequenceExpression") {
const expression = callee.expressions[callee.expressions.length - 1];
return expression !== void 0 && isModuleLoaderCallee(expression);
}
return callee.type === "Identifier" && callee.name === "require";
}
function containsModuleAccess(node) {
if (node.type === "ImportDeclaration" || node.type === "ImportExpression" || node.type === "MetaProperty" && node.meta.name === "import" || node.type === "CallExpression" && isModuleLoaderCallee(node.callee)) return true;
for (const value of Object.values(node)) {
if (Array.isArray(value)) {
for (const child of value) if (child !== null && typeof child === "object" && "type" in child && typeof child.type === "string" && containsModuleAccess(child)) return true;
continue;
}
if (value !== null && typeof value === "object" && "type" in value && typeof value.type === "string" && containsModuleAccess(value)) return true;
}
return false;
}
function typeScriptContainsModuleAccess(code, ts) {
const source = ts.createSourceFile("code-mode.ts", code, ts.ScriptTarget.ES2022, true, ts.ScriptKind.TS);
const isLoaderCallee = (expression) => {
if (ts.isParenthesizedExpression(expression)) return isLoaderCallee(expression.expression);
if (ts.isBinaryExpression(expression) && expression.operatorToken.kind === ts.SyntaxKind.CommaToken) return isLoaderCallee(expression.right);
return ts.isIdentifier(expression) && expression.text === "require";
};
const visit = (node) => {
if (ts.isImportDeclaration(node) || ts.isImportEqualsDeclaration(node) || ts.isMetaProperty(node) && node.keywordToken === ts.SyntaxKind.ImportKeyword || ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword || isLoaderCallee(node.expression))) return true;
return ts.forEachChild(node, (child) => visit(child) ? true : void 0) === true;
};
return visit(source);
}
function rejectsModuleAccess(code, typescriptRuntime) {
const parsed = parseCodeModeScriptSyntax(code);
if (parsed.ok) return containsModuleAccess(parsed.program);
if (typescriptRuntime) try {
return typeScriptContainsModuleAccess(code, typescriptRuntime);
} catch {}
const source = maskCodeLiteralsAndComments(code, typescriptRuntime);
return /\bimport\b\s*(?:\.|\(|["'`{*]|\w)|\brequire\b\s*\(/u.test(source);
}
async function prepareSource(input) {
const language = input.language ?? "javascript";
if (!input.config.languages.includes(language)) throw new ToolInputError(`code mode ${language} input is disabled.`);
if (language === "javascript") {
if (rejectsModuleAccess(input.code)) throw new ToolInputError("code mode module access is disabled.");
if (isShellLikeCodeModeSource(input.code)) throw new ToolInputError(CODE_MODE_SHELL_SOURCE_ERROR);
return input.code;
}
const ts = await loadCodeModeTypeScriptRuntime();
if (rejectsModuleAccess(input.code, ts)) throw new ToolInputError("code mode module access is disabled.");
const transformed = ts.transpileModule(input.code, {
compilerOptions: {
target: ts.ScriptTarget.ES2022,
module: ts.ModuleKind.ESNext,
importsNotUsedAsValues: ts.ImportsNotUsedAsValues.Remove,
sourceMap: false
},
reportDiagnostics: true
});
const diagnostics = transformed.diagnostics ?? [];
if (diagnostics.some((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error)) {
const message = diagnostics.map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")).join("\n");
throw new ToolInputError(`typescript transform failed: ${message}`);
}
if (rejectsModuleAccess(transformed.outputText, ts)) throw new ToolInputError("code mode module access is disabled.");
if (isShellLikeCodeModeSource(input.code, transformed.outputText) || isShellLikeCodeModeSource(transformed.outputText)) throw new ToolInputError(CODE_MODE_SHELL_SOURCE_ERROR);
return transformed.outputText;
}
//#endregion
//#region src/agents/code-mode.worker.ts
/**
* QuickJS worker for Code Mode guest execution and suspended VM snapshots.
*/
var CodeModeWorkerFailure = class extends Error {
constructor(code, message) {
super(message);
this.name = "CodeModeWorkerFailure";
this.code = code;
}
};
function isQuickJsInterruptedError(error) {
return error instanceof JSException && error.message === "interrupted";
}
function formatQuickJsError(name, message, stack) {
const header = message ? `${name}: ${message}` : name;
if (!stack || stack.split(/\r?\n/, 1)[0] === header) return header;
return `${header}\n${stack}`;
}
function errorMessage(error) {
if (error instanceof JSException) return formatQuickJsError(error.name, error.message, error.stack);
if (error instanceof Error) return error.message || String(error);
return String(error);
}
function buildUserSource(code) {
return `globalThis.__openclawResult = (async () => {\n${code}\n})()`;
}
function trackPromiseRejection(promise, reason, handled) {
const vm = promise.vm;
vm.global.getProp("__openclawTrackRejection").consume((track) => vm.callFunction(track, vm.undefined, promise, reason, handled ? vm.true : vm.false).dispose());
}
function createHostRequestHandler(params) {
return (methodHandle, argsHandle, bridgeIdHandle) => {
if (params.bridge.pendingRequests.length >= params.config.maxPendingToolCalls) {
params.bridge.admissionFailure ??= new CodeModeWorkerFailure("invalid_input", "too many pending code mode tool calls");
throw params.bridge.admissionFailure;
}
const method = methodHandle.toString();
if (method !== "search" && method !== "describe" && method !== "callValue" && method !== "nodes" && method !== "yield" && method !== "namespace" && method !== "agentSpawn" && method !== "agentWait" && method !== "skillsList" && method !== "skillsRead" && method !== "sleep" && method !== "swarmNote") throw new Error("unsupported code mode bridge method");
let args;
try {
args = JSON.parse(argsHandle.toString());
} catch {
args = [];
}
const id = bridgeIdHandle?.toString();
if (!id?.startsWith(`bridge:${method}:`) || !/^bridge:[A-Za-z]+:[1-9]\d*$/u.test(id)) throw new Error("invalid code mode bridge id");
if (params.bridge.pendingRequests.some((request) => request.id === id)) throw new Error("duplicate code mode bridge id");
params.bridge.pendingRequests.push({
id,
method,
args: Array.isArray(args) ? args : []
});
return params.vm.newString(id);
};
}
function createHostCancelRequestHandler(params) {
return (idHandle) => {
const id = idHandle.toString();
const index = params.bridge.pendingRequests.findIndex((request) => request.id === id);
if (index >= 0) {
params.bridge.pendingRequests.splice(index, 1);
params.bridge.canceledRequestIds.push(id);
}
return params.vm.undefined;
};
}
async function createVm(input, bridge) {
const startedAt = performance.now();
let timedOut = false;
const deadlineReached = () => performance.now() - startedAt >= input.config.timeoutMs;
const options = {
wasm: input.wasmModule,
memoryLimit: input.config.memoryLimitBytes,
timezoneOffset: 0,
onUnhandledRejection: trackPromiseRejection,
interruptHandler: () => {
timedOut = deadlineReached();
return timedOut;
}
};
const vm = input.kind === "resume" ? await QuickJS.restore(input.snapshot, options) : await QuickJS.create(options);
try {
const callbacks = [["__openclawHostRequest", createHostRequestHandler({
vm,
bridge,
config: input.config
})], ["__openclawHostCancelRequest", createHostCancelRequestHandler({
vm,
bridge
})]];
for (const [name, callback] of callbacks) if (input.kind === "resume") vm.registerHostCallback(name, callback);
else vm.newFunction(name, callback).consume((handle) => vm.global.setProp(name, handle));
if (input.kind === "exec") {
for (const [name, value] of [
["__openclawCatalog", input.catalog],
["__openclawNamespaces", input.namespaces],
["__openclawApiFiles", input.apiFiles ?? []],
["__openclawSwarmEnabled", input.swarmEnabled === true],
["__openclawMaxPendingToolCalls", input.config.maxPendingToolCalls]
]) vm.hostToHandle(value).consume((handle) => vm.global.setProp(name, handle));
vm.evalCode(CODE_MODE_CONTROLLER_SOURCE, "openclaw-code-mode:controller.js").dispose();
}
return {
vm,
didTimeout: () => timedOut || deadlineReached()
};
} catch (error) {
vm.dispose();
throw error;
}
}
function takeOutput(vm) {
return vm.global.getProp("__openclawTakeOutput").consume((take) => vm.callFunction(take, vm.undefined).consume((output) => {
const dumped = vm.dump(output);
return Array.isArray(dumped) ? dumped : [];
}));
}
function takeOutputSafely(vm) {
try {
return takeOutput(vm);
} catch {
return [];
}
}
function captureWorkerResult(result, config) {
const output = captureCodeModeOutput(result.output, config.maxOutputBytes);
if (result.status === "completed") return {
...result,
output,
value: captureCodeModeValue(result.value, config.maxOutputBytes)
};
return result.status === "failed" ? {
...result,
output,
error: boundCodeModeError(result.error, config.maxOutputBytes)
} : {
...result,
output
};
}
function failedWorkerResult(code, error, output = []) {
return {
status: "failed",
code,
error,
failurePhase: code === "invalid_input" ? "input" : "guest",
bridgeDispatchStarted: false,
output
};
}
function workerFailureResult(params) {
const timedOut = params.didTimeout() || isQuickJsInterruptedError(params.error);
const output = params.output.length > 0 ? params.output : takeOutputSafely(params.vm);
if (timedOut) return failedWorkerResult("timeout", "code mode timeout exceeded", output);
if (params.error instanceof CodeModeWorkerFailure) return failedWorkerResult(params.error.code, params.error.message, output);
if (output.length > 0) return failedWorkerResult("internal_error", errorMessage(params.error), output);
throw params.error;
}
async function readCompletedResult(vm, resultHandle) {
if (!resultHandle.isPromise) return serializeCompletedCatalogHandles(vm, resultHandle);
const settled = await vm.resolvePromise(resultHandle);
if ("error" in settled) return settled.error.consume((error) => {
const dumped = vm.dump(error);
if (dumped instanceof Error && dumped.name === "ReferenceError" && /^(?:require|module|process) is not defined$/u.test(dumped.message)) throw new CodeModeWorkerFailure("invalid_input", "code mode module access is disabled.");
const text = dumped instanceof Error ? formatQuickJsError(dumped.name, dumped.message, dumped.stack) : errorMessage(dumped);
throw new Error(text);
});
return settled.value.consume((value) => serializeCompletedCatalogHandles(vm, value));
}
function serializeCompletedCatalogHandles(vm, value) {
return vm.global.getProp("__openclawSerializeCatalogHandles").consume((serialize) => vm.callFunction(serialize, vm.undefined, value).consume((serialized) => vm.dump(serialized)));
}
function waitingResult(params) {
const snapshot = params.vm.snapshot();
const metadata = QuickJS.serializeSnapshot({
...snapshot,
memory: /* @__PURE__ */ new Uint8Array()
});
if (snapshot.memory.byteLength + metadata.byteLength > params.config.maxSnapshotBytes) throw new CodeModeWorkerFailure("snapshot_limit_exceeded", "code mode snapshot limit exceeded");
return {
status: "waiting",
snapshot,
pendingRequests: params.bridge.pendingRequests,
canceledRequestIds: params.bridge.canceledRequestIds,
settlementMode: params.settlementMode,
output: params.output
};
}
async function runVmExecution(params) {
let output = [];
try {
params.prepare();
params.vm.executePendingJobs();
if (params.bridge.admissionFailure) throw params.bridge.admissionFailure;
output = takeOutput(params.vm);
const resultHandle = params.vm.global.getProp("__openclawResult");
try {
try {
var _usingCtx$1 = _usingCtx();
const promisePending = resultHandle.isPromise && resultHandle.promiseState === 0;
if (promisePending && params.bridge.pendingRequests.length === 0) throw new Error("code mode promise is pending without host work");
const requiredPendingRequestIds = params.bridge.pendingRequests.map((request) => request.id);
if (promisePending || requiredPendingRequestIds.length > 0) return waitingResult({
vm: params.vm,
bridge: params.bridge,
settlementMode: promisePending ? { kind: "awaiting" } : {
kind: "draining",
requiredRequestIds: requiredPendingRequestIds
},
output,
config: params.config
});
const value = await readCompletedResult(params.vm, resultHandle);
const rejection = _usingCtx$1.u(params.vm.global.getProp("__openclawUnhandledRejection").consume((read) => params.vm.callFunction(read, params.vm.undefined)));
await readCompletedResult(params.vm, rejection);
return {
status: "completed",
value,
output
};
} catch (_) {
_usingCtx$1.e = _;
} finally {
_usingCtx$1.d();
}
} finally {
resultHandle.dispose();
}
} catch (error) {
return workerFailureResult({
error,
didTimeout: params.didTimeout,
output,
vm: params.vm
});
} finally {
params.vm.dispose();
}
}
async function run(input) {
const startedAt = performance.now();
const source = input.kind === "exec" ? await prepareSource({
code: input.source,
language: input.language,
config: input.config
}) : "";
const config = {
...input.config,
timeoutMs: Math.min(input.config.timeoutMs - (performance.now() - startedAt), input.kind === "exec" ? input.executionTimeoutMs ?? Infinity : Infinity)
};
if (config.timeoutMs <= 0) throw new CodeModeWorkerFailure("timeout", "code mode timeout exceeded");
const bridge = {
pendingRequests: input.kind === "resume" ? [...input.pendingRequests ?? []] : [],
canceledRequestIds: []
};
const { vm, didTimeout } = await createVm({
...input,
config
}, bridge);
return runVmExecution({
vm,
didTimeout,
bridge,
config,
prepare: () => {
if (input.kind === "exec") {
vm.evalCode(buildUserSource(`${input.prelude ?? ""}${source}`), "openclaw-code-mode:user.js", EvalFlags.ASYNC).dispose();
return;
}
vm.global.getProp("__openclawSettleBridge").consume((settle) => {
for (const request of input.settledRequests) {
const id = vm.newString(request.id);
const payload = vm.newString(JSON.stringify(request.ok ? request.value : request.error));
try {
vm.callFunction(settle, vm.undefined, id, request.ok ? vm.true : vm.false, payload).dispose();
} finally {
id.dispose();
payload.dispose();
}
}
});
}
});
}
function isQuickJsWasmModule(value) {
return Object.prototype.toString.call(value) === "[object WebAssembly.Module]";
}
async function main(input) {
if (!isRecord(input) || !isRecord(input.config) || !isQuickJsWasmModule(input.wasmModule)) return {
...failedWorkerResult("invalid_input", "invalid code mode worker input"),
output: EMPTY_CODE_MODE_OUTPUT
};
const config = input.config;
try {
if (config.timeoutMs <= 0) throw new CodeModeWorkerFailure("timeout", "code mode timeout exceeded");
if (input.kind === "exec" && typeof input.source === "string") return captureWorkerResult(await run({
kind: "exec",
wasmModule: input.wasmModule,
source: input.source,
language: input.language,
prelude: typeof input.prelude === "string" ? input.prelude : void 0,
executionTimeoutMs: typeof input.executionTimeoutMs === "number" ? input.executionTimeoutMs : void 0,
config,
catalog: Array.isArray(input.catalog) ? input.catalog : [],
apiFiles: Array.isArray(input.apiFiles) ? input.apiFiles : [],
namespaces: Array.isArray(input.namespaces) ? input.namespaces : [],
swarmEnabled: input.swarmEnabled === true
}), config);
const snapshot = input.snapshot;
if (input.kind === "resume" && snapshot?.memory instanceof Uint8Array) return captureWorkerResult(await run({
kind: "resume",
wasmModule: input.wasmModule,
snapshot,
config,
settledRequests: Array.isArray(input.settledRequests) ? input.settledRequests : [],
pendingRequests: Array.isArray(input.pendingRequests) ? input.pendingRequests : []
}), config);
return {
...failedWorkerResult("invalid_input", "invalid code mode worker input"),
output: EMPTY_CODE_MODE_OUTPUT
};
} catch (error) {
const timedOut = isQuickJsInterruptedError(error);
return captureWorkerResult(failedWorkerResult(timedOut ? "timeout" : error instanceof CodeModeWorkerFailure ? error.code : error instanceof ToolInputError ? "invalid_input" : "internal_error", timedOut ? "code mode timeout exceeded" : errorMessage(error)), config);
}
}
serveWorkerTasks(main, { transferList: (result) => result.status === "waiting" ? [result.snapshot.memory.buffer] : [] });
//#endregion
export {};