@luan.sh/pi-code-mode
Restricted JavaScript tool composition for Pi
Package details
Install @luan.sh/pi-code-mode from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@luan.sh/pi-code-mode- Package
@luan.sh/pi-code-mode- Version
0.3.9- Published
- Sep 12, 2026
- Downloads
- not available
- Author
- cfcluan
- License
- MIT
- Types
- extension
- Size
- 27.5 MB
- Dependencies
- 2 dependencies · 3 peers
Pi manifest JSON
{
"image": "https://pi.luan.sh/media/previews/pi-code-mode-1fb9bf32cd5a.png",
"video": "https://pi.luan.sh/media/previews/pi-code-mode-90738a72b416.mp4",
"extensions": [
"./src/extension.ts",
"./node_modules/@luan.sh/pi-libtui/src/extension.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
@luan.sh/pi-code-mode
Code Mode adds two tools to Pi:
execruns restricted JavaScript that calls selected Pi tools through atoolsobject, so one model turn can compose several tool calls.waitcollects more output from a runningexeccell or terminates it.
Tools moved under exec are removed from Pi's direct tool list, which keeps
the model's tool catalog small. Other Pi extensions can make their tools
available inside exec through the SDK described below.
Preview

Install
pi install npm:@luan.sh/pi-code-mode
Requires a Rust toolchain (https://rustup.rs). The code-mode-host binary
builds itself on first use under Pi's agent directory
(native/code-mode-host/<version>/). Set PI_CODE_MODE_HOST_BINARY to use a
prebuilt binary.
Optional companion: pi install npm:@luan.sh/pi-xsettings adds the
/xsettings editor for the settings listed below; without it the defaults
apply.
How the tool hierarchy works
Code Mode is the only component that decides whether a tool is direct or lives
under exec. At session start it lifts a tool only when all of these hold:
pi-code-mode.enabledis true.execis active. A strict--toolslist must includeexec.- The tool is active, registered, selected in
pi-code-mode.tools, and has a Code Mode adapter (registered through the SDK).
Lifted tools disappear from Pi's direct tool list and become methods on
tools inside exec. They are never available in both places. An adapter
only describes how to invoke a tool; it cannot move the tool, change Code Mode
settings, or create another hierarchy.
If Code Mode is disabled, it removes exec and wait from the active set and
lifts nothing. If exec is not active, it removes the unusable wait tool and
leaves other tools alone. Session-tree navigation stops live cells without
changing the hierarchy; session shutdown releases it.
Settings
Settings use the @luan.sh/pi-code-mode namespace and are edited with /xsettings when
@luan.sh/pi-xsettings is installed; otherwise the defaults apply.
| Key | Default | Meaning |
|---|---|---|
enabled |
true |
Move selected direct tools under exec. |
tools |
skill, web__run, exec_command, write_stdin, apply_patch, view_image (each only if an adapter exists) |
Unordered multi-select of tools available only through exec. |
defaultOutputTokens |
10000 |
Aggregate output token budget. Choices: 1000, 2500, 5000, 10000, 20000, 50000, 100000. |
defaultExecYieldMs |
30000 |
Initial exec yield time. Choices: 1000, 5000, 10000, 30000, 60000. |
defaultWaitYieldMs |
10000 |
Default wait yield time. Same choices. |
The tools picker lists every registered tool that has a Code Mode adapter,
including adapters supplied by other extensions. A selected name that is
inactive, unregistered, or unadapted is ignored; Code Mode never activates a
tool just because it is listed. Start a new session after changing the list so
the hierarchy is rebuilt.
Code Mode registers no keyboard actions.
Using exec
The input is JavaScript source, not a JSON wrapper or Markdown fence. The host
runs it in a fresh async V8 isolate with no Node.js APIs, filesystem, network,
or console.
Nested tools are methods on the global tools object, with names normalized
to JavaScript identifiers (mcp__server__tool stays as is; other characters
become _):
const status = await tools.exec_command({ cmd: "git status --short" });
text(status);
Independent calls can run concurrently:
const [left, right] = await Promise.all([tools.first_tool({ value: "left" }), tools.second_tool({ value: "right" })]);
text({ left, right });
The exec tool description lists the exact nested declarations; those are the
authoritative names and input shapes.
Host globals: text, image, generatedImage, store, load, notify,
exit, setTimeout, clearTimeout, yield_control, and ALL_TOOLS.
store/load persist values across exec calls in the same session. With
the openai-codex provider the description also advertises an audio(...)
helper; other providers do not receive it.
A first-line pragma overrides one call's limits (only these two fields are accepted):
// @exec: {"yield_time_ms": 10000, "max_output_tokens": 2000}
text(await tools.some_tool({ query: "example" }));
exec returns a cell ID when the script is still running after the yield time.
Call wait with that ID:
{"cell_id":"<id returned by exec>","yield_time_ms":10000}
wait returns only new output since the last yield. terminate: true stops
the cell. Omitted max_tokens and yield_time_ms fall back to the settings
above; max_tokens is capped at 100000.
Nested calls do not pass through Pi's tool_call or tool_result hooks. A
policy extension that must guard a tool on both paths needs a direct Pi hook
and a Code Mode preflight (see below).
Compact transcript mode shows nested tool components or the call's own result
directly and hides the Code Mode action row; Pi's expanded tool-output view
restores that row with Code and Result details. Failures always stay
visible. Successful exec calls with no output render nothing, and successful
wait calls update the original row instead of adding one. The details
payload on results is versioned and serializable: normalized input, timing,
output bounds, errors, and bounded nested call traces.
SDK for other extensions
Depend on @luan.sh/pi-code-mode and import from @luan.sh/pi-code-mode/sdk
(UI-free). Registering an adapter makes a tool eligible for lifting; the user
still selects it in pi-code-mode.tools.
An ordinary Pi function tool registers with registerCodeModeFunctionTool.
The bridge reuses the tool's execute, prepareArguments, renderCall, and
renderResult, so direct and nested calls share one execution path:
import { registerCodeModeFunctionTool } from "@luan.sh/pi-code-mode/sdk";
const dispose = registerCodeModeFunctionTool(tool, {
outputSchema: { type: "object", properties: { value: { type: "string" } }, required: ["value"] },
resultValue: (result) => result.details,
});
resultValue(result) picks the JavaScript value returned to the script; use
it when details is a presentation model rather than the programmatic result.
The full result stays in the bounded nested trace for rendering.
Use registerCodeModeToolAdapter for freeform tools or behaviour a
ToolDefinition cannot express:
import { registerCodeModeToolAdapter } from "@luan.sh/pi-code-mode/sdk";
const dispose = registerCodeModeToolAdapter({
name: "example_tool",
kind: "function", // or "freeform" for a raw string input
description: "Do one example operation.",
parameters: { type: "object", properties: { value: { type: "string" } }, required: ["value"] },
invoke(input, context, signal) {
return runExampleTool(input, context.extensionContext, signal);
},
});
Function adapters must provide parameters; freeform adapters receive a raw
string. context has cwd, toolCallId, extensionContext, and an optional
onUpdate. Optional members: outputSchema, yieldTimeMs, prepareInput,
resultValue, renderTrace(trace, context) (custom nested presentation),
presentationKey(trace) (share one transcript row across calls), and
onScopeChange(scope), which lets an adapter observe and narrow the other
tools currently under exec without changing the hierarchy policy. Every
register function returns a dispose callback.
From the package root, registerNestedToolPreflight(guard) adds a policy
check for nested calls. The guard receives { toolName, input, cwd, toolCallId, extensionContext, signal } and may return { block: true, reason }.
listCodeModeToolNames() returns the names currently lifted.
Troubleshooting
- A selected tool remains direct: check that
execis active, the tool is active at session start, its adapter package is loaded, and the setting uses the exact Pi tool name. waitis missing:execwas not active, or Code Mode is disabled.- The host fails to build: make sure
cargois onPATH, or setPI_CODE_MODE_HOST_BINARYto an executable host. - A nested call is blocked: check preflight registrations; nested calls do not fire Pi's normal tool hooks.
Layout
| Responsibility | File |
|---|---|
| Extension entry, settings wiring | src/extension.ts |
| Settings definitions | src/contributions/xsettings.ts |
| Hierarchy and session lifecycle | src/runtime/lifecycle.ts, src/runtime/code-mode.ts |
| Nested execution | src/runtime/delegation.ts |
| Adapter, hierarchy, preflight contracts and function-tool bridge | src/protocol/ |
| Native host client and protocol | src/host/ |
exec / wait tool definitions |
src/tools/exec/, src/tools/wait/ |
| Transcript presentation | src/ui/presentation.ts |
Develop
Source: https://github.com/luan/agents, directory
harnesses/pi/agent/packages/pi-code-mode. Run bun run typecheck and
bun test test in that directory.