UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

909 lines (885 loc) 41.1 kB
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 {};