Skip to content

feat(tmux-control): send/capture/launch tmux windows from pi#106

Merged
lantiscooperdev merged 3 commits into
mainfrom
feat/tmux-control
Jun 27, 2026
Merged

feat(tmux-control): send/capture/launch tmux windows from pi#106
lantiscooperdev merged 3 commits into
mainfrom
feat/tmux-control

Conversation

@lantisprime

Copy link
Copy Markdown
Owner

Summary

New extension tmux-control that exposes tmux send/capture primitives for the user's main tmux server. Complements the existing /agents bg workflow by letting you (and the LLM) interact with already-running agent windows.

What's included

  • 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 pi.on("input") hook for NL activation — patterns like "tail bg-abc123" bypass the LLM

Safety

  • argv-only execFile (no shell)
  • 5s hard timeout on every tmux call
  • prefix gate (default pi-agent-) refuses writes to non-agent windows
  • bounded inputs (4 KB text, 5000-line capture)
  • window-name validation rejects shell metacharacters

Bridge to bg-terminal

Dynamically imports agents/lib/bg-terminal.ts for runId → windowId resolution 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 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 — verifies all 6 commands + 4 tools register
  • test-real-tmux-smoke: 9 E2E steps against a real tmux server 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

Out of scope (deferred)

  • NL routing of /agents bg (the "via tmux" intent-gate addition) — owned by agents extension
  • Persisting prefix overrides across sessions
  • Multi-line send / paste-buffer mode
  • Window splitting / layout

Charlton D. Ho and others added 3 commits June 27, 2026 19:37
…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 lantiscooperdev merged commit 4cc5232 into main Jun 27, 2026
1 check passed
@lantisprime lantisprime deleted the feat/tmux-control branch June 27, 2026 11:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants