UNPKG

agents

Version:

A home for your AI agents

912 lines (891 loc) • 32.5 kB
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../classPrivateFieldGet2-DZBYAB34.js"; import { t as _classPrivateMethodInitSpec } from "../classPrivateMethodInitSpec-qMjJ6sHQ.js"; import { i as extensionOf, o as parseSkillResourcePath } from "../resource-B59OV2a2.js"; import { t as formatSkill } from "../markdown-BZ-VLtA1.js"; import { RpcTarget } from "cloudflare:workers"; import { DynamicWorkerExecutor, resolveProvider } from "@cloudflare/codemode"; import { Bash, defineCommand } from "just-bash"; //#region src/skills/script-workspace.ts /** Directory skill scripts and relative paths resolve against. */ const WORKSPACE_ROOT = "/workspace"; const TEXT_ENCODER = new TextEncoder(); const TEXT_DECODER = new TextDecoder(); /** * @internal Adapt a Workspace for skill scripts. Relative paths resolve * against `/workspace`, and paths outside `/workspace` are rejected. */ function createSkillWorkspaceAccess(workspace) { const { fs } = workspace; const access = { async readFile(path) { try { return TEXT_DECODER.decode(await drain(await fs.readFile(resolvePath(path)))); } catch (error) { if (isNotFound(error)) return null; throw error; } }, async listFiles(path) { const directory = resolvePath(path); return (await fs.readdir(directory)).map((entry) => ({ path: `${directory.replace(/\/$/, "")}/${entry.name}`, name: entry.name, type: entry.isDirectory ? "directory" : "file", size: entry.size })); }, async glob(pattern) { const segments = resolvePath(pattern).split("/").filter(Boolean); const firstGlob = segments.findIndex((part) => /[*?[\]{}]/.test(part)); if (firstGlob === -1) { const path = `/${segments.join("/")}`; const found = await access.stat(path); return found === null ? [] : [{ path, ...found }]; } let matches; try { matches = await fs.find(`/${segments.slice(0, firstGlob).join("/")}`, segments.slice(firstGlob).join("/")); } catch (error) { if (isNotFound(error)) return []; throw error; } return Promise.all(matches.filter((match) => isInsideRoot(match.path)).map(async (match) => match.type === "dir" ? { path: match.path, type: "directory", size: 0 } : { path: match.path, type: "file", size: (await fs.stat(match.path)).size })); }, async stat(path) { try { const info = await fs.stat(resolvePath(path)); return { type: info.isDirectory ? "directory" : "file", size: info.size }; } catch (error) { if (isNotFound(error)) return null; throw error; } }, async writeFile(path, content) { const absolute = resolvePath(path); const parent = absolute.slice(0, absolute.lastIndexOf("/")); if (parent) await fs.mkdir(parent, { recursive: true }); await fs.writeFile(absolute, typeof content === "string" ? TEXT_ENCODER.encode(content) : content); } }; return access; } function isInsideRoot(path) { return path === WORKSPACE_ROOT || path.startsWith(`${WORKSPACE_ROOT}/`); } /** A script asked for a path outside `/workspace`. */ var SkillWorkspacePathError = class extends Error { constructor(path) { super(`Skill scripts can only access paths under ${WORKSPACE_ROOT}: ${path}`); this.path = path; this._tag = "SkillWorkspacePathError"; this.name = "SkillWorkspacePathError"; } }; /** * Resolve a script-supplied path to a normalized absolute path under * `/workspace`. Relative paths resolve against `/workspace`; `.` and `..` * are applied before the check, so `../x` or `/workspace/../x` cannot reach * the rest of the Workspace filesystem. * * @throws SkillWorkspacePathError when the path resolves outside * `/workspace`. The host bridge reports it to the script as a failed call. */ function resolvePath(path) { if (path.includes("\0")) throw new SkillWorkspacePathError(path); const absolute = path.startsWith("/") ? path : `${WORKSPACE_ROOT}/${path}`; const parts = []; for (const part of absolute.split("/")) { if (part === "" || part === ".") continue; if (part === "..") { parts.pop(); continue; } parts.push(part); } const resolved = `/${parts.join("/")}`; if (!isInsideRoot(resolved)) throw new SkillWorkspacePathError(path); return resolved; } function isNotFound(error) { if (!error || typeof error !== "object") return false; const record = error; return record.code === "ENOENT" || typeof record.message === "string" && /ENOENT|no such file|not found/i.test(record.message); } async function drain(stream) { const parts = []; let total = 0; const reader = stream.getReader(); try { while (true) { const { value, done } = await reader.read(); if (done) break; if (!value || value.byteLength === 0) continue; parts.push(value); total += value.byteLength; } } finally { reader.releaseLock(); } if (parts.length === 1) return parts[0]; const bytes = new Uint8Array(total); let offset = 0; for (const part of parts) { bytes.set(part, offset); offset += part.byteLength; } return bytes; } //#endregion //#region src/skills/runner.ts const DEFAULT_SCRIPT_TIMEOUT_MS = 3e4; const MAX_OUTPUT_ARTIFACT_BYTES = 64e3; const MAX_OUTPUT_ARTIFACTS = 20; const SUPPORTED_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([ ".js", ".mjs", ".ts", ".tsx", ".py", ".sh", ".bash" ]); let runnerExperimentalWarned = false; function effectiveTimeout(options) { return options.timeout ?? DEFAULT_SCRIPT_TIMEOUT_MS; } function effectiveWorkspaceAccess(options) { if (options.workspace) return options.workspace; return options.workspaceInstance ? "read" : "none"; } function parseSkillScriptPath(path) { if (!path.startsWith("scripts/")) return { ok: false, error: `Skill script path must start with "scripts/": ${path}` }; if (!parseSkillResourcePath(path).ok) return { ok: false, error: `Skill script path must be a normalized relative path under "scripts/": ${path}` }; const extension = extensionOf(path); if (!SUPPORTED_SCRIPT_EXTENSIONS.has(extension)) return { ok: false, error: `Unsupported skill script extension "${extension || "(none)"}" for ${path}. Supported extensions: ${[...SUPPORTED_SCRIPT_EXTENSIONS].join(", ")}` }; if (extension === ".sh" || extension === ".bash") return { ok: true, runtime: "bash" }; if (extension === ".py") return { ok: true, runtime: "python" }; if (extension === ".ts" || extension === ".tsx") return { ok: true, runtime: "typescript" }; return { ok: true, runtime: "javascript" }; } function validateMountedResourcePaths(request) { for (const resource of request.resources ?? []) { const path = parseSkillResourcePath(resource.path); if (!path.ok) throw path.error; } } function skillScriptContext(request) { return { skill: { name: request.skill.name, description: request.skill.description, ...request.skill.license === void 0 ? {} : { license: request.skill.license }, ...request.skill.compatibility === void 0 ? {} : { compatibility: request.skill.compatibility }, metadata: request.skill.metadata, allowedTools: request.skill.allowedTools } }; } /** * Text bundled resources exposed to function-style JS/TS scripts via * `ctx.files`. Binary resources are omitted in v1. */ function textFilesMap(request) { const files = {}; for (const resource of request.resources ?? []) if ((resource.encoding ?? "text") === "text") files[resource.path] = resource.content; return files; } function base64ToBytes(value) { const binary = atob(value); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); return bytes; } function stdinText(stdin) { return typeof stdin === "string" ? stdin : String(stdin ?? ""); } function mountedFiles(request) { const files = { "/input.json": { content: JSON.stringify(request.input), encoding: "text" }, "/context.json": { content: JSON.stringify(skillScriptContext(request)), encoding: "text" }, "/skill/SKILL.md": { content: formatSkill(request.skill), encoding: "text" } }; for (const resource of request.resources ?? []) { const path = parseSkillResourcePath(resource.path); if (!path.ok) throw path.error; files[`/skill/${resource.path}`] = { content: resource.content, encoding: resource.encoding ?? "text" }; } files[`/skill/${request.path}`] = { content: request.source, encoding: "text" }; return files; } function bashFiles(request) { const files = { "/input.json": JSON.stringify(request.input), "/context.json": JSON.stringify(skillScriptContext(request)), "/skill-script.sh": request.source, "/skill/SKILL.md": formatSkill(request.skill), [`/skill/${request.path}`]: request.source }; for (const resource of request.resources ?? []) { const path = parseSkillResourcePath(resource.path); if (!path.ok) throw path.error; files[`/skill/${resource.path}`] = (resource.encoding ?? "text") === "base64" ? base64ToBytes(resource.content) : resource.content; } return files; } /** * Wrap a function-style JS/TS skill module so it runs inside the codemode * sandbox. The module must `export default` an async `run(input, ctx)`; we * rewrite the default export to a local binding, build the `ctx` capability * object from the host bridge proxy (`__host`), and invoke it. */ function scriptModule(source, request) { const runnableSource = stripStrayExports(source.replace(/^\s*export\s+default\s+/m, "const __skillRun = ")); const skillMeta = skillScriptContext(request).skill; return [ "async () => {", ` const input = ${JSON.stringify(request.input)};`, ` const __skill = ${JSON.stringify(skillMeta)};`, ` const __files = ${JSON.stringify(textFilesMap(request))};`, " const workspace = {", " readFile: (path) => __host.readFile(path),", " listFiles: (path = \".\") => __host.listFiles(path),", " glob: (pattern) => __host.glob(pattern),", " stat: (path) => __host.stat(path),", " writeFile: (path, content) => __host.writeFile({ path, content })", " };", " const tools = new Proxy(", " { call: (name, input) => __host.callTool({ name, input }) },", " {", " get: (target, prop) =>", " prop in target", " ? target[prop]", " : (input) => __host.callTool({ name: String(prop), input })", " }", " );", " const output = {", " writeFile: (name, content) => __host.writeOutput({ name, content })", " };", " const ctx = { skill: __skill, files: __files, workspace, tools, output };", "", runnableSource, "", " if (typeof __skillRun !== \"function\") {", " throw new Error(\"Skill script default export must be a function (input, ctx).\");", " }", " return await __skillRun(input, ctx);", "}" ].join("\n"); } /** * Whether a script declares a default export, in either the raw author form * (`export default ...`) or the bundled form esbuild emits * (`export { run as default }`). */ function hasDefaultExport(source) { return /^\s*export\s+default\s+/m.test(source) || /export\s*\{[^}]*\bas\s+default\b[^}]*\}/m.test(source); } /** * Remove `export { ... }` blocks, which are illegal inside the function wrapper * the runner builds around skill scripts. */ function stripStrayExports(source) { return source.replace(/\n?export\s*\{[\s\S]*?\};?/g, ""); } function rewriteBundledSource(source) { const defaultBinding = source.match(/\bexport\s*\{[^}]*\b([A-Za-z_$][\w$]*)\s+as\s+default\b[^}]*\}/m); const stripped = stripStrayExports(source); if (defaultBinding) return `${stripped}\nconst __skillRun = ${defaultBinding[1]};`; return stripped; } const RELATIVE_IMPORT = /(?:\bimport\s[^;]*?\bfrom\s*|\bimport\s*\(\s*|\bimport\s+)["']\.\.?\//; /** * Skill scripts run directly in the sandbox; there is no in-Worker bundler. * A script must be one self-contained JavaScript module: TypeScript and * scripts that import sibling files must be compiled before they are stored. * esbuild's `export { run as default }` output is normalized into the * `__skillRun` binding the wrapper expects. */ function prepareJavaScriptSource(request, runtime) { if (runtime === "javascript" && !RELATIVE_IMPORT.test(request.source)) return /^\s*export\s+default\s+/m.test(request.source) ? request.source : rewriteBundledSource(request.source); throw new Error(`Skill script "${request.path}" must be compiled to a self-contained JavaScript module before it can run. Bundle TypeScript or multi-file scripts (for example with esbuild) before storing the skill.`); } async function executeToolFromSet(tools, name, input) { const target = tools?.[name]; const execute = target && "execute" in target ? target.execute : void 0; if (!execute) throw new Error(`Tool not available: ${name}`); return execute(input); } function stringifyHostResult(result) { return JSON.stringify({ result }); } function stringifyHostError(error) { return JSON.stringify({ error: error instanceof Error ? error.message : String(error) }); } var _tools = /* @__PURE__ */ new WeakMap(); var _workspace = /* @__PURE__ */ new WeakMap(); var _workspaceAccess = /* @__PURE__ */ new WeakMap(); var _outputs = /* @__PURE__ */ new WeakMap(); var _SkillScriptHostBridge_brand = /* @__PURE__ */ new WeakSet(); /** * The single source of truth for skill-script capabilities and permission * enforcement. Every runtime delegates here: * * - JavaScript/TypeScript reach it through a codemode `ToolProvider`. * - Python receives it as an RPC `RpcTarget` (JSON-marshalling methods). * - Bash calls it from `just-bash` custom commands. * * Construct a fresh bridge per `run()` so the per-invocation `/output` * artifact buffer is never shared between concurrent script runs. */ var SkillScriptHostBridge = class extends RpcTarget { constructor(tools, workspace, workspaceAccess) { super(); _classPrivateMethodInitSpec(this, _SkillScriptHostBridge_brand); _classPrivateFieldInitSpec(this, _tools, void 0); _classPrivateFieldInitSpec(this, _workspace, void 0); _classPrivateFieldInitSpec(this, _workspaceAccess, void 0); _classPrivateFieldInitSpec(this, _outputs, /* @__PURE__ */ new Map()); _classPrivateFieldSet2(_tools, this, tools); _classPrivateFieldSet2(_workspace, this, workspace === void 0 ? void 0 : createSkillWorkspaceAccess(workspace)); _classPrivateFieldSet2(_workspaceAccess, this, workspaceAccess); } get workspaceAccess() { return _classPrivateFieldGet2(_workspaceAccess, this); } hasTools() { return Boolean(_classPrivateFieldGet2(_tools, this) && Object.keys(_classPrivateFieldGet2(_tools, this)).length > 0); } async callTool(name, input) { return executeToolFromSet(_classPrivateFieldGet2(_tools, this), name, input); } async readFile(path) { return _assertClassBrand(_SkillScriptHostBridge_brand, this, _requireWorkspace).call(this, "read").readFile(path); } async listFiles(path = ".") { return _assertClassBrand(_SkillScriptHostBridge_brand, this, _requireWorkspace).call(this, "read").listFiles(path); } async glob(pattern) { return _assertClassBrand(_SkillScriptHostBridge_brand, this, _requireWorkspace).call(this, "read").glob(pattern); } async stat(path) { return _assertClassBrand(_SkillScriptHostBridge_brand, this, _requireWorkspace).call(this, "read").stat(path); } async writeFile(path, content) { await _assertClassBrand(_SkillScriptHostBridge_brand, this, _requireWorkspace).call(this, "read-write").writeFile(path, content); } writeOutput(name, content) { const key = String(name); const text = typeof content === "string" ? content : String(content ?? ""); if (new TextEncoder().encode(text).byteLength > MAX_OUTPUT_ARTIFACT_BYTES) throw new Error(`Output artifact "${key}" exceeds ${MAX_OUTPUT_ARTIFACT_BYTES} bytes.`); if (!_classPrivateFieldGet2(_outputs, this).has(key) && _classPrivateFieldGet2(_outputs, this).size >= MAX_OUTPUT_ARTIFACTS) throw new Error(`Too many skill output artifacts (max ${MAX_OUTPUT_ARTIFACTS}).`); _classPrivateFieldGet2(_outputs, this).set(key, { path: key, encoding: "text", content: text }); } getOutputFiles() { return [..._classPrivateFieldGet2(_outputs, this).values()]; } async tool(name, inputJson = "{}") { try { const input = inputJson.trim() ? JSON.parse(inputJson) : {}; return stringifyHostResult(await this.callTool(name, input)); } catch (error) { return stringifyHostError(error); } } async workspaceReadFile(path) { try { return stringifyHostResult(await this.readFile(path)); } catch (error) { return stringifyHostError(error); } } async workspaceListFiles(path = ".") { try { return stringifyHostResult(await this.listFiles(path)); } catch (error) { return stringifyHostError(error); } } async workspaceGlob(pattern) { try { return stringifyHostResult(await this.glob(pattern)); } catch (error) { return stringifyHostError(error); } } async workspaceWriteFile(path, content) { try { await this.writeFile(path, content); return stringifyHostResult(null); } catch (error) { return stringifyHostError(error); } } }; function _requireWorkspace(access) { if (!_classPrivateFieldGet2(_workspace, this) || _classPrivateFieldGet2(_workspaceAccess, this) === "none") throw new Error("Workspace access is not available."); if (access === "read-write" && _classPrivateFieldGet2(_workspaceAccess, this) !== "read-write") throw new Error("Workspace write access is not available."); return _classPrivateFieldGet2(_workspace, this); } /** * Expose the bridge to JS/TS scripts as a single codemode provider namespace * (`__host`). The sandbox `ctx` object wraps these calls into the friendly * `workspace` / `tools` / `output` surface (see {@link scriptModule}). */ function hostProvider(bridge) { return { name: "__host", tools: { callTool: { execute: (a) => { const { name, input } = a; return bridge.callTool(name, input); } }, readFile: { execute: (a) => bridge.readFile(String(a)) }, listFiles: { execute: (a) => bridge.listFiles(typeof a === "string" ? a : ".") }, glob: { execute: (a) => bridge.glob(String(a)) }, stat: { execute: (a) => bridge.stat(String(a)) }, writeFile: { execute: (a) => { const { path, content } = a; return bridge.writeFile(path, content); } }, writeOutput: { execute: async (a) => { const { name, content } = a; bridge.writeOutput(name, content); return null; } } } }; } function pythonScriptModule(request) { const source = request.source; const sourceLiteral = JSON.stringify(source); const filesLiteral = JSON.stringify(mountedFiles(request)); return String.raw` import asyncio import base64 import contextlib import inspect import io import json import os import sys import time import types from js import Object from pyodide.ffi import to_js as pyodide_to_js from workers import WorkerEntrypoint SKILL_SOURCE = ${sourceLiteral} SKILL_FILES = ${filesLiteral} async def maybe_await(value): if inspect.isawaitable(value): return await value return value async def decode_host_response(raw): data = json.loads(str(raw)) if "error" in data: raise Exception(data["error"]) return data.get("result") def to_js(obj): return pyodide_to_js(obj, dict_converter=Object.fromEntries) def materialize_files(): os.makedirs("/output", exist_ok=True) for path, file in SKILL_FILES.items(): directory = os.path.dirname(path) if directory: os.makedirs(directory, exist_ok=True) mode = "wb" if file.get("encoding") == "base64" else "w" with open(path, mode) as handle: if file.get("encoding") == "base64": handle.write(base64.b64decode(file.get("content", ""))) else: handle.write(file.get("content", "")) def collect_output_files(): output_files = [] if not os.path.isdir("/output"): return output_files for root, _dirs, files in os.walk("/output"): for name in sorted(files): path = os.path.join(root, name) with open(path, "rb") as handle: content = handle.read() if len(content) > ${MAX_OUTPUT_ARTIFACT_BYTES}: raise Exception(f"Output artifact exceeds ${MAX_OUTPUT_ARTIFACT_BYTES} bytes: {path}") try: output_files.append({ "path": path, "encoding": "text", "content": content.decode("utf-8") }) except UnicodeDecodeError: output_files.append({ "path": path, "encoding": "base64", "content": base64.b64encode(content).decode("ascii") }) return sorted(output_files, key=lambda file: file["path"]) def looks_function_style(source): return "def run(" in source or "async def run(" in source def timeout_trace(deadline): def trace(frame, event, arg): if time.monotonic() > deadline: raise TimeoutError("Python script execution timed out") return trace return trace class ToolNamespace: def __init__(self, host): self.host = host async def call(self, name, input=None): raw = await self.host.tool(name, json.dumps(input if input is not None else {})) return await decode_host_response(raw) def __getattr__(self, name): async def call_tool(input=None): return await self.call(name, input) return call_tool class WorkspaceNamespace: def __init__(self, host): self.host = host async def read_file(self, path): raw = await self.host.workspaceReadFile(path) return await decode_host_response(raw) async def list_files(self, path="."): raw = await self.host.workspaceListFiles(path) return await decode_host_response(raw) async def glob(self, pattern): raw = await self.host.workspaceGlob(pattern) return await decode_host_response(raw) async def write_file(self, path, content): raw = await self.host.workspaceWriteFile(path, content) return await decode_host_response(raw) class Default(WorkerEntrypoint): async def evaluate(self, input, ctx, host, timeout_ms=None): materialize_files() try: if looks_function_style(SKILL_SOURCE): skill_module = types.ModuleType("skill_script") skill_module.tools = ToolNamespace(host) skill_module.workspace = WorkspaceNamespace(host) exec(SKILL_SOURCE, skill_module.__dict__) if not hasattr(skill_module, "run") or not callable(skill_module.run): raise Exception("Python function-style skill script must define a callable run(input, ctx).") execution = maybe_await(skill_module.run(input, ctx)) previous_trace = sys.gettrace() if timeout_ms is not None: sys.settrace(timeout_trace(time.monotonic() + (timeout_ms / 1000))) try: if timeout_ms is not None: result = await asyncio.wait_for(execution, timeout_ms / 1000) else: result = await execution finally: sys.settrace(previous_trace) return to_js({ "result": result, "logs": [], "mode": "function", "outputFiles": collect_output_files() }) stdout = io.StringIO() stderr = io.StringIO() previous_stdin = sys.stdin previous_trace = sys.gettrace() if timeout_ms is not None: sys.settrace(timeout_trace(time.monotonic() + (timeout_ms / 1000))) sys.stdin = io.StringIO(json.dumps(input)) try: namespace = {"__name__": "__main__", "__file__": "/skill/script.py"} with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr): exec(SKILL_SOURCE, namespace) finally: sys.stdin = previous_stdin sys.settrace(previous_trace) return to_js({ "result": { "stdout": stdout.getvalue(), "stderr": stderr.getvalue(), "exitCode": 0 }, "logs": [], "mode": "cli", "outputFiles": collect_output_files() }) except TimeoutError: return to_js({"error": "Python script execution timed out", "logs": []}) except SystemExit as err: return to_js({ "result": { "stdout": stdout.getvalue() if "stdout" in locals() else "", "stderr": stderr.getvalue() if "stderr" in locals() else "", "exitCode": int(err.code) if isinstance(err.code, int) else 1 }, "logs": [], "mode": "cli", "outputFiles": collect_output_files() }) except asyncio.TimeoutError: return to_js({"error": "Python script execution timed out", "logs": []}) except Exception as err: return to_js({"error": str(err), "logs": []}) `; } async function runPythonScript(request, options, bridge) { const execution = options.loader.get(`skill-python-${crypto.randomUUID()}`, () => ({ compatibilityDate: "2026-05-23", compatibilityFlags: ["python_workers", "disable_python_external_sdk"], mainModule: "skill_runner.py", modules: { "skill_runner.py": pythonScriptModule(request) }, globalOutbound: options.network ? void 0 : null })).getEntrypoint().evaluate(request.input, skillScriptContext(request), bridge, effectiveTimeout(options)); let timeout = null; const timeoutPromise = new Promise((_, reject) => { timeout = setTimeout(() => reject(/* @__PURE__ */ new Error("Python script execution timed out")), effectiveTimeout(options)); }); try { const response = await Promise.race([execution, timeoutPromise]); if (response.error) throw new Error(response.error); const outputFiles = response.outputFiles ?? []; if (response.mode === "cli") { if (typeof response.result === "object" && response.result !== null && outputFiles.length > 0) return { ...response.result, outputFiles }; return response.result; } if (response.logs?.length || outputFiles.length > 0) return { result: response.result, ...response.logs?.length ? { logs: response.logs } : {}, ...outputFiles.length > 0 ? { outputFiles } : {} }; return response.result; } finally { if (timeout) clearTimeout(timeout); } } async function runBashScript(request, options, bridge) { const customCommands = []; if (bridge.workspaceAccess !== "none") { customCommands.push(defineCommand("workspace-read", async (args) => { const path = args[0]; if (!path) return { stdout: "", stderr: "Missing path\n", exitCode: 2 }; try { return { stdout: await bridge.readFile(path) ?? "", stderr: "", exitCode: 0 }; } catch (error) { return { stdout: "", stderr: `${error instanceof Error ? error.message : String(error)}\n`, exitCode: 1 }; } }), defineCommand("workspace-list", async (args) => { const path = args[0] ?? "."; try { return { stdout: JSON.stringify(await bridge.listFiles(path)) + "\n", stderr: "", exitCode: 0 }; } catch (error) { return { stdout: "", stderr: `${error instanceof Error ? error.message : String(error)}\n`, exitCode: 1 }; } }), defineCommand("workspace-glob", async (args) => { const pattern = args[0]; if (!pattern) return { stdout: "", stderr: "Missing pattern\n", exitCode: 2 }; try { return { stdout: JSON.stringify(await bridge.glob(pattern)) + "\n", stderr: "", exitCode: 0 }; } catch (error) { return { stdout: "", stderr: `${error instanceof Error ? error.message : String(error)}\n`, exitCode: 1 }; } })); if (bridge.workspaceAccess === "read-write") customCommands.push(defineCommand("workspace-write", async (args, ctx) => { const path = args[0]; if (!path) return { stdout: "", stderr: "Missing path\n", exitCode: 2 }; try { await bridge.writeFile(path, stdinText(ctx.stdin)); return { stdout: "", stderr: "", exitCode: 0 }; } catch (error) { return { stdout: "", stderr: `${error instanceof Error ? error.message : String(error)}\n`, exitCode: 1 }; } })); } if (bridge.hasTools()) customCommands.push(defineCommand("tool", async (args, ctx) => { const name = args[0]; if (!name) return { stdout: "", stderr: "Missing tool name\n", exitCode: 2 }; try { const rawInput = args[1] ?? stdinText(ctx.stdin) ?? "{}"; const input = rawInput.trim() ? JSON.parse(rawInput) : {}; const result = await bridge.callTool(name, input); return { stdout: JSON.stringify(result) + "\n", stderr: "", exitCode: 0 }; } catch (error) { return { stdout: "", stderr: `${error instanceof Error ? error.message : String(error)}\n`, exitCode: 1 }; } })); const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), effectiveTimeout(options)); try { const result = await new Bash({ files: bashFiles(request), customCommands, defenseInDepth: true, network: options.network ? {} : void 0 }).exec("bash /skill-script.sh", { signal: controller.signal, stdin: JSON.stringify(request.input) }); return { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode }; } finally { if (timeout) clearTimeout(timeout); } } async function runJavaScriptScript(request, options, bridge, runtime) { if (!hasDefaultExport(request.source)) throw new Error("JS/TS skill scripts must `export default` an async run(input, ctx) function."); const source = prepareJavaScriptSource(request, runtime); const result = await new DynamicWorkerExecutor({ loader: options.loader, timeout: effectiveTimeout(options), globalOutbound: options.network ? void 0 : null }).execute(scriptModule(source, request), [resolveProvider(hostProvider(bridge))]); if (result.error) { const logs = result.logs?.length ? `\n\nConsole output:\n${result.logs.join("\n")}` : ""; throw new Error(`${result.error}${logs}`); } const outputFiles = bridge.getOutputFiles(); if (result.logs?.length || outputFiles.length > 0) return { result: result.result, ...result.logs?.length ? { logs: result.logs } : {}, ...outputFiles.length > 0 ? { outputFiles } : {} }; return result.result; } /** * Create a skill script runner backed by a Worker Loader. * * Capabilities are opt-in and enforced by a single host bridge: no network and * no tools by default, read-only workspace access when `workspaceInstance` is * provided. JS/TS scripts are function-style (`export default run(input, ctx)`) * and receive `ctx = { skill, files, workspace, tools, output }`. Python and * Bash use the path-based `/skill`, `/input.json`, `/output` contract. * * @experimental Skill script execution is experimental and may change before * stabilizing. */ function runner(options) { return { async run(request) { if (!runnerExperimentalWarned) { runnerExperimentalWarned = true; console.warn("[think] skills.runner script execution is experimental; the API and capabilities may change."); } const tools = typeof options.tools === "function" ? await options.tools() : options.tools; const validation = parseSkillScriptPath(request.path); if (!validation.ok) throw new Error(validation.error); validateMountedResourcePaths(request); const bridge = new SkillScriptHostBridge(tools, options.workspaceInstance, effectiveWorkspaceAccess(options)); if (validation.runtime === "bash") return await runBashScript(request, options, bridge); if (validation.runtime === "python") return await runPythonScript(request, options, bridge); return await runJavaScriptScript(request, options, bridge, validation.runtime); } }; } //#endregion export { runner }; //# sourceMappingURL=runner.js.map