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,15 @@ import { type AcpToolSnapshot, synthesizeCanceledSnapshots } from './acp-tool-sn
import { makeFrameEmitter, type FrameEmitter } from './frame-emitter.js';
import { buildAcpClient } from './acp-client.js';
/**
* Mode ids that enforce read-only at the agent's tool layer. When one of these is
* requested, applying it is safety-critical: a failure to set it must abort the
* turn (fail closed), never continue write-capable. `plan` is qwen's read-only
* approval mode (the orchestrator's gate, D-4); extend this set if another agent's
* read-only mode id is added to flows.
*/
const READ_ONLY_MODE_IDS = new Set(['plan']);
export interface AcpDispatchResult {
exitCode: number;
output: string;
@@ -65,6 +74,21 @@ async function applySessionOverrides(
try {
await connection.setSessionMode({ sessionId: acpSessionId, modeId });
} catch (err) {
// Defense-in-depth for the orchestrator read-only invariant (D-4): a
// read-only / plan mode that CANNOT be applied must FAIL CLOSED. If we only
// warned and continued (the default for non-read-only modes), the agent
// would run write-capable under a plan-mode request — exactly the silent
// gap this guards. Re-throw so dispatchViaAcp's catch marks the task failed
// rather than running an unguarded turn. (Orchestrator qwen+plan is routed
// to the PTY hard gate and never reaches here; this backstops any other
// route that lands a read-only mode on the ACP path.)
if (READ_ONLY_MODE_IDS.has(modeId)) {
log.error(
{ modeId, err: err instanceof Error ? err.message : String(err) },
'acp-dispatch: read-only setSessionMode failed — failing closed (aborting turn)',
);
throw err instanceof Error ? err : new Error(String(err));
}
log.warn({ modeId, err: err instanceof Error ? err.message : String(err) }, 'acp-dispatch: setSessionMode failed');
}
}