import 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, ) { 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(); private readonly changed = new Set(); 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(operation: () => A): Promise { return Effect.runPromise( this.grant.use("workspaceRead", () => agentValidation(operation)), ); } readFile(path: string): Promise { 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(); 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 { 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 { 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; } }