import { mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import type { Role, RoleResult, Schema, StartStep, WorkflowMessage } from "@uncaged/nerve-core"; import { END } from "@uncaged/nerve-core"; import { compileWorkflowSpec } from "@uncaged/nerve-daemon"; import { hermesAdapter } from "@uncaged/nerve-adapter-hermes"; import type { LlmProvider } from "@uncaged/nerve-workflow-utils"; import { createLlmExtractFn, isDryRun, zodMeta } from "@uncaged/nerve-workflow-utils"; import { z } from "zod"; import { buildPublishPrompt } from "./prompt.js"; export const publishMetaSchema = z.object({ success: z.boolean().describe("true if git push and tea pr create both succeeded"), }); export type PublishMeta = z.infer; export type BuildPublishDeps = { provider: LlmProvider; nerveRoot: string; }; function defaultAgentCreateContext(nerveRoot: string) { return (start: StartStep, messages: WorkflowMessage[]) => ({ start, messages, workdir: nerveRoot, signal: new AbortController().signal, }); } function logPath(nerveRoot: string): string { return join(nerveRoot, "logs", `solve-issue-publish-${Date.now()}.log`); } export function buildPublishRole({ provider, nerveRoot }: BuildPublishDeps): Role { const runHermes = compileWorkflowSpec( { name: "_publish-inner", roles: { main: { adapter: hermesAdapter, prompt: async (start: StartStep) => buildPublishPrompt({ threadId: start.meta.threadId, nerveRoot }), meta: zodMeta(publishMetaSchema), }, }, moderator: () => END, }, { extractFn: async (raw: string, schema: Schema, dryRun: boolean) => createLlmExtractFn({ provider, dryRun })(raw, schema), createContext: defaultAgentCreateContext(nerveRoot), }, ).roles.main; return async (start: StartStep, messages: WorkflowMessage[]): Promise> => { const file = logPath(nerveRoot); mkdirSync(join(file, ".."), { recursive: true }); if (isDryRun(start)) { const msg = "[dry-run] publish skipped (no git push / PR)"; writeFileSync(file, `${msg}\n`, "utf-8"); return { content: `[dry-run] publish skipped — log: ${file}`, meta: { success: true }, }; } try { return await runHermes(start, messages); } catch (e) { const msg = e instanceof Error ? e.message : String(e); const body = `publish failed: ${msg}\n`; writeFileSync(file, body, "utf-8"); return { content: `publish failed: ${msg}\nLog: ${file}`, meta: { success: false }, }; } }; }