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:
@@ -325,6 +325,39 @@ export const AgentStatusUpdatedFrame = z.object({
|
||||
at: IsoTimestamp,
|
||||
});
|
||||
|
||||
// ---- orchestrator frames ([D-6]) -------------------------------------------
|
||||
|
||||
const FlowStepManifestEntry = z.object({
|
||||
step_id: z.string().min(1),
|
||||
agent: z.string().min(1),
|
||||
kind: z.enum(['agent', 'code']),
|
||||
chat_id: Uuid,
|
||||
label: z.string().min(1),
|
||||
});
|
||||
|
||||
// Published once when a flow run starts. Carries the full step manifest so
|
||||
// the OrchestratorPane can build its roster immediately.
|
||||
export const FlowRunStartedFrame = z.object({
|
||||
type: z.literal('flow_run_started'),
|
||||
run_id: Uuid,
|
||||
flow_name: z.string().min(1),
|
||||
band: z.enum(['small', 'medium', 'large']),
|
||||
steps: z.array(FlowStepManifestEntry),
|
||||
});
|
||||
|
||||
// Published on every step status change and on run completion. `report` is
|
||||
// present (and non-null) only when `run_status === 'completed'` — it rides here
|
||||
// rather than a dedicated frame (D-6). Phase 6: `cancelled` added to both enums
|
||||
// so the stop route can publish cancel transitions (DB CHECKs already included it).
|
||||
export const FlowRunStepUpdatedFrame = z.object({
|
||||
type: z.literal('flow_run_step_updated'),
|
||||
run_id: Uuid,
|
||||
step_id: z.string().min(1),
|
||||
status: z.enum(['pending', 'running', 'completed', 'failed', 'skipped', 'cancelled']),
|
||||
run_status: z.enum(['running', 'completed', 'failed', 'cancelled']).optional(),
|
||||
report: z.string().optional(),
|
||||
});
|
||||
|
||||
// ---- discriminated union ---------------------------------------------------
|
||||
|
||||
export const WsFrameSchema = z.discriminatedUnion('type', [
|
||||
@@ -345,6 +378,9 @@ export const WsFrameSchema = z.discriminatedUnion('type', [
|
||||
PermissionResolvedFrame,
|
||||
AgentCommandsFrame,
|
||||
AgentStatusUpdatedFrame,
|
||||
// orchestrator
|
||||
FlowRunStartedFrame,
|
||||
FlowRunStepUpdatedFrame,
|
||||
// per-user
|
||||
ChatStatusFrame,
|
||||
SessionUpdatedFrame,
|
||||
@@ -387,6 +423,8 @@ export const KNOWN_FRAME_TYPES: readonly WsFrame['type'][] = [
|
||||
'permission_resolved',
|
||||
'agent_commands',
|
||||
'agent_status_updated',
|
||||
'flow_run_started',
|
||||
'flow_run_step_updated',
|
||||
'chat_status',
|
||||
'session_updated',
|
||||
'session_renamed',
|
||||
|
||||
Reference in New Issue
Block a user