feat(workflow): migrate supervisor to ThreadReactor (Phase 2)

- Rewrite supervisor to use createThreadReactor + createLlmFn
- No direct fetch/HTTP calls in supervisor
- All 266 tests passing

Refs #139, relates #141
This commit is contained in:
2026-05-09 02:26:39 +00:00
parent a7171f05f6
commit b8f9ffcb59
5 changed files with 149 additions and 173 deletions
+10 -8
View File
@@ -101,10 +101,10 @@ async function writeRegistryYaml(storageRoot: string, yaml: string): Promise<voi
await writeFile(join(storageRoot, "workflow.yaml"), yaml, "utf8");
}
/** Extract rounds reply with schema-shaped JSON in `content`; supervisor uses plain `content` (no tools advertised). */
/** Extract and supervisor both run via {@link createThreadReactor}; differentiate by `body.model`. */
function installMockExtractThenSupervisor(params: {
extractArgs: ReadonlyArray<Record<string, unknown>>;
supervisorContent: string;
supervisorDecision: "continue" | "stop";
onSupervisorCall?: () => void;
}): () => void {
const origFetch = globalThis.fetch;
@@ -114,9 +114,9 @@ function installMockExtractThenSupervisor(params: {
init?: RequestInit,
): Promise<Response> => {
const body = init?.body ? (JSON.parse(String(init.body)) as Record<string, unknown>) : {};
const tools = body.tools;
const hasTools = Array.isArray(tools) && tools.length > 0;
if (hasTools) {
const model = typeof body.model === "string" ? body.model : "";
const isSupervisor = model.startsWith("supervisor-");
if (!isSupervisor) {
const args =
params.extractArgs[extractI] ?? params.extractArgs[params.extractArgs.length - 1];
if (args === undefined) {
@@ -133,7 +133,9 @@ function installMockExtractThenSupervisor(params: {
params.onSupervisorCall?.();
return new Response(
JSON.stringify({
choices: [{ message: { content: params.supervisorContent } }],
choices: [
{ message: { content: JSON.stringify({ decision: params.supervisorDecision }) } },
],
}),
{ status: 200, headers: { "Content-Type": "application/json" } },
);
@@ -674,7 +676,7 @@ describe("executeThread", () => {
test("supervisor stops thread when interval elapses and model returns stop", async () => {
restoreFetch = installMockExtractThenSupervisor({
extractArgs: [{ plan: "do-it", files: ["a.ts"] }, { diff: "+ok" }],
supervisorContent: "stop",
supervisorDecision: "stop",
});
const root = await mkdtemp(join(tmpdir(), "wf-engine-sup-stop-"));
@@ -725,7 +727,7 @@ describe("executeThread", () => {
let supervisorCalls = 0;
restoreFetch = installMockExtractThenSupervisor({
extractArgs: [{ plan: "do-it", files: ["a.ts"] }, { diff: "+ok" }],
supervisorContent: "stop",
supervisorDecision: "stop",
onSupervisorCall: () => {
supervisorCalls += 1;
},