Repository navigation
feat(agents-builder): simulated agent runs, status tags, and project-scoped roster - #56
Conversation
…l trace - AskCard shows one question at a time with a footer pager, radio rows, and Skip/Continue on the right; `eyebrow` and `icon` props - ApprovalCard takes an `eyebrow` and uses a secondary skip button - ToolTrace hangs its calls from one continuous rail and gains an `error` status
- Each roster agent opens its own blueprint and run via `agentDetails`: live replay, approval, question, failure with retry, queued, finished - Opened agents stay mounted, so runs continue off screen and finishing marks the row unread - Agent rows end in a status tag (Approve, Answer, Failed, Queued); the collapsed rail shows the word under the initials - Run panel stops on shared cards for approval, question, failure, queued - Optional `projects` group the sidebar roster; folded projects carry their most pressing tag, and the breadcrumb names the project
|
Review complete. 🟡 4 medium 💬 Inline comments (3)
📍 Findings outside the diff (1) — 🟡 1 medium — defects on lines GitHub can't attach comments to🟡 Medium — Stopping a run reports it as failed · use-agent-run.ts:161–163 · // packages/ui/src/components/agents-builder/use-agent-run.ts
161 function settledState(script: AgentRunScript): AgentRunState {
162 return buildRunTimeline(script).reduce((state, frame) => frame.apply(state), INITIAL);
163 }
🧹 Nitpicks (1) — 🟢 1 low
This PR introduces the
Reviewed commit: 9e70347 |
There was a problem hiding this comment.
Adds an Agents Builder registry module with run lifecycle, AI cards, and tests; five defects center on run-state semantics and stale-session/patch caching.
Key findings
- 🟡 Stopping a run reports it as failed — use-agent-run.ts:161
- 🟡 Kept-mounted agent views replay a stale session — agent-view.tsx:146
- 🟡
updateAgentresurrects patches dropped by a roster refresh — agents-builder.tsx:140 - 🟡 Unmatched
RunFailure.stepIdplays the run as complete — use-agent-run.ts:109
| const [session, setSession] = React.useState(() => { | ||
| const summary = agents.find((candidate) => candidate.id === agent.id); | ||
| return { attempt: 0, start: runStart(summary ? agentStatus(summary) : 'idle', autoplay), queuedNote: summary?.statusNote }; | ||
| }); |
There was a problem hiding this comment.
🟡 bug · medium
Kept-mounted agent views replay a stale session
AgentViewBody derives its session (start mode and queuedNote) once in a React.useState initializer and never re-derives it (agent-view.tsx:146-149). Because this PR keeps previously opened AgentViews mounted while hidden, a host data refresh that changes the agent's status or replaces data.run is ignored by the kept view: reopening it still shows the old opening mode (e.g. a queued panel stays paused after the host started the run), while the sidebar row reflects the new status. The panel and the roster disagree about the same agent.
📋 Prompt for AI Agents
In packages/ui/src/components/agents-builder/agent-view.tsx around lines 146-149, session.start and session.queuedNote are captured once at mount and never updated, while agent views now stay mounted across host data updates (keptAgentIds in agents-builder.tsx). Add a render-phase adjustment or effect keyed on [currentAgent.run, active]: while the session has not been user-started, when currentAgent.run changes identity (or the agent's host status changes) and the view is not active, reset the session to a freshly derived value (runStart(agentStatus(summary), autoplay) and the latest statusNote). Keep user-initiated transitions (Start/Retry) authoritative once they happen.
| const base = { ...data.agents.find((agent) => agent.id === id), ...byId[id] }; | ||
| // A run reports its phase on every mount; an unchanged patch keeps the state, so nothing re-renders. | ||
| if (current.source === data.agents && (Object.keys(patch) as (keyof AgentPatch)[]).every((key) => base[key] === patch[key])) return current; | ||
| return { source: data.agents, byId: { ...byId, [id]: { ...byId[id], ...patch } } }; |
There was a problem hiding this comment.
🟡 bug · medium
updateAgent resurrects patches dropped by a roster refresh
updateAgent's non-no-op branch returns { source: data.agents, byId: { ...byId, ... } } where byId is the previous store's patches even when that store belongs to a stale roster (agents-builder.tsx:140), so patches the host refresh was meant to drop are carried into the new source and re-applied by the agents memo (agents-builder.tsx:127-130). An agent whose template patch said status: 'approval' but whose host roster now says idle flips back to 'approval' the next time any run reports a phase. The sidebar shows a decision request that no longer exists.
📋 Prompt for AI Agents
In packages/ui/src/components/agents-builder/agents-builder.tsx around lines 134-141, updateAgent's setAgentPatches updater returns { source: data.agents, byId: { ...byId, [id]: { ...byId[id], ...patch } } } even when current.source !== data.agents, re-applying patches a host roster refresh dropped. Compute const prev = current.source === data.agents ? current.byId : {} and use prev/prev[id] for both the no-op base and the returned store, so a roster change starts a fresh patch store.
| const failedStep = script.failure ? script.steps.findIndex((step) => step.id === script.failure!.stepId) : -1; | ||
| for (const [index] of script.steps.entries()) { | ||
| if (index === failedStep) { | ||
| at((state) => ({ ...state, failedStep: index, nodes: { ...state.nodes, tools: 'ready' } }), RUN_TIMING.stepMorph); | ||
| at((state) => ({ ...state, settled: true })); | ||
| return frames; | ||
| } |
There was a problem hiding this comment.
🟡 bug · medium
Unmatched RunFailure.stepId plays the run as complete
buildRunTimeline resolves script.failure.stepId with findIndex and never validates the result (use-agent-run.ts:109): an id matching no step yields -1, so the failure branch never fires, failedStep stays null, and the phase logic reports done (agent-run-panel.tsx:480-491) while the FailureBlock is never rendered (agent-run-panel.tsx:623). A host authoring a run with a typo'd or stale stepId gets a run that looks fully successful — the opposite of what the data declares, with no diagnostic.
📋 Prompt for AI Agents
In packages/ui/src/components/agents-builder/use-agent-run.ts line 109, after computing failedStep, add a development-mode guard: when script.failure is set and failedStep === -1, log a console.error naming the unmatched stepId and set failedStep to script.steps.length - 1 so the run fails at its final step instead of silently playing to 'Run complete'. This protects hosts authoring RunFailure data with a stale id.
Makes every agent in the Agents Builder template a realistically simulated thread, gives the roster a status language for what each agent needs, and lets the roster be scoped by project.
Agents Builder
agentDetailspairs each agent with a blueprint and a run script; the featuredagent/runstill work, and agents without a detail still go to the host throughopen-agent.RunQuestion), a failure at a tool call with a log and retry (RunFailure), queued with "Run now", and a finished run that opens without replaying.aria-hidden; each row's accessible name carries the state.projectsgroup the expanded roster. A folded project shows its most pressing tag, agents that need you are promoted past the per-project limit, and the breadcrumb names the project. Withoutprojectsthe roster stays flat.answer-run,retry-run,start-run.NavRowgainstipandrailBadge.AI kit
AskCardshows one question at a time with a pager, radio rows, and Skip/Continue on the right; neweyebrowandiconprops. Hosts with several questions now page through them.ApprovalCardtakes aneyebrow; its skip button issecondary.ToolTracehangs its calls from one continuous rail and gains anerrorstatus.Before / after
New run states:
🤖 Generated with Claude Code
https://claude.ai/code/session_01W81pjQJK31ut5W1L7wQavN