Codelet logoCodelet

Workspace Tools

The workspace's own files as tools any agent in the workbench can call — read, list, search, write, edit and open — plus a command left running in a shell of its own.

workspace-tools registers ten tools: seven over the files the reader is looking at, and three over a command that isn't meant to end. It draws nothing — what it adds shows up wherever tools are offered, in a conversation's tool picker, in whatever your chat participant calls, and in the browser's own agent if you also mount webmcp.

import { Workbench, FileSystem } from "codelet/workbench";
import { workspaceTools } from "codelet/extensions/workspace-tools";
import { chat } from "codelet/extensions/chat";
import { piAgent } from "codelet/extensions/pi-agent";

new Workbench({
  parent: document.getElementById("app")!,
  fs: new FileSystem({ "/index.ts": "export const hello = 1;\n" }),
  extensions: [chat, piAgent(), workspaceTools()],
});

#The tools

NameWhat it does
workspace_readA file, as the reader sees it — unsaved edits included — with numbered lines and a window into a long one.
workspace_listEvery file and directory, from the root or from a path, to a depth.
workspace_searchCase-insensitive text across the workspace, narrowed by a glob. The same search the Search view runs.
workspace_writeA whole file, created or replaced.
workspace_editExact text replaced in place, refusing anything that names more than one spot unless told otherwise.
workspace_openShows a file to the reader, at a line. Changes nothing.
workspace_activeWhat the reader is looking at: the file, the caret, the selection, the open tabs.

#A command that doesn't end

A shell tool waits for the line it runs — pi's bash does — so a dev server or a watcher would take the conversation's one shell away for every call after it. These three are the way round that:

NameWhat it does
bash_backgroundRuns a command in a shell of its own and answers with an id, plus whatever it printed in a second.
bash_outputWhat it has printed since the last read, and whether it's still running. filter is a regex.
bash_killStops it and closes its tab.

The command keeps running after the answer that started it, in a terminal tab the reader can watch and stop — a dev server they go on using is worth more than one that dies with the answer. Nothing else ends it: closing the tab does, and so does stopping this extension.

It needs a shell, which is another extension's: mount bash (and terminal to see the tab). A page with two shells names the one it means:

workspaceTools({ shell: "bash.shell" });

#What a write does

A write goes into the same buffer the editor types into, so the change lands in the tab the reader is in — their cursor stays where it was, and the squiggles and decorations move with the text. Then it's saved, so the file holds it.

The one exception is a file that already has unsaved changes in it: the write lands, the tab keeps its dot, and saving is left to the reader. Committing someone's half-finished edit isn't something a tool call asked for.

Note

workspace_write, workspace_edit and bash_background ask before they run. Turning that off is the chat.tools.autoApprove setting, or the Chat: Toggle Tool Confirmations command — the same switch pi-agent's own tools read.

#Alongside pi

pi-agent brings its own read, edit, write and bash inside its loop. Mounting both is fine and common: pi keeps its four, and everything else — another participant, a .vsix, a browser agent — gets these. Mount this one if you want the workbench's files reachable by whatever is answering, rather than only by pi.

The three background tools used to be pi-agent's, which is why pi's prompt talks about them: it writes that section only where they're registered, so a page mounting pi alone tells the model not to start a server rather than pointing it at a tool that isn't there.

#What it doesn't do

  • No delete or rename. Those are gestures the reader has in the tree, and a page has no bin to take a deleted directory out of.
  • No waiting on a command. A command that finishes belongs to whatever loop is answering — pi's bash does that. What's here is the case a waiting tool can't cover, and it needs bash mounted either way.