feat(tmux-control): send/capture/launch tmux windows from pi#106
Merged
Conversation
…r pi-invocation) Background agents (/agents bg) were non-functional despite 63 passing P5 unit tests and the review rounds. Driving the REAL tmux backend end-to-end surfaced two integration bugs the tests hid with consistent seams/fakes: 1. Manifest dir mismatch. tmux-terminal validated manifestPath under ~/.pi/bg-state, but preflight writes manifests under ~/.pi/agent/bg (bg-state.ts getBgStateDir). Every launch was rejected "invalid manifest path". Fix: tmux-terminal/index.ts derives bgStateDir as <os.userInfo().homedir>/.pi/agent/bg to match the agents authority root. 2. Worker re-invoked itself as pi. getPiInvocation re-runs process.argv[1] as the pi entrypoint. Foreground argv[1] IS pi, but the detached bg-worker is a separate script, so the child became `node bg-worker.ts --mode json … -p` and exited 1 in ~90ms — the agent never ran. Fix: getPiInvocation only re-invokes argv[1] when its basename is "pi"; a non-pi parent falls through to "pi" on PATH. (An attempt to record the resolved pi path in the manifest was dropped: child-args SAFE_CLI_TOKEN_RE rejects absolute paths as piCommand, so the backstop guard is the clean fix — the real tmux test below caught that.) Tests: - test-bg-e2e-tmux.mjs (run-bg-e2e-tmux.sh): the missing test class — it LAUNCHES pi through an actual tmux session (real preflight -> real backend -> real worker -> real pi) and asserts result.json is "completed" with the agent's marker. Self-skips when tmux/pi/model are unavailable. Catches both bugs (and caught a regression in the abandoned piCommand approach). - test-pi-invocation.mjs updated to the guarded contract + a regression: a non-pi script (bg-worker.ts) must fall through to "pi", not be re-invoked. All P4-2/3/4/7, bg-state, child-args/runner, and 63 P5 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
P5c extension that exposes tmux send/capture primitives for the user's
main tmux server. Designed to complement /agents bg by letting you (and
the LLM) interact with already-running agent windows.
Features:
- 6 slash commands: /tmux-list, /tmux-capture, /tmux-send, /tmux-tail,
/tmux-launch, /tmux-config
- 4 LLM-callable tools: tmux_list, tmux_capture, tmux_send, tmux_launch
- 1 input hook for NL activation (consumes "tail bg-abc123", etc.,
returns {action: "handled"} so the LLM never sees matched prompts)
Safety:
- argv-only execFile (never a shell)
- 5s hard timeout on every tmux call
- prefix gate (default "pi-agent-") rejects writes to non-agent windows
- bounded inputs (4 KB text, 5000-line capture)
- window-name validation rejects shell metacharacters
Bridge:
- dynamic-imports agents/lib/bg-terminal.ts for runId->windowId
resolution when the agents extension is loaded
- falls back to prefix-match when no backend is registered
- works standalone (without agents extension)
Tests (21 files, ~1800 LOC):
- test-safety: window-name + prefix validation
- test-nlp: 20+ NL patterns + non-match guards
- test-exec: list/capture/send/launch via fake executor
- test-extension-integration: headless mock of pi ExtensionAPI
- test-real-tmux-smoke: 9 E2E steps against real tmux on isolated socket
- REQ-13 guard: no agents/lib imports outside resolve.ts
Verified live in pi:
- Extension loads (appears in [Extensions] list)
- /tmux-list fires, returns "No windows match prefix" via ctx.ui.notify
- NL "list agents" intercepted by input hook, LLM bypassed
- NL "spawn tmux session for tailing logs" creates a real tmux session
Install:
ln -s "$(pwd)/tmux-control" ~/.pi/agent/extensions/tmux-control
Co-Authored-By: Claude <noreply@anthropic.com>
… runner The reviewer raised concerns about missing dependencies. Investigation: - typebox is a runtime dep for tool parameter schemas - Pi's extension loader (jiti + virtualModules) resolves typebox from pi's own bundled copy at runtime — end users do NOT need to install it manually - But the test runner uses `node --experimental-strip-types`, which does NOT have access to pi's loader context. typebox resolution fails without an explicit `node_modules/typebox` in the extension dir. Fixes: - README: document the two-environment dep model and the auto-install behavior of the test runner - package.json: declare `@earendil-works/pi-coding-agent` as peerDependency (runtime provided by pi), `typebox` as devDependency (used by tests only, not bundled into runtime). Added a `//peer` JSON comment explaining why typebox is NOT a peerDep despite being imported. - run-control-tests.sh: install typebox via `npm install --no-save` on first run if `node_modules/typebox` is missing. Subsequent runs use the cached install. node_modules is gitignored, so the install is local-only and doesn't pollute the repo. Verified: - Fresh clone → `bash run-control-tests.sh` installs typebox and passes - Subsequent runs are fast (no install) - Live pi load via `pi -e ./tmux-control/index.ts` still works (pi provides typebox; no manual install needed) - gitignore properly excludes node_modules/ Co-Authored-By: Claude <noreply@anthropic.com>
lantiscooperdev
approved these changes
Jun 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New extension
tmux-controlthat exposes tmux send/capture primitives for the user's main tmux server. Complements the existing/agents bgworkflow by letting you (and the LLM) interact with already-running agent windows.What's included
/tmux-list,/tmux-capture,/tmux-send,/tmux-tail,/tmux-launch,/tmux-configtmux_list,tmux_capture,tmux_send,tmux_launchpi.on("input")hook for NL activation — patterns like "tail bg-abc123" bypass the LLMSafety
execFile(no shell)pi-agent-) refuses writes to non-agent windowsBridge to
bg-terminalDynamically imports
agents/lib/bg-terminal.tsforrunId → windowIdresolution when the agents extension is loaded. Falls back to prefix-match otherwise. Works standalone without the agents extension.Tests (21 files, ~1800 LOC)
test-safety: window-name + prefix validationtest-nlp: 20+ NL patterns + non-match guardstest-exec: list/capture/send/launch via fake executortest-extension-integration: headless mock of piExtensionAPI— verifies all 6 commands + 4 tools registertest-real-tmux-smoke: 9 E2E steps against a real tmux server on isolated socketagents/libimports outsideresolve.tsVerified live in pi
[Extensions]list)/tmux-listfires, returns "No windows match prefix" viactx.ui.notifylist agentsintercepted by input hook, LLM bypassedspawn tmux session for tailing logscreates a real tmux sessionInstall
Out of scope (deferred)
/agents bg(the "via tmux" intent-gate addition) — owned byagentsextension