PedelecWorkspace API
PedelecWorkspace is the application-facing capability for one Workspace filesystem. Obtain it from pedelec.openWorkspace(), session.workspace, or resumeSession().workspace.
import type { PedelecWorkspace, WorkspaceRunResult } from "@kaoruisaac/pedelec";readonly path: string | nullAn explicit Workspace exposes its validated custom path. A Desktop-managed Workspace intentionally exposes null; applications must not infer or display its physical managed directory.
Open a Workspace and create Sessions
Section titled “Open a Workspace and create Sessions”const workspace = await pedelec.openWorkspace();if (!workspace) return; // the user cancelled the native picker
const session = await workspace.createSession({ model: "gpt-5.6-sol", effort: "high",});Pass an absolute path to skip the picker:
const workspace = await pedelec.openWorkspace("C:\\workspace\\project-a");const first = await workspace.createSession();const second = await workspace.createSession();One Workspace can own multiple Sessions. Session creation through this handle retains the same Workspace capability. pedelec.createSession() is the managed convenience flow and does not accept Workspace configuration; its returned session.workspace is still available.
listFiles(path?)
Section titled “listFiles(path?)”listFiles(path?: string): Promise<string[]>Recursively lists regular files below the Workspace-relative path. An omitted path means the Workspace root. Results use / separators, are Workspace-relative, and are sorted lexicographically. .pedelec-runtime is included. Symlinks and junctions are neither followed nor returned.
The result is returned as one response and is not silently truncated. A large result rejects with WORKSPACE_LIST_TOO_LARGE; retry with a narrower path.
listFolders(path?)
Section titled “listFolders(path?)”listFolders(path?: string): Promise<string[]>Has the same root, relative path, separator, sorting, .pedelec-runtime, symlink/junction, and oversized-response semantics as listFiles(), but returns directories.
uploadFile()
Section titled “uploadFile()”uploadFile(file: File): Promise<string>uploadFile(file: File, targetPath: string): Promise<string>Uploads one browser File into this Workspace. Paths are Workspace-relative, use /, and do not start with /.
const workspace = await pedelec.openWorkspace("C:\\workspace\\project");const file = new File(["hello"], "hello.txt", { type: "text/plain" });
const path = await workspace.uploadFile(file);// "hello.txt" at the Workspace root, with no generated prefix
await workspace.uploadFile(file, "references/photo.png");Without targetPath, the returned path is the original file.name placed at the Workspace root. An explicit targetPath is returned unchanged, and missing parent directories are created. The limit is 100 MiB. Uploads use the local transfer server and a one-time ticket; several uploadFile() calls may run at the same time. Pedelec does not lock the Workspace or resolve conflicts when concurrent writers choose the same path. The application owns that coordination. A successful upload replaces an existing regular file. Absolute paths, .., symlinks, and junctions are rejected.
readFile()
Section titled “readFile()”readFile(path: string, type: "text"): Promise<string>readFile<T = JsonValue>(path: string, type: "json"): Promise<T>readFile(path: string, type: "file"): Promise<File>Reads one Workspace-relative regular file, including paths under .pedelec-runtime. text requires strict UTF-8. json parses that text. file returns a browser File whose name is the target basename, whose type comes from the extension, and whose lastModified is the filesystem modification time. Reads are also limited to 100 MiB. A file that changes or is truncated during the read fails instead of returning partial bytes.
uploadAsset(), readAsset(), and listAssets() remain Session-scoped conveniences rooted at .pedelec-runtime/assets. Use uploadFile(), readFile(), and listFiles() for general Workspace filesystem access.
run(script, options?)
Section titled “run(script, options?)”For usage, start with Workspace Runtime Execution. The exact API contract follows.
run( script: string, options?: { timeoutMs?: number; runtimeModules?: readonly RuntimeModuleDefinition[]; },): Promise<WorkspaceRunResult>Runs application-supplied TypeScript code in this Workspace. The default timeout is 60 seconds; an override must be a positive integer.
const result = await workspace.run( "console.log(JSON.stringify({ ok: true }))", { timeoutMs: 60_000 },);
if (result.stdoutTruncated || result.stderrTruncated) { throw new Error("Workspace script output was truncated");}const value = JSON.parse(result.stdout);The runtimeModules option mounts Vite-prepared modules for this run. A Workspace-only declaration only needs the logical import name and build-time entry:
const sceneTools = defineRuntimeModule({ name: "scene-tools", entry: "./deno/scene-tools.ts",});
const result = await workspace.run( `import { buildScene } from "scene-tools";\nawait buildScene();`, { runtimeModules: [sceneTools] },);The Vite plugin must prepare each artifact before workspace.run() is called. entry is resolved only by Vite and is never sent to Desktop/Core. Each run receives an import map containing exactly the listed module names; it does not inherit modules from a Session or other Workspace run. Prepared packages are cached privately per Workspace and authorized SDK origin, so later runs can reuse ready modules without uploading the artifact again. A ready logical name is immutable for the lifetime of the Core process; use a new name for a different implementation.
The result is raw Deno output: exitCode, stdout, stderr, stdoutTruncated, and stderrTruncated. A non-zero exitCode resolves normally; the application decides how to interpret it and parses stdout itself when it wants a function-like value. An oversized browser response rejects with WORKSPACE_RUN_OUTPUT_TOO_LARGE.
Security boundary
Section titled “Security boundary”workspace.run() is a direct capability for the approved Web application. By default its TypeScript code has read and write access to the Workspace, but has no network, host environment, subprocess execution, FFI, or sys permission. In Sandbox, no-remote, cached-only, and no-npm runtime restrictions continue to apply. This default mode is not unrestricted host execution and is not an Agent-mediated action.
Session skills.runtimeModules remain owned by the Session/Thread that declared them and are available to Agent-side pedelec-ts-runtime execution for that Thread. Workspace-run modules are a separate, explicit per-run capability: workspace.run() does not inherit a Session import map, and workspace.createSession() does not inherit modules previously used by workspace.run(). Workspace-run modules never appear in Agent Host Context. Core resets the private Workspace-run cache when its runtime restarts; project files, assets, and Thread module snapshots are not cleared.
Concurrency and lifetime
Section titled “Concurrency and lifetime”Multiple Sessions, Agent/provider operations, and workspace.run() executions may operate concurrently in the same Workspace. A second turn on the same Session is still rejected while that Session is busy. Pedelec does not provide filesystem-level locking, snapshots, transactions, or conflict resolution between concurrent Workspace actors. Applications are responsible for coordinating shared file access when required.
Custom Workspaces are opened explicitly, can outlive and be shared by multiple Sessions, and are never deleted by Pedelec when a Session ends or during app cleanup. Managed Workspaces are created by Pedelec.createSession() and remain under Desktop cleanup ownership; Thread end does not imply immediate deletion. The browser capability is runtime-scoped, so reopen a custom Workspace after a Desktop/Core restart when needed.
resumeSession(sessionId) returns a Session that already includes session.workspace; do not separately call openWorkspace() for a resumed Session. Same-handle session.resume() preserves its existing Workspace handle, subject to the documented transport, Core thread, and recorded Workspace caveats.
workspace.run() defaults to Sandbox. A Full Instance with both Extension grants can opt in per invocation using { runtimeFullAccess: true }; no Session is required. Supplying both runtimeModules and legacy denoModules rejects with INVALID_INPUT.