@humanlayer/agentlayer-yjs-fs-secure-exec
v0.0.81
Published
Bridges [`@humanlayer/yjs-fs`](../yjs-fs) (a Y.js CRDT filesystem) into [`secure-exec`](https://github.com/rivet-dev/secure-exec)'s sandboxed Node runtime, and exposes it as an `agentlayer-core` tool. Code the agent runs inside the sandbox reads/writes th
Readme
agentlayer-yjs-fs-secure-exec
Bridges @humanlayer/yjs-fs (a Y.js CRDT filesystem) into secure-exec's sandboxed Node runtime, and exposes it as an agentlayer-core tool. Code the agent runs inside the sandbox reads/writes through node:fs as usual, but every operation is actually applied to the collaborative Y.js document — so filesystem edits made by sandboxed code stay in sync with other collaborators/editors.
Install
bun add @humanlayer/agentlayer-yjs-fs-secure-exec @humanlayer/yjs-fs secure-exec@humanlayer/yjs-fs and secure-exec are peer dependencies.
Usage
import { YjsFilesystem } from '@humanlayer/yjs-fs'
import { createYjsFsSecureExecTool } from '@humanlayer/agentlayer-yjs-fs-secure-exec/tools'
const fs = new YjsFilesystem()
fs.createFile('/input.txt', 'hello')
const tool = createYjsFsSecureExecTool(fs)
const raw = await tool.execute(
{
code: `import { readFileSync, writeFileSync } from 'node:fs'
writeFileSync('/output.txt', readFileSync('/input.txt', 'utf8') + ' world')
export const ok = true`,
filePath: '/entry.mjs',
},
{} as never,
)
fs.readFile('/output.txt') // 'hello world'
if ('operations' in raw) raw.operations // [{ type: 'read', path: '/input.txt', ... }, { type: 'write', path: '/output.txt', ... }]Wire the tool into an Agent (from @humanlayer/agentlayer-core) as tools: { secure_exec: tool }.
Key exports
createYjsFsSecureExecTool(fs, opts?)(from./tools) — builds thesecure_execdefineTooltool. Input is{ code, filePath? }(filePathdefaults to/entry.mjs); output is{ output, operations }, whereoutputis the truncated JSON-stringifiedsecure-execrun result andoperationsis the list of filesystem ops the code performed.createYjsFsRuntime(fs, opts?)(from./runtime/ root) — lower-level helper that builds asecure-execNodeRuntimewired to aYjsFsSecureExecAdapter. Returns{ runtime, adapter }. Options:permissions(merged overallowAllFs),enableNetwork(turns onsecure-exec's network adapter +allowAllNetwork),loopbackExemptPorts,moduleAccessCwd(exposes a host directory'snode_modulesread-only for module resolution inside the sandbox).YjsFsSecureExecAdapter(from./adapter/ root) — implementssecure-exec'sVirtualFileSysteminterface on top of aYjsFilesystem. Records every call (read/write/list/mkdir/delete/rename/truncate) as aYjsFsSecureExecOperation; drain them withadapter.consumeOperations().symlink/readlink/link/chmod/chownthrowENOTSUPsinceYjsFilesystemdoesn't model them.createYjsFsSecureExecPresenceHooks(fs, opts?)(from./hooks) — apostToolUsehook (forAgent'shooks.postToolUse) that publishes the most relevant tracked operation tofs's Y.js awareness/presence (currentFile,action,secureExecOperation(s)) and briefly selects the touched file's full text range, fading the selection afterselectionFadeMs(default 5000ms).
How it fits together
flowchart LR
Agent -- "secure_exec(code, filePath)" --> Tool["createYjsFsSecureExecTool"]
Tool --> Runtime["secure-exec NodeRuntime"]
Runtime -- "node:fs calls" --> Adapter["YjsFsSecureExecAdapter"]
Adapter --> YFS["YjsFilesystem (Y.js doc)"]
Adapter -- "recorded operations" --> Hooks["createYjsFsSecureExecPresenceHooks"]
Hooks -- "awareness/presence" --> YFSTests
test/adapter.test.ts covers the VirtualFileSystem mapping directly; test/runtime.test.ts covers running real code through createYjsFsRuntime/createYjsFsSecureExecTool (including network access via enableNetwork and host node_modules overlay via moduleAccessCwd); test/presence-hooks.test.ts exercises the hook end-to-end via a full Agent run.
