Files
boocode/conductor/src/run.ts
indifferentketchup 1937af8df9 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>
2026-06-03 15:22:48 +00:00

40 lines
1.7 KiB
TypeScript

/** CLI entry: run a Han-style flow through the deterministic code conductor. */
import { writeFile } from 'node:fs/promises';
import { runFlow } from './flow.js';
import { getFlow, describeFlows, FLOW_NAMES } from './flows/index.js';
import type { Band } from './types.js';
const argv = process.argv.slice(2);
const positional = argv.filter((a: string) => !a.startsWith('--'));
const flowName = positional[0];
const question = positional.slice(1).join(' ').trim();
const flow = flowName ? getFlow(flowName) : undefined;
if (!flow || !question) {
console.error('usage: tsx src/run.ts <flow> "<question>" [--size=small|medium|large] [--repo=/abs/path] [--fast]\n');
console.error('flows:');
console.error(describeFlows());
if (flowName && !flow) console.error(`\nunknown flow "${flowName}" — choose one of: ${FLOW_NAMES.join(', ')}`);
process.exit(1);
}
const repoArg = argv.find((a: string) => a.startsWith('--repo='));
const sizeArg = argv.find((a: string) => a.startsWith('--size='));
const concise = argv.includes('--fast') || argv.includes('--concise');
const band = (sizeArg ? sizeArg.slice('--size='.length) : 'small') as Band;
const input = {
question,
band,
...(repoArg ? { repoPath: repoArg.slice('--repo='.length) } : {}),
...(concise ? { concise: true } : {}),
};
const started = Date.now();
console.error(`conductor: "${flow.name}" — band=${band}${concise ? ' fast' : ''}`);
const { outputPath, artifact } = await runFlow(flow, input, { onLog: (m) => console.error(m) });
const path = outputPath ?? `conductor-report-${flow.name}.md`;
await writeFile(path, artifact, 'utf8');
console.error(`\n✓ conductor done in ${Math.round((Date.now() - started) / 1000)}s → ${path}`);
console.log(path);