Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175import type { SkillWorkspace } from "agents/skills";import * as Effect from "effect/Effect";import { agentValidation } from "./agent-io";import type { SkillExecutionGrant } from "./skill-execution-permissions";
export interface SkillWorkspaceFile { path: string; content: string;}
const encoder = new TextEncoder();const maxFiles = 8;const maxFileBytes = 16 * 1024;const maxTotalBytes = 64 * 1024;
function filePath(path: string): string { if ( typeof path !== "string" || path.length > 240 || !path .split("/") .every((part) => /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/.test(part)) ) throw new Error( "Workspace paths must be nonhidden portable relative paths.", ); return path;}
function validateFile( path: string, content: string, files: Map<string, string>,) { filePath(path); if ( typeof content !== "string" || content.length > maxFileBytes || encoder.encode(content).byteLength > maxFileBytes ) throw new Error( "Workspace files must contain at most 16 KiB of UTF-8 text.", ); if (!files.has(path) && files.size >= maxFiles) throw new Error("Workspace supports at most 8 files."); let total = encoder.encode(content).byteLength; for (const [existing, value] of files) { if (existing.startsWith(`${path}/`) || path.startsWith(`${existing}/`)) throw new Error("Workspace file and directory paths must not overlap."); if (existing !== path) total += encoder.encode(value).byteLength; } if (total > maxTotalBytes) throw new Error("Workspace supports at most 64 KiB of UTF-8 text.");}
/** An invocation-local mount. It never discovers or writes the Agent workspace. */export class SkillScriptWorkspace implements SkillWorkspace { private readonly files = new Map<string, string>(); private readonly changed = new Set<string>();
constructor( private readonly grant: SkillExecutionGrant, files: readonly SkillWorkspaceFile[] = [], ) { if (!Array.isArray(files) || files.length > maxFiles) throw new Error("Workspace supports at most 8 input files."); if (files.length) grant.check("workspaceRead"); for (const file of files) { if (!file || typeof file !== "object") throw new Error("Invalid workspace file."); validateFile(file.path, file.content, this.files); this.files.set(file.path, file.content); } if (files.length) grant.check("workspaceRead"); }
private read<A>(operation: () => A): Promise<A> { return Effect.runPromise( this.grant.use("workspaceRead", () => agentValidation(operation)), ); }
readFile(path: string): Promise<string | null> { return this.read(() => this.files.get(filePath(path)) ?? null); }
stat(path: string): Promise<{ type: string; size?: number } | null> { return this.read(() => { if (path === ".") path = ""; if (path !== "") filePath(path); const content = this.files.get(path); if (content !== undefined) return { type: "file", size: encoder.encode(content).byteLength }; if ( path === "" || [...this.files.keys()].some((name) => name.startsWith(`${path}/`)) ) return { type: "directory" }; return null; }); }
/** Empty path or native listFiles' default `.` denotes this mount's root only. */ readDir( path: string, ): Promise<{ name: string; type: "file" | "directory" }[]> { return this.read(() => { if (path === ".") path = ""; if (path !== "") filePath(path); const prefix = path ? `${path}/` : ""; const entries = new Map<string, "file" | "directory">(); for (const name of this.files.keys()) { if (!name.startsWith(prefix)) continue; const parts = name.slice(prefix.length).split("/"); entries.set(parts[0]!, parts.length > 1 ? "directory" : "file"); } return [...entries] .sort(([a], [b]) => a.localeCompare(b)) .map(([name, type]) => ({ name, type })); }); }
/** Exact file paths or one segment-local `*`; recursive and other glob syntax is rejected. */ glob(pattern: string): Promise<string[]> { return this.read(() => { if ( typeof pattern !== "string" || (pattern.match(/\*/g)?.length ?? 0) > 1 ) throw new Error( "Workspace glob supports exact paths or one nonrecursive *.", ); filePath(pattern.replace("*", "x")); const expression = new RegExp( `^${pattern.replace(/[.]/g, "\\.").replace("*", "[^/]*")}$`, ); return [...this.files.keys()] .filter((path) => expression.test(path)) .sort(); }); }
writeFile(path: string, content: string): Promise<void> { return Effect.runPromise( this.grant.use("workspaceWrite", () => agentValidation(() => { // No await separates the budget check from committing the local mutation. validateFile(path, content, this.files); this.grant.check("workspaceWrite"); const previous = this.files.get(path); this.files.set(path, content); try { this.grant.check("workspaceWrite"); } catch (error) { if (previous === undefined) this.files.delete(path); else this.files.set(path, previous); throw error; } this.changed.add(path); }), ), ); }
/** Trusted host output only; original mounted files are never echoed implicitly. */ snapshot(): SkillWorkspaceFile[] { this.grant.check("execute"); const output = [...this.changed] .sort() .map((path) => ({ path, content: this.files.get(path)! })); this.grant.check("execute"); return output; }}