import { mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import type { Role, RoleResult, RoleSpec, StartStep, WorkflowMessage } from "@uncaged/nerve-core"; import { hermesAdapter } from "@uncaged/nerve-adapter-hermes"; import type { LlmProvider } from "@uncaged/nerve-workflow-utils"; import { isDryRun } from "@uncaged/nerve-workflow-utils"; import { z } from "zod"; import { compileRoleSpec, defaultAgentCreateContext, zodMeta } from "../../../_shared/rfc003-compile.js"; 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 logPath(nerveRoot: string): string { return join(nerveRoot, "logs", `solve-issue-publish-${Date.now()}.log`); } export function buildPublishRole({ provider, nerveRoot }: BuildPublishDeps): Role { const innerSpec = { adapter: hermesAdapter, prompt: async (start: StartStep) => buildPublishPrompt({ threadId: start.meta.threadId, nerveRoot }), meta: zodMeta(publishMetaSchema), } satisfies RoleSpec; const runHermes = compileRoleSpec(innerSpec, { provider, createContext: defaultAgentCreateContext(nerveRoot), }); 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 }, }; } }; }