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.
This commit is contained in:
37
CHANGELOG.md
37
CHANGELOG.md
@@ -7,6 +7,40 @@ loosely follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- Per-child idle-state classifier with five states (`idle`, `working`,
|
||||
`thinking`, `permission`, `error`) and three pluggable strategies:
|
||||
`output_activity` (claude / opencode defaults), `osc_title_stability`
|
||||
(codex), and `osc_title_status` (gemini-style status-in-title agents).
|
||||
Optional `permission_patterns` / `thinking_patterns` / `error_patterns`
|
||||
regexes promote a base state when matched against the tail of recent
|
||||
output. State and last-match reason are exposed via MCP on
|
||||
`ProcessInfo` and `get_process_status` (`idle_state`, `idle_reason`).
|
||||
- New `idle_detection` block on `preset.Preset` for setting the strategy
|
||||
threshold, title-to-state map, and promoter regex lists. Bundled
|
||||
defaults are shipped for the first-party claude / codex / opencode
|
||||
presets.
|
||||
- Sidebar now renders a state glyph per process row (`○` idle, `●`
|
||||
working, `◐` thinking, `?` permission, `✕` error) and, when a process
|
||||
has a pending or paused timer, appends a nearest-timer indicator
|
||||
(`⏱ 12s` or `⏸ paused`).
|
||||
- MCP timer surface expanded to match Solo's tool set: `timer_set`,
|
||||
`timer_fire_when_idle_any`, `timer_fire_when_idle_all`, `timer_cancel`,
|
||||
`timer_pause`, `timer_resume`, `timer_list`. Idle-aware timers
|
||||
registered against already-idle children fire synchronously
|
||||
(`status: already_satisfied`) for `idle_all`, and report
|
||||
`already_idle` / `waiting_on` arrays so callers can introspect the
|
||||
watch set. Timer bodies are delivered to the owner process via the
|
||||
same orchestrator-injection path as `send_message`.
|
||||
- Timer tools accept an explicit `owner_process_id` so top-level
|
||||
(non-agent) callers — including the harness MCP client — can attribute
|
||||
timers to a specific process. Omitting it treats the caller as the
|
||||
orchestrator with universal cancel / pause / resume / list privileges.
|
||||
- libghostty-vt `Title()` accessor on the emulator surface, polled from
|
||||
the session pump so OSC 0/1/2 title updates feed into the classifier
|
||||
without a callback round-trip.
|
||||
- Harness `wait_until_mcp` step type that re-runs an MCP method until an
|
||||
assertion (Equals / Contains) holds or the timeout elapses. Used by
|
||||
the new idle / timer scenarios.
|
||||
- User-created top-level command processes now survive a patterm
|
||||
restart. Each spawn (palette form, command preset, or MCP
|
||||
`spawn_process` with `kind=command`) writes a record to
|
||||
@@ -64,6 +98,9 @@ loosely follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
after a child program disables mouse tracking.
|
||||
|
||||
### Changed
|
||||
- `timer_wait` is now a thin wrapper over the shared timer manager
|
||||
(`timer_set` semantics). Existing callers see no behavioural change;
|
||||
the timer is visible in `timer_list` while it's pending.
|
||||
- CLI flag parsing switched from Go's stdlib `flag` to `spf13/pflag`.
|
||||
`--project` (and the internal `--socket` / `--identity` /
|
||||
`--scenario` / `--patterm-bin` flags) are now the only accepted form
|
||||
|
||||
Reference in New Issue
Block a user