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:
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user