From 786c82c32acac5d989cf9777dc4a194feea64985 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:19:34 -0700 Subject: [PATCH] Batch conversation replies instead of posting per-comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Combine N unresolvable comments into a single PR conversation reply to avoid timeline spam. Review threads still get one nested reply each. Refactor helper functions to pr-comment-fixing-reply-body.ts and change replyAsConversation(comment, body) → replyAsConversation(body). --- .../components/right-sidebar/ChecksPanel.tsx | 14 +- .../pr-comments-ai-launch-ack.test.ts | 183 ++++++++++++++++-- .../pr-comments-ai-launch-ack.ts | 127 +++++++----- src/renderer/src/i18n/locales/en.json | 2 +- src/renderer/src/i18n/locales/es.json | 2 +- src/renderer/src/i18n/locales/ja.json | 2 +- src/renderer/src/i18n/locales/ko.json | 2 +- src/renderer/src/i18n/locales/zh.json | 2 +- 8 files changed, 259 insertions(+), 75 deletions(-) diff --git a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx index 3701d126614..665e3b31342 100644 --- a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx +++ b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx @@ -79,10 +79,10 @@ import { buildPRCommentsResolutionPrompt, isResolvablePRCommentGroup } from '../pr-comments-resolution-prompt' +import { buildPRCommentConversationReplyBody } from './pr-comment-fixing-reply-body' import { acknowledgePRCommentsAfterAiLaunch, attachPRReviewReplyParent, - buildPRCommentConversationReplyBody, canPostPRReviewThreadReply, checksPanelReviewStableKey, clearPendingPRCommentAiAck, @@ -3095,12 +3095,13 @@ export default function ChecksPanel(): React.JSX.Element { { value0: activeReview.provider === 'gitlab' ? 'MR' : 'PR' } ), // Why: only GitHub with a resolved PR target posts fixing replies; other providers - // must not be promised a reply the ack never sends. + // must not be promised a reply the ack never sends. Review threads get one reply + // each, conversation comments share one — so the copy stays deliberately vague. description: githubTarget && activeReview.provider === 'github' ? translate( 'auto.components.right.sidebar.ChecksPanel.ed3f79c031', - 'Review the prompt before starting an agent. After launch, Orca posts a fixing reply on each selected comment and resolves host threads when possible.' + 'Review the prompt before starting an agent. After launch, Orca replies to the selected comments and resolves host threads when possible.' ) : translate( 'auto.components.right.sidebar.ChecksPanel.abf59262fb', @@ -3234,8 +3235,9 @@ export default function ChecksPanel(): React.JSX.Element { return false } }, - // Why: CodeRabbit / review-summary / conversation comments have no nested-reply API. - replyAsConversation: async (comment, body) => { + // Why: CodeRabbit / review-summary / conversation comments have no nested-reply + // API, so the ack sends one combined body for all of them. + replyAsConversation: async (body) => { if (!githubTarget) { return false } @@ -3243,7 +3245,7 @@ export default function ChecksPanel(): React.JSX.Element { const result = await addPRConversationComment( githubTarget.repoPath, githubTarget.prNumber, - buildPRCommentConversationReplyBody(comment.author, body), + body, { repoId: githubTarget.repoId, prRepo: githubTarget.prRepo diff --git a/src/renderer/src/components/right-sidebar/pr-comments-ai-launch-ack.test.ts b/src/renderer/src/components/right-sidebar/pr-comments-ai-launch-ack.test.ts index 8af203056f8..d20a4d90265 100644 --- a/src/renderer/src/components/right-sidebar/pr-comments-ai-launch-ack.test.ts +++ b/src/renderer/src/components/right-sidebar/pr-comments-ai-launch-ack.test.ts @@ -4,18 +4,21 @@ import type { PRCommentGroup } from '@/lib/pr-comment-groups' import { acknowledgePRCommentsAfterAiLaunch, attachPRReviewReplyParent, - buildPRCommentConversationReplyBody, canPostPRReviewThreadReply, checksPanelReviewStableKey, clearPendingPRCommentAiAck, - formatPRCommentMentionHandle, getPRCommentGroupReplyTarget, PR_COMMENT_ACK_MAX_CONCURRENCY, - PR_COMMENT_AI_FIXING_REPLY, resolvePRReviewReplyThreadId, setPendingPRCommentAiAck, takePendingPRCommentAiAck } from './pr-comments-ai-launch-ack' +import { + buildPRCommentBatchConversationReplyBody, + buildPRCommentConversationReplyBody, + formatPRCommentMentionHandle, + PR_COMMENT_AI_FIXING_REPLY +} from './pr-comment-fixing-reply-body' function comment(overrides: Partial = {}): PRComment { return { @@ -123,8 +126,58 @@ describe('buildPRCommentConversationReplyBody', () => { }) }) +describe('buildPRCommentBatchConversationReplyBody', () => { + it('returns an empty body for an empty selection', () => { + expect(buildPRCommentBatchConversationReplyBody([])).toBe('') + }) + + it('keeps the plain @-reply for a single target', () => { + expect( + buildPRCommentBatchConversationReplyBody([comment({ author: 'coderabbitai[bot]' })]) + ).toBe(`@coderabbitai ${PR_COMMENT_AI_FIXING_REPLY}`) + }) + + it('lists every target once with author, kind, and a short label', () => { + const body = buildPRCommentBatchConversationReplyBody([ + comment({ + author: 'coderabbitai[bot]', + body: '\n## Nitpick comments (2)\n\ndetails', + url: 'https://github.com/acme/widgets/pull/42#pullrequestreview-999' + }), + comment({ + author: 'alice', + body: 'Please rename this variable.', + url: 'https://github.com/acme/widgets/pull/42#issuecomment-9' + }), + comment({ author: 'bob', body: 'Off by one.', path: 'src/a.ts', line: 12 }) + ]) + + expect(body).toBe( + [ + 'Fixing:', + '- @coderabbitai: review summary — Nitpick comments (2)', + '- @alice: comment — Please rename this variable.', + '- @bob: comment on src/a.ts:12 — Off by one.', + '', + 'Will be in the next commit.' + ].join('\n') + ) + }) + + it('truncates long bodies instead of quoting the whole comment', () => { + const body = buildPRCommentBatchConversationReplyBody([ + comment({ author: 'alice', body: 'a'.repeat(200) }), + comment({ author: 'bob', body: '' }) + ]) + + expect(body).toContain(`- @alice: comment — ${'a'.repeat(71)}…`) + expect(body).toContain('- @bob: comment\n') + }) +}) + describe('getPRCommentGroupReplyTarget', () => { - it('uses the latest reply in a thread when present', () => { + // Why: GitHub's replies endpoint keys off the top-level review comment id. + it('uses the thread root even when later replies exist', () => { const group: PRCommentGroup = { kind: 'thread', threadId: 'T1', @@ -134,7 +187,7 @@ describe('getPRCommentGroupReplyTarget', () => { comment({ id: 12, threadId: 'T1', path: 'a.ts', body: 'latest' }) ] } - expect(getPRCommentGroupReplyTarget(group).id).toBe(12) + expect(getPRCommentGroupReplyTarget(group).id).toBe(10) }) it('falls back to the root when there are no replies', () => { @@ -173,7 +226,6 @@ describe('attachPRReviewReplyParent', () => { }) }) }) - describe('resolvePRReviewReplyThreadId', () => { it('prefers the parent threadId', () => { expect( @@ -281,7 +333,26 @@ describe('acknowledgePRCommentsAfterAiLaunch', () => { }) expect(result).toEqual({ resolved: 0, replied: 1, skipped: 0, failed: 0 }) - expect(replyAsConversation).toHaveBeenCalledWith(group.comment, PR_COMMENT_AI_FIXING_REPLY) + expect(replyAsConversation).toHaveBeenCalledTimes(1) + expect(replyAsConversation).toHaveBeenCalledWith(`@alice ${PR_COMMENT_AI_FIXING_REPLY}`) + }) + + it('posts no conversation comment when nothing in the selection needs one', async () => { + const replyAsConversation = vi.fn() + + await acknowledgePRCommentsAfterAiLaunch({ + groups: [openThread('T1'), openThread('T2', 11)], + deps: { + isStillCurrent: () => true, + isThreadStillResolvable: () => true, + resolveThread: vi.fn().mockResolvedValue(true), + canReply: true, + replyInThread: vi.fn().mockResolvedValue(true), + replyAsConversation + } + }) + + expect(replyAsConversation).not.toHaveBeenCalled() }) it('posts a conversation @-reply for CodeRabbit review summaries', async () => { @@ -303,17 +374,17 @@ describe('acknowledgePRCommentsAfterAiLaunch', () => { expect(result).toEqual({ resolved: 0, replied: 1, skipped: 0, failed: 0 }) expect(replyInThread).not.toHaveBeenCalled() - expect(replyAsConversation).toHaveBeenCalledWith(group.comment, PR_COMMENT_AI_FIXING_REPLY) + expect(replyAsConversation).toHaveBeenCalledWith(`@coderabbitai ${PR_COMMENT_AI_FIXING_REPLY}`) }) - // Why: the reported bug — a selection of only unresolvable CodeRabbit summaries - // produced replied=0 because the ack ran the resolve path and nothing else. - it('replies to every unresolvable review summary in the selection', async () => { + // Why: the reported bug — N selected summaries used to produce N near-identical + // "Fixing." conversation comments; one combined post covers the whole set. + it('combines every unresolvable review summary into one conversation comment', async () => { const replyAsConversation = vi.fn().mockResolvedValue(true) const resolveThread = vi.fn() const result = await acknowledgePRCommentsAfterAiLaunch({ - groups: [codeRabbitReviewSummary(30), codeRabbitReviewSummary(31)], + groups: [codeRabbitReviewSummary(30), codeRabbitReviewSummary(31), standalone(32)], deps: { isStillCurrent: () => true, isThreadStillResolvable: () => false, @@ -324,7 +395,19 @@ describe('acknowledgePRCommentsAfterAiLaunch', () => { } }) - expect(result).toEqual({ resolved: 0, replied: 2, skipped: 0, failed: 0 }) + // Why: one host POST, so replied counts 1 — not one per selected comment. + expect(result).toEqual({ resolved: 0, replied: 1, skipped: 0, failed: 0 }) + expect(replyAsConversation).toHaveBeenCalledTimes(1) + expect(replyAsConversation.mock.calls[0]?.[0]).toBe( + [ + 'Fixing:', + '- @coderabbitai: review summary — Nitpick comments (2)', + '- @coderabbitai: review summary — Nitpick comments (2)', + '- @alice: comment — Overall looks good; one nit.', + '', + 'Will be in the next commit.' + ].join('\n') + ) expect(resolveThread).not.toHaveBeenCalled() }) @@ -348,7 +431,35 @@ describe('acknowledgePRCommentsAfterAiLaunch', () => { expect(result).toEqual({ resolved: 1, replied: 2, skipped: 0, failed: 0 }) expect(replyInThread).toHaveBeenCalledWith(thread.root, PR_COMMENT_AI_FIXING_REPLY) - expect(replyAsConversation).toHaveBeenCalledWith(summary.comment, PR_COMMENT_AI_FIXING_REPLY) + expect(replyAsConversation).toHaveBeenCalledWith(`@coderabbitai ${PR_COMMENT_AI_FIXING_REPLY}`) + }) + + // Why: nestable threads keep one reply each; only the conversation half is batched. + it('replies once per thread and once for the whole conversation set', async () => { + const replyInThread = vi.fn().mockResolvedValue(true) + const replyAsConversation = vi.fn().mockResolvedValue(true) + + const result = await acknowledgePRCommentsAfterAiLaunch({ + groups: [ + openThread('T1', 10), + codeRabbitReviewSummary(30), + openThread('T2', 11), + standalone(31) + ], + deps: { + isStillCurrent: () => true, + isThreadStillResolvable: () => true, + resolveThread: vi.fn().mockResolvedValue(true), + canReply: true, + replyInThread, + replyAsConversation + } + }) + + expect(result).toEqual({ resolved: 2, replied: 3, skipped: 0, failed: 0 }) + expect(replyInThread).toHaveBeenCalledTimes(2) + expect(replyAsConversation).toHaveBeenCalledTimes(1) + expect(replyAsConversation.mock.calls[0]?.[0]).toContain('Fixing:') }) it('counts a failed conversation reply instead of silently reporting success', async () => { @@ -367,7 +478,7 @@ describe('acknowledgePRCommentsAfterAiLaunch', () => { expect(result).toEqual({ resolved: 0, replied: 0, skipped: 0, failed: 1 }) }) - it('replies to the latest message in a multi-comment thread', async () => { + it('replies to the thread root in a multi-comment thread', async () => { const replyInThread = vi.fn().mockResolvedValue(true) const group: PRCommentGroup = { kind: 'thread', @@ -389,7 +500,7 @@ describe('acknowledgePRCommentsAfterAiLaunch', () => { }) expect(result).toEqual({ resolved: 0, replied: 1, skipped: 0, failed: 0 }) - expect(replyInThread).toHaveBeenCalledWith(group.replies[0], PR_COMMENT_AI_FIXING_REPLY) + expect(replyInThread).toHaveBeenCalledWith(group.root, PR_COMMENT_AI_FIXING_REPLY) }) it('skips when reply is unavailable and resolve cannot run', async () => { @@ -458,20 +569,54 @@ describe('acknowledgePRCommentsAfterAiLaunch', () => { expect(result).toEqual({ resolved: 0, replied: 3, skipped: 3, failed: 0 }) }) - it('counts a failed resolve on a group whose reply also failed', async () => { + // Why: resolving after a failed reply closes the thread with no ack at all. + it('leaves a thread open when its fixing reply failed', async () => { + const resolveThread = vi.fn().mockResolvedValue(true) + const result = await acknowledgePRCommentsAfterAiLaunch({ groups: [openThread('T1')], deps: { isStillCurrent: () => true, isThreadStillResolvable: () => true, - resolveThread: vi.fn().mockResolvedValue(false), + resolveThread, canReply: true, replyInThread: vi.fn().mockResolvedValue(false), replyAsConversation: vi.fn() } }) - expect(result).toEqual({ resolved: 0, replied: 0, skipped: 0, failed: 2 }) + expect(result).toEqual({ resolved: 0, replied: 0, skipped: 0, failed: 1 }) + expect(resolveThread).not.toHaveBeenCalled() + }) + + it('does not resolve batched conversation groups when the combined post failed', async () => { + const resolveThread = vi.fn() + const summaryThread: PRCommentGroup = { + kind: 'thread', + threadId: 'T_summary', + root: comment({ + id: 30, + threadId: 'T_summary', + isResolved: false, + url: 'https://github.com/acme/widgets/pull/42#pullrequestreview-999' + }), + replies: [] + } + + const result = await acknowledgePRCommentsAfterAiLaunch({ + groups: [summaryThread], + deps: { + isStillCurrent: () => true, + isThreadStillResolvable: () => true, + resolveThread, + canReply: true, + replyInThread: vi.fn(), + replyAsConversation: vi.fn().mockResolvedValue(false) + } + }) + + expect(result).toEqual({ resolved: 0, replied: 0, skipped: 0, failed: 1 }) + expect(resolveThread).not.toHaveBeenCalled() }) it('reports a resolvable thread as skipped when the panel context moved', async () => { diff --git a/src/renderer/src/components/right-sidebar/pr-comments-ai-launch-ack.ts b/src/renderer/src/components/right-sidebar/pr-comments-ai-launch-ack.ts index 1cca163ffaa..e8706f213df 100644 --- a/src/renderer/src/components/right-sidebar/pr-comments-ai-launch-ack.ts +++ b/src/renderer/src/components/right-sidebar/pr-comments-ai-launch-ack.ts @@ -1,9 +1,10 @@ import type { PRComment } from '../../../../shared/types' import { getPRCommentGroupRoot, type PRCommentGroup } from '@/lib/pr-comment-groups' import { isResolvablePRCommentGroup } from '../pr-comments-resolution-prompt' - -/** Posted when a selected review comment is sent to AI. */ -export const PR_COMMENT_AI_FIXING_REPLY = 'Fixing. Will be in the next commit' +import { + buildPRCommentBatchConversationReplyBody, + PR_COMMENT_AI_FIXING_REPLY +} from './pr-comment-fixing-reply-body' export type PRCommentAiLaunchAckCounts = { resolved: number @@ -25,27 +26,10 @@ export type PRCommentAiLaunchAckDeps = { */ replyInThread: (comment: PRComment, body: string) => Promise /** - * Top-level PR conversation comment (issues/.../comments), used for review - * summaries and other comments that have no nested-reply endpoint (e.g. CodeRabbit). + * One top-level PR conversation comment (issues/.../comments) covering every selected + * comment with no nested-reply endpoint (review summaries, CodeRabbit, plain comments). */ - replyAsConversation: (comment: PRComment, body: string) => Promise -} - -/** - * GitHub App logins carry a `[bot]` suffix that does not resolve as an @-mention - * (`@coderabbitai[bot]` renders literally; `@coderabbitai` reaches the bot). - */ -export function formatPRCommentMentionHandle(author: string | undefined): string { - return (author ?? '').replace(/\[bot\]$/i, '').trim() -} - -/** Top-level conversation reply body, addressed to the comment author. */ -export function buildPRCommentConversationReplyBody( - author: string | undefined, - body: string -): string { - const handle = formatPRCommentMentionHandle(author) - return handle ? `@${handle} ${body}` : body + replyAsConversation: (body: string) => Promise } /** @@ -72,12 +56,10 @@ export function canPostPRReviewThreadReply( } /** - * Prefer the latest message in a thread so the fixing reply sits after existing replies. + * Reply to the thread root: GitHub's replies endpoint keys off the top-level review + * comment id, and a later reply's id can 404 there. */ export function getPRCommentGroupReplyTarget(group: PRCommentGroup): PRComment { - if (group.kind === 'thread' && group.replies.length > 0) { - return group.replies.at(-1)! - } return getPRCommentGroupRoot(group) } @@ -174,10 +156,22 @@ async function mapWithBoundedConcurrency( return results } +type BatchedConversationReply = { + counts: PRCommentAiLaunchAckCounts + handled: ReadonlySet + replyOk: boolean +} + +const NO_BATCHED_CONVERSATION_REPLY: BatchedConversationReply = { + counts: EMPTY_ACK_COUNTS, + handled: new Set(), + replyOk: false +} + /** * After launching an agent for selected review feedback: - * post a fixing reply (nested when possible, else conversation @-reply), then - * resolve host threads when possible. + * post a fixing reply (one nested reply per review thread, one combined conversation + * comment for everything that has no nested-reply endpoint), then resolve host threads. * * Why: replies use a snapshotted GitHub target and must not abort just because * the checks-panel async key churned mid-launch. isStillCurrent only gates @@ -190,13 +184,22 @@ export async function acknowledgePRCommentsAfterAiLaunch(args: { groups: readonly PRCommentGroup[] deps: PRCommentAiLaunchAckDeps }): Promise { + // Why: conversation comments have no replies endpoint, so one post per selected item + // would spam the PR timeline with near-identical "Fixing." comments. + const conversationGroups = args.deps.canReply + ? args.groups.filter( + (group) => !canPostPRReviewThreadReply(getPRCommentGroupReplyTarget(group)) + ) + : [] + const batch = await postBatchedConversationReply(conversationGroups, args.deps) + const perGroup = await mapWithBoundedConcurrency( args.groups, PR_COMMENT_ACK_MAX_CONCURRENCY, - (group) => acknowledgePRCommentGroup(group, args.deps) + (group) => acknowledgePRCommentGroup(group, args.deps, batch) ) - return perGroup.reduce( + return [batch.counts, ...perGroup].reduce( (totals, counts) => ({ resolved: totals.resolved + counts.resolved, replied: totals.replied + counts.replied, @@ -207,32 +210,48 @@ export async function acknowledgePRCommentsAfterAiLaunch(args: { ) } +async function postBatchedConversationReply( + groups: readonly PRCommentGroup[], + deps: PRCommentAiLaunchAckDeps +): Promise { + if (groups.length === 0) { + return NO_BATCHED_CONVERSATION_REPLY + } + const replyOk = await deps.replyAsConversation( + buildPRCommentBatchConversationReplyBody(groups.map(getPRCommentGroupReplyTarget)) + ) + return { + // Why: one host POST covers the whole set, so the toast must count it once. + counts: { ...EMPTY_ACK_COUNTS, replied: replyOk ? 1 : 0, failed: replyOk ? 0 : 1 }, + handled: new Set(groups), + replyOk + } +} + async function acknowledgePRCommentGroup( group: PRCommentGroup, - deps: PRCommentAiLaunchAckDeps + deps: PRCommentAiLaunchAckDeps, + batch: BatchedConversationReply ): Promise { const counts = { ...EMPTY_ACK_COUNTS } - const replyTarget = getPRCommentGroupReplyTarget(group) const threadId = group.kind === 'thread' && isResolvablePRCommentGroup(group) ? group.threadId : null if (deps.canReply) { - // Why: prefer nested review-thread replies; fall back to a top-level conversation - // comment for review summaries / issue comments (CodeRabbit, etc.) that have no - // replies endpoint. - const repliedOk = canPostPRReviewThreadReply(replyTarget) - ? await deps.replyInThread(replyTarget, PR_COMMENT_AI_FIXING_REPLY) - : await deps.replyAsConversation(replyTarget, PR_COMMENT_AI_FIXING_REPLY) - if (repliedOk) { - counts.replied += 1 - } else { - counts.failed += 1 + const replyOk = batch.handled.has(group) + ? batch.replyOk + : await postInThreadFixingReply(group, deps, counts) + // Why: resolving a thread whose fixing reply never landed closes it silently — + // the user was promised an ack, so leave it open and let the toast report the failure. + if (!replyOk) { + return counts } - } else if (threadId == null || !deps.isThreadStillResolvable(threadId)) { - counts.skipped += 1 } if (threadId == null || !deps.isThreadStillResolvable(threadId)) { + if (!deps.canReply) { + counts.skipped += 1 + } return counts } @@ -253,6 +272,24 @@ async function acknowledgePRCommentGroup( return counts } +/** Mutates counts in place; returns whether the nested reply landed. */ +async function postInThreadFixingReply( + group: PRCommentGroup, + deps: PRCommentAiLaunchAckDeps, + counts: PRCommentAiLaunchAckCounts +): Promise { + const replyOk = await deps.replyInThread( + getPRCommentGroupReplyTarget(group), + PR_COMMENT_AI_FIXING_REPLY + ) + if (replyOk) { + counts.replied += 1 + } else { + counts.failed += 1 + } + return replyOk +} + /** Durable pending ack payload so dialog close / re-renders cannot drop it before launch. */ let pendingAiCommentAck: unknown = null diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 643662726c0..b3f602cc887 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -10142,7 +10142,7 @@ "653c105ecc": "More PR actions", "f316a8ca2b": "No unresolved comments selected.", "d00ebdc402": "Resolve {{value0}} Comments With AI", - "ed3f79c031": "Review the prompt before starting an agent. After launch, Orca posts a fixing reply on each selected comment and resolves host threads when possible.", + "ed3f79c031": "Review the prompt before starting an agent. After launch, Orca replies to the selected comments and resolves host threads when possible.", "f273f2271c": "Started the agent. Marked {{value0}} resolved, replied to {{value1}}, skipped {{value2}}, failed {{value3}}.{{value4}}", "aa95b81a3a": "Started the agent. Marked {{value0}} resolved, replied to {{value1}}, skipped {{value2}}, failed {{value3}}.", "495b2f8c4b": "Started the agent, but could not resolve or reply on the selected comments.", diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 7d3ae2323e3..351d64a638f 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -10069,7 +10069,7 @@ "653c105ecc": "Más acciones de PR", "f316a8ca2b": "No hay comentarios sin resolver seleccionados.", "d00ebdc402": "Resolver comentarios de {{value0}} con IA", - "ed3f79c031": "Revisa el prompt antes de iniciar un agente. Tras el lanzamiento, Orca publica una respuesta de corrección en cada comentario seleccionado y resuelve los hilos en el host cuando es posible.", + "ed3f79c031": "Revisa el prompt antes de iniciar un agente. Tras el lanzamiento, Orca responde a los comentarios seleccionados y resuelve los hilos en el host cuando es posible.", "f273f2271c": "Agente iniciado. Marcados como resueltos: {{value0}}; respondidos: {{value1}}; omitidos: {{value2}}; con error: {{value3}}.{{value4}}", "aa95b81a3a": "Agente iniciado. Marcados como resueltos: {{value0}}; respondidos: {{value1}}; omitidos: {{value2}}; con error: {{value3}}.", "495b2f8c4b": "Agente iniciado, pero no se pudieron resolver ni responder los comentarios seleccionados.", diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index a4af27ff358..66d0fb31207 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -10069,7 +10069,7 @@ "653c105ecc": "その他の PR 操作", "f316a8ca2b": "未解決のコメントが選択されていません。", "d00ebdc402": "{{value0}} のコメントを AI で解決", - "ed3f79c031": "agent を開始する前にプロンプトを確認してください。開始後、Orca は選択した各コメントに修正の返信を投稿し、可能な場合はホスト上のスレッドを解決します。", + "ed3f79c031": "agent を開始する前にプロンプトを確認してください。開始後、Orca は選択したコメントに返信し、可能な場合はホスト上のスレッドを解決します。", "f273f2271c": "agent を開始しました。{{value0}} 件を解決済み、{{value1}} 件に返信、{{value2}} 件をスキップ、{{value3}} 件が失敗しました。{{value4}}", "aa95b81a3a": "agent を開始しました。{{value0}} 件を解決済み、{{value1}} 件に返信、{{value2}} 件をスキップ、{{value3}} 件が失敗しました。", "495b2f8c4b": "agent を開始しましたが、選択したコメントの解決または返信ができませんでした。", diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 24ef93fbc32..de71dd1402e 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -10069,7 +10069,7 @@ "653c105ecc": "더 많은 PR 작업", "f316a8ca2b": "선택한 해결되지 않은 댓글이 없습니다.", "d00ebdc402": "AI로 {{value0}} 댓글 해결", - "ed3f79c031": "에이전트를 시작하기 전에 프롬프트를 확인하세요. 시작 후 Orca가 선택한 각 댓글에 수정 답글을 게시하고 가능한 경우 호스트 스레드를 해결합니다.", + "ed3f79c031": "에이전트를 시작하기 전에 프롬프트를 확인하세요. 시작 후 Orca가 선택한 댓글에 답글을 남기고 가능한 경우 호스트 스레드를 해결합니다.", "f273f2271c": "agent를 시작했습니다. {{value0}}개 해결됨, {{value1}}개 답글, {{value2}}개 건너뜀, {{value3}}개 실패.{{value4}}", "aa95b81a3a": "agent를 시작했습니다. {{value0}}개 해결됨, {{value1}}개 답글, {{value2}}개 건너뜀, {{value3}}개 실패.", "495b2f8c4b": "agent를 시작했지만 선택한 댓글을 해결하거나 답글할 수 없습니다.", diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 563bfa2f99c..bcf16373549 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -10069,7 +10069,7 @@ "653c105ecc": "更多 PR 操作", "f316a8ca2b": "未选择未解决的评论。", "d00ebdc402": "使用 AI 解决 {{value0}} 评论", - "ed3f79c031": "启动 agent 前请检查提示词。启动后,Orca 会在每条选中的评论下发布修复回复,并在可能时解决主机上的讨论串。", + "ed3f79c031": "启动 agent 前请检查提示词。启动后,Orca 会回复选中的评论,并在可能时解决主机上的讨论串。", "f273f2271c": "已启动智能体。已标记 {{value0}} 个为已解决,回复 {{value1}} 个,跳过 {{value2}} 个,失败 {{value3}} 个。{{value4}}", "aa95b81a3a": "已启动智能体。已标记 {{value0}} 个为已解决,回复 {{value1}} 个,跳过 {{value2}} 个,失败 {{value3}} 个。", "495b2f8c4b": "已启动智能体,但无法解决或回复选中的评论。",