Repository navigation
feat(tui): zypi export reads the session through the agent (plan decision 6) - #18
Merged
Merged
Conversation
`zypi export <id>` read the session files of the old Rust agent. For every session that the Python agent stored, it answered "Session '<id>' not found." (plan decision 6, option (a)). It now asks the agent, as a resume does: it starts the agent, sends `session/load`, collects the `session/update` messages that the agent replays, and renders them with the same code as `/export`. Output to stdout, to a file and to the clipboard is as before. - `export_cmd` becomes a directory: `mod.rs` (entry and rendering), `replay.rs` (agent and collection), `tests.rs` (18 tests). - The answer to `session/load` can arrive before the last replayed messages. The command waits until no replayed message has come for 500 ms. An agent that says nothing for 60 s is an error. A permission request gets `Cancelled`, any other request gets method-not-found. The agent is cancelled and joined when the command ends, also when it fails. - The `isReplay` marker is read from the notification `_meta` and from the update `_meta` (the Python agent sets it on the update). - `resume_matrix.py` cases 12 and 13 now require a success or a clear refusal, and no process left behind. The user guide, the README of `scripts/tui_pty`, `tui-support.md` and the CHANGELOG are updated. Measured on a debug build in WSL2: 2,000 entries 2.5 s, 5,000 entries 5.7 s, 20,000 entries 63 s. The agent alone needs 0.24 s, 0.53 s and 2.5 s. Tests: 18 unit tests (paused tokio time), 1 end-to-end test with the real Python agent, matrix 11 PASS / 2 GAP / 0 FAIL. The plan (section 10.15, continuation 6) has the details and three findings that this change does not fix.
PR 18 is open and CI passes on e3f3458: Ruff, pytest 3.11 to 3.13, Gates + cargo check (warnings 0, graph 980 of 980), cargo test (8684 passed, 0 failed, 22 ignored). Replace "this PR" with the link.
zy1233
added a commit
that referenced
this pull request
Oct 13, 2026
## Summary
`[ui.status_line]` drew nothing with the Python agent. The row waited for a `session_status` message that only the old Rust agent sent. Now `zypi` builds 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.
```text
shell_status_context()
start from the status the agent sent # it wins; empty if none
fill the empty fields from ClientOwnedFields:
cwd, session id, model id and name, version
session name: always the client's, because a rename happens here
fill_from_clocks:
cost.total_duration_ms = time since the session was attached
turn.started_at_ms = start of the running turn
left empty: context, the rest of cost, branch, repo # nobody on this side can know them
```
```diff
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 arrives
```
The 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 to `zypi` already, 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 in `pi-shell`, an old runtime crate. The user then chose (b).
## Evidence
- **Before:** `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_window` is an empty object and `cost` has only `total_duration_ms` (no `used_percentage`, `context_tokens`, `context_window_size`, `total_cost_usd`). With a status from the agent (the stand-in `status_proxy_agent.py`): the row shows what the agent sent.
- **Tests:** 16 new unit tests: 9 in `status_line_tests.rs` (what `zypi` fills in, what an agent status keeps, the two clocks) and 7 in `dispatch/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.
- **Checks that ran locally** (WSL2, debug build; on the code of the final commit, the last edits are documentation only): the `pi-pager` suite 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 check` exit 0: warnings 0, residue words unchanged, graph 980 = ceiling, `cargo check --workspace --tests` 0 errors. `ruff check` and `ruff format --check` pass. `pytest`: Windows 2,519 passed, 39 skipped, 31 deselected; WSL 2,542 passed, 16 skipped, 31 deselected.
- **A failure that this change did not cause:** my first run of the suite had 1 failure, `pin_ambiguous_title_errors_before_sandbox`. `main` and #18 fail the same way on my machine when the whole `--lib` suite 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 pins `grok_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_addressable` tests this), so the call returns `Ok` where the test wants an error. With an empty `HOME` the whole `--lib` suite passes (5,340 passed, 0 failed); with only `sessions/` copied in, it fails; with only `config.toml` and `trusted_folders.toml` copied 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.
- **CI** (on `bfd5425`: [TUI CI run](https://github.com/zy1233/pi-python/actions/runs/38348219064), [CI run](https://github.com/zy1233/pi-python/actions/runs/38348219013)): 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,681 passed, 0 failed, 22 ignored (`pi-pager`: 5,624 passed, 13 ignored, the same as my rerun). `Release build baseline` is manual and was skipped.
- **CI after merging `main`** (on `1fa18cf`, where #18 is merged into this branch; [TUI CI run 38353039701](https://github.com/zy1233/pi-python/actions/runs/38353039701), [CI run 38353039688](https://github.com/zy1233/pi-python/actions/runs/38353039688)): every job passes. `Gates + cargo check`: warnings 0, `leader` 245 of 245, `x.ai` 304 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 #18 and the 16 tests of this PR.
- **Not verified:** Windows and macOS (the probe runs on Linux only). A real model (the agent is a mock; the data in the row does not depend on the model). A real agent that sends `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. `context` and `cost` items 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 of `main` (`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 (the `context` and `cost` items stay empty). `tui/` changed, so the release build and the binary size are checked again on this tree (release dry run [run 38353038777](https://github.com/zy1233/pi-python/actions/runs/38353038777), `release_baseline` [run 38353042677](https://github.com/zy1233/pi-python/actions/runs/38353042677); both started on `1fa18cf`).
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
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).The answer to
session/loadcan 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 getCancelled, other requests from the agent get method-not-found.isReplayis read from the notification_metaand from the update_meta(the Python agent sets it on the update).Evidence
Before:
resume_matrix.pyonmain(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):
zypi exportsession/load)The agent is fast. The rest is
zypiturning 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_e2epasses 11 of 11. 1 new test inscripts/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 checkexit 0 (warnings 0, residue words unchanged, dependency graph 980 = ceiling,cargo check --workspace --tests0 errors).ruff checkandruff format --checkpass.pytest: Windows 2,517 passed, 39 skipped, 31 deselected; WSL 2,540 passed, 16 skipped, 31 deselected.CI (on
e3f3458: 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,684 passed, 0 failed, 22 ignored (pi-pager: 5,627 passed, 13 ignored).Release build baselineis 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.pywrites such a file above 4,096 entries; the pager readsisReplayonly 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 exportnow 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 newtest-utilfeature oftokiois 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 thezypi exportline 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).