@tduyng/pi-shell0.0.2
Background processes the agent parks on: one bg tool with start/output/kill/status, cursor reads that never re-read bytes, and a runtime wake-up when a task exits.
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
@tduyng/pi-shell
Background shell execution for pi. One bg tool covers the full lifecycle: start, read output, kill, and list tasks.
The bg tool
A single tool with an action field. Each bg task runs in the current working directory with a fresh shell, like pi's own bash. There is no persistent shell session.
start
Spawns a detached command. Returns task_id (8-char UUID prefix), pid, and the absolute log path. stdout and stderr stream to a log file at <agentDir>/shell-logs/<taskId>.log. The task runs silently until you ask about it. Optional timeout caps the task (max 600 seconds). Default is no timeout.
output
Reads new bytes since the last read using a byte cursor. The wait parameter controls behavior:
"now": returns immediately with whatever is available."next_write": parks the agent until the first new byte appears, then wakes it with the new output."exit": parks the agent until the process ends, then delivers the verdict plus the unread tail.
The cursor never advances on wake reads, so a later bg output call still sees every byte from the stored offset onward.
kill
Sends SIGTERM to the process group, polls every 50ms for up to 1 second, then escalates to SIGKILL. Accepts task_id, raw pid, or port (resolved to a pid via lsof -ti).
status
Lists all tasks with id, pid, state (running, exited, killed), cursor offset, and the first 80 characters of the command.
Honest exits
Pi 0.99 already reports a signal-killed command as a failure. Its bash backend returns 128 + signal for a child that died without an exit code, and raises when a command ends with neither an exit code nor a signal. This extension does not override that backend.
shell-process.test.ts runs kill -9 against pi's own bash tool and asserts a nonzero result in the 128+n family, so a pi release that reintroduces the silent success fails here.
A tool_result handler annotates the two cases where the exit code alone cannot tell the model what happened. An exit 1 from grep, rg, or find means no matches, and a command killed by its timeout did not finish cleanly. Each note stops the model from retrying a search that already succeeded.
Middle-out output view
Large logs are truncated to a head and tail, defaulting to 8KB total, with an exact omitted-byte count in between. The read window per side is 16KB, split on UTF-8 sequence boundaries so characters are never cut mid-byte. The full log stays on disk at the path always shown in results.
Wake on exit
Only a task the model parked on (via wait: "exit" or wait: "next_write") injects a user message when it finishes. A burst of exits coalesces into one message sharing a byte budget: default 8KB total, minimum 1KB per task. The wake carries the exit verdict, the unread tail, and the full log path per task, so the model gets the result without a read-back.
The wake fires from the onExit callback via queueMicrotask, not from polling. If the agent is mid-turn, the wake lands in the follow-up queue and is delivered at the next agent_settled boundary.
Stall check
A task the model parked on with wait: "exit" also gets a silence check. Every 30 seconds it reads the log size. After 10 minutes with no new bytes, the agent is woken once with the elapsed time and the log path. The wake fires again only after output resumes and stops again. No model call runs for the check.
Footer indicator
The footer shows a dot and the live task count (e.g. "2 bg running") while any background task is active. It clears when the last task exits.
Task lifecycle
Each bg task is in-memory only. It dies with the pi process. Persisted session entries record the command, log path, and start time so the model can read the log after a reload. The live process handle is gone. Detached grandchildren that inherit the shell's stdout keep writing to the log after the shell exits. The log stream stays open until both the stdout and stderr pipes close. Ending it on the first close would drop data the second pipe still delivers.
Install
pi install npm:@tduyng/pi-shell
Local checkout: pi install /path/to/dotfiles/pi/agent/extensions/pi-shell
Configure
SHELL: set to0,false,no, oroffto disable the extension entirely.