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:
2026-06-03 14:59:07 +00:00
parent 519b1d2ca1
commit 1937af8df9
118 changed files with 15723 additions and 27 deletions

View File

@@ -1,4 +1,4 @@
import { Code, Columns2, History, MessageSquare, Plus, RotateCcw, Terminal, X } from 'lucide-react';
import { Code, Columns2, History, MessageSquare, Plus, RotateCcw, Terminal, Workflow, X } from 'lucide-react';
import {
DropdownMenu,
DropdownMenuContent,
@@ -16,6 +16,9 @@ interface Props {
// (the second control) adds a new pane.
onNewTab: (kind: 'chat' | 'terminal' | 'coder') => void;
onSplitPane: (kind: 'chat' | 'terminal' | 'coder') => void;
// When provided, shows a "New Orchestrator" item that opens the flow launcher.
// Orchestrators are always split (run-bound; can't live as a tab in another pane).
onNewOrchestrator?: () => void;
onReopenPane?: () => void;
onShowHistory: () => void;
onRemovePane?: () => void;
@@ -31,6 +34,7 @@ const BTN =
export function PaneHeaderActions({
onNewTab,
onSplitPane,
onNewOrchestrator,
onReopenPane,
onShowHistory,
onRemovePane,
@@ -62,6 +66,11 @@ export function PaneHeaderActions({
<DropdownMenuItem onSelect={() => onNewTab('coder')}>
<Code size={14} /> New BooCode
</DropdownMenuItem>
{onNewOrchestrator && (
<DropdownMenuItem onSelect={onNewOrchestrator}>
<Workflow size={14} /> New Orchestrator
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
@@ -87,6 +96,11 @@ export function PaneHeaderActions({
<DropdownMenuItem onSelect={() => onSplitPane('coder')}>
<Code size={14} /> New BooCode
</DropdownMenuItem>
{onNewOrchestrator && (
<DropdownMenuItem onSelect={onNewOrchestrator}>
<Workflow size={14} /> New Orchestrator
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>