Commit Graph

38 Commits

Author SHA1 Message Date
de60b93bc6 Use built-in agent preset defaults 2026-05-18 11:28:00 +01:00
67b994f629 Clean up auto-summary settings menu 2026-05-18 10:17:25 +01:00
f10598601f Finish settings TODO cleanup 2026-05-18 10:05:26 +01:00
cadd4c8f64 Release v0.0.6
All checks were successful
release / build-linux-amd64 (push) Successful in 11m48s
2026-05-15 21:55:09 +01:00
98d1c059cf summarizer tweaks 2026-05-15 21:54:14 +01:00
cf65d5d707 Wrap toast bodies, slim the dismiss hint, and stop flicker
Toasts now render three content rows with word-wrapped bodies. The
in-toast "Ctrl-N · N more" hint is replaced by a short
"Ctrl-N · dismiss" entry on the status strip that only appears
while a notification is live.

The box stops flickering while the focused child repaints its TUI:
the overlay is stitched onto the per-chunk PTY write under outMu
and bracketed by DECSET 2026 so supporting terminals buffer the
child's redraw and the box paint into a single frame.
2026-05-15 21:24:18 +01:00
ef9b8e71c6 Release v0.0.5
All checks were successful
release / build-linux-amd64 (push) Successful in 11m51s
2026-05-15 20:56:38 +01:00
e64060e40f Calm down the focused-section labels in the command palette
Focused-section rows are now bare verbs (Rename, Close, Stop, Restart,
Delete, Edit) instead of repeating the focused name. The title bar
already carries the subject, and the row hint preserves fuzzy-search
matches like "close codex". Section banners are replaced by a single
blank spacer row so the verbs themselves carry the visual weight,
and the Open section no longer lists "Switch to <current>" for the
pane that's already focused.
2026-05-15 20:30:31 +01:00
f312b6d345 Add stackable toast notifications
Replaces the single-slot status-line flash with a top-right toast
stack over the focused pane. flashError, flashTransient, and
notifyAttention all push onto the same stack (cap 5, FIFO drop).
Ctrl-N dismisses the most recent toast; empty stack falls through to
the focused PTY so readline / nano / emacs / opencode bindings keep
working. A new "Clear notifications" palette item empties the stack.
2026-05-15 20:26:35 +01:00
c1ecba0624 Use mise to install zig + go in release CI; cut 0.0.4
All checks were successful
release / build-linux-amd64 (push) Successful in 13m7s
`mlugg/setup-zig` was chasing mirrors for ~4 minutes on every run
(see v0.0.1 / v0.0.2 logs) and `actions/setup-go` was spending
another ~4 minutes downloading Go before patterm started building.
mise already manages the project's zig pin; adding `go = "1.26.3"`
to `.mise.toml` (matching go.mod) lets `jdx/mise-action@v2` install
both with one cached step. Subsequent runs reuse the mise cache
instead of re-resolving mirror URLs and re-downloading toolchains.

Also adds an `actions/cache@v4` step for `~/.cache/go-build` and
`~/go/pkg/mod` keyed on `go.sum` so `go build` itself doesn't
re-pull modules every tag push.
2026-05-15 19:38:13 +01:00
878e9370bc Fix error flashes replacing focused pane 2026-05-15 19:27:42 +01:00
fd9c19e5c2 Fix release CI: upgrade mlugg/setup-zig to v2 and cut 0.0.3
Some checks failed
release / build-linux-amd64 (push) Has been cancelled
`mlugg/setup-zig@v1` is deprecated and only knows the pre-0.14
tarball name (`zig-linux-x86_64-<ver>.tar.xz`), so every mirror —
and the official ziglang.org/builds — returned 404 for Zig 0.15.2
on both the v0.0.1 and v0.0.2 release runs. v2 uses the new
`zig-x86_64-linux-<ver>.tar.xz` layout that Zig switched to in
0.14+.

Also rolls the existing CHANGELOG `[Unreleased]` work into a
dated `[0.0.3]` section and adds the CI fix to its Fixed list.
2026-05-15 19:14:21 +01:00
6d90cd7185 Match Solo summary cadence options 2026-05-15 19:13:54 +01:00
d648d5b775 Add auto-summary settings 2026-05-15 19:09:21 +01:00
81bc77366f Overhaul command palette UX
Six-phase sweep: section headers (Focused / Open / Spawn / Quit) with
header-skip cursor; chip strip mirroring sw/sp/k macros, driven by
Tab; unified Spawn verbs across agent / process / terminal / custom;
dropped duplicate global Close list in favor of Ctrl-X inline close
on a Switch row plus the [Close] chip; scored matching (prefix >
word-boundary > substring > fuzzy) with matched-char highlighting;
title bar surfaces focus subject; rename forms split long subject
onto its own row; new Alt-1..9 quick-pick, Home/End, ? help overlay,
and Ctrl-R relaunch toggle inside the spawn-process form. Scroll
indicator and cursor/total counter round out the footer.
2026-05-15 16:41:44 +01:00
0c960fa859 Clarify sub-agent reply routing in MCP tool descriptions
A sub-agent's reply to send_message lands in the caller's own pane
tagged [sub-agent:<name>], not in the sub-agent's output. The
descriptions for wait_for_pattern, send_message, both
timer_fire_when_idle_*, and the server-instructions preamble now
spell this out, along with the canonical send_message →
timer_fire_when_idle_any → read-own-pane pattern. help('readiness')
and help('coordination') updated to match. Previously agents reached
for wait_for_pattern on the sub-agent and deadlocked until timeout
because the reply had already been delivered to their own pane.
2026-05-15 16:08:07 +01:00
08187aed77 Don't steal focus when an agent spawns a child via MCP 2026-05-15 15:53:50 +01:00
24c8183832 Auto-snap child viewport to bottom when typing into scrollback
Typing into a focused child while its emulator viewport was
scrolled up left the keystroke heading to the PTY but the input
box invisible below the visible region — it looked like typing
did nothing. processStdin's flushForward now sets
pendingViewportBottom whenever bytes are actually injected, so
the existing post-loop handler snaps the viewport and repaints.

Wheel events and Ctrl-B paths are untouched: both are intercepted
before reaching forward, so wheel still scrolls into history and
Ctrl-B is still the explicit escape hatch. Only bytes that would
actually reach the child PTY trigger the snap.
2026-05-15 15:34:00 +01:00
b5dfaf39c4 Marquee long sidebar names; truncate with ellipsis otherwise
Sidebar rows that overflow the rail width used to spill characters
into the main viewport. They now truncate with a trailing "…"
when unfocused (or when the focused name still fits). The focused
row whose name overflows runs a pause-scroll-pause marquee: 1 s
hold on the head, ~150 ms per cell scroll, 1 s hold on the tail,
snap back. The row's geometry never moves while it animates, so
nothing below shifts.

A dedicated 150 ms goroutine flips sidebarDirty only while a row
is actively animating; the chrome ticker does the actual repaint.
Idle is a single cheap wakeup. focus / spawn / exit / restart all
reset the marquee state so the new focused row starts from frame
zero. When the row's budget is tight, the trailing timer
indicator drops before the name ellipses since the name is the
only identifier the row carries.

clampVisible() is a defensive net inside write(): even if a row's
decoration size were mis-computed, it will not spill past the
sidebar band into the PTY area.
2026-05-15 15:33:39 +01:00
1fb919c22a Keep parent tab highlighted when focus is on a sub-agent
The top tab bar compared against focusedID, so stepping into a
sub-agent dropped the parent tab's highlight even though the user
was still inside that thread. activeAgentID already walks the
parent chain to the top-level root for the sidebar's agent tree
— reuse it for the tab strip too.
2026-05-15 15:26:06 +01:00
4b4e7543e8 Release v0.0.2
Some checks failed
release / build-linux-amd64 (push) Failing after 10m12s
Bundles the in-flight work into the second tagged release. See
CHANGELOG.md `[0.0.2] - 2026-05-15` for the full per-change list.
Highlights:

- libghostty-vt was building in zig's silent Debug default, capping
  the full pipeline at 34-63 fps. Makefile now defaults to
  ReleaseFast (.mise.toml pins zig 0.15.2 so the build is
  reproducible). End-to-end pipeline now runs at 930-2030 fps —
  27-32× faster, with 7-16× headroom over a 120 fps target.
- --debug[=DIR] and --profile[=DIR] flags capture full PTY logs,
  pprof data, and per-hot-path metrics (chunks/sec, mean/max
  latencies, cache hit rates) for offline analysis. Nothing
  pollutes stdout/stderr.
- ASCII-video benchmark suite (8-colour / truecolor / Bad-Apple
  patterns at 30/60/120 fps) plus a renderer microbenchmark set
  for stable A/B comparisons across changes.
- Click-and-drag text selection from alt-screen TUIs (codex) now
  works — host mouse mode follows the focused child's screen side
  instead of being permanently armed.
- Long claude session resume + codex steady-state rendering pay
  less per chunk: drawSidebar deferred to the chrome ticker,
  emulator.Title CGO poll gated on a containsOSC scan.
- Vendor-TUI orientation: MCP initialize.instructions, the
  spawn_agent tool description, and help('spawning') all spell
  out the anti-patterns (shell-out, perl-into-socket) that
  produced codex's stray top-level tabs.
2026-05-15 14:22:59 +01:00
bda799a3c6 mise-pin zig 0.15.2; rebuild libghostty-vt ReleaseFast — 27-32x pipeline speedup
Added .mise.toml pinning zig = "0.15.2" (the minimum the vendored
Ghostty commit requires) and taught the Makefile to resolve zig
through mise when available, falling back to PATH. Contributors run
`mise install` once and `make deps` just works.

Re-ran the pipeline benchmarks after rebuilding libghostty-vt with
ReleaseFast (same hardware, AMD Ryzen 7 7800X3D):

                                Debug         ReleaseFast    speedup
  Pipeline 8-colour @120fps     63 fps         2030 fps       32x
  Pipeline truecolor @120fps    34 fps          931 fps       27x
  Emulator-only truecolor       34 fps         2051 fps       60x

7-16x headroom over 120 fps for the heaviest workload (truecolor
full-screen redraws). Static library size 33 MiB -> 13 MiB.

TODO.md baseline numbers updated to reflect post-fix throughput;
the "Debug-mode lib" finding is folded into the result it produced
rather than left as an open item.
2026-05-15 13:54:48 +01:00
2f109a84fa Stress-test ASCII video at 30/60/120 fps; fix libghostty-vt Debug build
Added a full ASCII-video benchmark suite that hammers the renderer
with 30 KiB / 70 KiB full-screen frames at 30, 60, and 120 fps
targets — both renderer-only and full-pipeline (em.Write + renderer
+ stdout). Each stream benchmark reports µs/frame, fps_ceiling, and
percent of the per-frame budget consumed.

The pipeline benchmarks revealed we were missing 120 fps by a wide
margin (190%-350% of budget at 120fps, 60-90 fps ceiling). Isolating
em.Write confirmed libghostty-vt is the bottleneck — 16-29 ms per
truecolor frame, library file at 33 MiB.

Root cause: the Makefile invoked `zig build` with no
-Doptimize, and Zig's standardOptimizeOption defaults to Debug. So
the shipped libghostty-vt was unoptimised. Fixed by pinning
ReleaseFast in the Makefile (override via GHOSTTY_VT_OPTIMIZE for
debug builds of the upstream lib).

Existing checkouts need `make clean-deps && make deps` to pick up
the rebuild.
2026-05-15 13:43:31 +01:00
1c590f8e32 Concrete perf metrics: live counters in --profile + benchmark suite
Live metrics (--profile):
- New metricsTracker instruments OnPTYOut, viewport renderer,
  stdout writes, libghostty-vt Write/Title CGO calls, sidebar /
  tabbar / status draws (with cache-hit accounting), snapshot
  replays, and the chrome ticker (so we can see ticker fires that
  did nothing).
- Writes metrics.jsonl (one snapshot per second) and metrics.json
  + summary.txt on exit, alongside the existing pprof files.
- All record* methods are nil-safe so disabled paths pay only a
  cheap nil check; counters are atomic so the per-PTY-chunk hot
  path stays lock-free.

Benchmark suite (go test -bench=.):
- Three workload fixtures — plain ASCII, SGR-styled lines, and a
  ratatui-style cursor-shuffling burst — plus a containsOSC
  microbenchmark. Reports ns/op, MB/s, allocs/op, B/op.
- Initial baseline numbers added to TODO under the perf-audit
  section, alongside two new findings (renderer allocs ~1 per 4
  bytes on styled chunks; styled throughput tops out near
  90 MB/s) those benchmarks surfaced.
2026-05-15 13:31:37 +01:00
c120342709 Clear TODO backlog: --debug/--profile, codex selection, MCP orientation, perf
- Add --debug[=DIR] / --profile[=DIR] flags that write run artefacts
  (patterm.log, events.jsonl, per-child raw PTY captures, CPU + heap
  + goroutine pprof) to a dir without polluting stdout/stderr.
- Strengthen vendor-TUI orientation in three places (MCP
  initialize.instructions, the spawn_agent tool description, and
  help('spawning')) to head off codex's habits of poking the Unix
  socket via perl and shelling out to launch peers — both bypass
  caller identity and produce orphaned top-level tabs.
- Fix click-and-drag text selection from alt-screen TUIs. Host SGR
  mouse reporting now follows the focused child's screen side
  instead of being permanently armed; alt-screen TUIs that need
  mouse re-enable it themselves and the toggle is forwarded.
- Move drawSidebar() off the per-PTY-chunk hot path. Long claude
  session resume was paying a full sidebar rebuild for every
  scrolled chunk; the chrome ticker now drains a dirty flag at 60 Hz.
- Gate the per-chunk Title() CGO poll on a containsOSC scan so
  codex/ratatui's many SGR-only chunks no longer pay a CGO call each.
2026-05-15 12:41:47 +01:00
01fc108086 Rename Kill to Close, add New Terminal palette entry, clean up exited terminals
- Palette's per-child "Kill <name>" action is now labelled "Close <name>"
  (action kind unchanged; still SIGTERM). Matches the existing "Close
  agent: …" context entry and reads less violent for a graceful term.
- New "New Terminal" palette entry spawns a bare interactive $SHELL pane
  via LaunchTerminal (kind=terminal). Replaces the default "shell"
  process preset that was seeded on first run.
- Exited KindTerminal entries are now dropped from the session in
  reapChild — terminals have no restart path, so leaving them behind as
  greyed rows in the Processes sidebar was just clutter. processList
  also filters defensively.
2026-05-15 11:30:46 +01:00
543c7cc59a Fix idle timer review issues 2026-05-15 11:18:03 +01:00
2b9e1ed77c Add idle-state classifier and Solo-parity timer tools
Classifies every running child as idle/working/thinking/permission/error
using one of three pluggable strategies (output_activity,
osc_title_stability, osc_title_status) plus optional regex promoters
applied to the tail of recent output. State and last-match reason are
exposed via MCP on ProcessInfo and get_process_status. Per-preset
configuration lives on a new preset.IdleDetection block with bundled
defaults for the first-party claude/codex/opencode presets.

OSC title plumbing is exposed as Emulator.Title(), polled from the
session pump after each emulator write so title-change activity feeds
into the classifier without an extra cgo callback.

The MCP timer surface expands to match Solo: timer_set,
timer_fire_when_idle_any/all, timer_cancel, timer_pause, timer_resume,
timer_list. timer_wait is now a thin wrapper that shares the same
manager so it shows up in timer_list while pending. Timer bodies are
delivered to the owner process through the existing
InjectAsOrchestrator path. Top-level (non-agent) callers can attach
timers to a specific process via owner_process_id; omitting it grants
universal cancel/pause/resume/list privileges.

The sidebar gains a state glyph per process row and appends a
nearest-timer indicator when one is pending or paused.

Tests: idle_test.go covers the classify() pure function across the
three strategies and regex promotion; timers_test.go covers the
manager. Harness scenarios cover output_activity, osc_title_stability,
osc_title_status, and regex promotion, plus timer_set delivery,
cancel, pause/resume, idle_any-on-transition, idle_all-pending, and
idle_all-already-satisfied. A new wait_until_mcp harness step type
polls an MCP method until an assertion holds.
2026-05-15 09:49:59 +01:00
05f92a3ed0 Add context-aware items to the command palette
When opened with Ctrl-K, the palette now prepends entries for whatever
is currently focused:

- Focused scratchpad: Delete / Rename (inline form) / Edit (fire-and-
  forget zed launch with stdio detached so the TUI is not suspended).
- Focused agent: Rename (inline form) / Close.
- Focused process: Rename / Delete (drops the entry; SIGKILL if alive)
  / Stop (SIGTERM, keep entry) / Restart (same argv).

The rename UX is a single-field inline form that mirrors the existing
spawn-process form, so the modal-input contract is unchanged.
scratchpad.Store grows Delete / Rename / Path so the palette can act
on a pad file by name. focusedPad is plumbed onto uiState ahead of the
scratchpad-focus UI work; until that lands it stays empty and the
scratchpad-context entries simply never surface.

Tested with palette_context_test.go and a new rename_process_via_palette
harness scenario.
2026-05-15 00:51:07 +01:00
81a8ac2ba0 Fix command palette over focused scratchpad
The stdin loop's scratchpad-input branch ran before the palette branch
and silently dropped every byte except a handful of app-level chords,
so palette typing and Esc never reached the palette while a pad was
focused. Skip the pad-input branch whenever st.palette != nil.

closePalette also called repaintFocused() on cancel / no-op action
paths, which paints the empty focused-child slot (focusedID == "" while
a pad is focused) and leaves the palette's top border drawn over the
pad. Route those branches through a restoreView helper that picks
repaintFocusedPad when a pad is focused.

Switching from a pad to a child via the palette now clears the pad
focus and wipes the viewport, matching focusProcess's pad-exit path.

Adds a harness scenario (palette_over_scratchpad) that opens a pad,
opens the palette, types a query, and verifies that Esc leaves the
pad correctly repainted with no palette chrome lingering.
2026-05-15 00:35:28 +01:00
0d578d54f1 wip 2026-05-15 00:28:06 +01:00
2f969fa215 Fix sidebar repaint and command restart navigation 2026-05-14 22:41:24 +01:00
83eb4f6b2d Add --version flag and enforce --long flags via pflag
Switches CLI flag parsing from Go's stdlib `flag` to spf13/pflag so
`--project` (and the internal `--socket` / `--identity` / `--scenario`
flags) are the only accepted form; single-hyphen long flags like
`-project` are now rejected. Help output renders the canonical `--`
form.

Adds `patterm --version`, which prints the build version, short commit,
and build date (e.g. `patterm v0.0.1 (commit abc1234, built 2026-05-14)`).
The version string is injected at build time — `make patterm` derives it
from `git describe --tags --always --dirty`, and the release workflow
injects the pushed tag. Commit/date come from the Go toolchain's
embedded VCS info via `runtime/debug.ReadBuildInfo`, so no manual
bumping is required.
2026-05-14 22:22:32 +01:00
52e06c914e Release v0.0.1
Some checks failed
release / build-linux-amd64 (push) Failing after 10m52s
Bundles the in-flight work into the first tagged release. See
CHANGELOG.md `[0.0.1] - 2026-05-14` for the full per-change list.
Highlights:

- Sidebar / chrome stability: clamp absolute cursor positioning and
  printable bytes to the viewport so long-running TUIs (claude, codex)
  can't spray into the right rail; bound tab bar's row clear to the
  viewport width so the rail isn't wiped on every tab redraw; flag
  scroll escapes (RI/IND/NEL/SU/SD/IL/DL) and clamp `CSI 0/1/2 J`/`K`
  to viewport columns.
- Palette: "Spawn process…" form, macros (`sw `, `k `, `sp `), kill
  entries mark the focused tab, dead agents drop out of the switch
  list.
- Sidebar: split into Processes (session-wide) + Agent Tree
  (per-active-agent) sections; relaunch indicator; Ctrl+W/S walks the
  combined list, Ctrl+A/D steps tabs.
- MCP: protocol handshake (`initialize`, `tools/list`, `tools/call`,
  `ping`), `mcp_injection.kind = cli_override / config_env` so codex
  and opencode pick up the server with no file writes, `lifecycle`
  help topic and tool-description cleanup-duty pointers.
- Lifecycle: orchestrator-spawned children cascade-killed when the
  parent dies; orchestrator-injected prompts end with CR + delayed
  Enter so claude submits cleanly.
2026-05-14 22:04:32 +01:00
56fd461fb3 Teach parent agents to clean up the processes they spawn
Add a `lifecycle` help topic spelling out that the caller owns the
processes it spawns and should `close_process` when a sub-agent or
spawned child is no longer needed. The `spawn_agent` and `spawn_process`
descriptions advertised via `tools/list` now restate the same duty
inline (with a pointer to `help('lifecycle')`), so vendor TUIs see the
expectation at the moment they reach for the tool. The `spawning` topic
and `topics` index cross-reference the new content.

Bundles two already-staged improvements that fall in the same area:
- OnChildSpawned primes the snapshot-replay budget for new panes so
  diff-based vendor TUIs come up clean without a manual Ctrl+W/Ctrl+S
  refresh.
- TODO drops the three items now actioned (prompt-injection preface,
  agent cleanup duty, opencode→claude view corruption) and keeps the
  unicode `<?>` entry with the investigation notes.
2026-05-14 21:17:03 +01:00
cc4bf9e904 Simplify session lifecycle and MCP cleanup 2026-05-14 20:51:37 +01:00
58dbb56937 Repaint sidebar after child scrolls the host scroll region
Codex (Ratatui) emits an 8x RI burst on startup right after setting
DECSTBM. RI at the top of the scroll region scrolls the region down,
and DECSTBM only constrains rows -- so the scroll spans every column
and drags the right-rail session-tree entries down with the main pane.
The chrome cache then hid the clobber because the computed sidebar
frame was unchanged.

The viewport renderer now flags any chunk containing RI / IND / NEL /
SU / SD / IL / DL and OnPTYOut drops the sidebar cache when the flag
is set, so the next drawSidebar repaints over the drift.

Adds unit tests for the new flag and a harness regression scenario
(sidebar_survives_ri_scroll) that fails without the fix.
2026-05-14 20:01:14 +01:00
3622c41fd0 Land staged session/MCP/chrome work + sidebar clear-J fix
This batches the in-flight [Unreleased] block from CHANGELOG.md into a
single commit. Highlights:

- Real MCP protocol layer (initialize / tools/list / tools/call) so
  vendor MCP clients can complete the handshake against the per-PID
  socket. Legacy direct-dispatch preserved for the harness.
- New mcp_injection kinds — cli_override for codex, config_env for
  opencode — joining the existing env-var and config_file paths so
  patterm can slot into more agents without touching their real
  config or auth.
- Ctrl+A/D and Ctrl+W/S focus navigation across tabs and intra-tab
  process lists, recognised in legacy / kitty CSI u / xterm
  modifyOtherKeys encodings.
- Palette macros (sw / k / sp ) and reordering so open sessions
  surface above spawn-new entries.
- Two-row tab bar, sidebar/tabbar/status chrome cache, viewport-wipe
  on agent spawn, CR-terminated orchestrator injections, and split-
  Enter PTY writes so paste-detecting TUIs see Enter as a key event.

Also fixes the bug logged in TODO: claude's Ctrl+O tool-call expansion
emits CSI 0 J, which the viewport renderer was forwarding verbatim —
wiping the sidebar to the right of the cursor and leaving the chrome
cache convinced nothing had changed. CSI 0 J and CSI 1 J are now
translated into per-row ECH sequences clamped to the viewport, same
as CSI 2 J and CSI K already were.

Agent guides (CLAUDE.md / AGENTS.md) now spell out the
TODO->CHANGELOG workflow so completed items land in the changelog
rather than as ticked entries left behind in TODO.
2026-05-14 19:09:35 +01:00