feat: in-app Orchestrator (Phase 2) — multi-agent conductor
Brings the deterministic Han-flow conductor into BooCode: launch any read-only flow from BooChat or BooCoder, watch each agent stream live in a Paseo-style run pane, get an evidence-disciplined report — on local Qwen, persisted and resumable. Read-only enforced hard via qwen --approval-mode plan (orchestrator tasks fail closed if qwen is unavailable; never fall to write-capable native). Backend (apps/coder): re-homed conductor defs, flow_runs/flow_steps schema, flow-runner + dispatcher onTaskTerminal hook, restart-resume, runs routes (launch/list/get/cancel), user-channel WS. Contracts: two flow_run_* frames. Web: orchestrator pane kind + OrchestratorPane, Workflow button + slash flows (BooChat/BooCoder parity), FlowLauncherDialog, "New Orchestrator" in the + and split menus, runs history + export. Plan: openspec/changes/orchestrator. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,4 +48,21 @@ export function registerWebSocket(
|
||||
socket.on('error', () => unsubscribe());
|
||||
},
|
||||
);
|
||||
|
||||
// User-channel WS: run-level orchestrator frames (flow_run_started,
|
||||
// flow_run_step_updated) published by the flow-runner via
|
||||
// broker.publishUserFrame('default'). Mirrors the BooChat server pattern.
|
||||
app.get('/api/ws/user', { websocket: true }, async (socket) => {
|
||||
const user = 'default';
|
||||
const unsubscribe = broker.subscribeUser(user, (frame) => {
|
||||
if (socket.readyState !== socket.OPEN) return;
|
||||
try {
|
||||
socket.send(JSON.stringify(frame));
|
||||
} catch (err) {
|
||||
app.log.warn({ err, user }, 'user ws send failed');
|
||||
}
|
||||
});
|
||||
socket.on('close', () => unsubscribe());
|
||||
socket.on('error', () => unsubscribe());
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user