story-kit: merge 117_story_show_startup_reconciliation_progress_in_ui

This commit is contained in:
Dave
2026-02-23 22:50:57 +00:00
parent e3d9813707
commit 85fddcb71a
7 changed files with 341 additions and 8 deletions

View File

@@ -1,4 +1,4 @@
use crate::agents::AgentPool;
use crate::agents::{AgentPool, ReconciliationEvent};
use crate::io::watcher::WatcherEvent;
use crate::state::SessionState;
use crate::store::JsonFileStore;
@@ -26,6 +26,10 @@ pub struct AppContext {
/// Broadcast channel for filesystem watcher events. WebSocket handlers
/// subscribe to this to push lifecycle notifications to connected clients.
pub watcher_tx: broadcast::Sender<WatcherEvent>,
/// Broadcast channel for startup reconciliation progress events.
/// WebSocket handlers subscribe to this to push real-time reconciliation
/// updates to connected clients.
pub reconciliation_tx: broadcast::Sender<ReconciliationEvent>,
/// Sender for permission requests originating from the MCP
/// `prompt_permission` tool. The MCP handler sends a [`PermissionForward`]
/// and awaits the oneshot response.
@@ -42,6 +46,7 @@ impl AppContext {
*state.project_root.lock().unwrap() = Some(project_root.clone());
let store_path = project_root.join(".story_kit_store.json");
let (watcher_tx, _) = broadcast::channel(64);
let (reconciliation_tx, _) = broadcast::channel(64);
let (perm_tx, perm_rx) = mpsc::unbounded_channel();
Self {
state: Arc::new(state),
@@ -49,6 +54,7 @@ impl AppContext {
workflow: Arc::new(std::sync::Mutex::new(WorkflowState::default())),
agents: Arc::new(AgentPool::new(3001)),
watcher_tx,
reconciliation_tx,
perm_tx,
perm_rx: Arc::new(tokio::sync::Mutex::new(perm_rx)),
}

View File

@@ -79,6 +79,14 @@ enum WsResponse {
ToolActivity {
tool_name: String,
},
/// Real-time progress from the server startup reconciliation pass.
/// `status` is one of: "checking", "gates_running", "advanced", "skipped",
/// "failed", "done". `story_id` is empty for the overall "done" event.
ReconciliationProgress {
story_id: String,
status: String,
message: String,
},
}
impl From<WatcherEvent> for WsResponse {
@@ -155,6 +163,30 @@ pub async fn ws_handler(ws: WebSocket, ctx: Data<&Arc<AppContext>>) -> impl poem
}
});
// Subscribe to startup reconciliation events and forward them to the client.
let tx_reconcile = tx.clone();
let mut reconcile_rx = ctx.reconciliation_tx.subscribe();
tokio::spawn(async move {
loop {
match reconcile_rx.recv().await {
Ok(evt) => {
if tx_reconcile
.send(WsResponse::ReconciliationProgress {
story_id: evt.story_id,
status: evt.status,
message: evt.message,
})
.is_err()
{
break;
}
}
Err(tokio::sync::broadcast::error::RecvError::Lagged(_)) => continue,
Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
}
}
});
// Map of pending permission request_id → oneshot responder.
// Permission requests arrive from the MCP `prompt_permission` tool via
// `ctx.perm_rx` and are forwarded to the client as `PermissionRequest`.