import { runConversation } from '../tasks/conversation/agent.ts'; import type { TaskOptions } from '../harness/runtime.ts'; import type { ScmAdapter } from '../scm/types.ts'; import { createTool } from '@mastra/core/tools'; import { z } from 'zod'; import { excerpt } from '../harness/context/policy.ts'; import { Trace } from '../harness/trace.ts'; type Finish = (status: string, extra?: { reason?: string; message?: string; [key: string]: unknown }) => Promise; type DeliverComment = (purpose: string, semanticKey: string, body: string, mentions: string[], source?: Record) => Promise<{ publication: unknown }>; function cleanString(trace: Trace, value: string) { const cleaned: unknown = JSON.parse(trace.clean(value)); if (typeof cleaned !== 'string') throw new Error('Conversation answer did not remain a string after redaction'); return cleaned; } export interface ConversationRunInput { repo: string; prNumber: number; runId: string; trace: Trace; scm: ScmAdapter; projectId: string; taskOptions: TaskOptions; seed: () => Promise; beginTask: (name: string) => Promise; deliverComment: DeliverComment; recipients: () => string[]; finish: Finish; } export async function runConversationReply(input: ConversationRunInput): Promise { const { prNumber, runId, trace, scm, projectId, taskOptions, seed, beginTask, deliverComment, recipients, finish } = input; await beginTask('conversation'); const conversationTools = { read_pr_comments: createTool({ id: 'read_pr_comments', description: 'Read prior PR conversation comments for context, paginated oldest first.', inputSchema: z.object({ page: z.number().int().positive().default(1) }), execute: async ({ page }: { page: number }) => { const comments = await scm.listComments(projectId, prNumber); return excerpt(JSON.stringify(comments.slice((page - 1) * 20, page * 20))); } }), }; const conversation = await runConversation({ ...taskOptions, tools: { ...conversationTools } }, await seed()); trace.save('conversation.json', conversation); if (conversation.kind !== 'reply') { return finish('needs_human', { reason: '当前 canonical conversation runner 不会在没有可验证 Conflict Proposal 绑定时发布讨论修订。' }); } const body = cleanString(trace, conversation.body); const publication = (await deliverComment('conversation_reply', 'run:' + runId + ':conversation', body, recipients(), { run_id: runId })).publication; trace.save('conversation-publication.json', publication); trace.emit('conversation_reply_published', publication as object); return finish('conversation_completed', { answer: body, publication }); }