New /perf/livepass.html mounts the real InteractivePane at production
scroll geometry and drives production-shaped SSE events through
handleEvent/replayHistory in real time (no virtual-time budget, no
forced reduced-motion — both corrupt the measurement), reporting:
replayHistory wall time at N messages, per-turn live-storm cost on top
of that transcript, tool_output_chunk throughput, busy/idle churn,
heap + node + agent-card counts across repeated replay cycles (the
detached-DOM leak probe), and longtask counts.
The --perf runner builds, serves, and launches headless Chrome with
--js-flags=--expose-gc and --enable-precise-memory-info so heap
numbers are real floors; the page POSTs its JSON report to
/perf/report. Reports carry a per-attempt run token the runner
validates, so a straggler POST from a killed prior attempt cannot be
misattributed to the next size, and the wait loop polls the Chrome
process so a sandbox startup failure bails to the --no-sandbox
fallback in seconds instead of burning the full timeout.
Address the Copilot review on #732 plus a task-agent sub-tool nesting
race surfaced alongside it.
Nesting (web UI):
- A sub-tool step whose task_agent row hasn't painted yet (the 4-wide
tool pool's ordering window) buffers and nests when the row lands,
instead of escaping to a top-level row that looks main-harness-issued.
- A row that never paints (id-correlation mismatch / aborted agent)
escapes its buffered steps back to a visible top-level paint after a
grace window, so steps are never buffered invisibly or leaked.
- The nested card survives the parent row's pending->resolved rebuild; a
call_id reused across turns builds a fresh card rather than stealing the
prior agent's steps.
- tool_info routes through the same nesting path (no duplicate top-level
row); a namespaced sub-tool result no longer grafts onto an unrelated
top-level row.
Denial reasons (backend):
- Preserve the specific denial reason a gate already stamped (operator
feedback, or the matched policy pattern; web and CLI contracts) instead
of clobbering it with a flat "Denied by user" -- in both the sub-agent
and the main tool loop.
Verified with the livepass task_agent harness (race + orphan-escape
scenarios, headless) and unit tests.
Final chunk of the task_agent modernization: rebuild a finished task
agent's card from /history (reload / reopen while the workstream is in
memory) and isolate each sub-agent's file-read tracking.
Recall: _project_agent_steps projects a sub-agent's trajectory into step
items (FIFO-per-call_id pairing via _iter_agent_tool_results, shared with
_cancel_ledger; output/arguments/count capped); _stash_agent_trajectory
keeps them on the UI in an LRU-bounded store; make_history_handler
attaches them as agent_steps to each task_agent tool_call, and
replayHistory/_replayAgentCard rebuild the collapsed card. In-memory only
(durable persistence deferred); a cold/evicted entry renders the flat
parent row ("not retained"), never a fabricated 0-step card.
Read isolation: _read_files (the blind-overwrite guard's memory) is now
per-sub-agent via the _active_read_files contextvar -- _exec_task copies
the parent's set on spawn and merges the agent's reads back on
completion, so a sibling in the 4-wide pool can't suppress another
agent's guard.
Also: _exec_task now self-reports the task_agent tool_result on every
path (the parent loop only reports error/denied results centrally) --
without it the live card never completed and a failed task recorded
is_error=False in the canonical trajectory. is_error flows from
_tool_error_flags to the recalled step; on_info suppression is per-thread
so a parallel sibling tool's progress isn't dropped.
Route a task agent's sub-tool events (tool_pending / approve_request,
tagged with parent_call_id) into a collapsible card under the task_agent
row, replacing the blue on_info turn-legs.
- conversation.js / interactive.js: buildAgentCardBody +
_routeAgentItems / _ensureAgentCard nest steps by parent_call_id.
Collapsed by default (a task agent can run 100+ steps and the parent
fans out many in parallel); the label carries the live count + state.
Auto-expand when a nested approval is pending so the blocking prompt
can't hide behind the toggle.
- session.py / session_ui_base.py: on_agent_step paints auto-tool step
rows; namespace child call_ids by parent so the 4-wide task pool can't
collide on local sequential ids (call_0); suppress sub-agent on_info on
the web pane (no call_id to nest by — the card carries steps + result).
- cli.py: on_agent_step prints a dim step leg (no card on the CLI, which
keeps its on_info).
- livepass.py: task-agent card harness driving the real InteractivePane.
Two-reviewer + sanity pass over the attachment previews:
- composer audio chip is icon+name+size only; the native <audio> player
renders on the sent message, not the staging chip (too heavy at chip scale)
- cap sent-message pills (+ in-pill audio/snippet) so they no longer overflow
the bubble at narrow widths; player and snippet drop to their own row
- clamp the chip filename in shared chat.css so long names ellipsize instead
of wrapping (console main + coordinator previously left it unclamped)
- merge the duplicated .composer-chip rule; drop unused kind-modifier classes
and inert vertical-align / inline-block declarations
- fix undefined var(--bg-base) -> var(--bg-surface) thumbnail backing
- label the <audio> control (aria-label) and drop the decorative snippet from
the a11y tree
scripts/livepass.py: add an attachments harness that drives the real
createAttachmentController + Pane.addUserMessage so these surfaces render
headlessly for review.
Four refinements from first live use:
- Per-pane ✕ chip, top-right of every visible pane. Split mode: hide
that cell (closeCell — the tab stays, the sibling absorbs the space).
Single-pane: close the pane outright (withheld from the unclosable
Dashboard). The click decides at click time; the label tracks the
mode. Manager-injected into the pane section — content untouched.
- Coordinator child links open BESIDE the coordinator (openPaneBeside:
split right of the focused cell, seeded with the child pane) instead
of replacing it — the parent stays on screen. Degrades to the plain
focused-cell swap when the split is denied (cap / narrow viewport).
splitFocused() gained an optional explicit-fill parameter for this.
- Tier-1 ws_closed now CLOSES the open interactive pane (tab gone, a
split cell collapses) — the coordinator-closes-its-child flow,
matching the standalone's pane-auto-close. The dead-banner lane
stays for streams that die without a ws_closed (node crash/network),
where the session may still be revivable.
- Paint bug: the focused-cell ring was an inset box-shadow on the
section, which paints in the element's own background layer — UNDER
opaque children touching the edges, so the status bar / composer
strip occluded it. The ring now rides a click-transparent ::after
overlay above pane content; the 2px top bar sits above the ring line.
The livepass shell surface's demo panes grew a .ws-status-bar footer so
the occlusion bug class stays visible to future passes.
Revives the split-pane feature retired with ui/static (step 6), rebuilt
on PaneManager: an optional binary layout tree (null = the one-pane-per-
tab behaviour, unchanged) renders visible panes as %-inset cells — no
reparenting, so live stream DOM, scroll state and media survive layout
changes. Tabs stay global: the active tab is the focused cell, a
backgrounded tab swaps into it, clicking inside a visible pane focuses
its cell, .shown marks visible-unfocused tabs. Separators resize by
pointer-capture drag and arrow keys (role=separator + aria-value*); the
tree persists in the working-set blob and rehydrate prunes leaves whose
pane did not restore. Limits: 6 cells, 200x150 cell minimums, denials
toast the manager's reason.
Affordance: Split right / Split down / Unsplit buttons in the tab-bar
tail replace the redundant [+] (the permanent Dashboard tab is the
launcher) — deliberately no contextmenu override this time. The dead
TS_APP.focusLauncher seam goes with it.
Measured chrome: the focused cell wears a 2px accent top bar (no thin
tinted ring clears 3:1 in both themes) plus a 55%-mix inset ring;
separators rest at --ink-4 with solid-accent hover/drag/focus; .shown
tabs carry an accent underline; the tail cluster is fenced and lifted
to --ink-3.
scripts/livepass.py grows a third surface: shell/livepass.html boots
the real shell.js + pane.js and drives ?split=right|down|three|none
(+ &theme=light), stamping SPLIT-READY-<cells> / SPLIT-FAILED-<reason>.
Designer-review round on the scroll fix found the harness's dialog-tier
gate silently green: confirm-dialog (and install/coord-delete) markup
lives OUTSIDE #admin-layout, so the fragment extraction never embedded
it — ?open=confirm threw at showConfirmModal and screenshot a normal,
dialog-less page. build() now injects every hatch dialog the fragment
does not already contain, and a driven ?open= that ends with no open
dialog stamps OPEN-FAILED-<state> into the title instead of passing.
Also upstreams the review's probe states: &focuslast=1 focuses the last
shelf-body control (the displaced-dock regression class — only .sh-body
may scroll; head/foot must stay pinned) and &scrolled=bottom shows the
24px scroll tail.
The L-shell height-pins the admin chain and .hatch-host clipped it, so no
box below the pane could scroll: tabs taller than the pane were cut dead,
and the overflow:hidden host doubled as a hidden scroll container that
focus-into-view silently scrolled — visually-hidden toggle/cap/radio
inputs escape the .sh-body scroller (abspos under an unpositioned label),
overhang the shelf, and a Tab keypress shoved the docked hatch off its
head with no scrollbar to recover by.
- .admin-content becomes the manage pane's interior scroller (the #main
precedent); switchAdminTab resets it on real tab changes only
- .hatch-host: overflow hidden -> clip — paint clipping without a scroll
container, so focus can never displace the dock
- position:relative anchors on the three hidden-input labels
(toggle-switch, .sh-body .cap, segmented-option); .settings-toggle
already carried one
- livepass: the console harness wraps the fragment in the REAL L-shell
chain (its bespoke height pin is exactly how this bug class stayed
invisible to the screenshot gates) and gains a ?tall=1/&scrolled=1
scroll state
The wiring-lint test used percent-formatted regex patterns — UP031 under
the ruff 0.15.6 the CI pre-commit pins (the older venv binary let it
through; checked repo-wide against the exact pin now). f-strings with
doubled quantifier braces, plus one over-long fixture line in the
livepass generator split.
Copilot threads, both validated rather than blindly applied:
- closeShelf's scrim-ownership scan now skips detached entries. The
thread's throw scenario doesn't occur on the real removal path (a pane
close detaches an ANCESTOR, so _hostOf still resolves inside the
detached subtree) — but a detached shelf is genuinely not a scrim
owner, so the guard is correct beyond being defensive.
- toast.js drops the popover attribute via removeAttribute instead of
the null assignment. The claim that null leaves popover="null" is
refuted — the IDL is nullable and null removes the attribute (verified
empirically in headless Chrome) — but removeAttribute reads correct
without requiring that spec knowledge.
The livepass harness — the headless-render rig that verified every
converted modal surface and click-drives submits (the dead-Save bug
class) — lived as ad hoc files in /tmp and got wiped once already.
The durable piece is the GENERATOR: the markup is extracted fresh from
the index files at build time (a committed snapshot would drift) and
the stylesheets/scripts are symlinked so edits are live on refresh.
scripts/livepass.py builds both harnesses into /tmp/livepass/ (ui:
all six dialog-tier surfaces incl. the real cards.js batch controller
drive; console: the admin-pane fragment hosting the shelves, with
schedule/model/policy/confirm/token fixtures and the model-save click
drive that flips document.title to PUT-OK-<n>). --serve included;
the chrome screenshot incantation and the ?open= registry are in the
module docstring. Governance fixtures (roles/HR/OGP/memory/skill) are
documented seams for when those surfaces need driving.