Repository navigation
fix(dashboard): new agents appear at once, not on the 30 s poll (#3109) - #3158
Merged
Merged
Conversation
After the first-run overlay closed on a fresh install, the Timeline showed no agents for about 30 seconds, although setup had already seeded four. Three gaps let that happen; each is fixed here. 1. Closing the overlay refetched nothing. Dashboard now watches the overlay's v-model and calls networkStore.fetchAgents() on true -> false, as onCreateModalClose already does for the create modal. That covers finish, skip and confirm-close. Setup's seeding does not go through the crud create path, so this refetch is what fixes the reported case. 2. stores/network.js had no `agent_created` handler. It now has one. The event triggers a refetch of the REST list, which is the access-controlled, tag-filtered source of the full row, rather than inserting the broadcast's partial row (the #918 thin-trigger rule). A burst (setup seeds four) is coalesced into one refetch within AGENT_CREATED_COALESCE_MS (300 ms). 3. The backend sent `agent_created` and `agent_deleted` with `event` but no `type`, and the dashboard dispatcher keys on `type`. Both now send `type` too, like agent_started and agent_stopped. That also revives the dead `agent_deleted` branch, which now reads the name from `data.data` where the payload carries it. The ent#467 /ws scoping reads `event` first, so it is unaffected (its tests pass). Tests: - test_3109_agent_ws_type.py: the real _broadcast_agent_created payload, plus an AST guard that every agent lifecycle broadcast in the backend carries a matching `type`. - networkAgentCreatedWs.spec.js: the store's own WebSocket fed the backend's envelope. Four creates cause one refetch, the payload is never inserted, and a delete removes the agent. - dashboardFirstRunRefetch.mount.spec.js: Dashboard mounted; closing the overlay refetches and opening it does not. Reverting each source file turns its tests red: crud.py 2, agents.py 1, network.js 3, Dashboard.vue 1. Full frontend suite 4,499 passed; the design token check and the build pass; the related backend suites (ent#467 scoping, ent#107 seed, capacity, backlog) pass. Fixes #3109 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…atches on (#3109) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 tasks done
Contributor
Author
/review report:
|
| changed symbol | executed by | live consumer | verdict |
|---|---|---|---|
_broadcast_agent_created type |
test_3109_agent_ws_type.py (real call) |
stores/network.js dispatcher |
✅ |
agents.py agent_deleted type (both sites) |
the AST guard in the same file | the same dispatcher | 🛡 guard (its live consumer is the dispatcher) |
network.js agent_created → coalesced fetchAgents |
networkAgentCreatedWs.spec.js (store's own socket) |
Dashboard / Timeline | ✅ |
network.js revived agent_deleted |
same | same | ✅ |
Dashboard.vue overlay-close refetch |
dashboardFirstRunRefetch.mount.spec.js |
FirstRunOverlay v-model |
✅ |
Fix mutations: reverting each file turns its tests red. crud.py: 2; agents.py: 1; network.js: 3; Dashboard.vue: 1.
Live check (local dev stack, 2026-10-01, dev + this PR)
With the dashboard open, an agent was created through POST /api/agents:
- it appeared on the dashboard 0.53 s after creation (before this PR: the 30 s poll);
- after
DELETEit disappeared after 0.52 s.
Both test agents were removed.
Critical findings
None.
Informational
- [I1]
utils/websocket.jsstill pushes theagent_createdpayload intoagentsStoreas a partial row (pre-existing). With ent#467's per-agent/wsscoping it only reaches viewers with access, andfetchAgentswrite-through replaces it on the next read. Not introduced here; noted for consistency with the refetch rule this PR follows. - [I2] The coalescing timer isn't cleared on
disconnectWebSocket(confidence 5/10). Worst case is one extra harmlessfetchAgentsafter a disconnect.
Clean categories
- ent#467 scoping: reads
eventfirst;test_ent467_ws_agent_scope.pypasses. - Disclosure: the broadcast payload is unchanged apart from
type. - Docs: bug-fix tier (commit message). A learnings fragment was added for the recurring "producer shape ≠ dispatcher key" class:
docs/memory/learnings/2026-10-01-ws-event-vs-type-key.md.
Summary: Critical 0 · Informational 2 · Scope clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On a fresh install, after the first-run overlay closed, the dashboard Timeline stayed empty for about 30 s, although setup had already seeded four agents. The issue names three gaps, and this fixes all of them:
v-model:openand callsnetworkStore.fetchAgents()when it goes from true to false, asonCreateModalClosedoes for the create modal. This covers finish, skip and confirm-close. Setup's seeding doesn't go through the crud create path, so this refetch is what fixes the reported case.stores/network.jshad noagent_createdhandler. It now refetches the REST list, which is access-controlled and tag-filtered and carries the full row, instead of inserting the broadcast's partial row (feat: agent-reported structured reports via MCP + dashboard display #918 thin trigger). A burst of creates is merged into one refetch (300 ms).agent_created/agent_deletedhad notypekey, and the dashboard dispatches ontype. Both now send it, likeagent_started/agent_stoppedalready do. This also revives the deadagent_deletedbranch, which now reads the name fromdata.data, where it actually is.The ent#467
/wsscoping readseventfirst, so it's unaffected; its tests pass.Changes
src/backend/services/agent_service/crud.py,src/backend/routers/agents.py: add"type"to theagent_createdandagent_deletedbroadcastssrc/frontend/src/stores/network.js: theagent_createdhandler (coalesced refetch) and the revivedagent_deletedbranchsrc/frontend/src/views/Dashboard.vue: refetch when the first-run overlay closestests/unit/test_3109_agent_ws_type.pysrc/frontend/tests/unit/networkAgentCreatedWs.spec.jssrc/frontend/tests/unit/dashboardFirstRunRefetch.mount.spec.jsTest Plan
_broadcast_agent_createdpayload carriestype;type, so a new event added witheventonly fails.agent_deletedremoves the agent.check-design-tokensandnpm run build: OK. Related backend suites (ent#467 scoping, ent#107 seed, capacity, backlog): 148 passed.Fixes #3109
🤖 Generated with Claude Code