Repository navigation
feat(tui): the status row is drawn from what the client knows - #19
Merged
Merged
Conversation
The row under [ui.status_line] was drawn only after the agent sent a session_status message. Only the old Rust agent sent one. With the Python agent the row never appeared, and a command script never ran. Build the row from what the client already holds: directory, session id, model, session name, version, and two clocks (time since the session was attached, start of the running turn). A status from the agent still wins; the client fills the empty fields only. Context and cost stay empty, because nobody on this side knows them. There is no wire change and no Python change. This is plan decision 15, option (b). Option (a), a new pi/ message from the agent, failed two of the five points of DESIGN 8.5 (no standard equivalent; the type would live in an old runtime crate). The start and the end of a turn make the row stale once, so a timer does not outlive its turn. Add 16 unit tests, rewrite 2 and adjust 1. Change the expectations of status_line_probe.py (4 of 4 pass on a real zypi) and the guide page 25-status-line.md. Record the step in the plan (r12, part 7).
CI passes on bfd5425: Ruff, pytest 3.11 to 3.13, Gates + cargo check (warnings 0, graph 980 of 980), cargo test (8681 passed, 0 failed, 22 ignored; pi-pager 5624, the same as the local rerun).
zy1233
added a commit
that referenced
this pull request
Oct 13, 2026
…sion 6) (#18) ## Summary `zypi export <id>` read the session files of the old Rust agent, so it failed for every session that the Python agent stored. It now asks the agent, as a resume does. This is plan decision 6, option (a). ```text zypi export <id> connect() # starts the Python agent session/load(id, cwd) collect session/update (isReplay) done when: the answer came AND no replayed update came for 500 ms before the answer: 60 s of silence is an error AcpUpdateTracker -> ScrollbackState -> render_blocks_to_markdown # same path as /export AgentProcessGuard drop: cancel + join # also when the command fails ``` ```diff pi-pager/src/ -├── export_cmd.rs # read the old Rust session store +└── export_cmd/ + ├── mod.rs # entry and rendering + ├── replay.rs # agent and collection of the replayed updates + └── tests.rs # 18 tests, paused tokio clock ``` The answer to `session/load` can come before the last replayed messages. So the answer alone does not end the collection: only the quiet period does, and only a replayed update restarts it. Permission requests get `Cancelled`, other requests from the agent get method-not-found. `isReplay` is read from the notification `_meta` and from the update `_meta` (the Python agent sets it on the update). ## Evidence - **Before:** `resume_matrix.py` on `main` (plan section 10.12): 10 PASS / 3 GAP. Case 12, `zypi export <id>`, was GAP: `Session '<id>' not found.`, exit 1. **After:** 11 PASS / 2 GAP / 0 FAIL on WSL2. Case 12 passes. Case 13 (unknown id) passes as before. The two GAP that remain are `--resume <title>` and `--session-id <id in use>`; this PR does not touch them. Cases 12 and 13 now also fail when a process is left behind. - **Time** (debug build, WSL2; three runs each, the session file is not changed): | Entries | File | `zypi export` | Agent alone (`session/load`) | |---|---|---|---| | 2,000 | 1.4 MB | 2.5 s | 0.24 s | | 5,000 | 3.6 MB | 5.6-5.7 s | 0.53 s | | 20,000 | 14.2 MB | 62.7-64.1 s | 2.5 s | The agent is fast. The rest is `zypi` turning the messages into text, and that grows faster than the size. A release build was not measured. - **Tests:** 18 unit tests in `export_cmd::tests`. 15 run on a paused tokio clock: they cover the order of the updates, the quiet period, the silence limit, requests from the agent, refusals, and an agent that exits or says nothing. 3 cover the rendering. 1 end-to-end test, `export_replays_a_stored_session_through_an_agent_of_its_own`, against the real Python agent; `python_agent_e2e` passes 11 of 11. 1 new test in `scripts/tests/test_tui_pty.py`. - **Checks that ran locally** (on the code of the final commit; the last edits were documentation only): `tui_baseline.py check` exit 0 (warnings 0, residue words unchanged, dependency graph 980 = ceiling, `cargo check --workspace --tests` 0 errors). `ruff check` and `ruff format --check` pass. `pytest`: Windows 2,517 passed, 39 skipped, 31 deselected; WSL 2,540 passed, 16 skipped, 31 deselected. - **CI** (on `e3f3458`: [TUI CI run](https://github.com/zy1233/pi-python/actions/runs/38344799030), [CI run](https://github.com/zy1233/pi-python/actions/runs/38344799403)): every job passes. `Ruff`. `pytest` 3.11, 3.12 and 3.13. `Gates + cargo check`: warnings 0, `leader` 245 of 245, `x.ai` 304 of 304, graph 980 of 980. `cargo test`: 8,684 passed, 0 failed, 22 ignored (`pi-pager`: 5,627 passed, 13 ignored). `Release build baseline` is manual and was skipped. - **Not verified:** Windows and macOS (only WSL2 ran `zypi export`). Release-build time. A real model (the agent is a mock; export does not call the model). - **Found, not fixed** (details in plan section 10.15, "continuation 6"): the Python agent loops forever on a session file with repeated entry ids; `scripts/session_list_bench.py` writes such a file above 4,096 entries; the pager reads `isReplay` only from the notification `_meta`, the agent sends it on the update (the resume path was not checked). ## Merge Danger **Door:** two-way The change reads session files and writes none. A revert restores the old behaviour, which fails for every session of the Python agent. **Blast Radius:** export `zypi export` now needs a working agent command, as a resume does; it did not before. A very large session takes about a minute on a debug build. Nothing else in the pager changes. The new `test-util` feature of `tokio` is a dev-dependency only. Merged on the owner's order, into V0.5 (`v0.5.0`). The CHANGELOG entry moves from `[Unreleased]` to `[0.5.0]`, and the `zypi export` line leaves the known issues, in the commit that follows (it is part of #19, which merges after this PR). `tui/` changed, so the release size is measured again on the final tree (`release_baseline`, condition 2) and CI must pass on the tag commit (condition 1).
zy1233
added a commit
that referenced
this pull request
Oct 13, 2026
… r12, part 8) (#20) The owner asked to merge #18 and #19 and to put both into V0.5. Both are on main now (ec7d455 and 9058ab3). This commit moves their two CHANGELOG entries from [Unreleased] to [0.5.0], because the release page shows only the section of the tag's version. - CHANGELOG.md: the Fixed entry (zypi export) and the Changed entry (status row) move into [0.5.0]. The date is the release day (2026-10-13). The known issue about zypi export goes. The known issue about [ui.status_line] now says what is left: no context or cost items. Release notes preview with the awk program of release.yml: 49,429 B, five groups. - Plan: the status of exit conditions 1 to 3 on the final tui/ tree (CI and TUI CI green on 1fa18cf and on main, zypi 420,667,200 B, release dry run green), the new status of decisions 6 and 15, and the known issues. - Execution log: part 8. - docs/baselines/tui.md: the new release size and build time. Local pytest and cargo tests were not run again, as the owner asked. Ruff passes. CI ran the tests on the same tui/ and packages/ trees that the tag will have, because this commit changes documents only.
zy1233
added a commit
that referenced
this pull request
Oct 13, 2026
…raws nothing (#21) The Added entry for scripts/tui_pty/status_line_probe.py said that the row draws nothing with the Python agent. #19 changed that, and the Changed entry of the same release says so. The release page would have shown both. The entry now says what the probe found when it was written and what it checks now. I searched the [0.5.0] section for other lines that #18 or #19 made old, with these words: export, status_line, status row, session_status, resume_matrix, GAP, probe, tui_pty. This was the only one. The release notes grow from 49,429 B to 49,640 B (awk program of release.yml). The plan and the execution log have the new number.
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
[ui.status_line]drew nothing with the Python agent. The row waited for asession_statusmessage that only the old Rust agent sent. Nowzypibuilds the row from what it already knows. There is no change on the wire and no change in Python. This is plan decision 15, option (b). Option (a), a new message from the Python agent, was chosen first and did not pass the review below.the row is built again when the source agent changes + cwd, session id, session name, model, or "a turn runs" changes # ClientOwnedFields a forced or due refresh arrivesThe flag "a turn runs" is the reason for the last line. A command row runs on every tick of a turn and on no tick after it. Without the flag, the last run would keep the turn in its payload, and a timer would stay on a built-in row after the turn.
Why not option (a). DESIGN section 8.5 lists five points for a
pi/extension; one failed point is enough to refuse it. Two failed: (1) what the Python agent could truthfully send is known tozypialready, or has a standard place in ACP (agentInfo.version,usage_update); the agent itself knows neither the context window nor a price. (4) The message type would live inpi-shell, an old runtime crate. The user then chose (b).Evidence
status_line_probe.py(r11, plan section 10.14): 4 runs, the row was not drawn with the Python agent and a command script did not run.After: 4/4 runs pass on a real debug
zypi(WSL2, 23 s). Without a status from the agent: the row is on the screen; a script runs and gets session id, cwd, model, version,workspace.current_dir;context_windowis an empty object andcosthas onlytotal_duration_ms(noused_percentage,context_tokens,context_window_size,total_cost_usd). With a status from the agent (the stand-instatus_proxy_agent.py): the row shows what the agent sent.status_line_tests.rs(whatzypifills in, what an agent status keeps, the two clocks) and 7 indispatch/tests/status_line.rs(when the row is built again, what a script is given). 2 tests rewritten (they said the row waits for the agent). 1 adjusted:only_a_row_that_can_change_mid_turn_holds_the_loop_awake_for_one. The start of a turn now makes the row stale once, on purpose; the old test looked for ticks right after the turn start and failed. It now lets that one rebuild happen first. A built-in row still asks for no tick; a command row still asks for one.pi-pagersuite of the manifest (--lib,settings_e2e,grok_home_paths,selection_model_public_api,python_agent_e2e, with the manifest environment): 5,624 passed, 0 failed, 13 ignored (the reference is 5,608; the 16 new tests are the difference).tui_baseline.py checkexit 0: warnings 0, residue words unchanged, graph 980 = ceiling,cargo check --workspace --tests0 errors.ruff checkandruff format --checkpass.pytest: Windows 2,519 passed, 39 skipped, 31 deselected; WSL 2,542 passed, 16 skipped, 31 deselected.pin_ambiguous_title_errors_before_sandbox.mainand feat(tui): zypi export reads the session through the agent (plan decision 6) #18 fail the same way on my machine when the whole--libsuite runs, and the test passes alone. The cause is a leftover directory in my real~/.pi-python/sessions, from an earlier run that did not finish. The test uses fixed session ids. In a whole-suite run, another test pinsgrok_home()to the real home first, so the test writes there. An id that exists in two directories cannot be found by title (duplicate_legacy_id_is_not_title_addressabletests this), so the call returnsOkwhere the test wants an error. With an emptyHOMEthe whole--libsuite passes (5,340 passed, 0 failed); with onlysessions/copied in, it fails; with onlyconfig.tomlandtrusted_folders.tomlcopied in, it passes. After I removed the directory, the suite passed (above). CI runners are clean, so CI does not see it. I did not change the fixture.bfd5425: TUI CI run, CI run): every job passes.Ruff.pytest3.11, 3.12 and 3.13.Gates + cargo check: warnings 0,leader245 of 245,x.ai304 of 304, graph 980 of 980.cargo test: 8,681 passed, 0 failed, 22 ignored (pi-pager: 5,624 passed, 13 ignored, the same as my rerun).Release build baselineis manual and was skipped.main(on1fa18cf, where feat(tui): zypi export reads the session through the agent (plan decision 6) #18 is merged into this branch; TUI CI run 38353039701, CI run 38353039688): every job passes.Gates + cargo check: warnings 0,leader245 of 245,x.ai304 of 304, graph 980 of 980.cargo test: 8,700 passed, 0 failed, 22 ignored (pi-pager: 5,643 passed, 13 ignored). That is the base of 8,665 plus the 19 tests of feat(tui): zypi export reads the session through the agent (plan decision 6) #18 and the 16 tests of this PR.session_status(only the stand-in does).Merge Danger
Door: two-way
No data format, no wire message and no Python code changes. A revert brings back the old state: no row with the Python agent.
Blast Radius: status row
Only a user who sets
[ui.status_line]sees a difference. The row now appears where it did not.contextandcostitems stay empty with the Python agent; the guide page says so. The row is built once more at the start of a turn and once more at its end: one more build for a built-in row, one more run of the script for a command row. The agent status still wins when an agent sends one.Merged on the owner's order, after #18, into V0.5 (
v0.5.0). #18 and this PR changed the same documentation lines (CHANGELOG.md, the plan, the execution log), so the branch has one merge ofmain(1fa18cf); the code did not conflict. A documentation PR that follows moves both CHANGELOG entries from[Unreleased]to[0.5.0]and narrows the[ui.status_line]known issue (thecontextandcostitems stay empty).tui/changed, so the release build and the binary size are checked again on this tree (release dry run run 38353038777,release_baselinerun 38353042677; both started on1fa18cf).