feat(rust): terminal-CLI activity (TERM-15), turn completion (TERM-16) + terminal.idle#525
Merged
Merged
Conversation
…urface
The frozen T0 inventory (27+52) predates legacy's amplifier activity
schemas and the NEW terminal.idle capability, so these are declared as
an explicit EXTENSION surface (EXTENSION_CLIENT_MESSAGE_TYPES /
EXTENSION_SERVER_MESSAGE_TYPES) with their own shape-pinning test,
leaving tests/inventory.rs and port/contract untouched.
terminal.idle pinned contract: { terminalId, at (server epoch ms),
reason: 'grace' | 'queue-empty' }, emitted once per busy->truly-idle
transition.
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier)
Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…rser, idle gate Ports of the frozen legacy stack: shared/turn-complete-signal.ts, turn-completion-ledger.ts, claude-activity-tracker.ts, the codex-activity-tracker.ts PTY lane, and the amplifier events reducer/tailer/tracker trio — plus the NEW terminal.idle grace gate. Zero-polling: every tracker is a pure state machine exposing next_deadline(); the hub arms one one-shot timer per provider and nothing is armed while nothing is busy. Documented deviations: codex tracks from create (the JSONL-reconcile lane is unported), deadman force-read repeats at the deadman interval instead of the legacy 5s sweep cadence. 62 unit tests: busy/turn/idle sequences, queued-submit re-arm, OSC/DCS BEL suppression, deadman self-heal, amplifier grace force-read-then-revert, tailer offset/partial/oversized/reset/schema paths, idle-gate arm/extend/cancel/once semantics. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
ActivityEvent (Created/Input/Output/Exit) + set_activity_observer on TerminalRegistry: Created/Exit fire for every mode (kill + natural exit paths), Input/Output only for CLI modes so shell terminals pay zero per-chunk tap cost. Observer runs on the caller / PTY reader thread and must stay non-blocking (the hub forwards into a channel). 3 tests: full CLI-mode event flow over a real PTY, shell-mode tap silence, natural-exit tap. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
ActivityHub consumes the registry tap, routes by mode into the claude/codex/amplifier trackers (gemini/kimi stay status-inert), broadcasts *.activity.updated / terminal.turn.complete / terminal.idle, and answers all four *.activity.list requests from live tracker state (reconnect seeding via per-terminal completionSeq). OpenCode list responds with the correct idle shape (phase enum only has 'busy'); its live SSE lane is deferred. Amplifier events lanes: one inotify watcher + offset tailer per associated terminal (attach at Start on locator association — the replayed prompt:submit confirms the provisional busy; Eof on resume). Watcher filters to data-mutation events so our own reads can't self-trigger; stats counters prove tail reads happen only on writes. Zero-polling: the hub task sleeps on the event channel plus at most ONE one-shot deadline (min of tracker next_deadline()s); idle state arms nothing. Includes the 20-idle-terminals zero-wake test. AgentProvider gains 'amplifier' (legacy TerminalTurnCompleteSchema parity); freshAgent dispatch drops it explicitly. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Constructs the ActivityHub at boot, installs its registry observer, and shares it into WsState. Resumed amplifier terminals resolve their events.jsonl via one bounded projects walk at create time; fresh sessions get their path from the locator association. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Three scenarios over real fake CLIs and RAW captured WS frames (a node-side capture socket) plus the blue chrome the frozen client derives from them: - claude PTY lane: busy blue on submit, BEL -> idle + exactly one terminal.turn.complete (seq 1) + exactly one terminal.idle (grace); reload during a slow busy turn re-seeds blue from the activity-list response and the later completion carries seq 2. - codex PTY lane: pending blue, BEL completes once, terminal.idle. - amplifier events lane: provisional busy from the PTY Enter, the locator association attaches the inotify tailer, prompt:complete drives idle + turn.complete (provider amplifier) + terminal.idle. New fixtures: fake-bel-cli.mjs (prompt-controlled turn duration, bare BEL turn-complete) and fake-amplifier-activity-cli.mjs (schema-carrying amplifier.log lifecycle records with live timestamps). Green 2x: 3 passed (39.1s), 3 passed (37.8s). 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…ty engine 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Conflict resolutions (both sides' behavior preserved everywhere): - crates/freshell-ws/src/lib.rs, terminal.rs, opencode_association.rs + 12 tests/*.rs WsState constructors: kept BOTH this branch's `activity: None` field and #523's new `session_existence` probe field (trivial adjacent-field conflicts). #523's brand-new tests/pane_reconcile.rs gained `activity: None` too. - crates/freshell-terminal/src/registry.rs: kept BOTH the TERM-15/16 activity tap (ActivityEvent/ActivityObserver, tap field + init, the Created-tap fire in create()) and #523's reconciliation additions (respawn liveness/cap consts + fields, keyed_create_inflight, the §5.4 duplicate-live-PTY backstop — ordered before the Created tap). create() gained #523's create_request_id param; this branch's two activity-observer tests updated to pass None for it. - crates/freshell-server/src/main.rs: kept #523's session_index block first (ws_state.session_existence references it), then this branch's activity-hub construction + registry observer install. - docs checklist + playwright.config.ts auto-merged (both sides' notes and MATRIX/rust-only spec entries retained). Cross-track integration (#524 truly-idle client): the Rust terminal.idle emission matches shared/ws-protocol.ts's new TerminalIdleSchema byte-for-byte ({type, terminalId, at, reason: 'grace'|'queue-empty'}), so the rust leg of test/e2e-browser/specs/truly-idle-alerting.spec.ts is now ENABLED (test.fixme removed) — the full production pipeline (PTY submit -> busy blue -> BEL -> idle + turn.complete -> 2s grace -> terminal.idle -> one alert edge + tab shade) passes against the real Rust server. Verification: cargo test --workspace 1656 passed / 0 failed; clippy clean on touched files; rust-chromium PW (3 activity + truly-idle rust leg) green 2x (4 passed 1.1m, 4 passed 42.5s); coordinated npm test full-suite success exit=0 (3982 + 4547 + 350 passed; two earlier runs each hit one unrelated pre-existing flake — DOM-identity, sidebar-filter, EADDRINUSE:40301 — each passes in isolation). 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
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.
TERM-15 + TERM-16 on the Rust server: terminal-CLI activity (blue/busy), turn completion, and the NEW
terminal.idletruly-idle edgeConfirmed diagnosis (the motivating bug)
Amplifier/codex terminal panes never turn blue on the Rust server because TERM-15 was entirely unimplemented there:
*.activity.listrequest into a comment-documented ignore fallback (crates/freshell-ws/src/terminal.rs: "Everything else (opencode fresh-agent, activity lists, other ui.) is out of scope for this path; ignore"*), so the frozen client's on-connect list requests (src/App.tsx:676-711) got no response.crates/emitted*.activity.updatedorterminal.turn.complete— there were no trackers at all.amplifier.activity.list/.list.response/.updateddidn't even exist infreshell-protocol(the frozen T0 inventory predates legacy's amplifier provider), so the client's amplifier list request wasn't even parseable.The client's
resolvePaneActivitypath was intact and proven working (it renders blue the moment records exist) — the server side was simply absent.What this PR adds
crates/freshell-activity(new crate, 62 unit tests) — pure, timer-free state machines ported from the frozen legacy parity reference:signal—shared/turn-complete-signal.tsport (BEL parser: OSC/DCS/CSI suppression, sandwiched-bell rejection, cross-chunk state).ledger—turn-completion-ledger.tsport (monotonic per-terminalcompletionSeq, latest-snapshot list for reconnect dedupe).claude—claude-activity-tracker.tsport (submit→busy, Stop-hook BEL→exactly one completion, busy deadman self-heal with NO completion).codex—codex-activity-tracker.tsPTY lane port (submit→pending[rendered blue], output liveness across long turns, BEL→idle + completion deduped per turn key, queued-submit re-arm).amplifier—amplifier-events-{reducer,tailer}.ts+amplifier-activity-tracker.tsports (schema gate, prefilter, bounded partial-line buffer with oversized-line skip [100k+-token lines are normal], offset-incremental reads, file-reset degrade; PTY Enter is only PROVISIONALLY busy with force-read-then-revert grace;prompt:submitconfirms;prompt:complete/session:endis the single boundary; deadman force-reads and STAYS busy — never fabricates a completion).idle— the NEW truly-idle gate.Registry activity tap (
freshell-terminal):ActivityEventCreated/Input/Output/Exit observer — Input/Output fire only for CLI modes (shell terminals pay zero per-chunk cost); Exit fires on both kill and natural exit (no stale blue).Activity hub (
freshell-ws/src/activity.rs): routes tap events by mode into the trackers, answers all four*.activity.listrequests from live state (reconnect seeding), broadcastsclaude|codex|amplifier.activity.updated,terminal.turn.complete, andterminal.idle; owns one inotify watcher + offset tailer per associated amplifier terminal (attach at Start on locator association — the replayedprompt:submitconfirms provisional busy; Eof on resume, resolved via a bounded one-time projects walk).Protocol: amplifier activity family +
terminal.idleadded as an explicit EXTENSION surface (EXTENSION_{CLIENT,SERVER}_MESSAGE_TYPES+ shape-pinning tests) sotests/inventory.rskeeps pinning the frozen T0 contract untouched.AgentProvidergainsamplifier(legacyTerminalTurnCompleteSchema.providerparity).NEW pinned contract —
terminal.idle:{ terminalId, at (server epoch ms), reason: 'grace' | 'queue-empty' }, emitted ONCE per busy→truly-idle transition: armed at the positive turn boundary, 2000ms grace window, cancelled by busy re-entry, extended by amplifier events-file appends (post-complete background naming events defer it). Subagent/tool completions inside a running turn never reach the gate — the trackers only report real turn boundaries.Zero-polling (hard constraint)
setIntervalsweeps, by design): every tracker exposesnext_deadline(); the hub arms at most ONE one-shot timer, re-armed only on events/expiry. Nothing busy ⇒ no timer, no wakes.idle_terminals_arm_no_timers_and_read_no_files(20 idle tracked terminals: zero timer wakes, zero file reads over an observed window,next_deadline() == None) and the inotify lane test (tail-read counter unchanged across a quiet window; increments only on a real write).What's proven per CLI
gracependingupsert + blue chromegraceamplifier, sessionIdfake-amp-*graceprompt:submitcancelsOpencodePhaseonly hasbusy)Production signal availability was verified: the Rust launch path already installs claude's Stop-hook bell (
--settings) and codex'stui.notification_method=bel+tui.notifications=['agent-turn-complete'](freshell-platform::cli_launch), so the PTY BEL lanes are live for real CLIs, not just fixtures.Verification
cargo test --workspace: 1599 passed, 0 failed (incl. 62 freshell-activity, 6 hub, 3 registry-tap, protocol extension + inventory tests).cargo fmtclean on all touched crates;cargo clippyintroduces zero new warnings (remaining ones pre-exist on main in untouched files).test/e2e-browser/specs/terminal-activity-rust.spec.ts(rust-chromium): green 2x — 3/3 in 39.1s, 3/3 in 37.8s. Raw frames asserted via a node-side capture socket; blue via the real tab chrome.FRESHELL_TEST_SUMMARY="track3 rust activity pre-PR" npm test: full-suite success exit=0 (default 3968 passed, server 4529 passed, electron 350 passed).Deferred / documented deviations
opencode servesidecar, which Rust terminal panes don't run; list contract answered, live updates deferred to the opencode research track.busy/unknownphases, resume-busy seeding from rollout files, app-server turn events) — unported; codex tracks from create with the PTY lane only (documented incrates/freshell-activity/src/codex.rs; legacy only tracked indexer-bound terminals, which would have reproduced the never-blue bug here).terminal.idle.🤖 Generated with Amplifier