refactor(engine): output inversion batch 2 — the lifecycle cluster loses its terminal imports (#429) - #461
Merged
Conversation
Batch 2 of #429: session_start, session_run, providers and hooks lose every terminal-cluster import (3/5/1/2 -> 0/0/0/0). What they used to print is a typed event now, rendered by session_render.zig — the lifecycle's terminal half, sibling of agent_stream_render.zig and agent_tool_render.zig. Eight new variants, each a moment the vocabulary had no way to say: session_notice the shared shape, reused at eleven call sites so the cluster does not need a variant per line. `tone` is the only rendering choice delegated (it is about meaning); `lead` carries a badge like "⚠ YOLO" that is colored apart from the rest of its line. session_banner the startup line. The key hints on it are frontend knowledge and moved into the sink. worktree_entered -w entered its scratch checkout; inline styling on the path forces structure. saved_model_unavailable startup's twin of provider_fallback. Fields, not prose: a frontend offering "use it anyway" needs them. mcp_consent_prompt TRANSITIONAL — only the QUESTION is inverted; the stdin read stays for #430. provider_fallback the one durable variant, because this moment has always been the wire's `model` event. The wire's fixed note and the terminal's context note differ, and that difference now lives at the one translation point instead of at the call site. session_saved the durable session file landed. run_finished #396's terminal handoff: the engine says the run is over, a terminal frontend hands raw mode back. Two of those four files printed nothing at all — hooks.zig wanted only the settings path and one Windows pipe peek, so those move to leaves (harness_settings.zig, win_api.zig) that approvals.zig and term.zig re-export. session_run's settings/theme phase does legitimately draw, so it moves whole to session_settings.zig rather than pretending otherwise. engine_sink.zig gains a writer-backed sink: this cluster runs before an Agent exists and after it stops mattering, and a null writer (a one-shot, `acp`) is a normal state, not an error. Its tests move to engine_sink_tests.zig — the file was one line under the ceiling. Line cap relieved where #422 said it would be: session_run 597 -> 450, session_start 461 -> 465, providers 589 -> 586, engine_sink 528 -> 327, term 564 -> 519. Nothing over 600. Proven byte-identical: 17 scenarios run under a real PTY against a binary built from main and one built from this branch, diffing RAW terminal bytes, escape codes included — banner, worktree, YOLO badge, displays-on, both MCP config reports, the consent prompt answered both ways, skipped servers, companion auto-connect failure, approvals/hooks loaded, saved-model-unavailable (blocked and not), both selftests, a fatal one-shot, --json, and a real turn through the offline mock down to the session-saved line. All identical. 1052/1052 tests pass (1043 before, +9), tier 1 green, Windows cross-build clean. Co-Authored-By: Codegraff <blackfloofie@codegraff.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.
Batch 2 of #429 (part of epic #422). Four files, zero banned imports remaining in each.
session_start.zigsession_run.zigproviders.zighooks.zigTwo of those imports (
anim/pickersinsession_start) were already dead on main.session_run.zigdropping 597 → 450 matters beyond this PR. It was at 599/600 with #440 applied, i.e. one line from the hard cap. This is the epic's claim that inversion "relieves 600-line cap pressure in exactly the files pinned at it", realized.Byte-identity: verified against raw terminal bytes
Two binaries built from
git archive mainand from this branch, run under a real PTY in identically seeded workspaces, diffing raw bytes including escape codes (only the random trace id, unix-ms session name and timings normalized).17 scenarios, all identical: banner in colour and no-colour, worktree line, YOLO badge, displays-on, both MCP invalid-config reports, the unsupported-config path, the consent prompt answered
yand declined, skipped-servers, companion auto-connect failure, approvals+hooks loaded, saved-model-unavailable both blocked and unblocked,--selftest-spinner,--selftest-markdown, fatal one-shot,--json, and a real turn againstscripts/openai_mock.pydown to the↩ session saved →line.Transcripts were dumped to confirm the harness actually renders the converted lines rather than comparing two empty runs, e.g.
\x1b[31m⚠ YOLO\x1b[0m mode (--yolo): …. Exact-byte unit tests cover what the PTY cannot reach:provider_fallback(terminal line, stderr fallback, and the{"seq":N,"type":"model",…}wire line), the save-failed warning, and every notice tone.scripts/test-tty-release-on-exit.py(the #396 guard, which is precisely whatrun_finishedre-plumbs) passes.Eight new events
session_notice(a shared shape reused at 11 call sites, wheretoneis the only delegated rendering choice andleadcarries a coloured badge), plussession_banner,worktree_entered,saved_model_unavailable,mcp_consent_prompt,provider_fallback,session_saved,run_finished. Onlyprovider_fallbackis durable — it has always been the wire'smodelevent. Structured variants exist only where inline styling or frontend-relevant fields forced them; everything else reusesNotice.Two design points worth review attention:
engine_sink.writerSink(?*Io.Writer)is new machinery rather than a second pattern. This cluster fires before an Agent exists and after it stops mattering, soforAgenthas nothing to hand it. A null writer (one-shot,acp) is a normal state; only the failover notice speaks then, to stderr, as before.enableColor()crosses the boundary as a call, not an event — it settles what the palette is before anything is drawn.Left for #430, deliberately
session_start.initRegistryConsentstill reads the consent answer inline viain.takeDelimiter. Only the question is inverted; the read is input-shaped and belongs to input inversion.mcp_consent_promptis marked TRANSITIONAL in the vocabulary so #430 replaces it rather than extending it.Verification
zig build test1052/1052 (baseline 1043, +9).scripts/eval-tier1.shgreen. Windows cross-build (-Dtarget=x86_64-windows-gnu) clean, which matters here because the win32 shim moved files.Integration note
This branch and #458 (batch 3) both edit
engine_events.zigandengine_sink.zigsubstantially in opposite directions (batch 2: sink 528→327, events 330→449; batch 3: events 330→420, sink 528→533). They will conflict and are being integrated deliberately rather than merged independently. Batch 4 (agent.zig) should be cut from the integrated result, since it depends on the vocabulary both batches extend.scripts/test-model-preference.pyfails with exit 127 on this branch and on main's tree — a pre-existing environment problem with that script, not a regression.