import { mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import type { AgentFn, Role, RoleResult, StartStep, WorkflowMessage } from "@uncaged/nerve-core"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import { createRole, isDryRun } 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 CreatePublishRoleDeps = { extract: LlmExtractorConfig; nerveRoot: string; }; function logPath(nerveRoot: string): string { return join(nerveRoot, "logs", `solve-issue-publish-${Date.now()}.log`); } export function createPublishRole( adapter: AgentFn, { extract, nerveRoot }: CreatePublishRoleDeps, ): Role { const innerRole = createRole( adapter, async (start: StartStep) => buildPublishPrompt({ threadId: start.meta.threadId, nerveRoot }), publishMetaSchema, extract, ); 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 }, }; } const innerStart = { ...start, meta: { ...start.meta, workdir: nerveRoot }, } as StartStep; try { return await innerRole(innerStart, 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 }, }; } }; }