mirror of
https://github.com/block/buzz.git
synced 2026-11-05 17:01:35 +00:00
A tool item's `executing`/`pending` status is written when the step starts and never revised if the agent dies first, so an abandoned step keeps it forever. On the activity feed that was a stale row label. In the work block this commit introduces it is a MODE: one `running` entry makes `summarizeWorkBlock` report `isActive`, which suppresses the folded summary line, holds the rail open and pulses a bullet. Scrolling back to a crashed turn therefore showed the reader live work indefinitely (Codex on #6536, discussion_r3848214880 — not stale, and worse here than on the card it was filed against). Status alone cannot answer "is this step happening?", so the block is given the missing half: `AgentSessionTranscriptTurnMeta.liveTurnId`, published by the list from the same display blocks it already reads for `streamingItemId`. `toolEntryState` treats `executing`/`pending` as `running` only when a session owns that step's turn. A turn id, not a boolean. "Some turn is live" is not the question: an agent that crashed during turn 1 and is now working on turn 2 IS live, yet turn 1's abandoned step is no more running than before — a global flag would keep it spinning in exactly the case a restarted agent makes common. The comparison is explicit (`liveTurnId !== null && ...`) so an item with no turn id cannot match a null live turn by `null === null`. An abandoned step is reported as `settled`, not as a new state and not as `failed`, per core-02: - We do not know it failed — only that nobody finished it — so it must not count toward the folded line's `N failed`. It folds to a neutral `N steps`. - No third entry state and no new glyph. It renders as the neutral step it is, with the same muted detail the list already shows for it outside a block. A visible "interrupted" marker would be a design addition, not a bug fix. `liveTurnId` is read from the display blocks rather than from the active-turn store, because the store's turn ids and the transcript's are populated by different paths and a mismatch would silently gate every step off — the mirror image of this bug. It is the last *turn* block, not the last block: a compaction notice arrives as a `single` block after the turn it belongs to, so reading the final block's kind would report no live turn at all. `projectWorkBlockEntries` takes the option as a required argument rather than defaulting it, so a future caller that has not thought about liveness cannot silently get the spins-forever behaviour back. Non-vacuous by mutation, each mutant run in isolation against the unit suite: - gate removed (always `running`): 4 failures. - gate inverted (`executing` never `running`): 5 — this is the fix that would have "passed" the bug report while breaking live work. - truthiness instead of turn ownership (any live turn resurrects an old abandoned step): 1. - `item.turnId === liveTurnId` without the null guard: 1. - abandoned step reported `failed` instead of `settled`: 4. - `liveTurnId` forced null in the meta builder: 2. - `lastTurnId` reading only the final block: 1. Measured in a real browser as well, not only JSDOM, because the failure mode is presentational and animation-dependent — a unit test can assert class names while the rendered rail is still wrong (the interim-note and relay-send bugs on this branch were both invisible to the unit suite). Two seeded scenarios in the preview harness, driving the real component through the cover drawer under Buzz Dark: - agent panic mid-step (the terminal `crates/buzz-acp` actually emits): folded label `3 steps`, rail collapses to 0 rows, `getAnimations` reports 0 infinite animations in the block, and opened the rail reads `["settled","settled","settled"]`. - the same events with no panic: no folded line, rail open, states `["settled","settled","running"]`, and exactly 1 infinite animation — the running bullet really does pulse. Both browser scenarios were mutation-checked too: removing the gate fails the first and passes the second, inverting it fails the second and passes the first, so neither can be satisfied by a one-sided fix. (Those two specs are not in this commit — they need Slice A's `conversation` variant pin to be reachable, which is not on this branch.) The row outside a block is untouched: `buildCompactToolSummary` still derives its own `running` from status, and no file under `activityRenderClasses/` or `agentSessionToolSummary.ts` is modified here. Verified at this tree: full desktop suite 5486 passing / 0 failing, `tsc --noEmit` clean, `pnpm check` findings identical to main's baseline (2 warnings + 2 infos, all pre-existing). Co-authored-by: Bradley Axen <baxen@squareup.com> Signed-off-by: Bradley Axen <baxen@squareup.com>
Buzz
Desktop chat shell with:
- Tauri + React + TypeScript + Vite
- Tailwind CSS
- shadcn/ui-ready shared components
- Biome (lint/format/check)
- Feature-driven frontend structure
Scripts
pnpm dev- run the web frontendpnpm tauri dev- run the desktop apppnpm build- typecheck and build frontendpnpm typecheck- TypeScript checkspnpm lint- Biome lintpnpm format- Biome format (write)pnpm check- Biome check
Structure
src/shared- reusable app-wide code (ui,lib,styles)src/features- feature modules (vertical slices)src/app- top-level app composition