agents
Version:
A home for your AI agents
912 lines (891 loc) • 32.5 kB
JavaScript
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