/** Sandboxed guest globals and host bridge for Code Mode QuickJS cells. */ import { CODE_MODE_SWARM_CONTROLLER_SOURCE } from "./code-mode-swarm-controller-source.js"; export const CODE_MODE_CONTROLLER_SOURCE = String.raw` (() => { const output = []; const pending = new Map(); const catalog = 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; delete globalThis.__openclawHostRequest; const bridgeSequences = new Map(); 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 request(method, args) { const methodName = String(method); const sequence = (bridgeSequences.get(methodName) ?? 0) + 1; bridgeSequences.set(methodName, sequence); const bridgeId = "bridge:" + methodName + ":" + String(sequence); const id = String(hostRequest(methodName, JSON.stringify(safe(args ?? [])), bridgeId)); return new Promise((resolve, reject) => { pending.set(id, { resolve, reject }); }); } ${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); } 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 baseTools = Object.create(null); Object.defineProperties(baseTools, { search: { value: (query, options) => request("search", [query, options]), enumerable: true }, describe: { value: (id) => request("describe", [id]), enumerable: true }, call: { value: (id, input) => request("call", [id, input]), enumerable: true }, callValue: { value: (id, input) => request("callValue", [id, input]), enumerable: true }, }); 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 safeNameCounts = new Map(); for (const tool of catalog) { const name = typeof tool?.name === "string" ? tool.name : ""; if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) continue; safeNameCounts.set(name, (safeNameCounts.get(name) ?? 0) + 1); } for (const tool of catalog) { const name = typeof tool?.name === "string" ? tool.name : ""; const id = typeof tool?.id === "string" ? tool.id : ""; if (!id || safeNameCounts.get(name) !== 1 || Object.prototype.hasOwnProperty.call(baseTools, name)) { continue; } Object.defineProperty(baseTools, name, { value: (input) => request("callValue", [id, input]), enumerable: true, }); } 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, }); } Object.defineProperties(globalThis, { ALL_TOOLS: { value: Object.freeze(catalog.slice()), enumerable: true }, API: { value: api, enumerable: true }, nodes: { value: nodes, enumerable: true }, namespaces: { value: Object.freeze(namespaceGlobals), enumerable: true }, skills: { value: skills, enumerable: true }, tools: { value: Object.freeze(baseTools), 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 }, __openclawTakeOutput: { value: () => output.splice(0) }, }); })(); `;