story-kit: merge 149_bug_web_ui_does_not_update_when_agents_are_started_or_stopped

This commit is contained in:
Dave
2026-02-24 23:09:13 +00:00
parent 51303c07d8
commit dc631d1933
8 changed files with 187 additions and 108 deletions

View File

@@ -52,7 +52,7 @@ impl AppContext {
state: Arc::new(state),
store: Arc::new(JsonFileStore::new(store_path).unwrap()),
workflow: Arc::new(std::sync::Mutex::new(WorkflowState::default())),
agents: Arc::new(AgentPool::new(3001)),
agents: Arc::new(AgentPool::new(3001, watcher_tx.clone())),
watcher_tx,
reconciliation_tx,
perm_tx,

View File

@@ -77,6 +77,10 @@ enum WsResponse {
/// `.story_kit/project.toml` was modified; the frontend should re-fetch the
/// agent roster. Does NOT trigger a pipeline state refresh.
AgentConfigChanged,
/// An agent's state changed (started, stopped, completed, etc.).
/// Triggers a pipeline state refresh and tells the frontend to re-fetch
/// the agent list.
AgentStateChanged,
/// Claude Code is requesting user approval before executing a tool.
PermissionRequest {
request_id: String,
@@ -120,6 +124,7 @@ impl From<WatcherEvent> for Option<WsResponse> {
commit_msg,
}),
WatcherEvent::ConfigChanged => Some(WsResponse::AgentConfigChanged),
WatcherEvent::AgentStateChanged => Some(WsResponse::AgentStateChanged),
}
}
}
@@ -184,15 +189,18 @@ pub async fn ws_handler(ws: WebSocket, ctx: Data<&Arc<AppContext>>) -> impl poem
loop {
match watcher_rx.recv().await {
Ok(evt) => {
let is_work_item =
matches!(evt, crate::io::watcher::WatcherEvent::WorkItem { .. });
let needs_pipeline_refresh = matches!(
evt,
crate::io::watcher::WatcherEvent::WorkItem { .. }
| crate::io::watcher::WatcherEvent::AgentStateChanged
);
let ws_msg: Option<WsResponse> = evt.into();
if let Some(msg) = ws_msg && tx_watcher.send(msg).is_err() {
break;
}
// Only push refreshed pipeline state after work-item changes,
// not after config-file changes.
if is_work_item
// Push refreshed pipeline state after work-item changes and
// agent state changes (so the board updates agent lozenges).
if needs_pipeline_refresh
&& let Ok(state) = load_pipeline_state(ctx_watcher.as_ref())
&& tx_watcher.send(state.into()).is_err()
{