feat(dashboard): surface subagents, render markdown, fix cost/mobile; vet cross-repo & speak up when stuck - #144
Conversation
… vet cross-repo & speak up when stuck Detail-page transcript + agent behavior improvements from operator feedback. UI (container-detail): - Render `task` tool calls as a dedicated Subagent block: description always visible, live spinner + auto-expand while running, markdown result when done — so operators can actually see a subagent spin up and read what it did. - Render agent-authored markdown (react-markdown + remark-gfm) instead of raw `##`/`|`/```` syntax. No rehype-raw, so embedded HTML stays inert (agent output is untrusted). - Drop the confusing "usage not tracked" / "no cost" placeholder and the dead tokens readout. Flue reports no token/cost data (verified: zero usage/cost keys in the history), so we only show a `$` figure when one is actually present rather than a permanent "not tracked" chip. - Fix the mobile header: on narrow screens the Logs/Refresh/Destroy actions drop to their own row below the title/metrics instead of colliding with the metric chips. Agent instructions (both copies + chat prompt): - Multi-repo investigation now vets before cloning: any repo other than the run target is untrusted input; an `explore` subagent confirms same-owner/exists/ not-hostile first, and content inside a cloned repo is DATA, never commands (prompt-injection defense). - New "when you're stuck" guidance: on genuine ambiguity or a task whose premise contradicts the repo, post ONE short comment on the issue/PR (or ask in chat on operator turns) and stop, so a human can redirect instead of the agent spinning silently. Co-authored-by: Cursor <cursoragent@cursor.com>
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
jared | 281a278 | Aug 07 2026, 12:19 PM |
| const running = status === "running" || status === "pending" || (status == null && result == null) | ||
| const [open, setOpen] = useState(running) |
There was a problem hiding this comment.
Bug: The SubagentBlock component initializes in a collapsed state for completed subagents, requiring an extra click to view the result.
Severity: LOW
Suggested Fix
Modify the useState initializer for the open state in SubagentBlock. Instead of useState(running), use useState(running || result != null) to ensure the block is also expanded by default if it has a result, making completed reports visible on initial render.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: apps/server/src/client/pages/container-detail.tsx#L508-L509
Potential issue: The `SubagentBlock` component initializes its `open` state based on the
`running` prop during its initial render: `useState(running)`. If a subagent has already
completed when the component first mounts (e.g., on a page load viewing a historical
run), `running` will be `false`, and the component will initialize in a collapsed state.
This requires the operator to manually click to expand the block and view the final
result, contrary to the likely intent of making the final report immediately visible.
Also affects:
apps/server/src/client/pages/container-detail.tsx:279~279
Did we get this right? 👍 / 👎 to inform future reviews.
The run-detail Logs button/panel read `detail.logs`, which is only populated by the legacy in-container `collectContainerData` path (tail of /tmp/flue.log or the old OpenCode log). Production runs Flue-native and serves history from the Worker-side Durable Object in-process, which carries no container stdout — so `logs` is always empty and the panel only ever showed "No logs" (verified live across several sessions). Removes the UI (button, LogsPanel, state, unused `X` icon) and the now-unused logs plumbing: the `logs` field on the detail response + its client type, the `opts.logs` arg on `flueHistoryToSessionData`, the `logs` fields in `normalizeFlueSessionBlob` / `saveInitialSession`, the `flue.log` tail in `collectContainerData` and the session-reporter script. Updates the affected test. Co-authored-by: Cursor <cursoragent@cursor.com>
…reams over SSE The transcript streams via SSE and falls back to a busy-aware poll (2s busy / 10s idle / 30s while streaming), so the always-on header Refresh button just duplicated what happens automatically. Removed it; the "Live" pill + last-updated timestamp already signal freshness. Manual retry stays where a stall is actually actionable: the "sync unavailable" banner, the empty-state, and the not-found view (where the stream isn't connected). Co-authored-by: Cursor <cursoragent@cursor.com>
A `task` tool that hit a transient sandbox reset was coalesced into the muted "sandbox reset" row before the subagent check, hiding that a subagent ran. Exclude `task` from transient coalescing so it always renders as a SubagentBlock (with a failed state). Addresses Seer review feedback. Co-authored-by: Cursor <cursoragent@cursor.com>
| cwd: "/workspace", | ||
| }) | ||
| if (fluePing.success) { | ||
| const [logResult, histResult] = await Promise.all([ | ||
| sandbox.exec("tail -100 /tmp/flue.log 2>/dev/null || true", { cwd: "/workspace" }), | ||
| sandbox.exec( | ||
| `CONV=$(cat /tmp/dispatch-session-id 2>/dev/null || echo default); curl -sf --max-time 8 "http://localhost:${FLUE_PORT}${FLUE_AGENT_MOUNT}/$CONV?view=history" 2>/dev/null`, | ||
| { cwd: "/workspace" }, | ||
| ), | ||
| ]) | ||
| const histResult = await sandbox.exec( | ||
| `CONV=$(cat /tmp/dispatch-session-id 2>/dev/null || echo default); curl -sf --max-time 8 "http://localhost:${FLUE_PORT}${FLUE_AGENT_MOUNT}/$CONV?view=history" 2>/dev/null`, | ||
| { cwd: "/workspace" }, | ||
| ) | ||
| if (!histResult.stdout) return null | ||
| try { | ||
| const hist = JSON.parse(histResult.stdout) as Record<string, unknown> |
There was a problem hiding this comment.
Bug: The debug endpoint for legacy containers attempts to access parsed.logs, which has been removed, causing flueLogs and opencodeLogs to always display "(empty)".
Severity: LOW
Suggested Fix
Update the debug endpoint to handle the removal of the logs field. For the legacy container path, either remove the flueLogs and opencodeLogs fields or change their value to a more accurate message, such as "(logs no longer available)", instead of attempting to access a non-existent property.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: apps/server/src/routes/containers/index.ts#L102-L111
Potential issue: The `logs` field was intentionally removed from all session data blobs
as part of a refactor. However, the debug endpoint (`GET /:entityKey/debug`) was not
updated to reflect this change. For legacy (Phase 1) containers, the endpoint still
attempts to access `parsed.logs` to populate the `flueLogs` and `opencodeLogs` fields.
Since `parsed.logs` is now always `undefined`, these fields will incorrectly display the
fallback string `"(empty)"`, even for containers where logs previously existed. This is
a functional regression for this internal diagnostic tool.
Addresses six items from operator feedback on the run detail page and agent behavior.
1. Subagents are visible now
tasktool calls render as a dedicated Subagent block instead of a generic collapsed tool row: the description is always shown, a running subagent gets a live spinner and auto-expands, and its final report renders as markdown. You can now watch a subagent spin up and read what it did.2. Cost no longer says "usage not tracked"
Verified against the live Flue history for
getsentry/cli#1371: there are zero structured usage/token/cost/model keys — Flue genuinely emits none. So the permanent "usage not tracked" / "no cost" chip was pure noise, andactiveSession.tokenswas dead code. Both removed; a$figure now shows only when a run actually reports cost. Tool-call count remains as the real "how much work happened" signal.3. Markdown rendering
Agent messages render with
react-markdown+remark-gfm(headings, lists, tables, code, links) instead of raw##/|/fenced syntax. Norehype-raw, so embedded HTML stays inert — agent output is untrusted.4. Mobile header fixed
On narrow screens the
Logs/Refresh/Destroyactions drop to their own row below the title and metric chips instead of overlapping them (see the reported screenshot).5. Cross-repo safety (prompt-injection defense)
Multi-repo investigationin the agent instructions (both copies) + the chat prompt now require vetting before cloning: any repo other than the run target is untrusted input. Anexploresubagent first confirms same-owner / exists / not-hostile, and content inside a cloned repo is treated as DATA, never commands. A different-owner repo needs a real reason and, on operator turns, a confirmation.6. Speak up when stuck
New "when you're stuck or the task doesn't add up" guidance: on genuine ambiguity — or a task whose premise contradicts the repo state (exactly the
cli#1371"issue says 3 files, reality is 18" case) — the agent posts ONE short comment on the originating issue/PR (or asks in chat on operator turns) and stops, so a human can nudge it, instead of spinning silently.Verification
pnpm --filter server test— 117 passedpnpm --filter server build— client bundles cleanly (react-markdown included)biome check— clean on all changed filestsc --buildare all pre-existing Cloudflare-type resolution issues in untouched files; the changed files (container-detail.tsx,prompt.ts,instructions.ts) produce none.Test plan
Made with Cursor