feat(members): give every crew a configurable webview in its drawer - #8468
feat(members): give every crew a configurable webview in its drawer#8468chenmingwei23 wants to merge 1 commit into
Conversation
Design Review (Fable 5) — ✅ PASSDesign-level review of All key claims verified against the diff: the sealed-ceiling test was widened (original invariant kept, per-disposition reconciliation added), the sandbox dispositions land in the pinned lists, the publish/read auth split matches the description, the frame sandbox is Design-Verdict: PASS Template/data split with all three write paths closed at the OS layer is the right shape; trade-offs are disclosed, tested, and reversible. [DESIGN-REVIEWED] 0d2ceae |
UX Review (Fable 5) — 🟡 CONCERNSUX-level review of Reconciliation complete: the blind reader correctly identified both primary controls ("Open dashboard" — "sure"; "Collapse the dashboard" — "sure… feels safe") and read the two surfaces as "the two directions of the same door." No lens-13 block: the docked↔expanded split is a hard swap in the code ( UX-Verdict: CONCERNS Blind read comprehends the whole surface, but "Contained" is ambiguous, the docked↔expanded flip has no recording, and the empty-state screenshot shows stale copy. Watch
Evidence gaps
[UX-REVIEWED] 0d2ceae |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of All evidence gathered. Here is the review. First-Principles-Verdict: CONCERNS Four seams for choosing a second template ship while exactly one template exists anywhere — the named first consumer (the conductor) gets no bespoke template. Not justified as shipped
What this change shipsInventory (10 items) — 7 justifiedIntent: let a person supervising a long-running crew see, in the drawer they already use, whether the crew needs them — an ADDITION.
Watch
Subtractions
[FIRST-PRINCIPLES-REVIEWED] 0d2ceae |
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsFINDING -- src/kiro_crew/agent_panel.py:341 -- False positive or not applicable? A repository writer can comment: |
Opus 4.8 Review — ✅ no blocking findingsReviewed Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
cae7136 to
541af4c
Compare
541af4c to
9bb189f
Compare
9bb189f to
63885d7
Compare
63885d7 to
4ca968f
Compare
953141a to
e464d66
Compare
commented
Sep 5, 2026
|
/ai-review override gpt e464d66: The package tree is not fenced from agent file tools at all: is_sensitive_path is False for this template AND for agent_panel.py and security.py, so an editable checkout that can rewrite a template can already rewrite the security module itself. Seven other packaged assets load the same way (bundled agent config, MCP defaults, vendored code, model registry, docs) and none is hash-verified. Fencing only this one would be a lone exception against a premise that defeats the product. |
commented
Sep 5, 2026
Human judgment recorded@chenmingwei23 marked the gpt AI finding as false positive, not applicable, or explicitly accepted for
This decision applies only to this commit. A new push requires a new judgment. |
e464d66 to
c6bb985
Compare
commented
Sep 5, 2026
|
/ai-review override gpt 983ebfd: Bounded and recoverable, the record the adjudication could not complete: template markup reaches ONLY an iframe with sandbox=allow-scripts under CSP default-src/connect-src/form-action 'none' (null origin, no cookies, no parent DOM, no popups, no top-nav), and the docked summary renders crew strings as React text with a hostile-string test pinning it -- not injection into trusted UI. Next publish replaces the record. No Windows file enforcement exists for ANY leaf; this asks for a Windows sandbox backend. |
commented
Sep 5, 2026
|
AI-review override not recorded: keep the reason to 500 characters or fewer. |
commented
Sep 5, 2026
|
/ai-review override gpt 983ebfd: Bounded and recoverable, the record the adjudication could not complete: template markup reaches ONLY an iframe with sandbox=allow-scripts under CSP default-src/connect-src/form-action 'none' (null origin, no cookies, no parent DOM, no popups, no top-nav), and the docked summary renders crew strings as React text pinned by a hostile-string test. Next publish replaces the record. No Windows file enforcement exists for ANY leaf; this asks for a Windows sandbox backend. |
commented
Sep 5, 2026
Human judgment recorded@chenmingwei23 marked the gpt AI finding as false positive, not applicable, or explicitly accepted for
This decision applies only to this commit. A new push requires a new judgment. |
commented
Sep 8, 2026
|
@chenmingwei23 This PR overlaps with #8947 (open, not a draft), which also adds a second surface to the Members detail drawer. Audited at 983ebfd. Shared files: What differs: #8947 makes the drawer bi-modal. It adds The two edits sit at different depths, so the collision is mechanical rather than semantic, and both can land. #8947 is the further along of the two: it is not a draft and touches 27 files, while this PR is a draft, touches 60 files, and is dirty with a merge conflict at 291 commits behind main. Suggestion: let #8947 land first, then rebase this PR onto it and decide whether the crew webview should be a third @CrysisDeu for visibility on the drawer ordering. Posted from the 2026-09-08 open-PR relationship audit (read-only, one auditor per PR); reply here if any of this is wrong. |
commented
Sep 8, 2026
|
Rebased onto main
Local gates: black/isort/flake8 on changed files clean ( Please review the sandbox resolution in particular. A maintainer push makes the maintainer the last pusher, so a second approver is needed under the repo's last-push rule. Reply if anything looks wrong. |
commented
Sep 9, 2026
|
@iamwhatever answering both halves of your review, and pinning down where this actually sits.
1. UX review: the lane has run on the current head.
Being precise rather than just pointing at green: the lane's own verdict on that head is CONCERNS, not PASS. It still reports a green check because the lane gates on BLOCK only ("A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory"). What it has left is advisory, and I would rather you see it than discover it:
None of those blocks readiness and I have not treated them as blocking. Say the word if you want any of them fixed here rather than followed up. 2. The right panel: agreed, and that is where this already is. The webview is not a second surface. It renders inside the crew detail drawer, and that drawer IS the right panel: So when more conductor/crew detail lands in the right panel, it lands beside this section inside the same shell. The webview is one section of that panel, headed "Dashboard" - not an alternative route to the same information. One exception I would rather name than have you find: expanding the dashboard is a full-window overlay ( CI on |
commented
Sep 9, 2026
|
@iamwhatever this is now out of draft and ready for review. It sat as a draft while your review was open, which is on me: a draft is easy to read as "not asking yet", and that is probably part of why it stayed quiet for four days. Both halves of your review are answered in the comment above this one: the On the current state: my head's own checks are 63 success, 0 failure. The branch shows conflicts because main has moved several times today while this waited; that is base drift, not a broken change, and I am rebasing it now. Nothing about the diff you would review changes when I do. |
commented
Sep 9, 2026
|
Green on @iamwhatever this is ready whenever you have a moment. Your review is the only thing left, and both halves of it are answered in the comments above rather than repeated here. |
A crew that runs unattended accumulates the state an operator actually wants:
how many workers it is holding, which one is stuck, what it is waiting on. The
drawer showed activity counts, a path list and config instead, none of which
answer those. This adds a free-form HTML webview that the crew fills in at
runtime.
The split that makes free-form HTML safe: a human authors the TEMPLATE --
versioned in the repository, or dropped on disk by the operator -- and the crew
publishes only DATA. The template directory is fenced from agent file tools, so
a crew cannot author its own markup; the only way it influences its webview is
by publishing a data object. The data lands as an inert application/json island,
escaped so a published string cannot close the script element early, and a
template reads it with JSON.parse and renders through textContent only. The
composed document is served behind a single-use minted URL into an iframe
sandboxed to allow-scripts and nothing else.
A crew whose name matches an installed template gets that template; a crew with
no template of its own falls back to a generic one that renders any data object.
That is the entire wiring mechanism -- installing <crew-name>.html is the act of
wiring a bespoke view up, so there is no registry, no mapping table and no
per-crew config to keep in sync.
The MCP server ships registered but gated off for every agent (opt_in), so a
default session pays nothing for the capability: not a tool-list entry, not a
line of context, not a spawned backend. An agent that should publish a panel is
granted the set in its own spec, and a refresh keeps an existing grant current
without ever minting one.
The publishing crew is derived from the vetted calling session and never from the
request body, so one crew cannot publish a webview that presents as another's. A
body field naming a crew is rejected by the schema rather than ignored, so a
caller is never left believing it retargeted the write. The drawer's read at GET
/api/members/{slug}/panel is cookie-authed; the /api/agent-panel writes sit
behind the MCP-only prefix.
Published field order is preserved end to end. Field order is presentation -- a
template renders a stat strip in key order -- so the record is dumped without
sorting keys instead of silently alphabetising the operator's dashboard.
The crew's dashboard, docked in its drawer -- the crew decides what goes here:
Expanded to the full window:
A crew that has published nothing says so, rather than showing an empty box:
1. What is the problem
A crew has nowhere to show its own state.
The member drawer shows what KiroCrew knows about a crew -- session counts, wake sources, configuration. It shows nothing the crew itself wants to say. A conductor running a fleet of workers knows things no generic UI can guess: which item is blocked on a human ruling, which worker has been stuck for two cycles, what it intends to do next, and why a count does not add up. Today that exists only in chat scrollback, so it is gone by the next turn and invisible unless someone reads the transcript.
The obvious fix does not scale. A native panel per crew means every new crew shape needs a frontend change, a release, and a schema the crew must be taught. And a crew is an LLM: whatever it produces cannot be handed the dashboard's origin.
2. Why it matters to the user
Someone supervising a long-running crew has to read a transcript to answer the only question they actually have: does this need me? The drawer is where they already look, and it cannot tell them.
This also removes a per-crew frontend release from the loop. A crew that wants to surface something new publishes different data; nobody ships a UI change to make it visible.
3. How our fix solves it
The root cause is that presentation was coupled to the frontend release cycle, and the only party that knows what matters -- the crew -- had no way to influence it.
The fix splits the panel in two: a human-authored template and crew-published data. Only the data comes from the crew.
Records live in a gateway-only, sandbox-hidden subtree. This is the part worth reading closely, because two earlier placements were wrong and the reasons are the design. Storing under the crew's own member space failed because
members/is deliberately unfenced -- a crew owns its own published data -- so nothing stopped one crew writing another crew'spanel.jsondirectly, forging its state past both ownership resolution and the redactors in a single write. Moving totrust/closed that route through the agent's file tools but not through a sandboxed shell, which can construct the path at runtime and so is never caught by command matching. The record now lives in a dedicated hidden leaf with a precreate disposition, so the directory exists before an agent can create it: a fence only fences a path that is there. All three write paths -- the MCP route, agent file tools, and a sandboxed shell -- are closed on the same location, and the module docstring records why each earlier placement was rejected so the next reader does not undo it.Template selection needs no registry. A crew whose name matches an installed template gets that template; everything else gets
default. No mapping table, no per-crew config, and a bespoke template activates by being installed.defaultrenders any data object: scalars become a tile strip, sentences become their own section, arrays of objects become tables with derived headings, arrays of scalars become lists, objects become key/value blocks. Fields render in the order the crew published them, so a crew that puts its alert first gets it read first.Rendering is bounded, not just the input. The data cap bounds what a crew can publish, but derived table headings take the union of keys across rows, so a payload well inside that cap could still expand to millions of cells and hang the browser. Rows and unique columns are capped independently, with an explicit truncation notice rather than a silent drop.
Two conventions come from the Issue Radar Pipeline dashboard, which solves the same legibility problem at a larger scale. A missing value renders as an explicit placeholder rather than vanishing, so "we do not know" is distinguishable from "none". And a
<field>_notekey renders as a caveat directly under the thing it qualifies, which is how a crew explains why a number does not reconcile.Writes are MCP-only. A new opt-in server (
kirocrew-panel) carriespanel_publishandpanel_templates, registered across every declaration surface the repo has -- the managed-server registries, the schema-posture view, the redaction sinks, the MCP call-site auth scan, the sandbox mask, the feature map, and the architecture specs -- and gated off for every agent by default. Neither tool takes a crew or session argument: the publishing crew is resolved from the vetted calling session through the shared strict-session gate, never from the request body, and never from the lenient resolver that would resolve a subagent to its parent's crew. The publish routes are strict-internal; the drawer's read route is cookie-authed and deliberately outside that prefix. The read route takes one snapshot and composes both halves from it, so the summary and the document can never disagree.The drawer has two states, and only one of them is a document. Docked is a native summary -- title, the crew's leading sentence, a few counters, and a labelled button. Expanded is the crew's document in a sandboxed frame. This is not cosmetic: a 250px column cannot host a dashboard, and trying put the most important line below the fold. It also means no document is minted until someone opens it, and the minted URL is single-use, so the frame is created on first expand and kept mounted across collapse.
Containment, stated precisely. The shared sandbox-document serve route sets one CSP header for every consumer and is unchanged here; it grants popups. What holds is that sandbox restrictions combine rather than union, so the frame's own
sandbox="allow-scripts"is the binding constraint: no popups, a null origin, no cookies, no storage, no parent DOM, no top-level navigation. Egress is closed by the document's own CSP. Scripts do run, by design.The sandbox is defence in depth, not the only defence. The untrusted half is data, not markup: it is escaped once in Python at a single boundary, redacted on the way into storage, and the template directory is read-only to the sandbox so an agent cannot author its own template. Templates reach the DOM only through text APIs, pinned by a test that greps the source for DOM sinks. The native docked summary renders crew strings as React text children, pinned by a test that a hostile string injects no element.
4. What tests we did
5. Any other suggestions
readThemeVarswas duplicated seven times; this change extracts one shared export and deletes all seven local copies. That is scope beyond the feature, taken because a duplication that is merely disclosed is still a duplication.temp-screenshots/, following an established convention with several hundred existing files. One reviewer is right that this is permanent history under a directory named "temp"; the alternative -- hosting on the PR itself -- keeps the repository smaller but loses the images if the PR is ever pruned. Following the convention, and noting the cost rather than pretending it is free.