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.
This commit is contained in:
@@ -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`),
|
||||
|
||||
@@ -390,7 +390,8 @@ export type WorkspacePaneKind =
|
||||
| 'empty'
|
||||
| 'settings'
|
||||
| 'markdown_artifact'
|
||||
| 'html_artifact';
|
||||
| 'html_artifact'
|
||||
| 'orchestrator';
|
||||
|
||||
// Mixed tabs: a pane can hold tabs of different kinds (a BooChat tab next to a
|
||||
// BooCode tab next to a Terminal tab). Each tab carries its own kind; the active
|
||||
@@ -416,6 +417,45 @@ export interface HtmlArtifactState {
|
||||
title: string;
|
||||
}
|
||||
|
||||
// Orchestrator pane state — carries run identity for fetch-on-mount + reopen.
|
||||
export interface OrchestratorState {
|
||||
run_id: string;
|
||||
flow_name: string;
|
||||
band: 'small' | 'medium' | 'large';
|
||||
}
|
||||
|
||||
// Orchestrator run API types (returned by GET /api/coder/runs/:id).
|
||||
export interface FlowRunRow {
|
||||
id: string;
|
||||
project_id: string;
|
||||
flow_name: string;
|
||||
band: 'small' | 'medium' | 'large';
|
||||
model: string;
|
||||
status: 'running' | 'completed' | 'failed' | 'cancelled';
|
||||
input: { question: string; band?: string; [key: string]: unknown };
|
||||
report: string | null;
|
||||
error: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface FlowStepRow {
|
||||
id: string;
|
||||
run_id: string;
|
||||
step_id: string;
|
||||
kind: 'agent' | 'code';
|
||||
agent: string | null;
|
||||
status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped' | 'cancelled';
|
||||
task_id: string | null;
|
||||
chat_id: string | null;
|
||||
session_id: string | null;
|
||||
input: string | null;
|
||||
output: string | null;
|
||||
error: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface WorkspacePane {
|
||||
id: string;
|
||||
// For a tabbed pane (chat/coder/terminal) this mirrors the ACTIVE tab's kind,
|
||||
@@ -433,6 +473,8 @@ export interface WorkspacePane {
|
||||
// v1.14.x: populated only when kind === 'markdown_artifact' / 'html_artifact'.
|
||||
markdown_artifact_state?: MarkdownArtifactState;
|
||||
html_artifact_state?: HtmlArtifactState;
|
||||
// orchestrator pane: populated only when kind === 'orchestrator'.
|
||||
orchestrator_state?: OrchestratorState;
|
||||
}
|
||||
|
||||
// Reopen LIFO stack entry. Shape unchanged from the prior module-level stack;
|
||||
@@ -527,4 +569,27 @@ export type WsFrame =
|
||||
status: 'working' | 'blocked' | 'idle' | 'error';
|
||||
reason?: string;
|
||||
at: string;
|
||||
}
|
||||
// orchestrator frames ([D-6]): run lifecycle. The per-agent content stream
|
||||
// reuses existing delta/tool_call/message_complete frames keyed by chat_id.
|
||||
| {
|
||||
type: 'flow_run_started';
|
||||
run_id: string;
|
||||
flow_name: string;
|
||||
band: 'small' | 'medium' | 'large';
|
||||
steps: Array<{
|
||||
step_id: string;
|
||||
agent: string;
|
||||
kind: 'agent' | 'code';
|
||||
chat_id: string;
|
||||
label: string;
|
||||
}>;
|
||||
}
|
||||
| {
|
||||
type: 'flow_run_step_updated';
|
||||
run_id: string;
|
||||
step_id: string;
|
||||
status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped' | 'cancelled';
|
||||
run_status?: 'running' | 'completed' | 'failed' | 'cancelled';
|
||||
report?: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user