import type { Schema, StartStep, WorkflowContext, WorkflowDefinition, WorkflowMessage, WorkflowSpec, } from "@uncaged/nerve-core"; import { END } from "@uncaged/nerve-core"; import { compileWorkflowSpec, type CompileWorkflowSpecDeps } from "@uncaged/nerve-daemon"; import { createCursorAdapter } from "@uncaged/nerve-adapter-cursor"; import { hermesAdapter } from "@uncaged/nerve-adapter-hermes"; import type { LlmProvider } from "@uncaged/nerve-workflow-utils"; import { createLlmExtractFn, zodMeta } from "@uncaged/nerve-workflow-utils"; import { coderPrompt } from "./roles/coder/prompt.js"; import { coderMetaSchema } from "./roles/coder/index.js"; import { plannerPrompt } from "./roles/planner/prompt.js"; import { plannerMetaSchema } from "./roles/planner/index.js"; import { reviewerPrompt } from "./roles/reviewer/prompt.js"; import { reviewerMetaSchema } from "./roles/reviewer/index.js"; import { testerPrompt } from "./roles/tester/prompt.js"; import { testerMetaSchema } from "./roles/tester/index.js"; import { buildCommitterRole } from "./roles/committer/index.js"; import { moderator } from "./moderator.js"; import type { WorkflowMeta } from "./moderator.js"; export type BuildWorkflowGeneratorDeps = { provider: LlmProvider; nerveRoot: string; }; const CURSOR_TIMEOUT_MS = 300_000; type WorkflowAgentMeta = Pick; function defaultAgentCreateContext( workdir: string, ): (start: StartStep, messages: WorkflowMessage[]) => WorkflowContext { return (start, messages) => ({ start, messages, workdir, signal: new AbortController().signal, }); } export function buildWorkflowGenerator({ provider, nerveRoot, }: BuildWorkflowGeneratorDeps): WorkflowDefinition { const compileDeps: CompileWorkflowSpecDeps = { extractFn: async (raw: string, schema: Schema, dryRun: boolean) => createLlmExtractFn({ provider, dryRun })(raw, schema), createContext: defaultAgentCreateContext(nerveRoot), }; const agentSpec: WorkflowSpec = { name: "develop-workflow", roles: { planner: { adapter: createCursorAdapter({ type: "cursor", model: "auto", timeout: CURSOR_TIMEOUT_MS, mode: "ask", }), prompt: async (start: StartStep) => plannerPrompt({ threadId: start.meta.threadId }), meta: zodMeta(plannerMetaSchema), }, coder: { adapter: createCursorAdapter({ type: "cursor", model: "auto", timeout: CURSOR_TIMEOUT_MS, }), prompt: async (start: StartStep) => coderPrompt({ threadId: start.meta.threadId }), meta: zodMeta(coderMetaSchema), }, reviewer: { adapter: hermesAdapter, prompt: async (start: StartStep) => reviewerPrompt({ threadId: start.meta.threadId, nerveRoot }), meta: zodMeta(reviewerMetaSchema), }, tester: { adapter: hermesAdapter, prompt: async (start: StartStep) => testerPrompt({ threadId: start.meta.threadId, nerveRoot }), meta: zodMeta(testerMetaSchema), }, }, moderator: () => END, }; const compiled = compileWorkflowSpec(agentSpec, compileDeps); return { name: "develop-workflow", roles: { planner: compiled.roles.planner, coder: compiled.roles.coder, reviewer: compiled.roles.reviewer, tester: compiled.roles.tester, committer: buildCommitterRole({ nerveRoot }), }, moderator, }; }