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

@@ -25,6 +25,8 @@ import type {
PermissionPrompt,
AgentCommand,
WorkspaceState,
FlowRunRow,
FlowStepRow,
} from './types';
// v2.6 Phase 1-UX §9b: chat-scoped agent-session rows. Returned by
@@ -483,6 +485,34 @@ export const api = {
}),
},
// Orchestrator run API — proxied to boocoder at /api/coder/runs/*.
runs: {
launch: (body: {
project_id: string;
flow_name: string;
band: 'small' | 'medium' | 'large';
input: { question: string };
model?: string;
}) =>
request<{ run_id: string }>('/api/coder/runs', {
method: 'POST',
body: JSON.stringify(body),
}),
list: (projectId: string) =>
request<{ runs: FlowRunRow[] }>(
`/api/coder/runs?project_id=${encodeURIComponent(projectId)}`,
),
get: (runId: string) =>
request<{ run: FlowRunRow; steps: FlowStepRow[] }>(
`/api/coder/runs/${encodeURIComponent(runId)}`,
),
cancel: (runId: string) =>
request<{ cancelled: boolean }>(
`/api/coder/runs/${encodeURIComponent(runId)}/cancel`,
{ method: 'POST' },
),
},
agents: {
list: (projectId: string) =>
request<AgentsResponse>(`/api/projects/${projectId}/agents`),