diff --git a/.changeset/pr-conversation-timeline.md b/.changeset/pr-conversation-timeline.md new file mode 100644 index 000000000000..b1a2c4b8f0ee --- /dev/null +++ b/.changeset/pr-conversation-timeline.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Show commits, force pushes, merges, approvals, and the pull request description in the Agent Manager PR conversation, in the order they happened. diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-panel-conversation-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-panel-conversation-chromium-linux.png new file mode 100644 index 000000000000..f6bb2a350249 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-panel-conversation-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42b709cf3c49944b9317c2f5f70122d74cdd2e67972fc49ab75dd0802af8bfad +size 43081 diff --git a/packages/kilo-ui/src/components/icon.tsx b/packages/kilo-ui/src/components/icon.tsx index 346cf691d8fd..1f0f1715ec23 100644 --- a/packages/kilo-ui/src/components/icon.tsx +++ b/packages/kilo-ui/src/components/icon.tsx @@ -11,6 +11,14 @@ const icons: Record = { // Stroked at 1.25 on the 20-unit grid (1px at 16px) to match the other outline icons. path: ``, }, + "git-commit": { + viewBox: "0 0 20 20", + path: ``, + }, + "git-merge": { + viewBox: "0 0 20 20", + path: ``, + }, refresh: { viewBox: "0 0 20 20", path: ``, diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index 059b3bb7f94c..893d7a129197 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -1847,7 +1847,6 @@ export class AgentManagerProvider implements Disposable { (...args) => this.log(...args), ) } - public postMessage(message: unknown): void { this.panel?.postMessage(message) } diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index 37c0fd4f2bd4..7be756b96bd6 100644 --- a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts @@ -1,7 +1,7 @@ import type { ExecFileOptionsWithStringEncoding } from "child_process" import { existsSync } from "fs" import type { Worktree } from "./WorktreeStateManager" -import type { PRStatus, PRCheck, PRReviewer, PRConversationComment } from "./types" +import type { PRStatus, PRCheck, PRReviewer, PRTimelineItem } from "./types" import { execWithShellEnv } from "./shell-env" import { execGhRead } from "./gh" import { classifyPRError } from "./git-import" @@ -12,18 +12,11 @@ import { signature, formatCheckDuration, parseComments, - parseConversation, parseReviewers, summarize, } from "./pr/am-pr-utils" -import type { - PRResult, - GhThread, - GhReviewRequest, - GhReview, - GhConversationComment, - GhReviewWithBody, -} from "./pr/am-pr-types" +import { TIMELINE_QUERY, parseTimeline } from "./pr/timeline" +import type { PRResult, GhThread, GhReviewRequest, GhReview, GhTimelineItem } from "./pr/am-pr-types" import { withContext } from "./pr/pr-comment-context" import { oid } from "../shared/pr-comment-preview" @@ -298,6 +291,8 @@ export class PRStatusPoller { headRefOid: pr.headRefOid, title: pr.title, body: pr.body, + author: pr.author, + createdAt: pr.createdAt, url: pr.url, state: pr.state, review: pr.review, @@ -345,7 +340,7 @@ export class PRStatusPoller { } private static readonly BASE_JSON_FIELDS = - "id,number,title,body,url,state,isDraft,reviewDecision,additions,deletions,changedFiles,headRefName,baseRefOid,headRefOid" + "id,number,title,body,url,state,isDraft,reviewDecision,additions,deletions,changedFiles,headRefName,baseRefOid,headRefOid,author,createdAt" private static readonly PR_JSON_FIELDS = `${PRStatusPoller.BASE_JSON_FIELDS},statusCheckRollup,reviewRequests,reviews` /** Return a cached PR lookup if still fresh, otherwise fetch and cache. @@ -512,7 +507,13 @@ export class PRStatusPoller { ): Promise< | Pick< PRStatus, - "comments" | "unresolvedThreads" | "conversation" | "baseRefOid" | "headRefOid" | "viewerDidAuthor" + | "comments" + | "unresolvedThreads" + | "conversation" + | "conversationHasEarlier" + | "baseRefOid" + | "headRefOid" + | "viewerDidAuthor" > | undefined > { @@ -548,36 +549,16 @@ export class PRStatusPoller { } }` : "" - let extra = full - ? `comments(last: 50) { - nodes { - id - author { login avatarUrl __typename } - body - createdAt - url - reactionGroups { content reactors { totalCount } viewerHasReacted } - viewerDidAuthor viewerCanUpdate viewerCanDelete - } - } - reviews(last: 50) { - nodes { - id - author { login avatarUrl __typename } - body - state - submittedAt - url - reactionGroups { content reactors { totalCount } viewerHasReacted } - } - }` - : "" + // Keep the timeline in the first review-thread request. This avoids a + // second GitHub round trip while leaving non-active worktree polls cheap. + let extra = full ? TIMELINE_QUERY : "" const nodes: GhThread[] = [] const cursors = new Set() const ids = new Set() let total: number | undefined let cursor: string | undefined - let conversation: PRConversationComment[] | undefined + let conversation: PRTimelineItem[] | undefined + let conversationHasEarlier: boolean | undefined while (true) { const query = `query($owner: String!, $repo: String!, $number: Int!, $cursor: String) { repository(owner: $owner, name: $repo) { @@ -626,7 +607,9 @@ export class PRStatusPoller { } nodes.push(...page.nodes) if (extra) { - conversation = parseConversationPayload(stdout) + const parsed = parseConversationPayload(stdout) + conversation = parsed.items + conversationHasEarlier = parsed.hasEarlier extra = "" } if (nodes.length > total) throw new Error("Incomplete PR review threads") @@ -647,6 +630,7 @@ export class PRStatusPoller { unresolvedThreads: unresolved, comments: { total, unresolved, comments }, conversation, + conversationHasEarlier, } } cursor = advance(page.pageInfo.endCursor, cursors) @@ -716,11 +700,11 @@ async function settled(thunks: (() => Promise)[], concurrency: number): Pr return results } -function parseConversationPayload(stdout: string): PRConversationComment[] | undefined { - const pr = JSON.parse(stdout)?.data?.repository?.pullRequest - if (!pr) return undefined - return parseConversation( - (pr.comments?.nodes ?? []) as GhConversationComment[], - (pr.reviews?.nodes ?? []) as GhReviewWithBody[], - ) +function parseConversationPayload(stdout: string): { items?: PRTimelineItem[]; hasEarlier: boolean } { + const page = JSON.parse(stdout)?.data?.repository?.pullRequest?.timelineItems + if (!page || !Array.isArray(page.nodes)) return { hasEarlier: false } + return { + items: parseTimeline(page.nodes as Array), + hasEarlier: page.pageInfo?.hasPreviousPage === true, + } } diff --git a/packages/kilo-vscode/src/agent-manager/pr/am-pr-types.ts b/packages/kilo-vscode/src/agent-manager/pr/am-pr-types.ts index 022b598a58b2..104d0801c03e 100644 --- a/packages/kilo-vscode/src/agent-manager/pr/am-pr-types.ts +++ b/packages/kilo-vscode/src/agent-manager/pr/am-pr-types.ts @@ -73,6 +73,44 @@ export interface GhReviewWithBody { reactionGroups?: GhReactionGroup[] } +export interface GhCommitAuthor { + user?: GhAuthor + name?: string +} + +export interface GhCommit { + oid?: string + abbreviatedOid?: string + messageHeadline?: string + committedDate?: string + url?: string + author?: GhCommitAuthor +} + +/** + * One node of `PullRequest.timelineItems`. Fields are a union of the selected + * inline fragments, so only `__typename` plus the matching fields are set. + */ +export interface GhTimelineItem { + __typename?: string + id?: string + author?: GhAuthor & { __typename?: string } + body?: string + createdAt?: string + url?: string + reactionGroups?: GhReactionGroup[] + viewerDidAuthor?: boolean + viewerCanUpdate?: boolean + viewerCanDelete?: boolean + state?: string + submittedAt?: string + commit?: GhCommit + actor?: GhAuthor + mergeRefName?: string + beforeCommit?: { abbreviatedOid?: string } + afterCommit?: { abbreviatedOid?: string } +} + export interface PRResult { id?: string number: number @@ -80,6 +118,8 @@ export interface PRResult { headRefOid?: string title: string body: string + author?: string + createdAt?: string url: string state: PRState review: ReviewDecision | null diff --git a/packages/kilo-vscode/src/agent-manager/pr/am-pr-utils.ts b/packages/kilo-vscode/src/agent-manager/pr/am-pr-utils.ts index c1aa8dd24607..797307fe1062 100644 --- a/packages/kilo-vscode/src/agent-manager/pr/am-pr-utils.ts +++ b/packages/kilo-vscode/src/agent-manager/pr/am-pr-utils.ts @@ -10,9 +10,10 @@ import type { PRReactionContent, PRReviewer, PRStatus, + ReviewDecision, ReviewerState, } from "../types" -import { PR_REACTION_CONTENT } from "../../../webview-ui/agent-manager/pr/pr-types" +import { PR_REACTION_CONTENT, isConversationComment } from "../../../webview-ui/agent-manager/pr/pr-types" import type { PRResult, GhAuthor, @@ -29,15 +30,7 @@ export function parsePRResult(json: string): PRResult | null { const data = JSON.parse(json) if (!data.number) return null const state = data.isDraft ? "draft" : (data.state?.toLowerCase() ?? "open") - const decision = data.reviewDecision as string | undefined - const review = - decision === "APPROVED" - ? "approved" - : decision === "CHANGES_REQUESTED" - ? "changes_requested" - : decision === "REVIEW_REQUIRED" - ? "pending" - : null + const review = reviewValue(data.reviewDecision) const result: PRResult = { id: data.id, number: data.number, @@ -45,6 +38,8 @@ export function parsePRResult(json: string): PRResult | null { ...(typeof data.headRefOid === "string" ? { headRefOid: data.headRefOid } : {}), title: data.title ?? "", body: data.body ?? "", + ...(typeof data.author?.login === "string" ? { author: data.author.login } : {}), + ...(typeof data.createdAt === "string" ? { createdAt: data.createdAt } : {}), url: data.url ?? "", state, review, @@ -59,6 +54,13 @@ export function parsePRResult(json: string): PRResult | null { return result } +function reviewValue(value: unknown): ReviewDecision | null { + if (value === "APPROVED") return "approved" + if (value === "CHANGES_REQUESTED") return "changes_requested" + if (value === "REVIEW_REQUIRED") return "pending" + return null +} + function checks(items: unknown[]): PRStatus["checks"] { const latest = new Map() items.forEach((item, index) => { @@ -278,7 +280,7 @@ function bot(author?: GhAuthor & { __typename?: string }): boolean { return author.__typename === "Bot" || author.login.endsWith("[bot]") || author.login === "kilo-code-bot" } -function commentItem(node: GhConversationComment): PRConversationComment | null { +export function commentItem(node: GhConversationComment): PRConversationComment | null { if (!node.id || !node.body?.trim()) return null const reactions = parseReactions(node.reactionGroups) return { @@ -296,8 +298,12 @@ function commentItem(node: GhConversationComment): PRConversationComment | null } } -function reviewItem(node: GhReviewWithBody): PRConversationComment | null { - if (!node.id || !node.body?.trim()) return null +export function reviewItem(node: GhReviewWithBody): PRConversationComment | null { + // A review without text is still an event: an approval or a change request + // has to show in the conversation even when the reviewer wrote nothing. + if (!node.id) return null + const state = REVIEWER_STATE[node.state ?? ""] + if (!node.body?.trim() && !state) return null const reactions = parseReactions(node.reactionGroups) return { id: node.id, @@ -306,32 +312,15 @@ function reviewItem(node: GhReviewWithBody): PRConversationComment | null { canDelete: false, author: node.author?.login ?? "unknown", avatar: node.author?.avatarUrl, - body: node.body, + body: node.body ?? "", createdAt: node.submittedAt ? new Date(node.submittedAt).getTime() : undefined, url: node.url, - state: REVIEWER_STATE[node.state ?? ""], + state, isBot: bot(node.author) || undefined, ...(reactions.length > 0 ? { reactions } : {}), } } -export function parseConversation( - comments: GhConversationComment[], - reviews: GhReviewWithBody[], -): PRConversationComment[] { - const items: PRConversationComment[] = [] - for (const node of comments) { - const item = commentItem(node) - if (item) items.push(item) - } - for (const node of reviews) { - const item = reviewItem(node) - if (item) items.push(item) - } - items.sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0)) - return items -} - /** * Short, user-facing reason from a failed `gh` invocation. The raw message * repeats the whole command line, which is useless inside a comment card. @@ -360,6 +349,7 @@ export function mergePRStatus(prev: PRStatus | undefined, next: PRStatus): PRSta comments: next.comments ?? current?.comments, unresolvedThreads: next.unresolvedThreads ?? next.comments?.unresolved ?? current?.unresolvedThreads, conversation: next.conversation ?? prev.conversation, + conversationHasEarlier: next.conversationHasEarlier ?? prev.conversationHasEarlier, } } @@ -388,17 +378,27 @@ export function signature(pr: PRStatus): string { pr.unresolvedThreads ?? null, commentsSig(pr.comments?.comments), ], - pr.conversation?.map((c) => [ - c.id, - c.author, - c.body, - c.state ?? "", - c.isBot ? 1 : 0, - c.reactions?.map((reaction) => [reaction.content, reaction.count, reaction.viewerHasReacted]) ?? [], - c.kind, - c.canEdit, - c.canDelete, - ]) ?? [], + pr.conversation?.map((item) => + isConversationComment(item) + ? [ + item.id, + item.author, + item.body, + item.state ?? "", + item.isBot ? 1 : 0, + item.reactions?.map((reaction) => [reaction.content, reaction.count, reaction.viewerHasReacted]) ?? [], + item.kind, + item.canEdit, + item.canDelete, + ] + : [ + item.kind, + item.id, + item.createdAt ?? null, + item.kind === "commit" ? item.sha : item.event, + item.kind === "event" ? (item.detail ?? "") : "", + ], + ) ?? [], ]) } diff --git a/packages/kilo-vscode/src/agent-manager/pr/mutate-comment.ts b/packages/kilo-vscode/src/agent-manager/pr/mutate-comment.ts index cdfc10ddd01d..fa8d0e414def 100644 --- a/packages/kilo-vscode/src/agent-manager/pr/mutate-comment.ts +++ b/packages/kilo-vscode/src/agent-manager/pr/mutate-comment.ts @@ -1,4 +1,6 @@ import type { PRStatus } from "../types" +import type { PRConversationComment } from "../../../webview-ui/agent-manager/pr/pr-types" +import { isConversationComment } from "../../../webview-ui/agent-manager/pr/pr-types" import { execGhInput } from "./PRActions" import { GH_MUTATION_TIMEOUT } from "./pr-constants" @@ -36,7 +38,10 @@ function target(m: Record, pr: PRStatus) { if (action !== "delete" && (typeof m.body !== "string" || !m.body.trim())) { throw new Error("Comment cannot be blank.") } - const issue = pr.conversation?.find((comment) => comment.id === m.commentId && comment.kind === "issue") + const issue = pr.conversation?.find( + (comment): comment is PRConversationComment => + isConversationComment(comment) && comment.kind === "issue" && comment.id === m.commentId, + ) const review = pr.comments?.comments .flatMap((comment) => [comment, ...(comment.replies ?? [])]) .find((comment) => comment.id && comment.id === m.commentId) diff --git a/packages/kilo-vscode/src/agent-manager/pr/timeline.ts b/packages/kilo-vscode/src/agent-manager/pr/timeline.ts new file mode 100644 index 000000000000..eb79537d165f --- /dev/null +++ b/packages/kilo-vscode/src/agent-manager/pr/timeline.ts @@ -0,0 +1,168 @@ +/** + * PR conversation timeline: commits, lifecycle events, comments, and reviews in + * one chronological list. The GitHub GraphQL `timelineItems` connection returns + * all of them, so the poller no longer needs separate `comments` and `reviews` + * queries for the conversation. + */ +import type { + PRCommitItem, + PREventItem, + PREventKind, + PRTimelineItem, +} from "../../../webview-ui/agent-manager/pr/pr-types" +import type { GhCommit, GhConversationComment, GhReviewWithBody, GhTimelineItem } from "./am-pr-types" +import { commentItem, reviewItem } from "./am-pr-utils" + +const TIMELINE_FIELDS = `__typename + ... on IssueComment { + id + author { login avatarUrl __typename } + body + createdAt + url + reactionGroups { content reactors { totalCount } viewerHasReacted } + viewerDidAuthor viewerCanUpdate viewerCanDelete + } + ... on PullRequestReview { + id + author { login avatarUrl __typename } + body + state + submittedAt + url + reactionGroups { content reactors { totalCount } viewerHasReacted } + } + ... on PullRequestCommit { + id + commit { + oid + abbreviatedOid + messageHeadline + committedDate + url + author { user { login avatarUrl } name } + } + } + ... on MergedEvent { + id + actor { login avatarUrl } + createdAt + mergeRefName + } + ... on ClosedEvent { + id + actor { login avatarUrl } + createdAt + } + ... on ReopenedEvent { + id + actor { login avatarUrl } + createdAt + } + ... on HeadRefForcePushedEvent { + id + actor { login avatarUrl } + createdAt + beforeCommit { abbreviatedOid } + afterCommit { abbreviatedOid } + }` + +/** + * Full `timelineItems` selection. The poller embeds this in the first review + * thread request, so timeline data arrives with the other PR details instead + * of using a second request or a separate cache. + */ +export const TIMELINE_QUERY = `timelineItems(last: 100, itemTypes: [ + ISSUE_COMMENT + PULL_REQUEST_REVIEW + PULL_REQUEST_COMMIT + MERGED_EVENT + CLOSED_EVENT + REOPENED_EVENT + HEAD_REF_FORCE_PUSHED_EVENT +]) { + pageInfo { hasPreviousPage } + nodes { + ${TIMELINE_FIELDS} + } +}` + +export function parseTimeline(nodes: Array): PRTimelineItem[] { + const items: PRTimelineItem[] = [] + for (const node of nodes) { + if (!node) continue + const item = timelineItem(node) + if (item) items.push(item) + } + items.sort((a, b) => (a.createdAt ?? 0) - (b.createdAt ?? 0)) + return items +} + +function timelineItem(node: GhTimelineItem): PRTimelineItem | null { + switch (node.__typename) { + case "IssueComment": + return commentItem(node as GhConversationComment) + case "PullRequestReview": + return reviewItem(node as GhReviewWithBody) + case "PullRequestCommit": + return commitItem(node) + case "MergedEvent": + return eventItem(node, "merged", node.mergeRefName) + case "ClosedEvent": + return eventItem(node, "closed") + case "ReopenedEvent": + return eventItem(node, "reopened") + case "HeadRefForcePushedEvent": + return eventItem(node, "force_pushed", pushDetail(node)) + default: + return null + } +} + +function commitItem(node: GhTimelineItem): PRCommitItem | null { + const commit = node.commit + const sha = commit?.oid + if (!node.id || !sha) return null + const created = commitTime(commit) + return { + kind: "commit", + id: node.id, + sha, + short: commit?.abbreviatedOid ?? sha.slice(0, 7), + message: commit?.messageHeadline ?? "", + author: commitAuthor(commit), + ...(commit?.author?.user?.avatarUrl ? { avatar: commit.author.user.avatarUrl } : {}), + ...(Number.isFinite(created) ? { createdAt: created } : {}), + ...(commit?.url ? { url: commit.url } : {}), + } +} + +function commitAuthor(commit?: GhCommit): string { + return commit?.author?.user?.login ?? commit?.author?.name ?? "unknown" +} + +function commitTime(commit?: GhCommit): number { + if (!commit?.committedDate) return Number.NaN + return Date.parse(commit.committedDate) +} + +function eventItem(node: GhTimelineItem, event: PREventKind, detail?: string): PREventItem | null { + if (!node.id) return null + const created = node.createdAt ? Date.parse(node.createdAt) : Number.NaN + return { + kind: "event", + event, + id: node.id, + actor: node.actor?.login ?? "unknown", + ...(node.actor?.avatarUrl ? { avatar: node.actor.avatarUrl } : {}), + ...(Number.isFinite(created) ? { createdAt: created } : {}), + ...(detail ? { detail } : {}), + } +} + +function pushDetail(node: GhTimelineItem): string | undefined { + const before = node.beforeCommit?.abbreviatedOid + const after = node.afterCommit?.abbreviatedOid + if (!before || !after) return undefined + return `${before} to ${after}` +} diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index 42027e12bc0a..3019c64d6d99 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -59,7 +59,12 @@ import type { PRComment, ReviewerState, PRReviewer, + PRStatus, PRConversationComment, + PRCommitItem, + PREventItem, + PREventKind, + PRTimelineItem, PRReaction, PRReactionContent, } from "../../webview-ui/agent-manager/pr/pr-types" @@ -74,43 +79,16 @@ export type { PRComment, ReviewerState, PRReviewer, + PRStatus, PRConversationComment, + PRCommitItem, + PREventItem, + PREventKind, + PRTimelineItem, PRReaction, PRReactionContent, } -export interface PRStatus { - viewerDidAuthor?: boolean - id?: string - number: number - baseRefOid?: string - headRefOid?: string - title: string - body?: string - url: string - state: PRState - review: ReviewDecision | null - checks: { - status: AggregateCheckStatus - total: number - passed: number - failed: number - pending: number - checks: PRCheck[] - } - reviewers: PRReviewer[] - unresolvedThreads?: number - comments?: { - total: number - unresolved: number - comments: PRComment[] - } - conversation?: PRConversationComment[] - additions: number - deletions: number - files: number -} - // --------------------------------------------------------------------------- // Extension → Webview messages (postToWebview) // --------------------------------------------------------------------------- diff --git a/packages/kilo-vscode/tests/fixtures/pr-comments-render.tsx b/packages/kilo-vscode/tests/fixtures/pr-comments-render.tsx index 162b37de848e..c771475c9310 100644 --- a/packages/kilo-vscode/tests/fixtures/pr-comments-render.tsx +++ b/packages/kilo-vscode/tests/fixtures/pr-comments-render.tsx @@ -1083,7 +1083,7 @@ assert.equal(commentState(target.worktreeId).open, true) assert.equal(jumps, 1) // Conversation comments render at the bottom of the PR panel -assert.match(second.textContent ?? "", /PR Comments/) +assert.match(second.textContent ?? "", /Conversation/) assert.match(second.textContent ?? "", /lead-reviewer/) assert.match(second.textContent ?? "", /Consider simplifying the signature serializer/) assert.match(second.textContent ?? "", /Approved/) diff --git a/packages/kilo-vscode/tests/fixtures/pr-conversation-render.tsx b/packages/kilo-vscode/tests/fixtures/pr-conversation-render.tsx new file mode 100644 index 000000000000..a2ac62f401ec --- /dev/null +++ b/packages/kilo-vscode/tests/fixtures/pr-conversation-render.tsx @@ -0,0 +1,96 @@ +import assert from "node:assert/strict" +import type { WebviewMessage } from "../../webview-ui/src/types/messages" +import { harness } from "./comment-harness" + +const { root, wait, mount, node } = await harness() +const { PRConversation } = await import("../../webview-ui/agent-manager/pr/PRConversation") + +const opened: string[] = [] +const dispose = mount(() => ( + opened.push(url)} + /> +)) + +await wait() +assert.match(root.textContent ?? "", /Initial PR body/) +assert.match(root.textContent ?? "", /Show earlier activity/) +assert.match(root.textContent ?? "", /marius added 2 commits/) +assert.doesNotMatch(root.textContent ?? "", /First commit/) +assert.match(root.textContent ?? "", /marius force-pushed aaaaaaa to bbbbbbb/) +assert.match(root.textContent ?? "", /alice merged into main/) +assert.match(root.textContent ?? "", /alice approved these changes/) +assert.match(root.textContent ?? "", /Please update the docs/) +const commitGroup = node("[data-timeline-row][aria-expanded]") +assert.equal(commitGroup.getAttribute("aria-expanded"), "false") +commitGroup.click() +await wait() +assert.equal(commitGroup.getAttribute("aria-expanded"), "true") +assert.match(root.textContent ?? "", /First commit/) +assert.match(root.textContent ?? "", /Second commit/) +const sha = node(".am-pr-timeline-link") +sha.click() +assert.deepEqual(opened, ["https://github.com/example/repo/commit/aaaaaaa"]) +const timelineComment = node('[data-thread-id="IC1"]') +assert.ok(timelineComment.querySelector('.am-pr-comment-actions [data-variant="primary"]')) +dispose() diff --git a/packages/kilo-vscode/tests/unit/am-pr-status-bridge.test.ts b/packages/kilo-vscode/tests/unit/am-pr-status-bridge.test.ts index 7b84a02cea93..24e84df709b8 100644 --- a/packages/kilo-vscode/tests/unit/am-pr-status-bridge.test.ts +++ b/packages/kilo-vscode/tests/unit/am-pr-status-bridge.test.ts @@ -372,8 +372,26 @@ describe("PRStatusPoller unresolved threads", () => { ) if (active && !after) Object.assign(data.data.repository.pullRequest, { - comments: { nodes: [{ id: "conversation", body: "General comment" }] }, - reviews: { nodes: [{ id: "review", body: "Review summary", state: "CHANGES_REQUESTED" }] }, + timelineItems: { + pageInfo: { hasPreviousPage: true }, + nodes: [ + { + __typename: "IssueComment", + id: "conversation", + author: { login: "alice" }, + body: "General comment", + createdAt: "2026-09-01T10:00:00Z", + }, + { + __typename: "PullRequestReview", + id: "review", + author: { login: "bob" }, + body: "Review summary", + state: "CHANGES_REQUESTED", + submittedAt: "2026-09-01T11:00:00Z", + }, + ], + }, }) return { stdout: JSON.stringify(data), stderr: "" } } @@ -391,8 +409,9 @@ describe("PRStatusPoller unresolved threads", () => { expect(query.includes("comments(first: 10)")).toBe(active) expect(query.includes("latest: comments(last: 10)")).toBe(active) expect(query.includes("body")).toBe(active) - expect(query.includes("comments(last: 50)")).toBe(active && !args.includes("cursor=next")) - expect(query.includes("reviews(last: 50)")).toBe(active && !args.includes("cursor=next")) + expect(query.includes("timelineItems(last: 100")).toBe(active && !args.includes("cursor=next")) + expect(query.includes("PULL_REQUEST_COMMIT")).toBe(active && !args.includes("cursor=next")) + expect(query.includes("pageInfo { hasPreviousPage }")).toBe(active && !args.includes("cursor=next")) expect(query.includes("viewerDidAuthor viewerCanUpdate viewerCanDelete")).toBe(active) } if (active) { @@ -403,6 +422,7 @@ describe("PRStatusPoller unresolved threads", () => { { id: "conversation", body: "General comment" }, { id: "review", body: "Review summary", state: "changes_requested" }, ]) + expect(status?.conversationHasEarlier).toBe(true) } if (!active) { expect(status?.comments).toBeUndefined() diff --git a/packages/kilo-vscode/tests/unit/am-pr-utils.test.ts b/packages/kilo-vscode/tests/unit/am-pr-utils.test.ts index 30dde8cc9cf8..63fdcce26843 100644 --- a/packages/kilo-vscode/tests/unit/am-pr-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/am-pr-utils.test.ts @@ -6,19 +6,14 @@ import { formatCheckDuration, ghErrorReason, parseComments, - parseConversation, parseReactions, parseReviewers, signature, summarize, } from "../../src/agent-manager/pr/am-pr-utils" -import type { - GhThread, - GhReviewRequest, - GhReview, - GhConversationComment, - GhReviewWithBody, -} from "../../src/agent-manager/pr/am-pr-types" +import { parseTimeline } from "../../src/agent-manager/pr/timeline" +import { isConversationComment } from "../../webview-ui/agent-manager/pr/pr-types" +import type { GhThread, GhReviewRequest, GhReview, GhTimelineItem } from "../../src/agent-manager/pr/am-pr-types" import type { PRComment, PRConversationComment, PRStatus } from "../../src/agent-manager/types" // --- parsePRResult --- @@ -43,8 +38,12 @@ describe("comment permissions", () => { const thread = parseComments([{ comments: { nodes: [node] }, latest: { nodes: [{ ...node, id: "reply" }] } }]).at(0) expect(thread).toMatchObject(expected) expect(thread?.replies?.at(0)).toMatchObject({ ...expected, id: "reply" }) - expect(parseConversation([node], []).at(0)).toMatchObject({ ...expected, kind: "issue" }) - expect(parseConversation([], [node]).at(0)).toMatchObject({ kind: "review", canEdit: false, canDelete: false }) + expect(parseTimeline([{ ...node, __typename: "IssueComment" }]).at(0)).toMatchObject({ ...expected, kind: "issue" }) + expect(parseTimeline([{ ...node, __typename: "PullRequestReview" }]).at(0)).toMatchObject({ + kind: "review", + canEdit: false, + canDelete: false, + }) }) }) @@ -760,16 +759,21 @@ describe("parseReviewers", () => { }) }) -// --- parseConversation --- +// --- parseTimeline --- -describe("parseConversation", () => { - it("parses empty lists to an empty array", () => { - expect(parseConversation([], [])).toEqual([]) +describe("parseTimeline", () => { + const comment = (item: ReturnType[number] | undefined) => + item && isConversationComment(item) ? item : undefined + + it("parses an empty timeline to an empty array", () => { + expect(parseTimeline([])).toEqual([]) + expect(parseTimeline([null])).toEqual([]) }) - it("extracts comments and reviews with non-empty bodies", () => { - const comments: GhConversationComment[] = [ + it("extracts comments and reviews", () => { + const nodes: GhTimelineItem[] = [ { + __typename: "IssueComment", id: "IC_1", author: { login: "alice", avatarUrl: "https://avatar/alice" }, body: "First comment", @@ -777,13 +781,13 @@ describe("parseConversation", () => { url: "https://github.com/org/repo/pull/1#issuecomment-1", }, { + __typename: "IssueComment", id: "IC_empty", author: { login: "bob" }, body: " ", }, - ] - const reviews: GhReviewWithBody[] = [ { + __typename: "PullRequestReview", id: "PRR_1", author: { login: "bob", avatarUrl: "https://avatar/bob" }, body: "Consider using rawJSON", @@ -791,15 +795,9 @@ describe("parseConversation", () => { submittedAt: "2026-09-01T11:00:00Z", url: "https://github.com/org/repo/pull/1#pullrequestreview-1", }, - { - id: "PRR_empty", - author: { login: "charlie" }, - body: "", - state: "APPROVED", - }, ] - const result = parseConversation(comments, reviews) + const result = parseTimeline(nodes) expect(result).toHaveLength(2) expect(result[0]).toEqual({ id: "IC_1", @@ -828,52 +826,138 @@ describe("parseConversation", () => { }) }) - it("sorts comments and reviews chronologically", () => { - const comments: GhConversationComment[] = [ + it("keeps a review without text so an approval is still visible", () => { + const result = parseTimeline([ { - id: "IC_late", + __typename: "PullRequestReview", + id: "PRR_approve", author: { login: "alice" }, - body: "Later comment", - createdAt: "2026-09-01T12:00:00Z", + state: "APPROVED", + submittedAt: "2026-09-01T11:00:00Z", }, - ] - const reviews: GhReviewWithBody[] = [ + ]) + expect(result).toHaveLength(1) + expect(result[0]).toMatchObject({ kind: "review", body: "", state: "approved", author: "alice" }) + }) + + it("drops a review without text or a known state", () => { + expect(parseTimeline([{ __typename: "PullRequestReview", id: "PRR_x", author: { login: "alice" } }])).toEqual([]) + }) + + it("parses commits with author, short SHA, and message", () => { + const result = parseTimeline([ { - id: "PRR_early", + __typename: "PullRequestCommit", + id: "PRC_1", + commit: { + oid: "a".repeat(40), + abbreviatedOid: "aaaaaaa", + messageHeadline: "Handle reconnect", + committedDate: "2026-09-01T12:00:00Z", + url: "https://github.com/org/repo/commit/aaaaaaa", + author: { user: { login: "marius", avatarUrl: "https://avatar/marius" }, name: "Marius" }, + }, + }, + ]) + expect(result).toEqual([ + { + kind: "commit", + id: "PRC_1", + sha: "a".repeat(40), + short: "aaaaaaa", + message: "Handle reconnect", + author: "marius", + avatar: "https://avatar/marius", + createdAt: new Date("2026-09-01T12:00:00Z").getTime(), + url: "https://github.com/org/repo/commit/aaaaaaa", + }, + ]) + }) + + it("falls back to the git author name when no user is linked", () => { + const result = parseTimeline([ + { __typename: "PullRequestCommit", id: "PRC_2", commit: { oid: "b".repeat(40), author: { name: "CI Bot" } } }, + ]) + expect(result[0]).toMatchObject({ kind: "commit", author: "CI Bot", short: "bbbbbbb" }) + }) + + it("parses lifecycle events with actor and detail", () => { + const result = parseTimeline([ + { + __typename: "MergedEvent", + id: "ME_1", + actor: { login: "alice" }, + createdAt: "2026-09-01T13:00:00Z", + mergeRefName: "main", + }, + { + __typename: "HeadRefForcePushedEvent", + id: "FP_1", + actor: { login: "marius" }, + createdAt: "2026-09-01T14:00:00Z", + beforeCommit: { abbreviatedOid: "aaaaaaa" }, + afterCommit: { abbreviatedOid: "bbbbbbb" }, + }, + { __typename: "ClosedEvent", id: "CE_1", actor: { login: "bob" }, createdAt: "2026-09-01T15:00:00Z" }, + { __typename: "ReopenedEvent", id: "RE_1", actor: { login: "bob" }, createdAt: "2026-09-01T16:00:00Z" }, + ]) + expect(result.map((item) => (item.kind === "event" ? [item.event, item.detail] : []))).toEqual([ + ["merged", "main"], + ["force_pushed", "aaaaaaa to bbbbbbb"], + ["closed", undefined], + ["reopened", undefined], + ]) + }) + + it("sorts comments, reviews, commits, and events chronologically", () => { + const result = parseTimeline([ + { + __typename: "PullRequestCommit", + id: "PRC_late", + commit: { oid: "c".repeat(40), committedDate: "2026-09-01T12:00:00Z" }, + }, + { + __typename: "IssueComment", + id: "IC_early", + author: { login: "alice" }, + body: "First", + createdAt: "2026-09-01T08:00:00Z", + }, + { + __typename: "PullRequestReview", + id: "PRR_mid", author: { login: "bob" }, - body: "Earlier review", + body: "Review", state: "CHANGES_REQUESTED", - submittedAt: "2026-09-01T08:00:00Z", + submittedAt: "2026-09-01T10:00:00Z", }, - ] - - const result = parseConversation(comments, reviews) - expect(result.map((c) => c.id)).toEqual(["PRR_early", "IC_late"]) + ]) + expect(result.map((item) => item.id)).toEqual(["IC_early", "PRR_mid", "PRC_late"]) }) it("identifies bot accounts", () => { - const comments: GhConversationComment[] = [ + const result = parseTimeline([ { + __typename: "IssueComment", id: "IC_bot1", author: { login: "kilo-code-bot", __typename: "Bot" }, body: "Review summary", }, + { __typename: "IssueComment", id: "IC_bot2", author: { login: "dependabot[bot]" }, body: "Bump dependency" }, { - id: "IC_bot2", - author: { login: "dependabot[bot]" }, - body: "Bump dependency", - }, - { + __typename: "IssueComment", id: "IC_user", author: { login: "alice", __typename: "User" }, body: "User comment", }, - ] + ]) + expect(comment(result.find((item) => item.id === "IC_bot1"))?.isBot).toBe(true) + expect(comment(result.find((item) => item.id === "IC_bot2"))?.isBot).toBe(true) + expect(comment(result.find((item) => item.id === "IC_user"))?.isBot).toBeUndefined() + }) - const result = parseConversation(comments, []) - expect(result.find((c) => c.id === "IC_bot1")?.isBot).toBe(true) - expect(result.find((c) => c.id === "IC_bot2")?.isBot).toBe(true) - expect(result.find((c) => c.id === "IC_user")?.isBot).toBeUndefined() + it("ignores timeline item types the conversation does not render", () => { + expect(parseTimeline([{ __typename: "LabeledEvent", id: "LE_1" }])).toEqual([]) }) }) diff --git a/packages/kilo-vscode/tests/unit/pr-conversation-render.test.ts b/packages/kilo-vscode/tests/unit/pr-conversation-render.test.ts new file mode 100644 index 000000000000..f2ddf7c352f0 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/pr-conversation-render.test.ts @@ -0,0 +1,8 @@ +import { it } from "bun:test" +import { fixture } from "../fixtures/run" + +it( + "renders the PR conversation timeline with commits and lifecycle events", + () => fixture("pr-conversation-render"), + 30_000, +) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts index 2a3018adfe3f..adf400670b50 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts @@ -312,7 +312,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "إزالة التفاعل", "agentManager.pr.comment.reactionPicker": "اختيار تفاعل", "agentManager.pr.comment.reactionFailed": "تعذّر تحديث التفاعل. {{error}}", - "agentManager.pr.conversation.title": "تعليقات طلب السحب", + "agentManager.pr.conversation.title": "المحادثة", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "تجاهل", "agentManager.pr.conversation.restore": "استعادة", "agentManager.pr.conversation.sendAll": "إصلاح {{count}} باستخدام Kilo", @@ -331,7 +341,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} مطوي", "agentManager.review.collapsedWithLarge": "{{collapsed}} مطوي، {{large}} كبير", "agentManager.review.largeFileCollapsed": "ملف كبير (مطوي)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts index cc635c324dc2..34fc82f5d725 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts @@ -318,7 +318,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Remover reação", "agentManager.pr.comment.reactionPicker": "Escolher uma reação", "agentManager.pr.comment.reactionFailed": "Não foi possível atualizar a reação. {{error}}", - "agentManager.pr.conversation.title": "Comentários do PR", + "agentManager.pr.conversation.title": "Conversa", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Descartar", "agentManager.pr.conversation.restore": "Restaurar", "agentManager.pr.conversation.sendAll": "Corrigir {{count}} com Kilo", @@ -337,7 +347,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} recolhidos", "agentManager.review.collapsedWithLarge": "{{collapsed}} recolhidos, {{large}} grandes", "agentManager.review.largeFileCollapsed": "Arquivo grande (recolhido)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts index 6e4d4980647e..8ea375603d95 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts @@ -316,7 +316,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Ukloni reakciju", "agentManager.pr.comment.reactionPicker": "Odaberi reakciju", "agentManager.pr.comment.reactionFailed": "Reakciju nije moguće ažurirati. {{error}}", - "agentManager.pr.conversation.title": "PR komentari", + "agentManager.pr.conversation.title": "Konverzacija", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Odbaci", "agentManager.pr.conversation.restore": "Vrati", "agentManager.pr.conversation.sendAll": "Popravi {{count}} pomoću Kilo", @@ -335,7 +345,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} sažeto", "agentManager.review.collapsedWithLarge": "{{collapsed}} sažeto, {{large}} velikih", "agentManager.review.largeFileCollapsed": "Velika datoteka (sažeto)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts index 96b408787bcc..566332d0d7ab 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts @@ -317,7 +317,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Fjern reaktion", "agentManager.pr.comment.reactionPicker": "Vælg en reaktion", "agentManager.pr.comment.reactionFailed": "Kunne ikke opdatere reaktionen. {{error}}", - "agentManager.pr.conversation.title": "PR-kommentarer", + "agentManager.pr.conversation.title": "Samtale", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Afvis", "agentManager.pr.conversation.restore": "Gendan", "agentManager.pr.conversation.sendAll": "Ret {{count}} med Kilo", @@ -336,7 +346,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} foldet sammen", "agentManager.review.collapsedWithLarge": "{{collapsed}} foldet sammen, {{large}} store", "agentManager.review.largeFileCollapsed": "Stor fil (sammenklappet)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts index f9be0e4c2d82..dda359d2b1b7 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts @@ -324,7 +324,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Reaktion entfernen", "agentManager.pr.comment.reactionPicker": "Reaktion auswählen", "agentManager.pr.comment.reactionFailed": "Reaktion konnte nicht aktualisiert werden. {{error}}", - "agentManager.pr.conversation.title": "PR-Kommentare", + "agentManager.pr.conversation.title": "Konversation", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Verwerfen", "agentManager.pr.conversation.restore": "Wiederherstellen", "agentManager.pr.conversation.sendAll": "{{count}} mit Kilo beheben", @@ -343,7 +353,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} eingeklappt", "agentManager.review.collapsedWithLarge": "{{collapsed}} eingeklappt, {{large}} groß", "agentManager.review.largeFileCollapsed": "Große Datei (eingeklappt)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts index 7b82e8567501..20e3b9d19517 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts @@ -317,7 +317,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Remove reaction", "agentManager.pr.comment.reactionPicker": "Choose a reaction", "agentManager.pr.comment.reactionFailed": "Could not update reaction. {{error}}", - "agentManager.pr.conversation.title": "PR Comments", + "agentManager.pr.conversation.title": "Conversation", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Dismiss", "agentManager.pr.conversation.restore": "Restore", "agentManager.pr.conversation.sendAll": "Fix {{count}} with Kilo", @@ -336,7 +346,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} collapsed", "agentManager.review.collapsedWithLarge": "{{collapsed}} collapsed, {{large}} large", "agentManager.review.largeFileCollapsed": "Large file (collapsed)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts index b9c57e67151c..8d6291928d1b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts @@ -321,7 +321,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Quitar reacción", "agentManager.pr.comment.reactionPicker": "Elegir una reacción", "agentManager.pr.comment.reactionFailed": "No se pudo actualizar la reacción. {{error}}", - "agentManager.pr.conversation.title": "Comentarios del PR", + "agentManager.pr.conversation.title": "Conversación", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Descartar", "agentManager.pr.conversation.restore": "Restaurar", "agentManager.pr.conversation.sendAll": "Corregir {{count}} con Kilo", @@ -340,7 +350,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} contraídos", "agentManager.review.collapsedWithLarge": "{{collapsed}} contraídos, {{large}} grandes", "agentManager.review.largeFileCollapsed": "Archivo grande (contraído)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts index f8d72ba6c3f8..88eb15b3fdef 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts @@ -320,7 +320,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "حذف واکنش", "agentManager.pr.comment.reactionPicker": "انتخاب واکنش", "agentManager.pr.comment.reactionFailed": "به‌روزرسانی واکنش ممکن نیست. {{error}}", - "agentManager.pr.conversation.title": "دیدگاه‌های درخواست ادغام", + "agentManager.pr.conversation.title": "گفتگو", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "نادیده‌گرفتن", "agentManager.pr.conversation.restore": "بازیابی", "agentManager.pr.conversation.sendAll": "رفع اشکال {{count}} مورد با Kilo", @@ -339,7 +349,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} جمع‌شده", "agentManager.review.collapsedWithLarge": "{{collapsed}} جمع‌شده، {{large}} بزرگ", "agentManager.review.largeFileCollapsed": "فایل بزرگ (جمع‌شده)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts index a30bd1c0ab0b..5bc67688cc6f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts @@ -324,7 +324,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Supprimer la réaction", "agentManager.pr.comment.reactionPicker": "Choisir une réaction", "agentManager.pr.comment.reactionFailed": "Impossible de mettre à jour la réaction. {{error}}", - "agentManager.pr.conversation.title": "Commentaires de la PR", + "agentManager.pr.conversation.title": "Conversation", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Ignorer", "agentManager.pr.conversation.restore": "Restaurer", "agentManager.pr.conversation.sendAll": "Corriger {{count}} avec Kilo", @@ -343,7 +353,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} repliés", "agentManager.review.collapsedWithLarge": "{{collapsed}} repliés, {{large}} volumineux", "agentManager.review.largeFileCollapsed": "Fichier volumineux (replié)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts index 31be16d77ad1..fe448fa77c9d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts @@ -326,7 +326,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Rimuovi reazione", "agentManager.pr.comment.reactionPicker": "Scegli una reazione", "agentManager.pr.comment.reactionFailed": "Impossibile aggiornare la reazione. {{error}}", - "agentManager.pr.conversation.title": "Commenti della PR", + "agentManager.pr.conversation.title": "Conversazione", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Ignora", "agentManager.pr.conversation.restore": "Ripristina", "agentManager.pr.conversation.sendAll": "Correggi {{count}} con Kilo", @@ -345,7 +355,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} compressi", "agentManager.review.collapsedWithLarge": "{{collapsed}} compressi, {{large}} grandi", "agentManager.review.largeFileCollapsed": "File grande (compresso)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts index 3f35222a6e33..86e34a3b163d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts @@ -317,7 +317,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "リアクションを削除", "agentManager.pr.comment.reactionPicker": "リアクションを選択", "agentManager.pr.comment.reactionFailed": "リアクションを更新できませんでした。{{error}}", - "agentManager.pr.conversation.title": "PRコメント", + "agentManager.pr.conversation.title": "会話", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "閉じる", "agentManager.pr.conversation.restore": "復元", "agentManager.pr.conversation.sendAll": "Kiloで{{count}}件を修正", @@ -336,7 +346,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} 件折りたたみ", "agentManager.review.collapsedWithLarge": "{{collapsed}} 件折りたたみ、{{large}} 件がサイズ大", "agentManager.review.largeFileCollapsed": "大きなファイル(折りたたみ)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts index 81e6a5c016f5..42671e918d8a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts @@ -315,7 +315,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "반응 삭제", "agentManager.pr.comment.reactionPicker": "반응 선택", "agentManager.pr.comment.reactionFailed": "반응을 업데이트할 수 없습니다. {{error}}", - "agentManager.pr.conversation.title": "PR 댓글", + "agentManager.pr.conversation.title": "대화", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "숨기기", "agentManager.pr.conversation.restore": "복원", "agentManager.pr.conversation.sendAll": "Kilo로 {{count}}개 수정", @@ -334,7 +344,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}}개 접힘", "agentManager.review.collapsedWithLarge": "{{collapsed}}개 접힘, {{large}}개 대용량", "agentManager.review.largeFileCollapsed": "큰 파일(접힘)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts index 593d7b850129..73d241652d6e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts @@ -324,7 +324,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Reactie verwijderen", "agentManager.pr.comment.reactionPicker": "Kies een reactie", "agentManager.pr.comment.reactionFailed": "Kan de reactie niet bijwerken. {{error}}", - "agentManager.pr.conversation.title": "PR-opmerkingen", + "agentManager.pr.conversation.title": "Gesprek", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Negeren", "agentManager.pr.conversation.restore": "Herstellen", "agentManager.pr.conversation.sendAll": "{{count}} herstellen met Kilo", @@ -343,7 +353,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} ingeklapt", "agentManager.review.collapsedWithLarge": "{{collapsed}} ingeklapt, {{large}} groot", "agentManager.review.largeFileCollapsed": "Groot bestand (ingeklapt)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts index e1e6ecd8ac2a..dbfe15ab8841 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts @@ -315,7 +315,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Fjern reaksjon", "agentManager.pr.comment.reactionPicker": "Velg en reaksjon", "agentManager.pr.comment.reactionFailed": "Kunne ikke oppdatere reaksjonen. {{error}}", - "agentManager.pr.conversation.title": "PR-kommentarer", + "agentManager.pr.conversation.title": "Samtale", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Avvis", "agentManager.pr.conversation.restore": "Gjenopprett", "agentManager.pr.conversation.sendAll": "Fiks {{count}} med Kilo", @@ -334,7 +344,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} kollapset", "agentManager.review.collapsedWithLarge": "{{collapsed}} kollapset, {{large}} store", "agentManager.review.largeFileCollapsed": "Stor fil (sammenfoldet)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts index 96ea8137ff7b..f81e13247db9 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts @@ -317,7 +317,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Usuń reakcję", "agentManager.pr.comment.reactionPicker": "Wybierz reakcję", "agentManager.pr.comment.reactionFailed": "Nie udało się zaktualizować reakcji. {{error}}", - "agentManager.pr.conversation.title": "Komentarze do PR", + "agentManager.pr.conversation.title": "Konwersacja", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Odrzuć", "agentManager.pr.conversation.restore": "Przywróć", "agentManager.pr.conversation.sendAll": "Napraw {{count}} za pomocą Kilo", @@ -336,7 +346,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} zwiniętych", "agentManager.review.collapsedWithLarge": "{{collapsed}} zwiniętych, {{large}} dużych", "agentManager.review.largeFileCollapsed": "Duży plik (zwinięty)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts index d19a3b887112..245f86df9653 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts @@ -320,7 +320,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Удалить реакцию", "agentManager.pr.comment.reactionPicker": "Выбрать реакцию", "agentManager.pr.comment.reactionFailed": "Не удалось обновить реакцию. {{error}}", - "agentManager.pr.conversation.title": "Комментарии к PR", + "agentManager.pr.conversation.title": "Обсуждение", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Скрыть", "agentManager.pr.conversation.restore": "Восстановить", "agentManager.pr.conversation.sendAll": "Исправить {{count}} с помощью Kilo", @@ -339,7 +349,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} свернуто", "agentManager.review.collapsedWithLarge": "{{collapsed}} свернуто, {{large}} больших", "agentManager.review.largeFileCollapsed": "Большой файл (свернут)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts index 7b1097b4710f..985a01f92c1e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts @@ -311,7 +311,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "ลบรีแอ็กชัน", "agentManager.pr.comment.reactionPicker": "เลือกรีแอ็กชัน", "agentManager.pr.comment.reactionFailed": "อัปเดตรีแอ็กชันไม่สำเร็จ {{error}}", - "agentManager.pr.conversation.title": "ความคิดเห็นของ PR", + "agentManager.pr.conversation.title": "การสนทนา", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "ละเว้น", "agentManager.pr.conversation.restore": "คืนค่า", "agentManager.pr.conversation.sendAll": "แก้ไข {{count}} รายการด้วย Kilo", @@ -330,7 +340,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "ยุบ {{count}} รายการ", "agentManager.review.collapsedWithLarge": "ยุบ {{collapsed}} รายการ, ขนาดใหญ่ {{large}} รายการ", "agentManager.review.largeFileCollapsed": "ไฟล์ขนาดใหญ่ (พับอยู่)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts index 6086dc91d170..22f0b9b4925a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts @@ -325,7 +325,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Tepkiyi kaldır", "agentManager.pr.comment.reactionPicker": "Bir tepki seç", "agentManager.pr.comment.reactionFailed": "Tepki güncellenemedi. {{error}}", - "agentManager.pr.conversation.title": "PR Yorumları", + "agentManager.pr.conversation.title": "Konuşma", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Kapat", "agentManager.pr.conversation.restore": "Geri yükle", "agentManager.pr.conversation.sendAll": "Kilo ile {{count}} öğeyi düzelt", @@ -344,7 +354,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} daraltıldı", "agentManager.review.collapsedWithLarge": "{{collapsed}} daraltıldı, {{large}} büyük", "agentManager.review.largeFileCollapsed": "Büyük dosya (daraltıldı)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts index 6329e7048426..7a089e08e528 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts @@ -328,7 +328,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "Видалити реакцію", "agentManager.pr.comment.reactionPicker": "Вибрати реакцію", "agentManager.pr.comment.reactionFailed": "Не вдалося оновити реакцію. {{error}}", - "agentManager.pr.conversation.title": "Коментарі до PR", + "agentManager.pr.conversation.title": "Обговорення", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "Сховати", "agentManager.pr.conversation.restore": "Відновити", "agentManager.pr.conversation.sendAll": "Виправити {{count}} за допомогою Kilo", @@ -347,7 +357,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} згорнуто", "agentManager.review.collapsedWithLarge": "{{collapsed}} згорнуто, {{large}} великих", "agentManager.review.largeFileCollapsed": "Великий файл (згорнуто)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts index 0d7c318b73e1..da14471b88ab 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts @@ -307,7 +307,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "移除反应", "agentManager.pr.comment.reactionPicker": "选择反应", "agentManager.pr.comment.reactionFailed": "无法更新反应。{{error}}", - "agentManager.pr.conversation.title": "PR 评论", + "agentManager.pr.conversation.title": "对话", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "忽略", "agentManager.pr.conversation.restore": "恢复", "agentManager.pr.conversation.sendAll": "使用 Kilo 修复 {{count}} 个问题", @@ -326,7 +336,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} 个已折叠", "agentManager.review.collapsedWithLarge": "{{collapsed}} 个已折叠,{{large}} 个过大", "agentManager.review.largeFileCollapsed": "大文件(已折叠)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts index 4563046d916b..ae1dabe189b9 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts @@ -307,7 +307,17 @@ export const dict = { "agentManager.pr.comment.removeReaction": "移除反應", "agentManager.pr.comment.reactionPicker": "選擇反應", "agentManager.pr.comment.reactionFailed": "無法更新反應。{{error}}", - "agentManager.pr.conversation.title": "PR 留言", + "agentManager.pr.conversation.title": "對話", + "agentManager.pr.timeline.opened": "opened this pull request", + "agentManager.pr.timeline.commits": "{{author}} added {{count}} commits", + "agentManager.pr.timeline.merged": "{{actor}} merged into {{branch}}", + "agentManager.pr.timeline.closed": "{{actor}} closed this pull request", + "agentManager.pr.timeline.reopened": "{{actor}} reopened this pull request", + "agentManager.pr.timeline.forcePushed": "{{actor}} force-pushed {{detail}}", + "agentManager.pr.timeline.approved": "{{author}} approved these changes", + "agentManager.pr.timeline.changesRequested": "{{author}} requested changes", + "agentManager.pr.timeline.commented": "{{author}} reviewed", + "agentManager.pr.timeline.earlier": "Show earlier activity", "agentManager.pr.conversation.dismiss": "忽略", "agentManager.pr.conversation.restore": "還原", "agentManager.pr.conversation.sendAll": "使用 Kilo 修復 {{count}} 個問題", @@ -326,7 +336,7 @@ export const dict = { "agentManager.pr.summary.conversation.other": "{{count}} PR comments", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", - "agentManager.pr.summary.jump.conversation": "Go to PR comments", + "agentManager.pr.summary.jump.conversation": "Go to conversation", "agentManager.review.collapsedOnly": "{{count}} 個已摺疊", "agentManager.review.collapsedWithLarge": "{{collapsed}} 個已摺疊,{{large}} 個過大", "agentManager.review.largeFileCollapsed": "大型檔案(已摺疊)", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRConversation.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRConversation.tsx index cc4ad71db67c..67b0601cee30 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRConversation.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRConversation.tsx @@ -1,5 +1,5 @@ /** @jsxImportSource solid-js */ -import { For, Show, createMemo } from "solid-js" +import { For, Match, Show, Switch, createMemo } from "solid-js" import { Button } from "@kilocode/kilo-ui/button" import { Icon } from "@kilocode/kilo-ui/icon" import { IconButton } from "@kilocode/kilo-ui/icon-button" @@ -13,9 +13,20 @@ import { SectionHeading } from "./SectionHeading" import { actionableConversation, sendConversation } from "./pr-actions" import { commentState, createReactionController, patchCommentState } from "./pr-comment-state" import { githubUrl, prConversationMarkdown, preview, SEND_LIMIT } from "./pr-comment-payload" -import type { PRConversationComment, PRReaction, PRReactionContent, ReviewerState } from "./pr-types" +import type { + PRCommitItem, + PRConversationComment, + PREventItem, + PREventKind, + PRReaction, + PRReactionContent, + PRTimelineItem, + ReviewerState, +} from "./pr-types" import { PRReactions } from "./PRReactions" import { PRCommentForm } from "./PRCommentForm" +import { PRDescription } from "./PRDescription" +import { PRTimelineRow } from "./PRTimelineRow" const REVIEWER_ICON: Record = { approved: "circle-check", @@ -31,6 +42,13 @@ const REVIEWER_LABEL: Record = { pending: "Pending", } +const EVENT_ICON: Record = { + merged: "git-merge", + closed: "circle-x-outline", + reopened: "circle-check", + force_pushed: "arrow-right", +} + interface CardProps { projectId?: string worktreeId: string @@ -157,10 +175,136 @@ function PRConversationCard(props: CardProps) { ) } +function CommitRow(props: { commit: PRCommitItem; onOpenUrl?: (url: string) => void }) { + const href = () => githubUrl(props.commit.url) + const open = () => { + const url = href() + if (url) props.onOpenUrl?.(url) + } + return ( +
+ + {props.commit.short}}> + + + + {props.commit.message} + + +
+ ) +} + +function CommitGroup(props: { + commits: PRCommitItem[] + open: boolean + onToggle: () => void + onOpenUrl?: (url: string) => void +}) { + const { t } = useLanguage() + const count = () => props.commits.length + const latest = () => props.commits.at(-1) + return ( + 1} fallback={}> +
+ + +
+ {(commit) => } +
+
+
+
+ ) +} + +function EventRow(props: { item: PREventItem }) { + const { t } = useLanguage() + const label = () => { + const item = props.item + switch (item.event) { + case "merged": + return t("agentManager.pr.timeline.merged", { actor: item.actor, branch: item.detail ?? "" }) + case "closed": + return t("agentManager.pr.timeline.closed", { actor: item.actor }) + case "reopened": + return t("agentManager.pr.timeline.reopened", { actor: item.actor }) + case "force_pushed": + return t("agentManager.pr.timeline.forcePushed", { actor: item.actor, detail: item.detail ?? "" }) + } + } + return +} + +function ReviewRow(props: { comment: PRConversationComment }) { + const { t } = useLanguage() + const key = () => + props.comment.state === "approved" + ? "agentManager.pr.timeline.approved" + : props.comment.state === "changes_requested" + ? "agentManager.pr.timeline.changesRequested" + : "agentManager.pr.timeline.commented" + return ( + + ) +} + +interface Group { + id: string + comment?: PRConversationComment + commits?: PRCommitItem[] + event?: PREventItem +} + +/** Consecutive commits by the same author collapse into one expandable group. */ +function groupItems(items: PRTimelineItem[]): Group[] { + const groups: Group[] = [] + for (const item of items) { + if (item.kind === "commit") { + const last = groups.at(-1) + if (last?.commits && last.commits[0]!.author === item.author) { + last.commits.push(item) + continue + } + groups.push({ id: item.id, commits: [item] }) + continue + } + if (item.kind === "event") { + groups.push({ id: item.id, event: item }) + continue + } + groups.push({ id: item.id, comment: item }) + } + return groups +} + interface Props { prNumber: number prUrl: string - comments: PRConversationComment[] + items: PRTimelineItem[] + hasEarlier?: boolean + description?: string + author?: string + createdAt?: number projectId?: string worktreeId: string activeTerminalId?: string @@ -177,7 +321,7 @@ export function PRConversation(props: Props) { onMessage: vscode.onMessage, fail: (error) => t("agentManager.pr.comment.reactionFailed", { error: error || t("common.requestFailed") }), }) - const index = createMemo(() => new Map(props.comments.map((comment) => [comment.id, comment]))) + const index = createMemo(() => new Map(groupItems(props.items).map((group) => [group.id, group]))) const state = () => commentState(props.worktreeId) const patch = (fn: (prev: ReturnType) => Partial>) => patchCommentState(props.worktreeId, fn) @@ -203,10 +347,20 @@ export function PRConversation(props: Props) { })) } - const actionable = createMemo(() => actionableConversation(props.comments, state())) + const toggleCommits = (id: string) => { + const next = !(state().commitsOpen[id] ?? false) + patch((prev) => ({ commitsOpen: { ...prev.commitsOpen, [id]: next } })) + } + + const actionable = createMemo(() => actionableConversation(props.items, state())) function send(ids: string[]) { - sendConversation(props.worktreeId, props.comments, ids, state(), props.activeTerminalId) + sendConversation(props.worktreeId, props.items, ids, state(), props.activeTerminalId) + } + + const earlier = () => { + const url = githubUrl(props.prUrl) + return url && props.onOpenUrl ? () => props.onOpenUrl?.(url) : undefined } return ( @@ -217,7 +371,7 @@ export function PRConversation(props: Props) { title={t("agentManager.pr.conversation.title")} open={open()} onToggle={() => setOpen(!open())} - count={props.comments.length > 0 ? String(props.comments.length) : undefined} + count={props.items.length > 0 ? String(props.items.length) : undefined} /> 1}> @@ -230,34 +384,64 @@ export function PRConversation(props: Props) { )} + + {(body) => } + + + +
{(id) => ( - {(comment) => ( - toggleOpen(comment())} - onSend={() => send([id])} - onDismiss={() => toggleDismiss(comment())} - reactionError={reactions.error(id)} - reactions={reactions.list(id, comment().reactions)} - reactionPending={(content) => reactions.pending(id, content)} - onReaction={(content, add) => reactions.toggle(id, content, add)} - onOpenUrl={ - githubUrl(comment().url) && props.onOpenUrl - ? () => props.onOpenUrl?.(githubUrl(comment().url)!) - : undefined - } - /> + {(group) => ( + + + {(comment) => ( + toggleOpen(comment())} + onSend={() => send([id])} + onDismiss={() => toggleDismiss(comment())} + reactionError={reactions.error(id)} + reactions={reactions.list(id, comment().reactions)} + reactionPending={(content) => reactions.pending(id, content)} + onReaction={(content, add) => reactions.toggle(id, content, add)} + onOpenUrl={ + githubUrl(comment().url) && props.onOpenUrl + ? () => props.onOpenUrl?.(githubUrl(comment().url)!) + : undefined + } + /> + } + > + + + )} + + + {(commits) => ( + toggleCommits(id)} + onOpenUrl={props.onOpenUrl} + /> + )} + + {(event) => } + )} )} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRDescription.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRDescription.tsx index 524be50e2ece..7c1ea21abcf9 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRDescription.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRDescription.tsx @@ -1,21 +1,28 @@ /** @jsxImportSource solid-js */ -import { Show, createSignal } from "solid-js" -import { Markdown } from "@kilocode/kilo-ui/markdown" -import { SectionHeading } from "./SectionHeading" +import { Icon } from "@kilocode/kilo-ui/icon" +import { useLanguage } from "../../src/context/language" +import { PRCommentMarkdown } from "./PRCommentMarkdown" +import { PRCommentTime } from "./PRCommentTime" -export function PRDescription(props: { body: string }) { - const [open, setOpen] = createSignal(true) +/** + * Opening card of the PR conversation, like the first post of the GitHub + * timeline. The body is the current description, not a historical snapshot. + */ +export function PRDescription(props: { body: string; author?: string; createdAt?: number }) { + const { t } = useLanguage() return ( - <> -
-
- setOpen((v) => !v)} /> - -
- -
-
+
+
+ + {props.author ?? "unknown"} + {t("agentManager.pr.timeline.opened")} +
+ +
- +
+ +
+
) } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx index 3311b50756ce..3af2844318be 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx @@ -9,7 +9,6 @@ import { useLanguage } from "../../src/context/language" import { PRBadge } from "./PRBadge" import { PROverview } from "./PROverview" import { PRReviewers } from "./PRReviewers" -import { PRDescription } from "./PRDescription" import { PRChecks } from "./PRChecks" import { PRComments } from "./PRComments" import { PRConversation } from "./PRConversation" @@ -185,7 +184,14 @@ export const PRPanel: Component = (props) => { }) const conversation = createMemo< - | { project?: string; worktree: string; number: number; url: string; value: NonNullable } + | { + project?: string + worktree: string + number: number + url: string + value: NonNullable + hasEarlier?: boolean + } | undefined >((prev) => { const next = props.pr.conversation @@ -196,6 +202,7 @@ export const PRPanel: Component = (props) => { number: props.pr.number, url: props.pr.url, value: next, + hasEarlier: props.pr.conversationHasEarlier, } if ( prev && @@ -215,6 +222,13 @@ export const PRPanel: Component = (props) => { later() }) + const created = createMemo(() => { + const value = props.pr.createdAt + if (!value) return undefined + const time = Date.parse(value) + return Number.isFinite(time) ? time : undefined + }) + return (
@@ -285,7 +299,6 @@ export const PRPanel: Component = (props) => { 0}> - {(body) => } 0}>
@@ -310,7 +323,11 @@ export const PRPanel: Component = (props) => {
{ - const value = props.pr.conversation ?? [] + // Commits and lifecycle events are history, not feedback the agent can fix. + const value = (props.pr.conversation ?? []).filter(isConversationComment) if (value.length === 0) return const terminal = props.activeTerminalId const ids = actionableConversation(value, state()) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRTimelineRow.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRTimelineRow.tsx new file mode 100644 index 000000000000..105a96d1bab8 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRTimelineRow.tsx @@ -0,0 +1,45 @@ +/** @jsxImportSource solid-js */ +import { Show, type JSX } from "solid-js" +import { Icon } from "@kilocode/kilo-ui/icon" +import { PRCommentTime } from "./PRCommentTime" + +/** + * One compact conversation line for commits and lifecycle events. Comments and + * reviews keep their full card; these rows exist so a push or a merge is + * visible without looking like a discussion. + */ +export function PRTimelineRow(props: { + icon: string + label: JSX.Element | string + time?: number + onClick?: () => void +}) { + const content = ( + <> + + {props.label} + + + ) + return ( + + {content} +
+ } + > + {(onClick) => ( + + )} + + ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-actions.ts b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-actions.ts index 11972551caac..fc5e5492e7aa 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-actions.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-actions.ts @@ -9,7 +9,7 @@ import { sendReviewComments } from "../../diff-viewer/review-annotations" import type { PRReviewCommentData } from "../../../src/shared/review-comments" import { SEND_LIMIT, prConversationPayload, prPayload } from "./pr-comment-payload" import { type CommentState, patchCommentState } from "./pr-comment-state" -import type { PRComment, PRConversationComment } from "./pr-types" +import { isConversationComment, type PRComment, type PRTimelineItem } from "./pr-types" export type JumpTarget = "checks" | "comments" | "conversation" @@ -23,9 +23,18 @@ export function unsentThreads(comments: PRComment[], state: CommentState): strin return comments.filter((item) => !resolvedFor(item, state) && !state.sent[item.threadId]).map((item) => item.threadId) } -/** Human conversation comments not yet sent or dismissed. */ -export function actionableConversation(comments: PRConversationComment[], state: CommentState): string[] { - return comments.filter((c) => !c.isBot && !state.sent[c.id] && !state.dismissed[c.id]).map((c) => c.id) +/** Human conversation comments with text, not yet sent or dismissed. */ +export function actionableConversation(items: PRTimelineItem[], state: CommentState): string[] { + return items + .filter( + (item) => + isConversationComment(item) && + item.body.trim().length > 0 && + !item.isBot && + !state.sent[item.id] && + !state.dismissed[item.id], + ) + .map((item) => item.id) } function send( @@ -65,10 +74,10 @@ export function sendThreads( export function sendConversation( worktree: string, - comments: PRConversationComment[], + items: PRTimelineItem[], ids: string[], state: CommentState, terminal?: string, ): void { - send(worktree, comments, ids, state, (item) => item.id, prConversationPayload, terminal) + send(worktree, items.filter(isConversationComment), ids, state, (item) => item.id, prConversationPayload, terminal) } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-comment-state.ts b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-comment-state.ts index 4b0ab2d3965e..4a7add7f933e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-comment-state.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-comment-state.ts @@ -28,6 +28,8 @@ export interface CommentState { reactionErrors: Record /** commentId -> dismissed locally without sending. */ dismissed: Record + /** commit group id (first commit) -> expanded. */ + commitsOpen: Record open: boolean doneOpen: boolean conversationOpen: boolean @@ -49,6 +51,7 @@ const BLANK: CommentState = Object.freeze({ reactionPicked: {}, reactionErrors: {}, dismissed: {}, + commitsOpen: {}, open: true, doneOpen: false, conversationOpen: true, diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css index a240fca93ed8..f94e2c03f6a3 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-panel.css @@ -359,6 +359,84 @@ flex-shrink: 0; } +/* Conversation timeline rows: commits and lifecycle events */ +.am-pr-timeline-row { + width: 100%; + gap: 6px; + padding: 3px 8px; + min-height: 24px; + min-width: 0; + background: none; + border: none; + border-radius: 4px; + text-align: left; +} + +.am-pr-timeline-toggle { + cursor: pointer; +} + +.am-pr-timeline-toggle:hover { + background: var(--vscode-list-hoverBackground); +} + +.am-pr-timeline-toggle:focus-visible { + outline: 1px solid var(--vscode-focusBorder); + outline-offset: -1px; +} + +.am-pr-timeline-icon { + flex-shrink: 0; + opacity: 0.8; +} + +.am-pr-timeline-chevron { + flex-shrink: 0; + opacity: 0.6; +} + +.am-pr-timeline-label { + flex: 1; + font-size: var(--kilo-font-size-12); + color: var(--text-weak); + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.am-pr-timeline-sha { + font-family: var(--font-mono, monospace); + font-size: var(--kilo-font-size-11); + color: var(--vscode-textLink-foreground, #3794ff); + flex-shrink: 0; +} + +.am-pr-timeline-link { + background: none; + border: none; + padding: 0; + cursor: pointer; +} + +.am-pr-timeline-link:hover { + text-decoration: underline; +} + +.am-pr-timeline-link:focus-visible { + outline: 1px solid var(--vscode-focusBorder); + outline-offset: 2px; + border-radius: 2px; +} + +.am-pr-timeline-children { + display: flex; + flex-direction: column; + margin-left: 16px; + border-left: 1px solid var(--vscode-panel-border); + padding-left: 4px; +} + .am-pr-comment-body { padding: 6px 8px 0; } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-types.ts b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-types.ts index 69a7ae824d88..98964f9fdbfe 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-types.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-types.ts @@ -1,5 +1,5 @@ -// PR sub-types — source of truth for all PR-related types used in the PR panel. -// PRStatus lives in src/types/messages/agent-manager.ts for broad consumption. +// PR types — source of truth for all PR-related types used by the PR panel and +// the extension/webview message boundary. export type PRState = "open" | "draft" | "merged" | "closed" export type ReviewDecision = "approved" | "changes_requested" | "pending" @@ -84,6 +84,42 @@ export interface PRReviewer { state: ReviewerState } +export interface PRStatus { + viewerDidAuthor?: boolean + id?: string + number: number + baseRefOid?: string + headRefOid?: string + title: string + body?: string + author?: string + createdAt?: string + url: string + state: PRState + review: ReviewDecision | null + checks: { + status: AggregateCheckStatus + total: number + passed: number + failed: number + pending: number + checks: PRCheck[] + } + reviewers: PRReviewer[] + unresolvedThreads?: number + comments?: { + total: number + unresolved: number + comments: PRComment[] + } + conversation?: PRTimelineItem[] + /** Whether GitHub has timeline items before the loaded window. */ + conversationHasEarlier?: boolean + additions: number + deletions: number + files: number +} + export interface PRConversationComment { kind?: "issue" | "review" canEdit?: boolean @@ -98,3 +134,37 @@ export interface PRConversationComment { isBot?: boolean reactions?: PRReaction[] } + +export interface PRCommitItem { + kind: "commit" + id: string + sha: string + short: string + message: string + author: string + avatar?: string + createdAt?: number + url?: string +} + +export type PREventKind = "merged" | "closed" | "reopened" | "force_pushed" + +export interface PREventItem { + kind: "event" + event: PREventKind + id: string + actor: string + avatar?: string + createdAt?: number + /** merged: target branch. force_pushed: `before to after` short SHAs. */ + detail?: string + url?: string +} + +/** One entry of the PR conversation: a comment, a commit, or a lifecycle event. */ +export type PRTimelineItem = PRConversationComment | PRCommitItem | PREventItem + +/** Comments and reviews render as cards; commits and events render as rows. */ +export function isConversationComment(item: PRTimelineItem): item is PRConversationComment { + return item.kind !== "commit" && item.kind !== "event" +} diff --git a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx index 73c518d989f3..6ac8d1f35aa1 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx @@ -33,8 +33,9 @@ import { ThinkingSelectorBase } from "../components/shared/ThinkingSelector" import { DeferredPopover } from "../components/shared/DeferredPopover" import { ProjectSelect } from "../../agent-manager/ProjectSelect" import { PRComments } from "../../agent-manager/pr/PRComments" +import { PRConversation } from "../../agent-manager/pr/PRConversation" +import type { PRComment, PRTimelineItem } from "../../agent-manager/pr/pr-types" import { PRPanel } from "../../agent-manager/pr/PRPanel" -import type { PRComment } from "../../agent-manager/pr/pr-types" import { For, createSignal, onCleanup, onMount, type JSX } from "solid-js" import type { AgentProjectSnapshot, @@ -2105,6 +2106,82 @@ export const PRPanelComments200: Story = { ), } +const prConversation: PRTimelineItem[] = [ + { + kind: "commit", + id: "commit-1", + sha: "a".repeat(40), + short: "a9f21c3", + message: "Guard the missing gh fallback", + author: "octocat", + createdAt: Date.now() - 50 * 60 * 1000, + url: "https://github.com/org/repo/commit/a9f21c3", + }, + { + kind: "commit", + id: "commit-2", + sha: "b".repeat(40), + short: "b7d4e12", + message: "Add a regression test for the cached status", + author: "octocat", + createdAt: Date.now() - 45 * 60 * 1000, + url: "https://github.com/org/repo/commit/b7d4e12", + }, + { + kind: "event", + event: "force_pushed", + id: "force-push-1", + actor: "octocat", + detail: "a9f21c3 to b7d4e12", + createdAt: Date.now() - 40 * 60 * 1000, + }, + { + kind: "review", + id: "review-1", + author: "hubot", + body: "", + state: "approved", + createdAt: Date.now() - 30 * 60 * 1000, + }, + { + id: "conversation-1", + kind: "issue", + author: "octocat", + body: "Thanks, this also covers the empty response case.", + createdAt: Date.now() - 20 * 60 * 1000, + }, + { + kind: "event", + event: "merged", + id: "merged-1", + actor: "hubot", + detail: "main", + createdAt: Date.now() - 10 * 60 * 1000, + }, +] + +export const PRPanelConversation: Story = { + name: "PR panel — conversation timeline", + render: () => ( + +
+ {}} + /> +
+
+ ), +} + const summaryPR: PRStatus = { ...basePR, review: "changes_requested", diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts b/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts index 1aa3a7081498..81fff48c1a9d 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts @@ -65,7 +65,7 @@ import type { PRCheck, PRComment, PRReviewer, - PRConversationComment, + PRTimelineItem, } from "../../../agent-manager/pr/pr-types" export type { PRState, @@ -76,43 +76,16 @@ export type { PRComment, PRCommentReply, PRReviewer, + PRStatus, PRConversationComment, + PRCommitItem, + PREventItem, + PREventKind, + PRTimelineItem, PRReaction, PRReactionContent, } from "../../../agent-manager/pr/pr-types" -export interface PRStatus { - id?: string - viewerDidAuthor?: boolean - number: number - baseRefOid?: string - headRefOid?: string - title: string - body?: string - url: string - state: PRState - review: ReviewDecision | null - checks: { - status: AggregateCheckStatus - total: number - passed: number - failed: number - pending: number - checks: PRCheck[] - } - reviewers: PRReviewer[] - unresolvedThreads?: number - comments?: { - total: number - unresolved: number - comments: PRComment[] - } - conversation?: PRConversationComment[] - additions: number - deletions: number - files: number -} - export type RunState = "idle" | "running" | "stopping" export interface RunStatus {