diff --git a/.changeset/pr-merge-readiness.md b/.changeset/pr-merge-readiness.md new file mode 100644 index 000000000000..fd0f866e0de9 --- /dev/null +++ b/.changeset/pr-merge-readiness.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Show pull request approvals and merge readiness in Agent Manager, with GitHub-backed branch updates, merge methods, auto-merge, and conflict resolution with Kilo. diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-panel-summary-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-panel-summary-chromium-linux.png index c47631acbd06..a00d88dce8bf 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-panel-summary-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/pr-panel-summary-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a3675f45f8f3c8d02fa0457d0e0f15f58b12a742ed3e7ea13fcf20562a119d3b -size 45879 +oid sha256:81be93a32e96103e772ba0a24175cccdcd0336c930af766c14b007a45639dbd3 +size 45801 diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index d6ff9d3c57e1..4865bce7e178 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -275,6 +275,7 @@ export class AgentManagerProvider implements Disposable { presence: (presence) => this.onWorktreePresence(presence), openExternal: (u) => this.host.openExternal(u), log: (...args) => this.log(...args), + mergeMethods: this.host, }) this.statsPoller = pollers.stats this.prBridge = pollers.pr @@ -451,8 +452,7 @@ export class AgentManagerProvider implements Disposable { return } // When the .kilocode → .kilo migration rewrote git worktree refs, nudge - // VS Code's git extension to re-discover them. Without this, worktrees - // won't appear in Source Control until the next VS Code restart. + // VS Code's git extension to re-discover them and avoid stale Source Control. if (init.refsFixed > 0) { this.log(`Migration fixed ${init.refsFixed} git worktree ref(s), refreshing git`) this.host.refreshGit() diff --git a/packages/kilo-vscode/src/agent-manager/GitOps.ts b/packages/kilo-vscode/src/agent-manager/GitOps.ts index be7f3f87e669..f0d0ee3eaed3 100644 --- a/packages/kilo-vscode/src/agent-manager/GitOps.ts +++ b/packages/kilo-vscode/src/agent-manager/GitOps.ts @@ -13,6 +13,7 @@ import { } from "./git-import" import type { Semaphore } from "./semaphore" import { lines } from "./git-stats-snapshot" +import { oid } from "../shared/pr-comment-preview" interface GitOpsOptions { log: (...args: unknown[]) => void @@ -129,6 +130,7 @@ export class GitOps { private readonly injected: boolean private executableCache: Promise | undefined private readonly resolutionCache = new Map() + private readonly conflictCache = new Map; expires: number }>() private static readonly CACHE_TTL_MS = 60000 private static readonly DEFAULT_BRANCH_CACHE_TTL_MS = 10 * 60_000 private static readonly MAX_CACHE_SIZE = 100 @@ -168,6 +170,7 @@ export class GitOps { this.controller.abort() } this.resolutionCache.clear() + this.conflictCache.clear() } private getCached(key: string): string | undefined { @@ -585,6 +588,60 @@ export class GitOps { return [{ reason: "Patch does not apply cleanly" }] } + /** + * Conflicting file paths between two commits, computed with `git merge-tree` + * so the worktree, index, and stash stay untouched. Missing commits are + * fetched first because a conflicting PR head often only exists remotely. + */ + conflicts(cwd: string, remote: string, base: string, head: string): Promise { + if (!oid(base) || !oid(head)) return Promise.reject(new Error("Invalid pull request commit ID")) + if (!/^[A-Za-z0-9._-]+$/.test(remote)) return Promise.reject(new Error("Invalid Git remote")) + const key = `${cwd}\u0000${remote}\u0000${base}\u0000${head}` + const now = Date.now() + const cached = this.conflictCache.get(key) + if (cached && cached.expires > now) return cached.value + if (cached) this.conflictCache.delete(key) + const task = this.computeConflicts(cwd, remote, base, head) + if (this.conflictCache.size >= GitOps.MAX_CACHE_SIZE) { + let oldestKey: string | undefined + let oldestExpiry = Infinity + for (const [entryKey, entry] of this.conflictCache) { + if (entry.expires < oldestExpiry) { + oldestExpiry = entry.expires + oldestKey = entryKey + } + } + if (oldestKey) this.conflictCache.delete(oldestKey) + } + this.conflictCache.set(key, { value: task, expires: now + GitOps.CACHE_TTL_MS }) + void task.catch(() => { + if (this.conflictCache.get(key)?.value === task) this.conflictCache.delete(key) + }) + return task + } + + private async computeConflicts(cwd: string, remote: string, base: string, head: string): Promise { + const missing: string[] = [] + for (const sha of [base, head]) { + const probe = await this.exec(["cat-file", "-e", `${sha}^{commit}`], cwd) + if (probe.code !== 0) missing.push(sha) + } + if (missing.length > 0) { + const fetch = await this.exec(["fetch", "--no-tags", remote, "--", ...missing], cwd, { + env: nonInteractiveEnv(), + timeout: 60_000, + }) + if (fetch.code !== 0) throw new Error(fetch.stderr.trim() || "Failed to fetch pull request commits") + } + const result = await this.exec( + ["-c", "core.quotePath=false", "merge-tree", "--write-tree", "--name-only", base, head], + cwd, + ) + if (result.code === 0) return [] + if (result.code !== 1) throw new Error(result.stderr.trim() || "Failed to compute merge conflicts") + return parseConflictPaths(result.stdout) + } + /** * Run a git command returning `{code, stdout, stderr}`. Gated by the shared * semaphore and respects the dispose abort signal. Never throws — commands @@ -706,3 +763,13 @@ export class GitOps { }) } } + +/** Conflicting paths from `git merge-tree --write-tree --name-only` output. */ +export function parseConflictPaths(output: string): string[] { + const files: string[] = [] + for (const line of output.split(/\r?\n/).slice(1)) { + if (!line) break + files.push(line) + } + return files +} diff --git a/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts b/packages/kilo-vscode/src/agent-manager/PRStatusPoller.ts index 019f81aceaf8..a290ecaaee9d 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, PRTimelineItem } from "./types" +import type { PRMergeMethod, PRStatus, PRCheck, PRReviewer, PRTimelineItem } from "./types" import { execWithShellEnv } from "./shell-env" import { execGhRead } from "./gh" import { classifyPRError } from "./git-import" @@ -34,6 +34,16 @@ interface PRStatusPollerOptions { /** Shared concurrency gate for child process spawning. */ semaphore?: Semaphore getBranch?: (worktree: Worktree) => Promise + getPRMergeMethod?: (repo: string) => PRMergeMethod | undefined +} + +interface RepoInfo { + owner: string + name: string + root: string + methods: PRMergeMethod[] + autoAllowed: boolean + canWrite: boolean } const GH_PROBE_TTL = 300_000 // 5 minutes — gh installation state rarely changes at runtime @@ -56,11 +66,13 @@ export class PRStatusPoller { private ghProbeTime = 0 private rich = true private activeWorktreeId: string | undefined - private cachedRepo: { owner: string; name: string; root: string } | undefined + private cachedRepo: RepoInfo | undefined + private repoRequest: { root: string; promise: Promise } | undefined private prCache = new Map() /** Reviewer avatars are stable, so look them up once per login and reuse them. */ private readonly avatars = new Map() private readonly resolvedAvatars = new Set() + private readonly refreshTimers = new Map[]>() private lastFullSync = 0 // timestamp of last full (all-worktree) sync private readonly intervalMs: number private readonly semaphore: Semaphore | undefined @@ -123,6 +135,7 @@ export class PRStatusPoller { clearTimeout(this.timer) this.timer = undefined } + this.clearRefreshTimers() } stop(): void { @@ -140,19 +153,49 @@ export class PRStatusPoller { this.ghProbeTime = 0 this.rich = true this.cachedRepo = undefined + this.repoRequest = undefined this.prCache.clear() this.avatars.clear() this.resolvedAvatars.clear() this.lastFullSync = 0 + this.clearRefreshTimers() } /** Force-refresh a specific worktree immediately, bypassing the PR cache. */ - refresh(worktreeId: string): void { + refresh(worktreeId: string, settle = false): void { + this.clearRefreshTimers(worktreeId) const wt = this.options.getWorktrees().find((w) => w.id === worktreeId) if (wt) this.prCache.delete(this.key(wt.branch, wt.path)) this.lastHash.delete(worktreeId) if (!this.active) return - void this.fetchOne(worktreeId, this.generation, true) + const generation = this.generation + void this.fetchOne(worktreeId, generation, true).catch(() => undefined) + if (!settle || !this.visible) return + const delays = [2_000, 8_000] + this.refreshTimers.set( + worktreeId, + delays.map((delay) => + setTimeout(() => { + if (!this.active || !this.visible || this.stale(generation)) return + const current = this.options.getWorktrees().find((w) => w.id === worktreeId) + if (current) this.prCache.delete(this.key(current.branch, current.path)) + this.lastHash.delete(worktreeId) + void this.fetchOne(worktreeId, generation, true).catch(() => undefined) + }, delay), + ), + ) + } + + private clearRefreshTimers(worktreeId?: string): void { + if (worktreeId) { + for (const timer of this.refreshTimers.get(worktreeId) ?? []) clearTimeout(timer) + this.refreshTimers.delete(worktreeId) + return + } + for (const timers of this.refreshTimers.values()) { + for (const timer of timers) clearTimeout(timer) + } + this.refreshTimers.clear() } setActiveWorktreeId(id: string | undefined): void { @@ -273,22 +316,17 @@ export class PRStatusPoller { if (this.stale(generation)) return const pr = await this.cachedFetchPR(branch ?? wt.branch, wt.path) if (this.stale(generation)) return - if (!pr) { - const hash = `${worktreeId}:${branch ?? wt.branch}:none` - if (this.lastHash.get(worktreeId) === hash) return - this.lastHash.set(worktreeId, hash) - this.options.onStatus(worktreeId, null, undefined, branch) - return - } + if (!pr) return this.empty(worktreeId, branch ?? wt.branch, branch) + const repo = await this.getRepoInfo(wt.path) const [checks, reviewers, threads] = await Promise.all([ ...this.extras(pr, wt.path), this.fetchThreads(pr.number, wt.path, full), ]) if (this.stale(generation)) return - if (threads && (threads.baseRefOid !== pr.baseRefOid || threads.headRefOid !== pr.headRefOid)) - this.prCache.delete(this.key(branch ?? wt.branch, wt.path)) + this.invalidateThreadCache(pr, threads, branch ?? wt.branch, wt.path) + const merge = mergeStatus(pr.merge, repo, this.options.getPRMergeMethod?.(`${repo.owner}/${repo.name}`)) const status: PRStatus = { id: pr.id, number: pr.number, @@ -301,6 +339,7 @@ export class PRStatusPoller { url: pr.url, state: pr.state, review: pr.review, + ...(merge ? { merge } : {}), checks, reviewers, ...threads, @@ -325,6 +364,24 @@ export class PRStatusPoller { return [pr.checks ?? this.fetchChecks(pr.number, cwd), this.reviewers(pr, cwd)] as const } + private empty(worktreeId: string, fallback: string, branch: string | undefined): void { + const hash = `${worktreeId}:${fallback}:none` + if (this.lastHash.get(worktreeId) === hash) return + this.lastHash.set(worktreeId, hash) + this.options.onStatus(worktreeId, null, undefined, branch) + } + + private invalidateThreadCache( + pr: PRResult, + threads: { baseRefOid?: string; headRefOid?: string } | undefined, + branch: string, + cwd: string, + ): void { + if (!threads) return + if (threads.baseRefOid === pr.baseRefOid && threads.headRefOid === pr.headRefOid) return + this.prCache.delete(this.key(branch, cwd)) + } + /** * `gh pr view --json reviews` returns reviewer logins without avatars, so * merge avatar URLs from the GraphQL query and cache them per login. States @@ -367,7 +424,7 @@ export class PRStatusPoller { private static readonly BASE_JSON_FIELDS = "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` + private static readonly PR_JSON_FIELDS = `${PRStatusPoller.BASE_JSON_FIELDS},statusCheckRollup,reviewRequests,reviews,mergeable,mergeStateStatus,autoMergeRequest` /** Return a cached PR lookup if still fresh, otherwise fetch and cache. * Keyed by branch name so multiple worktrees on the same branch share @@ -472,17 +529,67 @@ export class PRStatusPoller { } } - private async getRepoInfo(cwd: string): Promise<{ owner: string; name: string }> { + private async getRepoInfo(cwd: string): Promise { const root = this.options.getWorkspaceRoot() ?? cwd if (this.cachedRepo?.root === root) return this.cachedRepo - const { stdout } = await this.gh(["repo", "view", "--json", "owner,name"], { - cwd, - timeout: 10_000, - }) - const data = JSON.parse(stdout) - const info = { owner: data.owner.login as string, name: data.name as string, root } - this.cachedRepo = info - return info + if (this.repoRequest?.root === root) return this.repoRequest.promise + const promise = this.fetchRepoInfo(cwd, root) + .then((info) => { + this.cachedRepo = info + return info + }) + .catch((err) => { + if (this.repoRequest?.promise === promise) this.repoRequest = undefined + throw err + }) + this.repoRequest = { root, promise } + return promise + } + + private async fetchRepoInfo(cwd: string, root: string): Promise { + const fields = "owner,name,mergeCommitAllowed,squashMergeAllowed,rebaseMergeAllowed,viewerPermission" + const stdout = await this.gh(["repo", "view", "--json", fields], { cwd, timeout: 10_000 }).then( + (result) => result.stdout, + (err) => { + const msg = err instanceof Error ? err.message : String(err) + if (!/unknown.*field|does(?:n't| not) exist|not accessible/i.test(msg)) throw err + return this.gh(["repo", "view", "--json", "owner,name,viewerPermission"], { cwd, timeout: 10_000 }).then( + (result) => result.stdout, + (fallback) => { + const reason = fallback instanceof Error ? fallback.message : String(fallback) + if (!/unknown.*field|does(?:n't| not) exist|not accessible/i.test(reason)) throw fallback + return this.gh(["repo", "view", "--json", "owner,name"], { cwd, timeout: 10_000 }).then( + (result) => result.stdout, + ) + }, + ) + }, + ) + const data = JSON.parse(stdout) as Record + const owner = typeof data.owner === "string" ? data.owner : (data.owner as { login?: string } | undefined)?.login + const name = typeof data.name === "string" ? data.name : undefined + if (!owner || !name) throw new Error("GitHub repository identity is missing") + const settings = await this.gh(["api", `repos/${owner}/${name}`], { cwd, timeout: 10_000 }).then( + (result) => JSON.parse(result.stdout) as { allow_auto_merge?: boolean }, + (err) => { + this.options.log("Failed to read GitHub auto-merge settings:", err) + return { allow_auto_merge: undefined } + }, + ) + const methods = [ + ...(data.squashMergeAllowed !== false ? (["squash"] as const) : []), + ...(data.mergeCommitAllowed !== false ? (["merge"] as const) : []), + ...(data.rebaseMergeAllowed !== false ? (["rebase"] as const) : []), + ] + return { + owner, + name, + root, + methods: methods.length > 0 ? [...methods] : ["squash"], + autoAllowed: settings.allow_auto_merge === true, + canWrite: + data.viewerPermission === "WRITE" || data.viewerPermission === "MAINTAIN" || data.viewerPermission === "ADMIN", + } } private async fetchReviewers(prNumber: number, cwd: string): Promise<{ items: PRReviewer[]; ok: boolean }> { @@ -737,3 +844,18 @@ function parseConversationPayload(stdout: string): { items?: PRTimelineItem[]; h hasEarlier: page.pageInfo?.hasPreviousPage === true, } } + +function mergeStatus(merge: PRResult["merge"], repo: RepoInfo, saved: PRMergeMethod | undefined): PRStatus["merge"] { + if (!merge) return undefined + const method = + saved && repo.methods.includes(saved) + ? saved + : (repo.methods.find((item) => item === "squash") ?? repo.methods[0] ?? "squash") + return { + ...merge, + methods: repo.methods, + method, + autoAllowed: repo.autoAllowed, + canWrite: repo.canWrite, + } +} diff --git a/packages/kilo-vscode/src/agent-manager/host.ts b/packages/kilo-vscode/src/agent-manager/host.ts index 478cf9c7ba30..ee8f70f533e2 100644 --- a/packages/kilo-vscode/src/agent-manager/host.ts +++ b/packages/kilo-vscode/src/agent-manager/host.ts @@ -142,6 +142,10 @@ export interface Host { /** Persist the additional-project registry payload. */ writeProjects(value: unknown): Promise + /** Read and persist the user's last PR merge method per repository. */ + getPRMergeMethod?(repo: string): "merge" | "squash" | "rebase" | undefined + savePRMergeMethod?(repo: string, method: "merge" | "squash" | "rebase"): Promise + unregisterProjectRoutes(projectId: string): void /** Subscribe to workspace folder changes (pinned project re-derivation). */ diff --git a/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts b/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts index 4724e007a46d..1d238e171556 100644 --- a/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts +++ b/packages/kilo-vscode/src/agent-manager/pr-status-bridge.ts @@ -5,7 +5,7 @@ * so the provider only needs thin delegation calls. */ import type { Worktree } from "./WorktreeStateManager" -import type { AgentManagerOutMessage, PRStatus } from "./types" +import type { AgentManagerOutMessage, PRMergeMethod, PRStatus } from "./types" import type { Disposable } from "./host" import type { Semaphore } from "./semaphore" import { PRStatusPoller } from "./PRStatusPoller" @@ -20,6 +20,7 @@ import { import { ghErrorReason, mergePRStatus, retainPRStatus } from "./pr/am-pr-utils" import { mutateComment } from "./pr/mutate-comment" import { PRReviewActions } from "./pr/review-actions" +import { PRMergeActions } from "./pr/merge-actions" import { PRSuggestionActions } from "./pr/suggestion-actions" import type { PRReviewContext, PRReviewHost } from "./pr/review-context" @@ -34,6 +35,9 @@ interface PRBridgeHost { semaphore?: Semaphore projectId?: () => string | undefined dirtyFiles?: () => string[] + conflicts?: (cwd: string, remote: string, base: string, head: string) => Promise + getPRMergeMethod?: (repo: string) => PRMergeMethod | undefined + savePRMergeMethod?: (repo: string, method: PRMergeMethod) => Promise } /** Minimal panel surface needed by the bridge (subset of PanelContext). */ @@ -68,6 +72,7 @@ function hasComment(pr: PRStatus, id: string): boolean { export class PRStatusBridge { readonly poller: PRStatusPoller private reviews: PRReviewActions + private merges: PRMergeActions private suggestions: PRSuggestionActions private readonly cache = new Map() /** Branch each cached PR was found on, so a branch switch still clears it. */ @@ -82,13 +87,18 @@ export class PRStatusBridge { const actions: PRReviewHost = { context: (message) => this.context(message), post: (message) => host.postToWebview(message), - refresh: (context) => { - if (host.projectId?.() === context.projectId) this.poller.refresh(context.worktreeId) + refresh: (context, settle) => { + if (host.projectId?.() === context.projectId) this.poller.refresh(context.worktreeId, settle) }, dirtyFiles: () => host.dirtyFiles?.() ?? [], + conflicts: async (context, base, head) => + (await host.conflicts?.(context.directory, context.remote ?? "origin", base, head)) ?? [], + getPRMergeMethod: host.getPRMergeMethod, + savePRMergeMethod: host.savePRMergeMethod, } this.actionHost = actions this.reviews = new PRReviewActions(actions) + this.merges = new PRMergeActions(actions) this.suggestions = new PRSuggestionActions(actions) } @@ -103,6 +113,9 @@ export class PRStatusBridge { semaphore?: Semaphore projectId?: () => string | undefined dirtyFiles?: () => string[] + conflicts?: (cwd: string, remote: string, base: string, head: string) => Promise + getPRMergeMethod?: (repo: string) => PRMergeMethod | undefined + savePRMergeMethod?: (repo: string, method: PRMergeMethod) => Promise }): PRStatusBridge { return new PRStatusBridge(opts) } @@ -132,7 +145,7 @@ export class PRStatusBridge { /** Handle an incoming webview message. Returns true if handled. */ handleMessage(m: Record): boolean { - if (this.reviews.handle(m) || this.suggestions.handle(m)) return true + if (this.actions(m)) return true if (m.type === "agentManager.refreshPR") { if (typeof m.projectId === "string" && m.projectId !== this.host.projectId?.()) return true this.poller.refresh(m.worktreeId as string) @@ -160,6 +173,10 @@ export class PRStatusBridge { return false } + private actions(m: Record): boolean { + return this.reviews.handle(m) || this.merges.handle(m) || this.suggestions.handle(m) + } + private handleReaction(m: Record): boolean { if (typeof m.projectId === "string" && m.projectId !== this.host.projectId?.()) return true const request = reactionRequest(m) @@ -211,7 +228,7 @@ export class PRStatusBridge { } if (m.prNumber !== cached.pr.number || m.prUrl !== cached.pr.url) throw new Error("Pull request changed. Refresh and try again.") - return { pr: cached.pr, directory: wt.path, worktreeId: wt.id, projectId, branch: wt.branch } + return { pr: cached.pr, directory: wt.path, worktreeId: wt.id, projectId, branch: wt.branch, remote: wt.remote } } private comment( @@ -308,6 +325,7 @@ export class PRStatusBridge { this.poller.stop() this.suggestions.dispose() this.reviews = new PRReviewActions(this.actionHost) + this.merges = new PRMergeActions(this.actionHost) this.suggestions = new PRSuggestionActions(this.actionHost) this.cache.clear() this.branches.clear() @@ -344,6 +362,7 @@ function bridgePollerOpts(bridge: PRStatusBridge, host: PRBridgeHost) { accept(bridge, host, id, pr) }, log: (...args: unknown[]) => host.log(...args), + getPRMergeMethod: host.getPRMergeMethod, } } 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 104d0801c03e..2ff844cc9e6d 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 @@ -1,4 +1,4 @@ -import type { PRState, PRStatus, ReviewDecision } from "../types" +import type { PRMergeability, PRMergeMethod, PRMergeState, PRState, PRStatus, ReviewDecision } from "../types" // Raw shapes returned by `gh pr view --json` @@ -123,6 +123,7 @@ export interface PRResult { url: string state: PRState review: ReviewDecision | null + merge?: { mergeable: PRMergeability; state: PRMergeState; auto: PRMergeMethod | null } additions: number deletions: number files: number 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 6d96ffa90912..039c21cb84dc 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 @@ -6,6 +6,9 @@ import type { PRComment, PRCommentReply, PRConversationComment, + PRMergeability, + PRMergeMethod, + PRMergeState, PRReaction, PRReactionContent, PRReviewer, @@ -31,6 +34,7 @@ export function parsePRResult(json: string): PRResult | null { if (!data.number) return null const state = data.isDraft ? "draft" : (data.state?.toLowerCase() ?? "open") const review = reviewValue(data.reviewDecision) + const merge = parseMerge(data) const result: PRResult = { id: data.id, number: data.number, @@ -43,6 +47,7 @@ export function parsePRResult(json: string): PRResult | null { url: data.url ?? "", state, review, + ...(merge ? { merge } : {}), additions: data.additions ?? 0, deletions: data.deletions ?? 0, files: data.changedFiles ?? 0, @@ -54,6 +59,47 @@ export function parsePRResult(json: string): PRResult | null { return result } +function parseMerge(data: Record): PRResult["merge"] { + const mergeable = mergeability(data.mergeable) + const state = mergeState(data.mergeStateStatus) + const auto = mergeMethod((data.autoMergeRequest as Record | undefined)?.mergeMethod) + if (!mergeable && !state && auto === undefined) return undefined + return { + mergeable: mergeable ?? "unknown", + state: state ?? "unknown", + auto: auto ?? null, + } +} + +function mergeability(value: unknown): PRMergeability | undefined { + if (value === "MERGEABLE") return "mergeable" + if (value === "CONFLICTING") return "conflicting" + if (value === "UNKNOWN") return "unknown" + return undefined +} + +function mergeState(value: unknown): PRMergeState | undefined { + if (typeof value !== "string") return undefined + const values: Record = { + CLEAN: "clean", + BEHIND: "behind", + BLOCKED: "blocked", + DIRTY: "dirty", + UNSTABLE: "unstable", + DRAFT: "draft", + HAS_HOOKS: "has_hooks", + UNKNOWN: "unknown", + } + return values[value] +} + +function mergeMethod(value: unknown): PRMergeMethod | undefined { + if (value === "MERGE") return "merge" + if (value === "SQUASH") return "squash" + if (value === "REBASE") return "rebase" + return undefined +} + function reviewValue(value: unknown): ReviewDecision | null { if (value === "APPROVED") return "approved" if (value === "CHANGES_REQUESTED") return "changes_requested" diff --git a/packages/kilo-vscode/src/agent-manager/pr/merge-actions.ts b/packages/kilo-vscode/src/agent-manager/pr/merge-actions.ts new file mode 100644 index 000000000000..b473de3784d9 --- /dev/null +++ b/packages/kilo-vscode/src/agent-manager/pr/merge-actions.ts @@ -0,0 +1,209 @@ +import type { PRMergeRequest, PRMergeResult } from "../../shared/pr-comment-actions" +import { oid } from "../../shared/pr-comment-preview" +import { execGhRead } from "../gh" +import { ghErrorReason } from "./am-pr-utils" +import type { PRReviewContext, PRReviewHost } from "./review-context" + +type Method = "merge" | "squash" | "rebase" + +function request(message: Record): PRMergeRequest | undefined { + const base = { + projectId: typeof message.projectId === "string" ? message.projectId : undefined, + worktreeId: typeof message.worktreeId === "string" ? message.worktreeId : "", + requestId: typeof message.requestId === "string" ? message.requestId : "", + prNumber: typeof message.prNumber === "number" ? message.prNumber : 0, + prUrl: typeof message.prUrl === "string" ? message.prUrl : "", + } + if (!base.worktreeId || !base.requestId || !base.prNumber || !base.prUrl) return undefined + if (message.type === "agentManager.updatePRBranch" && typeof message.head === "string") + return { ...base, type: message.type, head: message.head } + if ( + message.type === "agentManager.mergePR" && + method(message.method) && + typeof message.auto === "boolean" && + typeof message.head === "string" + ) + return { ...base, type: message.type, method: message.method, auto: message.auto, head: message.head } + if (message.type === "agentManager.disablePRAutoMerge") return { ...base, type: message.type } + if (message.type === "agentManager.loadPRConflicts" && oid(message.base) && oid(message.head)) + return { ...base, type: message.type, base: message.base, head: message.head } + return undefined +} + +function method(value: unknown): value is Method { + return value === "merge" || value === "squash" || value === "rebase" +} + +function flag(value: Method): "--merge" | "--squash" | "--rebase" { + if (value === "merge") return "--merge" + if (value === "rebase") return "--rebase" + return "--squash" +} + +function endpoint(context: PRReviewContext): string { + const url = new URL(context.pr.url) + const match = /^\/([^/]+)\/([^/]+)\/pull\/(\d+)\/?$/.exec(url.pathname) + if ( + url.protocol !== "https:" || + url.hostname !== "github.com" || + url.port || + url.username || + url.password || + url.search || + url.hash || + !match || + Number(match[3]) !== context.pr.number + ) + throw new Error("Unsupported pull request URL.") + if (!/^[\w.-]+$/.test(match[1]!) || !/^[\w.-]+$/.test(match[2]!)) throw new Error("Invalid repository.") + return `repos/${match[1]}/${match[2]}/pulls/${context.pr.number}` +} + +function result(request: PRMergeRequest, success: boolean, error?: string): PRMergeResult { + return { + type: + request.type === "agentManager.updatePRBranch" + ? "agentManager.updatePRBranchResult" + : request.type === "agentManager.mergePR" + ? "agentManager.mergePRResult" + : request.type === "agentManager.loadPRConflicts" + ? "agentManager.loadPRConflictsResult" + : "agentManager.disablePRAutoMergeResult", + projectId: request.projectId, + worktreeId: request.worktreeId, + requestId: request.requestId, + prNumber: request.prNumber, + prUrl: request.prUrl, + success, + ...(error ? { error } : {}), + } +} + +export class PRMergeActions { + private readonly pending = new Set() + + constructor(private readonly host: PRReviewHost) {} + + handle(message: Record): boolean { + if ( + message.type !== "agentManager.updatePRBranch" && + message.type !== "agentManager.mergePR" && + message.type !== "agentManager.disablePRAutoMerge" && + message.type !== "agentManager.loadPRConflicts" + ) + return false + void this.run(message) + return true + } + + private async run(message: Record): Promise { + const initial = request(message) + const resultType = + message.type === "agentManager.updatePRBranch" + ? "agentManager.updatePRBranchResult" + : message.type === "agentManager.mergePR" + ? "agentManager.mergePRResult" + : message.type === "agentManager.loadPRConflicts" + ? "agentManager.loadPRConflictsResult" + : "agentManager.disablePRAutoMergeResult" + const fallback = { + type: resultType, + projectId: typeof message.projectId === "string" ? message.projectId : undefined, + worktreeId: typeof message.worktreeId === "string" ? message.worktreeId : "", + requestId: typeof message.requestId === "string" ? message.requestId : "", + prNumber: typeof message.prNumber === "number" ? message.prNumber : 0, + prUrl: typeof message.prUrl === "string" ? message.prUrl : "", + } as Omit + const key = JSON.stringify([fallback.projectId, fallback.worktreeId, fallback.type]) + if (this.pending.has(key)) { + this.host.post({ + ...fallback, + success: false, + error: "Another pull request action is already running.", + } as PRMergeResult) + return + } + this.pending.add(key) + try { + if (!initial) throw new Error("Invalid pull request merge request.") + const context = this.host.context(message) + if (initial.type !== "agentManager.disablePRAutoMerge" && initial.head !== context.pr.headRefOid) + throw new Error("Pull request changed. Refresh and try again.") + if ( + initial.type === "agentManager.loadPRConflicts" && + (initial.base !== context.pr.baseRefOid || initial.head !== context.pr.headRefOid) + ) + throw new Error("Pull request changed. Refresh and try again.") + if (context.pr.state !== "open") throw new Error("The pull request is no longer open.") + const extra = await this.execute(initial, context) + this.host.post({ ...result(initial, true), ...extra } as PRMergeResult) + if (initial.type !== "agentManager.loadPRConflicts") this.refresh(context, true) + } catch (error) { + const reason = error instanceof Error ? ghErrorReason(error.message) : String(error) + this.host.post({ ...fallback, success: false, error: reason } as PRMergeResult) + } finally { + this.pending.delete(key) + } + } + + private async execute(request: PRMergeRequest, context: PRReviewContext): Promise> { + if (request.type === "agentManager.loadPRConflicts") { + const files = (await this.host.conflicts?.(context, request.base, request.head)) ?? [] + return { files } as Partial + } + const path = endpoint(context) + if (request.type === "agentManager.updatePRBranch") { + await execGhRead( + [ + "api", + "--hostname", + "github.com", + "--method", + "PUT", + `${path}/update-branch`, + "-f", + `expected_head_sha=${request.head}`, + ], + { cwd: context.directory, timeout: 30_000, maxBuffer: 16 * 1024 * 1024 }, + ) + return {} + } + if (request.type === "agentManager.disablePRAutoMerge") { + await execGhRead(["pr", "merge", context.pr.url, "--disable-auto"], { + cwd: context.directory, + timeout: 30_000, + maxBuffer: 16 * 1024 * 1024, + }) + return {} + } + await execGhRead( + [ + "pr", + "merge", + context.pr.url, + flag(request.method), + "--match-head-commit", + request.head, + ...(request.auto ? ["--auto"] : []), + ], + { cwd: context.directory, timeout: 30_000, maxBuffer: 16 * 1024 * 1024 }, + ) + const repo = new URL(context.pr.url).pathname.split("/").slice(1, 3).join("/") + await this.save(repo, request.method) + return {} + } + + private async save(repo: string, method: Method): Promise { + const save = this.host.savePRMergeMethod + if (!save) return + await save(repo, method).catch((error) => console.error("[Kilo New] Failed to save PR merge method", error)) + } + + private refresh(context: PRReviewContext, settle = false): void { + try { + this.host.refresh(context, settle) + } catch (error) { + console.error("[Kilo New] Failed to refresh pull request after merge action", error) + } + } +} diff --git a/packages/kilo-vscode/src/agent-manager/pr/review-context.ts b/packages/kilo-vscode/src/agent-manager/pr/review-context.ts index 07fc2a23a2fd..2c613515ade3 100644 --- a/packages/kilo-vscode/src/agent-manager/pr/review-context.ts +++ b/packages/kilo-vscode/src/agent-manager/pr/review-context.ts @@ -1,5 +1,5 @@ -import type { PRReviewResult } from "../../shared/pr-comment-actions" -import type { PRStatus } from "../types" +import type { PRMergeResult, PRReviewResult } from "../../shared/pr-comment-actions" +import type { PRMergeMethod, PRStatus } from "../types" export interface PRReviewContext { pr: PRStatus @@ -7,11 +7,15 @@ export interface PRReviewContext { worktreeId: string projectId?: string branch: string + remote?: string } export interface PRReviewHost { context(message: Record): PRReviewContext - post(message: PRReviewResult): void - refresh(context: PRReviewContext): void + post(message: PRReviewResult | PRMergeResult): void + refresh(context: PRReviewContext, settle?: boolean): void dirtyFiles(): string[] + conflicts?: (context: PRReviewContext, base: string, head: string) => Promise + getPRMergeMethod?: (repo: string) => PRMergeMethod | undefined + savePRMergeMethod?: (repo: string, method: PRMergeMethod) => Promise } diff --git a/packages/kilo-vscode/src/agent-manager/project/pollers.ts b/packages/kilo-vscode/src/agent-manager/project/pollers.ts index 335cce61505a..9dde501ac604 100644 --- a/packages/kilo-vscode/src/agent-manager/project/pollers.ts +++ b/packages/kilo-vscode/src/agent-manager/project/pollers.ts @@ -12,6 +12,7 @@ */ import type { GitOps } from "../GitOps" +import type { Host } from "../host" import { GitStatsPoller, type LocalStats, type WorktreePresenceResult, type WorktreeStats } from "../GitStatsPoller" import { PRStatusBridge } from "../pr-status-bridge" import type { PRStatus } from "../types" @@ -46,6 +47,7 @@ interface PollerDeps { openExternal: (url: string) => void visible: () => boolean log: (...args: unknown[]) => void + mergeMethods?: Pick } function hot(state: WorktreeStateManager | undefined): Set { @@ -83,6 +85,11 @@ function createPollerPair(ctx: ProjectContext, deps: PollerDeps): PollerPair { log: deps.log, semaphore: deps.semaphore, projectId: () => ctx.id, + conflicts: (cwd, remote, base, head) => deps.git.conflicts(cwd, remote, base, head), + getPRMergeMethod: (repo) => deps.mergeMethods?.getPRMergeMethod?.(repo), + savePRMergeMethod: async (repo, method) => { + await deps.mergeMethods?.savePRMergeMethod?.(repo, method) + }, }) return { stats, pr } } @@ -185,6 +192,7 @@ export function createPollers(opts: { openExternal: (url: string) => void log: (...args: unknown[]) => void hot?: () => Set + mergeMethods?: Pick }): { stats: GitStatsPoller; pr: PRStatusBridge; projects: ProjectPollers } { const stats = new GitStatsPoller({ getWorktrees: () => opts.state()?.getWorktrees() ?? [], @@ -216,6 +224,11 @@ export function createPollers(opts: { log: opts.log, semaphore: opts.semaphore, projectId: opts.activeId, + conflicts: (cwd, remote, base, head) => opts.git.conflicts(cwd, remote, base, head), + getPRMergeMethod: (repo) => opts.mergeMethods?.getPRMergeMethod?.(repo), + savePRMergeMethod: async (repo, method) => { + await opts.mergeMethods?.savePRMergeMethod?.(repo, method) + }, }) const projects = new ProjectPollers({ dirtyFiles: opts.dirtyFiles, @@ -226,6 +239,7 @@ export function createPollers(opts: { openExternal: opts.openExternal, visible: opts.visible, log: opts.log, + mergeMethods: opts.mergeMethods, }) return { stats, pr, projects } } diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index 3019c64d6d99..ba08f7058c6f 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -67,6 +67,10 @@ import type { PRTimelineItem, PRReaction, PRReactionContent, + PRMergeMethod, + PRMergeability, + PRMergeState, + PRMergeStatus, } from "../../webview-ui/agent-manager/pr/pr-types" export type { @@ -87,6 +91,10 @@ export type { PRTimelineItem, PRReaction, PRReactionContent, + PRMergeMethod, + PRMergeability, + PRMergeState, + PRMergeStatus, } // --------------------------------------------------------------------------- diff --git a/packages/kilo-vscode/src/agent-manager/vscode-host.ts b/packages/kilo-vscode/src/agent-manager/vscode-host.ts index 5a0a4c131733..c07d26f7709b 100644 --- a/packages/kilo-vscode/src/agent-manager/vscode-host.ts +++ b/packages/kilo-vscode/src/agent-manager/vscode-host.ts @@ -21,6 +21,8 @@ import type { RemoteStatusService } from "../services/RemoteStatusService" import type { CaffeinationService } from "../services/caffeination" const INTRO_KEY = "kilo.agentManager.introDismissed" +const PR_MERGE_METHODS_KEY = "agentManager.prMergeMethod" +type PRMergeMethod = "merge" | "squash" | "rebase" export class VscodeHost implements Host { private diffVirtual: DiffVirtualProvider | undefined @@ -290,6 +292,18 @@ export class VscodeHost implements Host { await this.context.globalState.update("agentManager.projects", value) } + getPRMergeMethod(repo: string): PRMergeMethod | undefined { + const values = this.context.globalState.get>(PR_MERGE_METHODS_KEY) + const value = values?.[repo] + if (value === "merge" || value === "squash" || value === "rebase") return value + return undefined + } + + async savePRMergeMethod(repo: string, method: PRMergeMethod): Promise { + const values = this.context.globalState.get>(PR_MERGE_METHODS_KEY) ?? {} + await this.context.globalState.update(PR_MERGE_METHODS_KEY, { ...values, [repo]: method }) + } + unregisterProjectRoutes(projectId: string): void { this.routes.unregisterProject(projectId) } diff --git a/packages/kilo-vscode/src/shared/pr-comment-actions.ts b/packages/kilo-vscode/src/shared/pr-comment-actions.ts index 2e5ef23108a7..d7de0a23a797 100644 --- a/packages/kilo-vscode/src/shared/pr-comment-actions.ts +++ b/packages/kilo-vscode/src/shared/pr-comment-actions.ts @@ -55,11 +55,29 @@ export type PRReviewResult = | (Result & { type: "agentManager.previewPRSuggestionResult"; preview?: PRSuggestionPreview }) | (Result & { type: "agentManager.applyPRSuggestionResult" }) +export type PRMergeRequest = + | (Request & { type: "agentManager.updatePRBranch"; head: string }) + | (Request & { + type: "agentManager.mergePR" + method: "merge" | "squash" | "rebase" + auto: boolean + head: string + }) + | (Request & { type: "agentManager.disablePRAutoMerge" }) + | (Request & { type: "agentManager.loadPRConflicts"; base: string; head: string }) + +export type PRMergeResult = + | (Result & { type: "agentManager.updatePRBranchResult" }) + | (Result & { type: "agentManager.mergePRResult" }) + | (Result & { type: "agentManager.disablePRAutoMergeResult" }) + | (Result & { type: "agentManager.loadPRConflictsResult"; files?: string[] }) + type Route = { projectId?: string; worktreeId: string; requestId: string } type Outcome = Route & { success: boolean; error?: string } export type PRCommentRequest = | PRReviewRequest + | PRMergeRequest | (Route & { type: "agentManager.replyComment"; threadId: string; body: string }) | (Request & { type: "agentManager.mutateComment" @@ -70,5 +88,6 @@ export type PRCommentRequest = export type PRCommentResult = | PRReviewResult + | PRMergeResult | (Outcome & { type: "agentManager.replyCommentResult"; threadId: string }) | (Outcome & { type: "agentManager.mutateCommentResult" }) 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 815a7c723bfb..bdfd6a1f86d8 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 @@ -399,6 +399,8 @@ describe("PRStatusPoller unresolved threads", () => { nodes.at(100)?.comments.nodes.push({ id: "reply", body: "Agreed" }) internal.gh = async (args) => { if (args[0] === "repo") return { stdout: JSON.stringify({ owner: { login: "x" }, name: "y" }), stderr: "" } + if (args[0] === "api" && args[1] !== "graphql") + return { stdout: JSON.stringify({ allow_auto_merge: true }), stderr: "" } if (args[0] === "pr") { return { stdout: JSON.stringify({ ...pr, statusCheckRollup: [], reviewRequests: [], reviews: [] }), stderr: "" } } 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 784fe8c20a53..8ca528fbe2d6 100644 --- a/packages/kilo-vscode/tests/unit/am-pr-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/am-pr-utils.test.ts @@ -182,6 +182,19 @@ describe("parsePRResult", () => { expect(parsePRResult(JSON.stringify(raw))?.review).toBe("pending") }) + it("parses GitHub mergeability and auto-merge state", () => { + const result = parsePRResult( + JSON.stringify({ + number: 1, + state: "OPEN", + mergeable: "CONFLICTING", + mergeStateStatus: "DIRTY", + autoMergeRequest: { mergeMethod: "SQUASH" }, + }), + ) + expect(result?.merge).toEqual({ mergeable: "conflicting", state: "dirty", auto: "squash" }) + }) + it("returns null review for unknown decision", () => { const raw = { number: 1, diff --git a/packages/kilo-vscode/tests/unit/git-ops.test.ts b/packages/kilo-vscode/tests/unit/git-ops.test.ts index 81e2610149fd..74f39c9074da 100644 --- a/packages/kilo-vscode/tests/unit/git-ops.test.ts +++ b/packages/kilo-vscode/tests/unit/git-ops.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "bun:test" import * as fs from "fs/promises" import * as os from "os" import * as nodePath from "path" -import { GitOps } from "../../src/agent-manager/GitOps" +import { GitOps, parseConflictPaths } from "../../src/agent-manager/GitOps" import { Semaphore } from "../../src/agent-manager/semaphore" function ops(handler: (args: string[], cwd: string) => Promise, semaphore?: Semaphore): GitOps { @@ -770,3 +770,48 @@ describe("GitOps", () => { }) }) }) + +describe("GitOps conflicts", () => { + it("rejects non-OID conflict revisions before invoking Git", async () => { + const git = new GitOps({ log: () => undefined }) + await expect(git.conflicts("/repo", "origin", "refs/heads/main", "head")).rejects.toThrow( + "Invalid pull request commit ID", + ) + git.dispose() + }) + + it("parses merge-tree name-only output", () => { + expect( + parseConflictPaths("treeoid\na.txt\nb.txt\n\nAuto-merging a.txt\nCONFLICT (content): Merge conflict in a.txt"), + ).toEqual(["a.txt", "b.txt"]) + expect(parseConflictPaths("treeoid\n")).toEqual([]) + }) + + it("lists conflicting files for divergent commits without changing the worktree", async () => { + await withRepo(async (cwd) => { + runGit(cwd, ["config", "user.email", "test@example.com"]) + runGit(cwd, ["config", "user.name", "Test"]) + await fs.writeFile(nodePath.join(cwd, "a.txt"), "base\n") + runGit(cwd, ["add", "."]) + runGit(cwd, ["commit", "-m", "base"]) + const branch = runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]) + runGit(cwd, ["checkout", "-b", "feature"]) + await fs.writeFile(nodePath.join(cwd, "a.txt"), "feature\n") + runGit(cwd, ["commit", "-am", "feature"]) + const head = runGit(cwd, ["rev-parse", "HEAD"]) + runGit(cwd, ["checkout", branch]) + await fs.writeFile(nodePath.join(cwd, "a.txt"), "main\n") + runGit(cwd, ["commit", "-am", "main"]) + const base = runGit(cwd, ["rev-parse", "HEAD"]) + + const git = new GitOps({ log: () => undefined }) + expect(await git.conflicts(cwd, "origin", base, head)).toEqual(["a.txt"]) + expect(await git.conflicts(cwd, "origin", base, head)).toEqual(["a.txt"]) + expect((git as unknown as { conflictCache: Map }).conflictCache.size).toBe(1) + expect(await fs.readFile(nodePath.join(cwd, "a.txt"), "utf8")).toBe("main\n") + expect(runGit(cwd, ["status", "--porcelain"])).toBe("") + git.dispose() + expect((git as unknown as { conflictCache: Map }).conflictCache.size).toBe(0) + }) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/pr-review-actions.test.ts b/packages/kilo-vscode/tests/unit/pr-review-actions.test.ts index e22e245c92df..9b44059a8130 100644 --- a/packages/kilo-vscode/tests/unit/pr-review-actions.test.ts +++ b/packages/kilo-vscode/tests/unit/pr-review-actions.test.ts @@ -2,8 +2,9 @@ import { readFile } from "node:fs/promises" import { afterAll, beforeEach, describe, expect, it, spyOn } from "bun:test" import * as gh from "../../src/agent-manager/gh" import { PRReviewActions } from "../../src/agent-manager/pr/review-actions" +import { PRMergeActions } from "../../src/agent-manager/pr/merge-actions" import type { PRReviewContext } from "../../src/agent-manager/pr/review-context" -import type { PRReviewResult } from "../../src/shared/pr-comment-actions" +import type { PRMergeResult, PRReviewResult } from "../../src/shared/pr-comment-actions" import { parsePatch } from "../../src/shared/pr-patch" const execute = spyOn(gh, "execGhRead") @@ -59,6 +60,8 @@ function harness() { title: "Review", state: "open", review: null, + baseRefOid: base, + headRefOid: head, checks: { status: "none", total: 0, passed: 0, failed: 0, pending: 0, checks: [] }, reviewers: [], additions: 2, @@ -480,3 +483,156 @@ describe("commit-bound PR review actions", () => { }, ) }) + +describe("GitHub PR merge actions", () => { + it("updates the remote branch with the expected head", async () => { + execute.mockResolvedValue({ stdout: "{}", stderr: "" }) + const context = harness().context + const sent: PRMergeResult[] = [] + const completion = Promise.withResolvers() + const host = { + context: (_message: Record) => context, + post: (message: PRMergeResult) => { + sent.push(message) + completion.resolve(message) + }, + refresh: spyOn({ run: (_value: PRReviewContext) => {} }, "run"), + dirtyFiles: () => [], + } + const actions = new PRMergeActions(host) + expect( + actions.handle({ + type: "agentManager.updatePRBranch", + projectId: context.projectId, + worktreeId: context.worktreeId, + requestId: "update", + prNumber: context.pr.number, + prUrl: context.pr.url, + head, + }), + ).toBe(true) + expect(await completion.promise).toMatchObject({ type: "agentManager.updatePRBranchResult", success: true }) + expect(execute.mock.calls.at(-1)?.[0]).toEqual([ + "api", + "--hostname", + "github.com", + "--method", + "PUT", + "repos/owner/repo/pulls/7/update-branch", + "-f", + `expected_head_sha=${head}`, + ]) + }) + + it("merges with the selected method and persists it after GitHub succeeds", async () => { + execute.mockResolvedValue({ stdout: "", stderr: "" }) + const context = harness().context + const sent: Array = [] + const completion = Promise.withResolvers() + const save = spyOn({ run: async (_repo: string, _method: "merge" | "squash" | "rebase") => {} }, "run") + const refresh = spyOn({ run: (_value: PRReviewContext, _settle?: boolean) => {} }, "run") + const host = { + context: (_message: Record) => context, + post: (message: PRMergeResult) => { + sent.push(message) + completion.resolve(message) + }, + refresh, + dirtyFiles: () => [], + savePRMergeMethod: save, + } + const actions = new PRMergeActions(host) + actions.handle({ + type: "agentManager.mergePR", + projectId: context.projectId, + worktreeId: context.worktreeId, + requestId: "merge", + prNumber: context.pr.number, + prUrl: context.pr.url, + method: "squash", + auto: true, + head, + }) + expect(await completion.promise).toMatchObject({ type: "agentManager.mergePRResult", success: true }) + expect(execute.mock.calls.at(-1)?.[0]).toEqual([ + "pr", + "merge", + url, + "--squash", + "--match-head-commit", + head, + "--auto", + ]) + expect(save).toHaveBeenCalledWith("owner/repo", "squash") + expect(refresh).toHaveBeenCalledWith(context, true) + }) + + it("loads conflicting files without refreshing the pull request", async () => { + const context = harness().context + const completion = Promise.withResolvers() + const conflicts = spyOn( + { run: async (_context: PRReviewContext, _base: string, _head: string) => ["a.txt"] }, + "run", + ) + const refresh = spyOn({ run: (_value: PRReviewContext) => {} }, "run") + const host = { + context: (_message: Record) => context, + post: (message: PRMergeResult) => completion.resolve(message), + refresh, + dirtyFiles: () => [], + conflicts, + } + const actions = new PRMergeActions(host) + expect( + actions.handle({ + type: "agentManager.loadPRConflicts", + projectId: context.projectId, + worktreeId: context.worktreeId, + requestId: "conflicts", + prNumber: context.pr.number, + prUrl: context.pr.url, + base, + head, + }), + ).toBe(true) + expect(await completion.promise).toMatchObject({ + type: "agentManager.loadPRConflictsResult", + success: true, + files: ["a.txt"], + }) + expect(conflicts).toHaveBeenCalledWith(context, base, head) + expect(refresh).not.toHaveBeenCalled() + }) + + it("rejects stale or unsafe conflict revisions", async () => { + const context = harness().context + const completion = Promise.withResolvers() + const conflicts = spyOn( + { run: async (_context: PRReviewContext, _base: string, _head: string) => ["a.txt"] }, + "run", + ) + const host = { + context: (_message: Record) => context, + post: (message: PRMergeResult) => completion.resolve(message), + refresh: spyOn({ run: (_value: PRReviewContext) => {} }, "run"), + dirtyFiles: () => [], + conflicts, + } + const actions = new PRMergeActions(host) + actions.handle({ + type: "agentManager.loadPRConflicts", + projectId: context.projectId, + worktreeId: context.worktreeId, + requestId: "stale-conflicts", + prNumber: context.pr.number, + prUrl: context.pr.url, + base: "c".repeat(40), + head, + }) + expect(await completion.promise).toMatchObject({ + type: "agentManager.loadPRConflictsResult", + success: false, + }) + expect(conflicts).not.toHaveBeenCalled() + }) +}) diff --git a/packages/kilo-vscode/tests/unit/pr-review-request.test.ts b/packages/kilo-vscode/tests/unit/pr-review-request.test.ts index 61a66edc9da7..5095d403e287 100644 --- a/packages/kilo-vscode/tests/unit/pr-review-request.test.ts +++ b/packages/kilo-vscode/tests/unit/pr-review-request.test.ts @@ -72,4 +72,49 @@ describe("reviewRequest", () => { globalThis.window = previous } }) + + it("can cancel without settling a result", () => { + const events = new EventTarget() + let active = 0 + const target = { + addEventListener(type: string, listener: EventListenerOrEventListenerObject) { + if (type === "message") active++ + events.addEventListener(type, listener) + }, + removeEventListener(type: string, listener: EventListenerOrEventListenerObject) { + if (type === "message") active-- + events.removeEventListener(type, listener) + }, + dispatchEvent: (event: Event) => events.dispatchEvent(event), + } + const previous = globalThis.window + globalThis.window = target as typeof globalThis.window + + try { + const message: PRCommentRequest = { + type: "agentManager.loadPRConflicts", + projectId: "project", + worktreeId: "worktree", + prNumber: 1, + prUrl: "https://github.com/owner/repo/pull/1", + base: "a".repeat(40), + head: "b".repeat(40), + requestId: "request", + } + const results: PRCommentResult[] = [] + const dispose = reviewRequest( + message, + () => {}, + (result) => results.push(result), + 5, + true, + ) + expect(active).toBe(1) + dispose() + expect(active).toBe(0) + expect(results).toHaveLength(0) + } finally { + globalThis.window = previous + } + }) }) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 0f1c6067ebda..998100473694 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -2630,7 +2630,7 @@ const AgentManagerContent: Component = () => { worktree={activePR()!.wt} worktreeId={activePR()!.selected} activeTerminalId={terms.activeId()} - sessionId={diffCtx()} + sessionId={activeDiffSession()} onOpenDiff={remote.open} jump={comments.jump()} onJump={comments.complete} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css index 174e033b8c48..1f855bb1ae5e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -2789,6 +2789,56 @@ body.am-wt-dragging-active * { border-radius: 0 5px 5px 0; } +.am-split-button[data-variant="primary"] { + gap: 0; + align-items: stretch; + border-radius: 2px; + background: var(--surface-brand-base); + color: #ffffff; +} + +.am-split-button[data-variant="primary"] > [data-component="button"] { + min-height: 26px; + border: 0; + border-radius: 2px 0 0 2px; + background: transparent; + color: inherit; +} + +.am-split-button[data-variant="primary"] > .am-split-arrow { + width: 26px; + height: auto; + min-height: 26px; + border-left: 1px solid rgba(255, 255, 255, 0.28); + border-radius: 0 2px 2px 0; + background: transparent; + color: inherit; + opacity: 1; +} + +.am-split-button[data-variant="primary"] > .am-split-arrow [data-component="icon"], +.am-split-button[data-variant="primary"] > .am-split-arrow [data-slot="icon-svg"] { + color: #ffffff; +} + +.am-split-button[data-variant="primary"] > [data-component="button"]:hover:not(:disabled), +.am-split-button[data-variant="primary"] > .am-split-arrow:hover:not(:disabled), +.am-split-button[data-variant="primary"] > .am-split-arrow[data-expanded] { + background: var(--surface-brand-hover); +} + +.am-split-button[data-variant="primary"][data-disabled] { + opacity: 0.5; +} + +.am-split-button[data-variant="primary"] > [data-component="button"]:disabled, +.am-split-button[data-variant="primary"] > .am-split-arrow:disabled { + background: transparent; + color: inherit; + opacity: 1; + cursor: not-allowed; +} + .am-split-menu { min-width: 250px; } 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 adf400670b50..9dffe7fcd8d0 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts @@ -331,6 +331,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} موافقة", + "agentManager.pr.summary.approvedCount.other": "{{count}} موافقات", + "agentManager.pr.merge.conflicts": "تعارضات الدمج", + "agentManager.pr.merge.conflictsHint": "حلّ التعارضات قبل الدمج.", + "agentManager.pr.merge.conflictsLoading": "جارٍ تحميل الملفات المتعارضة...", + "agentManager.pr.merge.behind": "الفرع متأخر عن الفرع الأساسي", + "agentManager.pr.merge.blocked": "الدمج محظور", + "agentManager.pr.merge.unstable": "تفشل عمليات التحقق", + "agentManager.pr.merge.draft": "طلب سحب مسودة", + "agentManager.pr.merge.checking": "جارٍ التحقق من إمكانية الدمج", + "agentManager.pr.merge.ready": "جاهز للدمج", + "agentManager.pr.merge.fix": "إصلاح باستخدام Kilo", + "agentManager.pr.merge.update": "تحديث الفرع", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "تمكين auto-merge", + "agentManager.pr.merge.disableAuto": "تعطيل auto-merge", + "agentManager.pr.merge.autoNote": "سينفذ GitHub الدمج عند استيفاء المتطلبات.", + "agentManager.pr.merge.behindHint": "ادمج أحدث تغييرات الفرع الأساسي في هذا الفرع.", + "agentManager.pr.merge.blockedHint": "لم يتم استيفاء المتطلبات بعد.", + "agentManager.pr.merge.unstableHint": "تفشل بعض عمليات التحقق.", + "agentManager.pr.merge.draftHint": "حدّد طلب السحب على أنه جاهز للمراجعة قبل دمجه.", + "agentManager.pr.merge.autoMethodNote": "سيُدمج باستخدام {{method}} عند استيفاء المتطلبات.", + "agentManager.pr.merge.unavailableNote": "الدمج غير متاح لطلب السحب هذا بعد.", + "agentManager.pr.merge.chooseMethod": "اختر طريقة الدمج", + "agentManager.pr.merge.method.merge": "إنشاء التزام دمج", + "agentManager.pr.merge.method.squash": "Squash ودمج", + "agentManager.pr.merge.method.rebase": "Rebase ودمج", + "agentManager.pr.merge.confirmTitle": "دمج طلب السحب", + "agentManager.pr.merge.confirmDescription": + "هل تريد دمج طلب السحب هذا باستخدام {{method}}؟ سينفذ GitHub الدمج عن بُعد.", + "agentManager.pr.merge.cancel": "إلغاء", + "agentManager.pr.merge.confirm": "دمج طلب السحب", + "agentManager.pr.merge.failed": "تعذر على GitHub إكمال هذا الإجراء.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 34fc82f5d725..50696625a1dd 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts @@ -337,6 +337,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} aprovação", + "agentManager.pr.summary.approvedCount.other": "{{count}} aprovações", + "agentManager.pr.merge.conflicts": "Conflitos de merge", + "agentManager.pr.merge.conflictsHint": "Resolva os conflitos antes de fazer o merge.", + "agentManager.pr.merge.conflictsLoading": "Carregando arquivos com conflitos...", + "agentManager.pr.merge.behind": "A branch está atrasada em relação à branch base", + "agentManager.pr.merge.blocked": "Merge bloqueado", + "agentManager.pr.merge.unstable": "As verificações estão falhando", + "agentManager.pr.merge.draft": "Pull request em rascunho", + "agentManager.pr.merge.checking": "Verificando se o merge é possível", + "agentManager.pr.merge.ready": "Pronta para fazer o merge", + "agentManager.pr.merge.fix": "Corrigir com Kilo", + "agentManager.pr.merge.update": "Atualizar branch", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "Ativar auto-merge", + "agentManager.pr.merge.disableAuto": "Desativar auto-merge", + "agentManager.pr.merge.autoNote": "O GitHub fará o merge quando os requisitos forem atendidos.", + "agentManager.pr.merge.behindHint": "Faça o merge das alterações mais recentes da branch base nesta branch.", + "agentManager.pr.merge.blockedHint": "Os requisitos ainda não foram atendidos.", + "agentManager.pr.merge.unstableHint": "Algumas verificações estão falhando.", + "agentManager.pr.merge.draftHint": "Marque a pull request como pronta para revisão antes de fazer o merge.", + "agentManager.pr.merge.autoMethodNote": "Faz o merge com {{method}} quando os requisitos forem atendidos.", + "agentManager.pr.merge.unavailableNote": "O merge ainda não está disponível para esta pull request.", + "agentManager.pr.merge.chooseMethod": "Escolher método de merge", + "agentManager.pr.merge.method.merge": "Criar commit de merge", + "agentManager.pr.merge.method.squash": "Fazer squash e merge", + "agentManager.pr.merge.method.rebase": "Fazer rebase e merge", + "agentManager.pr.merge.confirmTitle": "Fazer merge da pull request", + "agentManager.pr.merge.confirmDescription": + "Fazer merge desta pull request com {{method}}? O GitHub fará o merge remotamente.", + "agentManager.pr.merge.cancel": "Cancelar", + "agentManager.pr.merge.confirm": "Fazer merge da pull request", + "agentManager.pr.merge.failed": "O GitHub não pôde concluir esta ação.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 8ea375603d95..786933dd7f77 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts @@ -335,6 +335,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} odobrenje", + "agentManager.pr.summary.approvedCount.other": "{{count}} odobrenja", + "agentManager.pr.merge.conflicts": "Konflikti pri spajanju", + "agentManager.pr.merge.conflictsHint": "Riješite konflikte prije spajanja.", + "agentManager.pr.merge.conflictsLoading": "Učitavanje datoteka s konfliktima...", + "agentManager.pr.merge.behind": "Grana zaostaje za osnovnom granom", + "agentManager.pr.merge.blocked": "Spajanje blokirano", + "agentManager.pr.merge.unstable": "Provjere ne prolaze", + "agentManager.pr.merge.draft": "Nacrt pull requesta", + "agentManager.pr.merge.checking": "Provjera mogućnosti spajanja", + "agentManager.pr.merge.ready": "Spremno za spajanje", + "agentManager.pr.merge.fix": "Popravi pomoću Kilo", + "agentManager.pr.merge.update": "Ažuriraj granu", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "Omogući auto-merge", + "agentManager.pr.merge.disableAuto": "Onemogući auto-merge", + "agentManager.pr.merge.autoNote": "GitHub će spojiti promjene kada zahtjevi budu ispunjeni.", + "agentManager.pr.merge.behindHint": "Spojite najnovije promjene osnovne grane u ovu granu.", + "agentManager.pr.merge.blockedHint": "Zahtjevi još nisu ispunjeni.", + "agentManager.pr.merge.unstableHint": "Neke provjere ne prolaze.", + "agentManager.pr.merge.draftHint": "Označite pull request kao spreman za pregled prije spajanja.", + "agentManager.pr.merge.autoMethodNote": "Spaja se pomoću {{method}} kada zahtjevi budu ispunjeni.", + "agentManager.pr.merge.unavailableNote": "Spajanje još nije dostupno za ovaj pull request.", + "agentManager.pr.merge.chooseMethod": "Odaberite način spajanja", + "agentManager.pr.merge.method.merge": "Napravi commit spajanja", + "agentManager.pr.merge.method.squash": "Squash i spajanje", + "agentManager.pr.merge.method.rebase": "Rebase i spajanje", + "agentManager.pr.merge.confirmTitle": "Spoji pull request", + "agentManager.pr.merge.confirmDescription": + "Spojiti ovaj pull request pomoću {{method}}? GitHub će izvršiti spajanje na daljinu.", + "agentManager.pr.merge.cancel": "Otkaži", + "agentManager.pr.merge.confirm": "Spoji pull request", + "agentManager.pr.merge.failed": "GitHub nije mogao dovršiti ovu radnju.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 566332d0d7ab..954b34e272ef 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts @@ -336,6 +336,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} godkendelse", + "agentManager.pr.summary.approvedCount.other": "{{count}} godkendelser", + "agentManager.pr.merge.conflicts": "Flettekonflikter", + "agentManager.pr.merge.conflictsHint": "Løs konflikterne, før du fletter.", + "agentManager.pr.merge.conflictsLoading": "Indlæser filer med konflikter...", + "agentManager.pr.merge.behind": "Branchen er bagud i forhold til basisbranchen", + "agentManager.pr.merge.blocked": "Fletning blokeret", + "agentManager.pr.merge.unstable": "Kontroller mislykkes", + "agentManager.pr.merge.draft": "Kladde-pull request", + "agentManager.pr.merge.checking": "Kontrollerer, om den kan flettes", + "agentManager.pr.merge.ready": "Klar til at flette", + "agentManager.pr.merge.fix": "Ret med Kilo", + "agentManager.pr.merge.update": "Opdater branch", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "Aktivér auto-merge", + "agentManager.pr.merge.disableAuto": "Deaktivér auto-merge", + "agentManager.pr.merge.autoNote": "GitHub fletter, når kravene er opfyldt.", + "agentManager.pr.merge.behindHint": "Flet de seneste ændringer fra basisbranchen ind i denne branch.", + "agentManager.pr.merge.blockedHint": "Kravene er endnu ikke opfyldt.", + "agentManager.pr.merge.unstableHint": "Nogle kontroller mislykkes.", + "agentManager.pr.merge.draftHint": "Markér pull requesten som klar til gennemsyn, før den flettes.", + "agentManager.pr.merge.autoMethodNote": "Fletter med {{method}}, når kravene er opfyldt.", + "agentManager.pr.merge.unavailableNote": "Fletning er endnu ikke tilgængelig for denne pull request.", + "agentManager.pr.merge.chooseMethod": "Vælg flette-metode", + "agentManager.pr.merge.method.merge": "Opret merge-commit", + "agentManager.pr.merge.method.squash": "Squash og flet", + "agentManager.pr.merge.method.rebase": "Rebase og flet", + "agentManager.pr.merge.confirmTitle": "Flet pull request", + "agentManager.pr.merge.confirmDescription": + "Flet denne pull request med {{method}}? GitHub udfører fletningen eksternt.", + "agentManager.pr.merge.cancel": "Annuller", + "agentManager.pr.merge.confirm": "Flet pull request", + "agentManager.pr.merge.failed": "GitHub kunne ikke fuldføre denne handling.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 dda359d2b1b7..4384386718c4 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts @@ -343,6 +343,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} Genehmigung", + "agentManager.pr.summary.approvedCount.other": "{{count}} Genehmigungen", + "agentManager.pr.merge.conflicts": "Merge-Konflikte", + "agentManager.pr.merge.conflictsHint": "Löse die Konflikte vor dem Mergen.", + "agentManager.pr.merge.conflictsLoading": "Konfliktdateien werden geladen...", + "agentManager.pr.merge.behind": "Branch liegt hinter dem Basis-Branch zurück", + "agentManager.pr.merge.blocked": "Mergen blockiert", + "agentManager.pr.merge.unstable": "Checks schlagen fehl", + "agentManager.pr.merge.draft": "Pull Request als Entwurf", + "agentManager.pr.merge.checking": "Mergebarkeit wird geprüft", + "agentManager.pr.merge.ready": "Bereit zum Mergen", + "agentManager.pr.merge.fix": "Mit Kilo beheben", + "agentManager.pr.merge.update": "Branch aktualisieren", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "auto-merge aktivieren", + "agentManager.pr.merge.disableAuto": "auto-merge deaktivieren", + "agentManager.pr.merge.autoNote": "GitHub führt den Merge aus, sobald alle Anforderungen erfüllt sind.", + "agentManager.pr.merge.behindHint": "Führe die neuesten Änderungen des Basis-Branches in diesen Branch zusammen.", + "agentManager.pr.merge.blockedHint": "Die Anforderungen sind noch nicht erfüllt.", + "agentManager.pr.merge.unstableHint": "Einige Checks schlagen fehl.", + "agentManager.pr.merge.draftHint": "Markiere den Pull Request vor dem Mergen als bereit zur Überprüfung.", + "agentManager.pr.merge.autoMethodNote": "Wird mit {{method}} gemergt, sobald alle Anforderungen erfüllt sind.", + "agentManager.pr.merge.unavailableNote": "Mergen ist für diesen Pull Request noch nicht verfügbar.", + "agentManager.pr.merge.chooseMethod": "Merge-Methode auswählen", + "agentManager.pr.merge.method.merge": "Merge-Commit erstellen", + "agentManager.pr.merge.method.squash": "Squash und mergen", + "agentManager.pr.merge.method.rebase": "Rebasen und mergen", + "agentManager.pr.merge.confirmTitle": "Pull Request mergen", + "agentManager.pr.merge.confirmDescription": + "Diesen Pull Request mit {{method}} mergen? GitHub führt den Merge remote aus.", + "agentManager.pr.merge.cancel": "Abbrechen", + "agentManager.pr.merge.confirm": "Pull Request mergen", + "agentManager.pr.merge.failed": "GitHub konnte diese Aktion nicht abschließen.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 20e3b9d19517..01e27fcf5a6a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts @@ -344,9 +344,42 @@ export const dict = { "agentManager.pr.summary.comments.other": "{{count}} comments", "agentManager.pr.summary.conversation.one": "{{count}} PR comment", "agentManager.pr.summary.conversation.other": "{{count}} PR comments", + "agentManager.pr.summary.approvedCount.one": "{{count}} approval", + "agentManager.pr.summary.approvedCount.other": "{{count}} approvals", "agentManager.pr.summary.jump.checks": "Go to checks", "agentManager.pr.summary.jump.comments": "Go to comments", "agentManager.pr.summary.jump.conversation": "Go to conversation", + "agentManager.pr.merge.conflicts": "Merge conflicts", + "agentManager.pr.merge.conflictsHint": "Resolve conflicts before merging.", + "agentManager.pr.merge.conflictsLoading": "Loading conflicting files...", + "agentManager.pr.merge.behind": "Branch is behind the base branch", + "agentManager.pr.merge.blocked": "Merging blocked", + "agentManager.pr.merge.unstable": "Checks are failing", + "agentManager.pr.merge.draft": "Draft pull request", + "agentManager.pr.merge.checking": "Checking mergeability", + "agentManager.pr.merge.ready": "Ready to merge", + "agentManager.pr.merge.fix": "Fix with Kilo", + "agentManager.pr.merge.update": "Update branch", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "Enable auto-merge", + "agentManager.pr.merge.disableAuto": "Disable auto-merge", + "agentManager.pr.merge.autoNote": "GitHub will merge when requirements are met.", + "agentManager.pr.merge.behindHint": "Merge the latest base branch changes into this branch.", + "agentManager.pr.merge.blockedHint": "Requirements have not been met yet.", + "agentManager.pr.merge.unstableHint": "Some checks are failing.", + "agentManager.pr.merge.draftHint": "Mark the pull request ready for review before merging.", + "agentManager.pr.merge.autoMethodNote": "Merges with {{method}} when requirements are met.", + "agentManager.pr.merge.unavailableNote": "Merging is not available for this pull request yet.", + "agentManager.pr.merge.chooseMethod": "Choose merge method", + "agentManager.pr.merge.method.merge": "Create merge commit", + "agentManager.pr.merge.method.squash": "Squash and merge", + "agentManager.pr.merge.method.rebase": "Rebase and merge", + "agentManager.pr.merge.confirmTitle": "Merge pull request", + "agentManager.pr.merge.confirmDescription": + "Merge this pull request with {{method}}? GitHub will perform the merge remotely.", + "agentManager.pr.merge.cancel": "Cancel", + "agentManager.pr.merge.confirm": "Merge pull request", + "agentManager.pr.merge.failed": "GitHub could not complete this action.", "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 8d6291928d1b..58b6138743ca 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts @@ -340,6 +340,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} aprobación", + "agentManager.pr.summary.approvedCount.other": "{{count}} aprobaciones", + "agentManager.pr.merge.conflicts": "Conflictos de fusión", + "agentManager.pr.merge.conflictsHint": "Resuelve los conflictos antes de fusionar.", + "agentManager.pr.merge.conflictsLoading": "Cargando archivos con conflictos...", + "agentManager.pr.merge.behind": "La rama está detrás de la rama base", + "agentManager.pr.merge.blocked": "Fusión bloqueada", + "agentManager.pr.merge.unstable": "Las comprobaciones están fallando", + "agentManager.pr.merge.draft": "Pull request en borrador", + "agentManager.pr.merge.checking": "Comprobando si se puede fusionar", + "agentManager.pr.merge.ready": "Lista para fusionar", + "agentManager.pr.merge.fix": "Corregir con Kilo", + "agentManager.pr.merge.update": "Actualizar rama", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "Activar auto-merge", + "agentManager.pr.merge.disableAuto": "Desactivar auto-merge", + "agentManager.pr.merge.autoNote": "GitHub fusionará cuando se cumplan los requisitos.", + "agentManager.pr.merge.behindHint": "Fusiona los cambios más recientes de la rama base en esta rama.", + "agentManager.pr.merge.blockedHint": "Aún no se han cumplido los requisitos.", + "agentManager.pr.merge.unstableHint": "Algunas comprobaciones están fallando.", + "agentManager.pr.merge.draftHint": "Marca el pull request como listo para revisión antes de fusionarlo.", + "agentManager.pr.merge.autoMethodNote": "Se fusiona con {{method}} cuando se cumplen los requisitos.", + "agentManager.pr.merge.unavailableNote": "La fusión aún no está disponible para este pull request.", + "agentManager.pr.merge.chooseMethod": "Elegir método de fusión", + "agentManager.pr.merge.method.merge": "Crear commit de fusión", + "agentManager.pr.merge.method.squash": "Hacer squash y fusionar", + "agentManager.pr.merge.method.rebase": "Hacer rebase y fusionar", + "agentManager.pr.merge.confirmTitle": "Fusionar pull request", + "agentManager.pr.merge.confirmDescription": + "¿Fusionar este pull request con {{method}}? GitHub realizará la fusión de forma remota.", + "agentManager.pr.merge.cancel": "Cancelar", + "agentManager.pr.merge.confirm": "Fusionar pull request", + "agentManager.pr.merge.failed": "GitHub no pudo completar esta acción.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 88eb15b3fdef..8ff591fb0f54 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts @@ -339,6 +339,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} تأیید", + "agentManager.pr.summary.approvedCount.other": "{{count}} تأیید", + "agentManager.pr.merge.conflicts": "تعارض‌های ادغام", + "agentManager.pr.merge.conflictsHint": "پیش از ادغام، تعارض‌ها را برطرف کنید.", + "agentManager.pr.merge.conflictsLoading": "در حال بارگیری فایل‌های دارای تعارض...", + "agentManager.pr.merge.behind": "شاخه از شاخهٔ پایه عقب‌تر است", + "agentManager.pr.merge.blocked": "ادغام مسدود است", + "agentManager.pr.merge.unstable": "بررسی‌ها ناموفق هستند", + "agentManager.pr.merge.draft": "درخواست ادغام پیش‌نویس", + "agentManager.pr.merge.checking": "در حال بررسی امکان ادغام", + "agentManager.pr.merge.ready": "آمادهٔ ادغام", + "agentManager.pr.merge.fix": "رفع اشکال با Kilo", + "agentManager.pr.merge.update": "به‌روزرسانی شاخه", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "فعال‌سازی auto-merge", + "agentManager.pr.merge.disableAuto": "غیرفعال‌سازی auto-merge", + "agentManager.pr.merge.autoNote": "GitHub هنگام برآورده شدن الزامات، ادغام را انجام می‌دهد.", + "agentManager.pr.merge.behindHint": "جدیدترین تغییرات شاخهٔ پایه را در این شاخه ادغام کنید.", + "agentManager.pr.merge.blockedHint": "الزامات هنوز برآورده نشده‌اند.", + "agentManager.pr.merge.unstableHint": "برخی بررسی‌ها ناموفق هستند.", + "agentManager.pr.merge.draftHint": "پیش از ادغام، درخواست ادغام را برای بررسی آماده علامت‌گذاری کنید.", + "agentManager.pr.merge.autoMethodNote": "پس از برآورده شدن الزامات، با {{method}} ادغام می‌شود.", + "agentManager.pr.merge.unavailableNote": "ادغام این درخواست ادغام هنوز در دسترس نیست.", + "agentManager.pr.merge.chooseMethod": "روش ادغام را انتخاب کنید", + "agentManager.pr.merge.method.merge": "ایجاد کامیت ادغام", + "agentManager.pr.merge.method.squash": "Squash و ادغام", + "agentManager.pr.merge.method.rebase": "Rebase و ادغام", + "agentManager.pr.merge.confirmTitle": "ادغام درخواست ادغام", + "agentManager.pr.merge.confirmDescription": + "این درخواست ادغام با {{method}} ادغام شود؟ GitHub ادغام را از راه دور انجام می‌دهد.", + "agentManager.pr.merge.cancel": "لغو", + "agentManager.pr.merge.confirm": "ادغام درخواست ادغام", + "agentManager.pr.merge.failed": "GitHub نتوانست این اقدام را کامل کند.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 5bc67688cc6f..f0bef734c425 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts @@ -343,6 +343,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} approbation", + "agentManager.pr.summary.approvedCount.other": "{{count}} approbations", + "agentManager.pr.merge.conflicts": "Conflits de fusion", + "agentManager.pr.merge.conflictsHint": "Résolvez les conflits avant de fusionner.", + "agentManager.pr.merge.conflictsLoading": "Chargement des fichiers en conflit...", + "agentManager.pr.merge.behind": "La branche est en retard sur la branche de base", + "agentManager.pr.merge.blocked": "Fusion bloquée", + "agentManager.pr.merge.unstable": "Les vérifications échouent", + "agentManager.pr.merge.draft": "Pull request en brouillon", + "agentManager.pr.merge.checking": "Vérification de la possibilité de fusion", + "agentManager.pr.merge.ready": "Prête à fusionner", + "agentManager.pr.merge.fix": "Corriger avec Kilo", + "agentManager.pr.merge.update": "Mettre à jour la branche", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "Activer l’auto-merge", + "agentManager.pr.merge.disableAuto": "Désactiver l’auto-merge", + "agentManager.pr.merge.autoNote": "GitHub fusionnera lorsque les exigences seront remplies.", + "agentManager.pr.merge.behindHint": "Fusionnez les dernières modifications de la branche de base dans cette branche.", + "agentManager.pr.merge.blockedHint": "Les exigences ne sont pas encore remplies.", + "agentManager.pr.merge.unstableHint": "Certaines vérifications échouent.", + "agentManager.pr.merge.draftHint": "Marquez la pull request comme prête à être examinée avant de la fusionner.", + "agentManager.pr.merge.autoMethodNote": "Fusionne avec {{method}} lorsque les exigences sont remplies.", + "agentManager.pr.merge.unavailableNote": "La fusion n’est pas encore disponible pour cette pull request.", + "agentManager.pr.merge.chooseMethod": "Choisir la méthode de fusion", + "agentManager.pr.merge.method.merge": "Créer un commit de fusion", + "agentManager.pr.merge.method.squash": "Squash et fusionner", + "agentManager.pr.merge.method.rebase": "Rebaser et fusionner", + "agentManager.pr.merge.confirmTitle": "Fusionner la pull request", + "agentManager.pr.merge.confirmDescription": + "Fusionner cette pull request avec {{method}} ? GitHub effectuera la fusion à distance.", + "agentManager.pr.merge.cancel": "Annuler", + "agentManager.pr.merge.confirm": "Fusionner la pull request", + "agentManager.pr.merge.failed": "GitHub n’a pas pu terminer cette action.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 fe448fa77c9d..ba04dafd8ca2 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts @@ -345,6 +345,40 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} approvazione", + "agentManager.pr.summary.approvedCount.other": "{{count}} approvazioni", + "agentManager.pr.merge.conflicts": "Conflitti di merge", + "agentManager.pr.merge.conflictsHint": "Risolvi i conflitti prima del merge.", + "agentManager.pr.merge.conflictsLoading": "Caricamento dei file in conflitto...", + "agentManager.pr.merge.behind": "Il branch è indietro rispetto al branch di base", + "agentManager.pr.merge.blocked": "Merge bloccato", + "agentManager.pr.merge.unstable": "I controlli stanno fallendo", + "agentManager.pr.merge.draft": "Pull request in bozza", + "agentManager.pr.merge.checking": "Verifica della possibilità di merge", + "agentManager.pr.merge.ready": "Pronta per il merge", + "agentManager.pr.merge.fix": "Correggi con Kilo", + "agentManager.pr.merge.update": "Aggiorna il branch", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "Abilita auto-merge", + "agentManager.pr.merge.disableAuto": "Disabilita auto-merge", + "agentManager.pr.merge.autoNote": "GitHub eseguirà il merge quando i requisiti saranno soddisfatti.", + "agentManager.pr.merge.behindHint": + "Esegui il merge delle modifiche più recenti del branch di base in questo branch.", + "agentManager.pr.merge.blockedHint": "I requisiti non sono ancora soddisfatti.", + "agentManager.pr.merge.unstableHint": "Alcuni controlli stanno fallendo.", + "agentManager.pr.merge.draftHint": "Imposta la pull request come pronta per la revisione prima del merge.", + "agentManager.pr.merge.autoMethodNote": "Esegue il merge con {{method}} quando i requisiti sono soddisfatti.", + "agentManager.pr.merge.unavailableNote": "Il merge non è ancora disponibile per questa pull request.", + "agentManager.pr.merge.chooseMethod": "Scegli il metodo di merge", + "agentManager.pr.merge.method.merge": "Crea un commit di merge", + "agentManager.pr.merge.method.squash": "Esegui squash e merge", + "agentManager.pr.merge.method.rebase": "Esegui rebase e merge", + "agentManager.pr.merge.confirmTitle": "Esegui il merge della pull request", + "agentManager.pr.merge.confirmDescription": + "Eseguire il merge di questa pull request con {{method}}? GitHub eseguirà il merge da remoto.", + "agentManager.pr.merge.cancel": "Annulla", + "agentManager.pr.merge.confirm": "Esegui il merge della pull request", + "agentManager.pr.merge.failed": "GitHub non ha potuto completare questa azione.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 86e34a3b163d..4d8c6ef21068 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts @@ -336,6 +336,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} 件の承認", + "agentManager.pr.summary.approvedCount.other": "{{count}} 件の承認", + "agentManager.pr.merge.conflicts": "マージの競合", + "agentManager.pr.merge.conflictsHint": "マージする前に競合を解決してください。", + "agentManager.pr.merge.conflictsLoading": "競合しているファイルを読み込み中...", + "agentManager.pr.merge.behind": "ブランチがベースブランチより遅れています", + "agentManager.pr.merge.blocked": "マージがブロックされています", + "agentManager.pr.merge.unstable": "チェックに失敗しています", + "agentManager.pr.merge.draft": "ドラフト pull request", + "agentManager.pr.merge.checking": "マージ可能か確認中", + "agentManager.pr.merge.ready": "マージする準備ができています", + "agentManager.pr.merge.fix": "Kiloで修正", + "agentManager.pr.merge.update": "ブランチを更新", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "auto-mergeを有効化", + "agentManager.pr.merge.disableAuto": "auto-mergeを無効化", + "agentManager.pr.merge.autoNote": "要件を満たすとGitHubがマージします。", + "agentManager.pr.merge.behindHint": "ベースブランチの最新の変更をこのブランチにマージしてください。", + "agentManager.pr.merge.blockedHint": "要件はまだ満たされていません。", + "agentManager.pr.merge.unstableHint": "一部のチェックに失敗しています。", + "agentManager.pr.merge.draftHint": "マージする前に、pull requestをレビュー可能としてマークしてください。", + "agentManager.pr.merge.autoMethodNote": "要件を満たすと{{method}}でマージします。", + "agentManager.pr.merge.unavailableNote": "このpull requestではまだマージを利用できません。", + "agentManager.pr.merge.chooseMethod": "マージ方法を選択", + "agentManager.pr.merge.method.merge": "マージコミットを作成", + "agentManager.pr.merge.method.squash": "Squashしてマージ", + "agentManager.pr.merge.method.rebase": "Rebaseしてマージ", + "agentManager.pr.merge.confirmTitle": "pull requestをマージ", + "agentManager.pr.merge.confirmDescription": + "このpull requestを{{method}}でマージしますか?GitHubがリモートでマージを実行します。", + "agentManager.pr.merge.cancel": "キャンセル", + "agentManager.pr.merge.confirm": "pull requestをマージ", + "agentManager.pr.merge.failed": "GitHubはこの操作を完了できませんでした。", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 42671e918d8a..959e6ebe3653 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts @@ -334,6 +334,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}}개 승인", + "agentManager.pr.summary.approvedCount.other": "{{count}}개 승인", + "agentManager.pr.merge.conflicts": "병합 충돌", + "agentManager.pr.merge.conflictsHint": "병합하기 전에 충돌을 해결하세요.", + "agentManager.pr.merge.conflictsLoading": "충돌하는 파일 로드 중...", + "agentManager.pr.merge.behind": "브랜치가 베이스 브랜치보다 뒤처져 있습니다", + "agentManager.pr.merge.blocked": "병합이 차단됨", + "agentManager.pr.merge.unstable": "검사에 실패하고 있습니다", + "agentManager.pr.merge.draft": "초안 pull request", + "agentManager.pr.merge.checking": "병합 가능 여부 확인 중", + "agentManager.pr.merge.ready": "병합할 준비가 되었습니다", + "agentManager.pr.merge.fix": "Kilo로 수정하기", + "agentManager.pr.merge.update": "브랜치 업데이트", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "auto-merge 활성화", + "agentManager.pr.merge.disableAuto": "auto-merge 비활성화", + "agentManager.pr.merge.autoNote": "요구 사항이 충족되면 GitHub가 병합합니다.", + "agentManager.pr.merge.behindHint": "베이스 브랜치의 최신 변경 사항을 이 브랜치로 병합하세요.", + "agentManager.pr.merge.blockedHint": "요구 사항이 아직 충족되지 않았습니다.", + "agentManager.pr.merge.unstableHint": "일부 검사에 실패하고 있습니다.", + "agentManager.pr.merge.draftHint": "병합하기 전에 pull request를 검토 가능 상태로 표시하세요.", + "agentManager.pr.merge.autoMethodNote": "요구 사항이 충족되면 {{method}} 방식으로 병합합니다.", + "agentManager.pr.merge.unavailableNote": "이 pull request는 아직 병합할 수 없습니다.", + "agentManager.pr.merge.chooseMethod": "병합 방법 선택", + "agentManager.pr.merge.method.merge": "병합 커밋 생성", + "agentManager.pr.merge.method.squash": "Squash 후 병합", + "agentManager.pr.merge.method.rebase": "Rebase 후 병합", + "agentManager.pr.merge.confirmTitle": "pull request 병합", + "agentManager.pr.merge.confirmDescription": + "이 pull request를 {{method}} 방식으로 병합할까요? GitHub가 원격으로 병합을 수행합니다.", + "agentManager.pr.merge.cancel": "취소", + "agentManager.pr.merge.confirm": "pull request 병합", + "agentManager.pr.merge.failed": "GitHub가 이 작업을 완료하지 못했습니다.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 73d241652d6e..4c2e62106848 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts @@ -343,6 +343,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} goedkeuring", + "agentManager.pr.summary.approvedCount.other": "{{count}} goedkeuringen", + "agentManager.pr.merge.conflicts": "Samenvoegconflicten", + "agentManager.pr.merge.conflictsHint": "Los conflicten op voordat je samenvoegt.", + "agentManager.pr.merge.conflictsLoading": "Conflicterende bestanden laden...", + "agentManager.pr.merge.behind": "De branch loopt achter op de basisbranch", + "agentManager.pr.merge.blocked": "Samenvoegen geblokkeerd", + "agentManager.pr.merge.unstable": "Controles mislukken", + "agentManager.pr.merge.draft": "Pull request als concept", + "agentManager.pr.merge.checking": "Samenvoegbaarheid controleren", + "agentManager.pr.merge.ready": "Klaar om samen te voegen", + "agentManager.pr.merge.fix": "Herstellen met Kilo", + "agentManager.pr.merge.update": "Branch bijwerken", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "auto-merge inschakelen", + "agentManager.pr.merge.disableAuto": "auto-merge uitschakelen", + "agentManager.pr.merge.autoNote": "GitHub voegt samen zodra aan de vereisten is voldaan.", + "agentManager.pr.merge.behindHint": "Voeg de nieuwste wijzigingen van de basisbranch samen met deze branch.", + "agentManager.pr.merge.blockedHint": "Er is nog niet aan de vereisten voldaan.", + "agentManager.pr.merge.unstableHint": "Sommige controles mislukken.", + "agentManager.pr.merge.draftHint": "Markeer de pull request als klaar voor beoordeling voordat je deze samenvoegt.", + "agentManager.pr.merge.autoMethodNote": "Wordt met {{method}} samengevoegd zodra aan de vereisten is voldaan.", + "agentManager.pr.merge.unavailableNote": "Samenvoegen is nog niet beschikbaar voor deze pull request.", + "agentManager.pr.merge.chooseMethod": "Kies een samenvoegmethode", + "agentManager.pr.merge.method.merge": "Merge-commit maken", + "agentManager.pr.merge.method.squash": "Squashen en samenvoegen", + "agentManager.pr.merge.method.rebase": "Rebasen en samenvoegen", + "agentManager.pr.merge.confirmTitle": "Pull request samenvoegen", + "agentManager.pr.merge.confirmDescription": + "Deze pull request samenvoegen met {{method}}? GitHub voert het samenvoegen op afstand uit.", + "agentManager.pr.merge.cancel": "Annuleren", + "agentManager.pr.merge.confirm": "Pull request samenvoegen", + "agentManager.pr.merge.failed": "GitHub kon deze actie niet voltooien.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 dbfe15ab8841..c3202c70b7cd 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts @@ -334,6 +334,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} godkjenning", + "agentManager.pr.summary.approvedCount.other": "{{count}} godkjenninger", + "agentManager.pr.merge.conflicts": "Flettingskonflikter", + "agentManager.pr.merge.conflictsHint": "Løs konfliktene før du fletter.", + "agentManager.pr.merge.conflictsLoading": "Laster inn filer med konflikter...", + "agentManager.pr.merge.behind": "Branchen ligger etter base-branchen", + "agentManager.pr.merge.blocked": "Fletting blokkert", + "agentManager.pr.merge.unstable": "Kontroller mislykkes", + "agentManager.pr.merge.draft": "Pull request som kladd", + "agentManager.pr.merge.checking": "Kontrollerer om den kan flettes", + "agentManager.pr.merge.ready": "Klar til å flette", + "agentManager.pr.merge.fix": "Fiks med Kilo", + "agentManager.pr.merge.update": "Oppdater branch", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "Aktiver auto-merge", + "agentManager.pr.merge.disableAuto": "Deaktiver auto-merge", + "agentManager.pr.merge.autoNote": "GitHub fletter når kravene er oppfylt.", + "agentManager.pr.merge.behindHint": "Flett de nyeste endringene fra base-branchen inn i denne branchen.", + "agentManager.pr.merge.blockedHint": "Kravene er ikke oppfylt ennå.", + "agentManager.pr.merge.unstableHint": "Noen kontroller mislykkes.", + "agentManager.pr.merge.draftHint": "Merk pull requesten som klar for gjennomgang før den flettes.", + "agentManager.pr.merge.autoMethodNote": "Fletter med {{method}} når kravene er oppfylt.", + "agentManager.pr.merge.unavailableNote": "Fletting er ikke tilgjengelig for denne pull requesten ennå.", + "agentManager.pr.merge.chooseMethod": "Velg flettemetode", + "agentManager.pr.merge.method.merge": "Opprett merge-commit", + "agentManager.pr.merge.method.squash": "Squash og flette", + "agentManager.pr.merge.method.rebase": "Rebase og flette", + "agentManager.pr.merge.confirmTitle": "Flett pull request", + "agentManager.pr.merge.confirmDescription": + "Flette denne pull requesten med {{method}}? GitHub utfører flettingen eksternt.", + "agentManager.pr.merge.cancel": "Avbryt", + "agentManager.pr.merge.confirm": "Flett pull request", + "agentManager.pr.merge.failed": "GitHub kunne ikke fullføre denne handlingen.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 f81e13247db9..ca3d0ef15cda 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts @@ -336,6 +336,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} zatwierdzenie", + "agentManager.pr.summary.approvedCount.other": "{{count}} zatwierdzeń", + "agentManager.pr.merge.conflicts": "Konflikty scalania", + "agentManager.pr.merge.conflictsHint": "Rozwiąż konflikty przed scaleniem.", + "agentManager.pr.merge.conflictsLoading": "Wczytywanie plików z konfliktami...", + "agentManager.pr.merge.behind": "Gałąź jest opóźniona względem gałęzi bazowej", + "agentManager.pr.merge.blocked": "Scalanie zablokowane", + "agentManager.pr.merge.unstable": "Kontrole kończą się niepowodzeniem", + "agentManager.pr.merge.draft": "Pull request w wersji roboczej", + "agentManager.pr.merge.checking": "Sprawdzanie możliwości scalania", + "agentManager.pr.merge.ready": "Gotowy do scalenia", + "agentManager.pr.merge.fix": "Napraw za pomocą Kilo", + "agentManager.pr.merge.update": "Zaktualizuj gałąź", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "Włącz auto-merge", + "agentManager.pr.merge.disableAuto": "Wyłącz auto-merge", + "agentManager.pr.merge.autoNote": "GitHub scali zmiany po spełnieniu wymagań.", + "agentManager.pr.merge.behindHint": "Scal najnowsze zmiany z gałęzi bazowej z tą gałęzią.", + "agentManager.pr.merge.blockedHint": "Wymagania nie zostały jeszcze spełnione.", + "agentManager.pr.merge.unstableHint": "Niektóre kontrole kończą się niepowodzeniem.", + "agentManager.pr.merge.draftHint": "Oznacz pull request jako gotowy do przeglądu przed scaleniem.", + "agentManager.pr.merge.autoMethodNote": "Scalanie za pomocą {{method}} po spełnieniu wymagań.", + "agentManager.pr.merge.unavailableNote": "Scalanie nie jest jeszcze dostępne dla tego pull requesta.", + "agentManager.pr.merge.chooseMethod": "Wybierz metodę scalania", + "agentManager.pr.merge.method.merge": "Utwórz commit scalający", + "agentManager.pr.merge.method.squash": "Wykonaj squash i scal", + "agentManager.pr.merge.method.rebase": "Wykonaj rebase i scal", + "agentManager.pr.merge.confirmTitle": "Scal pull request", + "agentManager.pr.merge.confirmDescription": + "Scalić ten pull request za pomocą {{method}}? GitHub wykona scalanie zdalnie.", + "agentManager.pr.merge.cancel": "Anuluj", + "agentManager.pr.merge.confirm": "Scal pull request", + "agentManager.pr.merge.failed": "GitHub nie może ukończyć tej czynności.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 245f86df9653..0e8abb52bc82 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts @@ -339,6 +339,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} одобрение", + "agentManager.pr.summary.approvedCount.other": "{{count}} одобрений", + "agentManager.pr.merge.conflicts": "Конфликты слияния", + "agentManager.pr.merge.conflictsHint": "Устраните конфликты перед слиянием.", + "agentManager.pr.merge.conflictsLoading": "Загрузка файлов с конфликтами...", + "agentManager.pr.merge.behind": "Ветка отстаёт от базовой ветки", + "agentManager.pr.merge.blocked": "Слияние заблокировано", + "agentManager.pr.merge.unstable": "Проверки завершаются с ошибкой", + "agentManager.pr.merge.draft": "Черновик pull request", + "agentManager.pr.merge.checking": "Проверка возможности слияния", + "agentManager.pr.merge.ready": "Готово к слиянию", + "agentManager.pr.merge.fix": "Исправить с помощью Kilo", + "agentManager.pr.merge.update": "Обновить ветку", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "Включить auto-merge", + "agentManager.pr.merge.disableAuto": "Отключить auto-merge", + "agentManager.pr.merge.autoNote": "GitHub выполнит слияние, когда требования будут выполнены.", + "agentManager.pr.merge.behindHint": "Выполните слияние последних изменений базовой ветки с этой веткой.", + "agentManager.pr.merge.blockedHint": "Требования ещё не выполнены.", + "agentManager.pr.merge.unstableHint": "Некоторые проверки завершаются с ошибкой.", + "agentManager.pr.merge.draftHint": "Перед слиянием отметьте pull request как готовый к проверке.", + "agentManager.pr.merge.autoMethodNote": "Слияние с помощью {{method}} после выполнения требований.", + "agentManager.pr.merge.unavailableNote": "Слияние для этого pull request пока недоступно.", + "agentManager.pr.merge.chooseMethod": "Выберите способ слияния", + "agentManager.pr.merge.method.merge": "Создать коммит слияния", + "agentManager.pr.merge.method.squash": "Выполнить squash и слияние", + "agentManager.pr.merge.method.rebase": "Выполнить rebase и слияние", + "agentManager.pr.merge.confirmTitle": "Слить pull request", + "agentManager.pr.merge.confirmDescription": + "Слить этот pull request с помощью {{method}}? GitHub выполнит слияние удалённо.", + "agentManager.pr.merge.cancel": "Отмена", + "agentManager.pr.merge.confirm": "Слить pull request", + "agentManager.pr.merge.failed": "GitHub не смог выполнить это действие.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 985a01f92c1e..c169772521f2 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts @@ -330,6 +330,38 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "อนุมัติ {{count}} ครั้ง", + "agentManager.pr.summary.approvedCount.other": "อนุมัติ {{count}} ครั้ง", + "agentManager.pr.merge.conflicts": "ข้อขัดแย้งในการรวม", + "agentManager.pr.merge.conflictsHint": "แก้ไขข้อขัดแย้งก่อนรวม", + "agentManager.pr.merge.conflictsLoading": "กำลังโหลดไฟล์ที่มีข้อขัดแย้ง...", + "agentManager.pr.merge.behind": "สาขาอยู่หลังสาขาฐาน", + "agentManager.pr.merge.blocked": "การรวมถูกบล็อก", + "agentManager.pr.merge.unstable": "การตรวจสอบล้มเหลว", + "agentManager.pr.merge.draft": "คำขอดึงฉบับร่าง", + "agentManager.pr.merge.checking": "กำลังตรวจสอบว่าสามารถรวมได้หรือไม่", + "agentManager.pr.merge.ready": "พร้อมรวม", + "agentManager.pr.merge.fix": "แก้ไขด้วย Kilo", + "agentManager.pr.merge.update": "อัปเดตสาขา", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "เปิดใช้ auto-merge", + "agentManager.pr.merge.disableAuto": "ปิดใช้ auto-merge", + "agentManager.pr.merge.autoNote": "GitHub จะรวมให้เมื่อเป็นไปตามข้อกำหนด", + "agentManager.pr.merge.behindHint": "รวมการเปลี่ยนแปลงล่าสุดจากสาขาฐานเข้ากับสาขานี้", + "agentManager.pr.merge.blockedHint": "ยังไม่เป็นไปตามข้อกำหนด", + "agentManager.pr.merge.unstableHint": "การตรวจสอบบางรายการล้มเหลว", + "agentManager.pr.merge.draftHint": "ทำเครื่องหมายคำขอดึงว่าพร้อมตรวจสอบก่อนรวม", + "agentManager.pr.merge.autoMethodNote": "จะรวมด้วย {{method}} เมื่อเป็นไปตามข้อกำหนด", + "agentManager.pr.merge.unavailableNote": "ยังไม่สามารถรวมคำขอดึงนี้ได้", + "agentManager.pr.merge.chooseMethod": "เลือกวิธีการรวม", + "agentManager.pr.merge.method.merge": "สร้างคอมมิตการรวม", + "agentManager.pr.merge.method.squash": "Squash และรวม", + "agentManager.pr.merge.method.rebase": "Rebase และรวม", + "agentManager.pr.merge.confirmTitle": "รวมคำขอดึง", + "agentManager.pr.merge.confirmDescription": "รวมคำขอดึงนี้ด้วย {{method}} หรือไม่ GitHub จะดำเนินการรวมจากระยะไกล", + "agentManager.pr.merge.cancel": "ยกเลิก", + "agentManager.pr.merge.confirm": "รวมคำขอดึง", + "agentManager.pr.merge.failed": "GitHub ดำเนินการนี้ไม่สำเร็จ", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 22f0b9b4925a..97db13c415b2 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts @@ -344,6 +344,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} onay", + "agentManager.pr.summary.approvedCount.other": "{{count}} onay", + "agentManager.pr.merge.conflicts": "Birleştirme çakışmaları", + "agentManager.pr.merge.conflictsHint": "Birleştirmeden önce çakışmaları çözün.", + "agentManager.pr.merge.conflictsLoading": "Çakışan dosyalar yükleniyor...", + "agentManager.pr.merge.behind": "Dal, temel daldan geride", + "agentManager.pr.merge.blocked": "Birleştirme engellendi", + "agentManager.pr.merge.unstable": "Kontroller başarısız", + "agentManager.pr.merge.draft": "Taslak pull request", + "agentManager.pr.merge.checking": "Birleştirilebilirlik kontrol ediliyor", + "agentManager.pr.merge.ready": "Birleştirmeye hazır", + "agentManager.pr.merge.fix": "Kilo ile düzelt", + "agentManager.pr.merge.update": "Dalı güncelle", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "auto-merge'i etkinleştir", + "agentManager.pr.merge.disableAuto": "auto-merge'i devre dışı bırak", + "agentManager.pr.merge.autoNote": "Gereksinimler karşılandığında GitHub birleştirme işlemini yapar.", + "agentManager.pr.merge.behindHint": "Temel daldaki en son değişiklikleri bu dala birleştirin.", + "agentManager.pr.merge.blockedHint": "Gereksinimler henüz karşılanmadı.", + "agentManager.pr.merge.unstableHint": "Bazı kontroller başarısız oluyor.", + "agentManager.pr.merge.draftHint": "Birleştirmeden önce pull request'i incelemeye hazır olarak işaretleyin.", + "agentManager.pr.merge.autoMethodNote": "Gereksinimler karşılandığında {{method}} ile birleştirir.", + "agentManager.pr.merge.unavailableNote": "Bu pull request için birleştirme henüz kullanılamıyor.", + "agentManager.pr.merge.chooseMethod": "Birleştirme yöntemini seç", + "agentManager.pr.merge.method.merge": "Birleştirme commit'i oluştur", + "agentManager.pr.merge.method.squash": "Squash ve birleştir", + "agentManager.pr.merge.method.rebase": "Rebase ve birleştir", + "agentManager.pr.merge.confirmTitle": "Pull request'i birleştir", + "agentManager.pr.merge.confirmDescription": + "Bu pull request {{method}} ile birleştirilsin mi? GitHub birleştirme işlemini uzaktan gerçekleştirecek.", + "agentManager.pr.merge.cancel": "İptal", + "agentManager.pr.merge.confirm": "Pull request'i birleştir", + "agentManager.pr.merge.failed": "GitHub bu işlemi tamamlayamadı.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 7a089e08e528..08755e2b1c97 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts @@ -347,6 +347,39 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} схвалення", + "agentManager.pr.summary.approvedCount.other": "{{count}} схвалень", + "agentManager.pr.merge.conflicts": "Конфлікти злиття", + "agentManager.pr.merge.conflictsHint": "Усуньте конфлікти перед злиттям.", + "agentManager.pr.merge.conflictsLoading": "Завантаження файлів із конфліктами...", + "agentManager.pr.merge.behind": "Гілка відстає від базової гілки", + "agentManager.pr.merge.blocked": "Злиття заблоковано", + "agentManager.pr.merge.unstable": "Перевірки завершуються з помилкою", + "agentManager.pr.merge.draft": "Чернетка запиту на злиття", + "agentManager.pr.merge.checking": "Перевірка можливості злиття", + "agentManager.pr.merge.ready": "Готово до злиття", + "agentManager.pr.merge.fix": "Виправити за допомогою Kilo", + "agentManager.pr.merge.update": "Оновити гілку", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "Увімкнути auto-merge", + "agentManager.pr.merge.disableAuto": "Вимкнути auto-merge", + "agentManager.pr.merge.autoNote": "GitHub виконає злиття, коли вимоги буде виконано.", + "agentManager.pr.merge.behindHint": "Злийте останні зміни базової гілки з цією гілкою.", + "agentManager.pr.merge.blockedHint": "Вимоги ще не виконано.", + "agentManager.pr.merge.unstableHint": "Деякі перевірки завершуються з помилкою.", + "agentManager.pr.merge.draftHint": "Позначте запит на злиття як готовий до перевірки перед злиттям.", + "agentManager.pr.merge.autoMethodNote": "Злиття за допомогою {{method}} після виконання вимог.", + "agentManager.pr.merge.unavailableNote": "Злиття для цього запиту на злиття поки недоступне.", + "agentManager.pr.merge.chooseMethod": "Виберіть спосіб злиття", + "agentManager.pr.merge.method.merge": "Створити коміт злиття", + "agentManager.pr.merge.method.squash": "Виконати squash і злити", + "agentManager.pr.merge.method.rebase": "Виконати rebase і злити", + "agentManager.pr.merge.confirmTitle": "Злити запит на злиття", + "agentManager.pr.merge.confirmDescription": + "Злити цей запит на злиття за допомогою {{method}}? GitHub виконає злиття віддалено.", + "agentManager.pr.merge.cancel": "Скасувати", + "agentManager.pr.merge.confirm": "Злити запит на злиття", + "agentManager.pr.merge.failed": "GitHub не зміг завершити цю дію.", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 da14471b88ab..cfd7cd449e67 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts @@ -326,6 +326,38 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} 项批准", + "agentManager.pr.summary.approvedCount.other": "{{count}} 项批准", + "agentManager.pr.merge.conflicts": "合并冲突", + "agentManager.pr.merge.conflictsHint": "请在合并前解决冲突。", + "agentManager.pr.merge.conflictsLoading": "正在加载冲突文件...", + "agentManager.pr.merge.behind": "分支落后于基础分支", + "agentManager.pr.merge.blocked": "合并被阻止", + "agentManager.pr.merge.unstable": "检查失败", + "agentManager.pr.merge.draft": "草稿 pull request", + "agentManager.pr.merge.checking": "正在检查是否可以合并", + "agentManager.pr.merge.ready": "可以合并", + "agentManager.pr.merge.fix": "使用 Kilo 修复", + "agentManager.pr.merge.update": "更新分支", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "启用 auto-merge", + "agentManager.pr.merge.disableAuto": "停用 auto-merge", + "agentManager.pr.merge.autoNote": "满足要求后,GitHub 将执行合并。", + "agentManager.pr.merge.behindHint": "将基础分支的最新更改合并到此分支。", + "agentManager.pr.merge.blockedHint": "要求尚未满足。", + "agentManager.pr.merge.unstableHint": "部分检查失败。", + "agentManager.pr.merge.draftHint": "请先将 pull request 标记为可供审查,再进行合并。", + "agentManager.pr.merge.autoMethodNote": "满足要求后使用 {{method}} 合并。", + "agentManager.pr.merge.unavailableNote": "此 pull request 暂时无法合并。", + "agentManager.pr.merge.chooseMethod": "选择合并方式", + "agentManager.pr.merge.method.merge": "创建合并提交", + "agentManager.pr.merge.method.squash": "Squash 并合并", + "agentManager.pr.merge.method.rebase": "Rebase 并合并", + "agentManager.pr.merge.confirmTitle": "合并 pull request", + "agentManager.pr.merge.confirmDescription": "要使用 {{method}} 合并此 pull request 吗?GitHub 将在远程执行合并。", + "agentManager.pr.merge.cancel": "取消", + "agentManager.pr.merge.confirm": "合并 pull request", + "agentManager.pr.merge.failed": "GitHub 无法完成此操作。", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", 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 ae1dabe189b9..194cd10acd57 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts @@ -326,6 +326,38 @@ export const dict = { "agentManager.pr.summary.checksPassing": "All checks passing", "agentManager.pr.summary.checksPassed": "{{passed}}/{{total}} checks passed", "agentManager.pr.summary.approved": "Approved", + "agentManager.pr.summary.approvedCount.one": "{{count}} 項核准", + "agentManager.pr.summary.approvedCount.other": "{{count}} 項核准", + "agentManager.pr.merge.conflicts": "合併衝突", + "agentManager.pr.merge.conflictsHint": "請在合併前解決衝突。", + "agentManager.pr.merge.conflictsLoading": "正在載入衝突檔案...", + "agentManager.pr.merge.behind": "分支落後於基礎分支", + "agentManager.pr.merge.blocked": "合併已封鎖", + "agentManager.pr.merge.unstable": "檢查失敗", + "agentManager.pr.merge.draft": "草稿 pull request", + "agentManager.pr.merge.checking": "正在檢查是否可合併", + "agentManager.pr.merge.ready": "準備好合併", + "agentManager.pr.merge.fix": "使用 Kilo 修復", + "agentManager.pr.merge.update": "更新分支", + "agentManager.pr.merge.button": "{{method}}", + "agentManager.pr.merge.autoButton": "啟用 auto-merge", + "agentManager.pr.merge.disableAuto": "停用 auto-merge", + "agentManager.pr.merge.autoNote": "符合要求後,GitHub 將執行合併。", + "agentManager.pr.merge.behindHint": "將基礎分支的最新變更合併到此分支。", + "agentManager.pr.merge.blockedHint": "要求尚未符合。", + "agentManager.pr.merge.unstableHint": "部分檢查失敗。", + "agentManager.pr.merge.draftHint": "請先將 pull request 標記為可供審查,再進行合併。", + "agentManager.pr.merge.autoMethodNote": "符合要求後使用 {{method}} 合併。", + "agentManager.pr.merge.unavailableNote": "此 pull request 目前無法合併。", + "agentManager.pr.merge.chooseMethod": "選擇合併方式", + "agentManager.pr.merge.method.merge": "建立合併提交", + "agentManager.pr.merge.method.squash": "Squash 並合併", + "agentManager.pr.merge.method.rebase": "Rebase 並合併", + "agentManager.pr.merge.confirmTitle": "合併 pull request", + "agentManager.pr.merge.confirmDescription": "要使用 {{method}} 合併此 pull request 嗎?GitHub 將在遠端執行合併。", + "agentManager.pr.merge.cancel": "取消", + "agentManager.pr.merge.confirm": "合併 pull request", + "agentManager.pr.merge.failed": "GitHub 無法完成此操作。", "agentManager.pr.summary.changesRequested": "Changes requested", "agentManager.pr.summary.reviewPending": "Review pending", "agentManager.pr.summary.unresolved.one": "{{count}} unresolved comment", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRMerge.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRMerge.tsx new file mode 100644 index 000000000000..f1c5955ce7b8 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRMerge.tsx @@ -0,0 +1,318 @@ +import { Button } from "@kilocode/kilo-ui/button" +import { DropdownMenu } from "@kilocode/kilo-ui/dropdown-menu" +import { Icon } from "@kilocode/kilo-ui/icon" +import { Spinner } from "@kilocode/kilo-ui/spinner" +import { Show, For, createEffect, createSignal, onCleanup } from "solid-js" +import { useLanguage } from "../../src/context/language" +import { useVSCode } from "../../src/context/vscode" +import type { PRMergeMethod, PRStatus } from "../../src/types/messages" +import type { PRMergeRequest } from "../../../src/shared/pr-comment-actions" +import { reviewRequest } from "./pr-review-request" +import { conflictFailed, conflictFiles, setConflictFailed, setConflictFiles } from "./pr-conflict-state" +import { useBaseUpdate } from "../update-from-base" + +interface Props { + pr: PRStatus + worktreeId: string + projectId?: string + sessionId?: string + mode: "status" | "footer" +} + +function label(method: PRMergeMethod, t: (key: string) => string): string { + if (method === "merge") return t("agentManager.pr.merge.method.merge") + if (method === "rebase") return t("agentManager.pr.merge.method.rebase") + return t("agentManager.pr.merge.method.squash") +} + +function autoEligible(value: PRStatus["merge"], current: string): boolean { + return ( + value?.autoAllowed === true && + value.mergeable === "mergeable" && + (current === "clean" || current === "blocked" || current === "unstable" || current === "has_hooks") + ) +} + +export function PRMerge(props: Props) { + const { t } = useLanguage() + const vscode = useVSCode() + const baseUpdate = useBaseUpdate() + const [pending, setPending] = createSignal(false) + const [error, setError] = createSignal() + const [chosen, setChosen] = createSignal() + const [confirming, setConfirming] = createSignal(false) + const [loadingConflicts, setLoadingConflicts] = createSignal(false) + const merge = () => props.pr.merge + const conflictKey = () => `${props.worktreeId}:${props.pr.headRefOid ?? ""}` + const conflicts = () => (merge()?.mergeable === "conflicting" ? conflictFiles(conflictKey()) : undefined) + const methods = () => merge()?.methods ?? [] + const selected = () => { + const value = chosen() + return value && methods().includes(value) ? value : (merge()?.method ?? methods().at(0) ?? "squash") + } + const state = () => merge()?.state ?? "unknown" + const auto = () => autoEligible(merge(), state()) + const blocked = () => pending() || (state() !== "clean" && !auto()) + const target = () => ({ + projectId: props.projectId, + worktreeId: props.worktreeId, + prNumber: props.pr.number, + prUrl: props.pr.url, + }) + const send = (message: PRMergeRequest) => { + if (pending()) return + setPending(true) + setError(undefined) + reviewRequest(message, vscode.postMessage, (result) => { + if (result.type !== `${message.type}Result`) return + setPending(false) + if (!result.success) setError(result.error ?? t("agentManager.pr.merge.failed")) + }) + } + const update = () => { + const head = props.pr.headRefOid + if (!head) return + send({ + ...target(), + type: "agentManager.updatePRBranch", + requestId: crypto.randomUUID(), + head, + }) + } + const mergePR = (auto: boolean) => { + const head = props.pr.headRefOid + if (!head) return + send({ + ...target(), + type: "agentManager.mergePR", + requestId: crypto.randomUUID(), + method: selected(), + auto, + head, + }) + } + const disableAuto = () => { + send({ ...target(), type: "agentManager.disablePRAutoMerge", requestId: crypto.randomUUID() }) + } + let requestKey: string | undefined + let cancel: (() => void) | undefined + createEffect(() => { + if (props.mode !== "status") return + const value = merge() + if (value?.mergeable !== "conflicting") return + const base = props.pr.baseRefOid + const head = props.pr.headRefOid + if (!base || !head) return + const key = conflictKey() + if (conflictFiles(key) || conflictFailed(key) || requestKey === key) return + cancel?.() + requestKey = key + setLoadingConflicts(true) + const dispose = reviewRequest( + { ...target(), type: "agentManager.loadPRConflicts", requestId: crypto.randomUUID(), base, head }, + vscode.postMessage, + (result) => { + if (result.type !== "agentManager.loadPRConflictsResult") return + setLoadingConflicts(false) + if (result.success) setConflictFiles(key, result.files ?? []) + else setConflictFailed(key) + }, + 30_000, + true, + ) + cancel = dispose + onCleanup(() => { + dispose() + if (cancel === dispose) cancel = undefined + if (requestKey === key) { + requestKey = undefined + setLoadingConflicts(false) + } + }) + }) + const confirm = () => { + setConfirming(true) + } + const fix = () => baseUpdate(props.worktreeId, props.projectId, props.sessionId) + const statusLabel = () => { + if (merge()?.mergeable === "conflicting") return t("agentManager.pr.merge.conflicts") + if (state() === "behind") return t("agentManager.pr.merge.behind") + if (state() === "blocked") return t("agentManager.pr.merge.blocked") + if (state() === "unstable") return t("agentManager.pr.merge.unstable") + if (state() === "draft") return t("agentManager.pr.merge.draft") + if (state() === "has_hooks") return t("agentManager.pr.merge.checking") + if (state() === "clean") return t("agentManager.pr.merge.ready") + return t("agentManager.pr.merge.checking") + } + const statusHint = () => { + if (merge()?.mergeable === "conflicting") return t("agentManager.pr.merge.conflictsHint") + if (state() === "behind") return t("agentManager.pr.merge.behindHint") + if (state() === "blocked") return t("agentManager.pr.merge.blockedHint") + if (state() === "unstable") return t("agentManager.pr.merge.unstableHint") + if (state() === "draft") return t("agentManager.pr.merge.draftHint") + return undefined + } + const footerNote = () => { + if (merge()?.auto) return t("agentManager.pr.merge.autoNote") + if (state() === "clean") return undefined + if (auto()) return t("agentManager.pr.merge.autoMethodNote", { method: label(selected(), t) }) + return t("agentManager.pr.merge.unavailableNote") + } + const statusIcon = () => { + if (merge()?.mergeable === "conflicting" || state() === "unstable" || state() === "behind") return "warning" + if (state() === "clean") return "circle-check" + return "warning" + } + + return ( + + + + + + + + + + + + + + + + + + + {(item) => ( + setChosen(item)}> + + {label(item, t)} + + )} + + + + + + } + > +
+ +
+
+ + {footerNote()} + +
+ + } + > +
+
+
+ }> + + +
+
+ {statusLabel()} + + {statusHint()} + +
+ + + + + + +
+ + +
+ + {t("agentManager.pr.merge.conflictsLoading")} +
+
+ +
+ + {(file) => ( +
+ + {file} +
+ )} +
+
+
+
+
+
+
+ ) +} 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 ac5e2b28034e..48835c7cbfb5 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanel.tsx @@ -26,6 +26,7 @@ interface PRPanelProps { projectId?: string worktreeId: string activeTerminalId?: string + sessionId?: string jump?: number onJump?: (id: number) => void onClose: () => void @@ -283,6 +284,8 @@ export const PRPanel: Component = (props) => { diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanelHost.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanelHost.tsx index d41d9cd7b8d2..7492456813d6 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanelHost.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRPanelHost.tsx @@ -83,6 +83,7 @@ export function PRPanelHost(props: Props) { worktree={props.worktree} worktreeId={props.worktreeId} activeTerminalId={props.activeTerminalId} + sessionId={props.sessionId} jump={props.jump} onJump={props.onJump} onClose={props.onClose} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx index 250f780306a3..02e7c43e8b5e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/PRSummary.tsx @@ -9,6 +9,8 @@ import type { PRStatus } from "../../src/types/messages" import { useConfig } from "../../src/context/config" import { useLanguage } from "../../src/context/language" import { sendReviewComments } from "../../diff-viewer/review-annotations" +import { PRAvatar } from "./PRAvatar" +import { PRMerge } from "./PRMerge" import { checkFeedback } from "./pr-check-feedback" import { SEND_LIMIT } from "./pr-comment-payload" import { commentState } from "./pr-comment-state" @@ -18,6 +20,8 @@ import { type JumpTarget, actionableConversation, sendConversation, sendThreads, interface PRSummaryProps { pr: PRStatus worktreeId: string + projectId?: string + sessionId?: string activeTerminalId?: string onJump?: (target: JumpTarget) => void } @@ -29,6 +33,7 @@ interface Row { target?: JumpTarget /** Secondary marks lower-confidence feedback, like general discussion. */ action?: { label: string; run: () => void; variant?: "primary" | "secondary" } + avatars?: Array<{ login: string; avatar?: string }> } const JUMP_KEY: Record = { @@ -73,15 +78,28 @@ export function PRSummary(props: PRSummaryProps) { const review = (): Row | undefined => { const value = props.pr.review - if (!value) return - const status = value === "approved" ? "success" : value === "changes_requested" ? "failure" : "pending" + const approved = props.pr.reviewers.filter((reviewer) => reviewer.state === "approved") + if (!value && approved.length === 0) return + const status = + value === "changes_requested" ? "failure" : value === "approved" || approved.length > 0 ? "success" : "pending" const key = status === "success" ? "agentManager.pr.summary.approved" : status === "failure" ? "agentManager.pr.summary.changesRequested" : "agentManager.pr.summary.reviewPending" - return { icon: statusIcon(status), label: t(key), status } + const label = + approved.length > 0 + ? t( + approved.length === 1 + ? "agentManager.pr.summary.approvedCount.one" + : "agentManager.pr.summary.approvedCount.other", + { + count: approved.length, + }, + ) + : t(key) + return { icon: statusIcon(status), label, status, avatars: approved } } const threads = (): Row | undefined => { @@ -144,10 +162,10 @@ export function PRSummary(props: PRSummaryProps) { } } - const rows = createMemo(() => [checks(), review(), threads(), conversation()].filter((row) => row !== undefined)) + const rows = createMemo(() => [review(), checks(), threads(), conversation()].filter((row) => row !== undefined)) return ( - 0}> + 0 || !!props.pr.merge}>
{t("agentManager.pr.summary.title")} @@ -164,6 +182,13 @@ export function PRSummary(props: PRSummaryProps) {
+ {(row) => (
@@ -171,6 +196,17 @@ export function PRSummary(props: PRSummaryProps) { {(icon) => } {row.label} + + + + {(reviewer) => ( + + + + )} + + + @@ -205,6 +241,13 @@ export function PRSummary(props: PRSummaryProps) { )}
+
) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-conflict-state.ts b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-conflict-state.ts new file mode 100644 index 000000000000..3c64d352e483 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-conflict-state.ts @@ -0,0 +1,33 @@ +import { createSignal } from "solid-js" + +const cache = new Map() +const failed = new Set() +const [version, setVersion] = createSignal(0) + +function bump(): void { + setVersion((value) => value + 1) +} + +/** Conflicting files already loaded for a worktree and head commit. */ +export function conflictFiles(key: string): string[] | undefined { + version() + return cache.get(key) +} + +export function setConflictFiles(key: string, files: string[]): void { + if (cache.has(key)) return + cache.set(key, files) + bump() +} + +/** True when loading conflicting files failed for this worktree and head. */ +export function conflictFailed(key: string): boolean { + version() + return failed.has(key) +} + +export function setConflictFailed(key: string): void { + if (failed.has(key)) return + failed.add(key) + bump() +} 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 44cb0e1b38ec..045130761cc3 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 @@ -978,6 +978,160 @@ color: var(--vscode-charts-yellow, #fbbf24); } +.am-pr-summary-row.am-pr-merge-status[data-status="error"] .am-pr-summary-icon { + color: var(--vscode-testing-iconFailed, #f87171); +} +.am-pr-summary-row.am-pr-merge-status[data-status="behind"] .am-pr-summary-icon, +.am-pr-summary-row.am-pr-merge-status[data-status="blocked"] .am-pr-summary-icon, +.am-pr-summary-row.am-pr-merge-status[data-status="unstable"] .am-pr-summary-icon, +.am-pr-summary-row.am-pr-merge-status[data-status="unknown"] .am-pr-summary-icon { + color: var(--vscode-charts-yellow, #fbbf24); +} +.am-pr-summary-row.am-pr-merge-status[data-status="clean"] .am-pr-summary-icon { + color: var(--vscode-testing-iconPassed, #34d399); +} + +.am-pr-merge-status { + padding: 5px 0; +} + +.am-pr-merge-status .am-pr-summary-main { + display: flex; + flex: 1; + flex-direction: column; + gap: 1px; +} + +.am-pr-merge-hint { + color: var(--text-weak); + font-size: var(--kilo-font-size-11); + white-space: normal; +} + +.am-pr-merge-block { + display: flex; + flex-direction: column; + min-width: 0; +} + +.am-pr-conflict-loading { + gap: 6px; + margin: 0 0 4px 23px; + color: var(--text-weak); + font-size: var(--kilo-font-size-11); +} + +.am-pr-conflict-files { + display: flex; + flex-direction: column; + max-height: 132px; + overflow: auto; + margin: 0 0 4px 23px; + border: 1px solid var(--vscode-panel-border); + border-radius: 4px; + background: color-mix(in srgb, var(--vscode-editor-background) 92%, var(--vscode-foreground)); +} + +.am-pr-conflict-file { + gap: 6px; + padding: 3px 8px; + min-width: 0; + color: var(--text-weak); + font-size: var(--kilo-font-size-11); +} + +.am-pr-conflict-file span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.am-pr-summary-main { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.am-pr-summary-main strong { + font-weight: 500; +} + +.am-pr-summary-avatars { + display: inline-flex; + align-items: center; + margin-left: 2px; +} + +.am-pr-summary-avatars [data-component="avatar"] { + width: 18px; + height: 18px; + margin-left: -3px; + border: 2px solid var(--vscode-editor-background); + border-radius: 50%; +} + +.am-pr-merge-footer { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 10px; + padding: 10px 12px; + border-top: 1px solid var(--vscode-panel-border); + background: color-mix(in srgb, var(--vscode-editor-background) 92%, var(--vscode-foreground)); +} + +.am-pr-merge-footer .am-split-button { + flex-shrink: 0; +} + +.am-pr-merge-controls { + display: flex; + align-items: stretch; + gap: 1px; + flex-shrink: 0; +} + +.am-pr-merge-auto-note { + min-width: 0; + overflow: hidden; + color: var(--text-weak); + font-size: var(--kilo-font-size-11); + text-overflow: ellipsis; + white-space: nowrap; +} + +.am-pr-merge-error { + flex: 1; + min-width: 0; + overflow-wrap: anywhere; + color: var(--vscode-testing-iconFailed, #f87171); + font-size: var(--kilo-font-size-11); +} + +.am-pr-merge-spinner { + width: 12px; + height: 12px; + margin-right: 4px; +} + +.am-pr-merge-confirm { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + width: 100%; + gap: 12px; + color: var(--vscode-foreground); + font-size: var(--kilo-font-size-11); +} + +.am-pr-merge-dialog-actions { + display: flex; + gap: 8px; +} + .am-pr-summary-label { color: var(--vscode-foreground); min-width: 0; diff --git a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-review-request.ts b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-review-request.ts index 4afb7f3349ab..9bce1b0b694e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-review-request.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/pr/pr-review-request.ts @@ -6,6 +6,7 @@ export function reviewRequest( post: (message: never) => void, settle: (result: PRCommentResult) => void, timeout = 30_000, + cancelOnCleanup = false, ) { let timer: ReturnType | undefined let done = false @@ -53,5 +54,15 @@ export function reviewRequest( window.addEventListener("message", handler) timer = setTimeout(() => finish(failure), timeout) post(message as never) - return () => finish(failure) + return () => { + if (!cancelOnCleanup) { + finish(failure) + return + } + if (done) return + done = true + window.removeEventListener("message", handler) + if (timer !== undefined) clearTimeout(timer) + timer = undefined + } } 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 98964f9fdbfe..e8ba2c1e58fc 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 @@ -78,6 +78,20 @@ export interface PRComment { export type ReviewerState = "approved" | "changes_requested" | "pending" | "commented" +export type PRMergeMethod = "merge" | "squash" | "rebase" +export type PRMergeability = "mergeable" | "conflicting" | "unknown" +export type PRMergeState = "clean" | "behind" | "blocked" | "dirty" | "unstable" | "draft" | "has_hooks" | "unknown" + +export interface PRMergeStatus { + mergeable: PRMergeability + state: PRMergeState + auto: PRMergeMethod | null + methods: PRMergeMethod[] + method: PRMergeMethod + autoAllowed: boolean + canWrite: boolean +} + export interface PRReviewer { login: string avatar?: string @@ -97,6 +111,7 @@ export interface PRStatus { url: string state: PRState review: ReviewDecision | null + merge?: PRMergeStatus checks: { status: AggregateCheckStatus total: number 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 81fff48c1a9d..ffb2ae640181 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 @@ -66,6 +66,7 @@ import type { PRComment, PRReviewer, PRTimelineItem, + PRMergeStatus, } from "../../../agent-manager/pr/pr-types" export type { PRState, @@ -84,6 +85,10 @@ export type { PRTimelineItem, PRReaction, PRReactionContent, + PRMergeMethod, + PRMergeability, + PRMergeState, + PRMergeStatus, } from "../../../agent-manager/pr/pr-types" export type RunState = "idle" | "running" | "stopping" diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts index 52174e8eb8c0..11fbb62dcb80 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts @@ -19,6 +19,7 @@ import type { AgentManagerSidebarTarget } from "./webview-messages" import type { PermissionRequest } from "./permissions" import type { AnacondaDesktopExtensionMessage } from "../../../../src/shared/anaconda-desktop-messages" import type { BrowserFeedbackData, BrowserReference } from "../../../../src/shared/browser-feedback" +import type { PRMergeResult } from "../../../../src/shared/pr-comment-actions" export type { BrowserReference } from "../../../../src/shared/browser-feedback" @@ -1702,6 +1703,7 @@ export type ExtensionMessage = | AgentManagerPRStatusMessage | AgentManagerPRErrorMessage | AgentManagerCommentReactionResultMessage + | PRMergeResult | AgentManagerTerminalCreatedMessage | AgentManagerTerminalRestartedMessage | AgentManagerTerminalFontChangedMessage diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts index d0eda04695d4..2dac6ce191f4 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts @@ -15,6 +15,7 @@ import type { MemoryShowMessage, MemoryOperationMessage, RequestMemoryMessage } import type { RequestSessionBoardMessage, ResetSessionBoardMessage } from "./board" import type { Activity } from "../../utils/session-activity" import type { PRReactionContent } from "../../../agent-manager/pr/pr-types" +import type { PRMergeRequest } from "../../../../src/shared/pr-comment-actions" // ============================================ // Messages FROM webview TO extension @@ -1560,6 +1561,7 @@ export interface DismissAgentMigrationBannerMessage { export type WebviewMessage = | import("./agent-manager").BaseUpdateRequest + | PRMergeRequest | { type: "sessionActivity"; state: Activity } | { type: "acknowledgeSession"; sessionID: string; eventID: string } | DocumentRequestMessage diff --git a/plans/agent-manager-pr-merge-readiness.md b/plans/agent-manager-pr-merge-readiness.md new file mode 100644 index 000000000000..d5cba1283f5b --- /dev/null +++ b/plans/agent-manager-pr-merge-readiness.md @@ -0,0 +1,36 @@ +# Agent Manager PR Merge Readiness + +## Goal + +Show approvals, merge readiness, conflicts, branch updates, and merge actions in the Agent Manager PR panel without duplicating the existing PR status loading path. + +## Data and caching + +- Extend the existing PR status query with GitHub mergeability, merge state, auto-merge, and head/base commit IDs. +- Reuse the existing PR poller, repository cache, bridge cache, signature dedupe, and webview replay. +- Store the last merge method per repository in extension `globalState`. +- Load conflicting paths on demand. `GitOps.conflicts` fetches missing commits and runs `git merge-tree --write-tree --name-only` without changing the worktree, index, or stash. Cache results by worktree, remote, base, and head IDs. + +## Actions + +- `agentManager.updatePRBranch` calls GitHub's Update branch endpoint. +- `agentManager.mergePR` uses `gh pr merge` with the selected method and expected head SHA. +- `agentManager.disablePRAutoMerge` disables GitHub auto-merge. +- `agentManager.loadPRConflicts` returns the conflict paths computed by `GitOps`. +- All mutations use the existing PR action bridge, request dedupe, refresh, and error result patterns. + +## UI + +- Show merge readiness first in the PR summary. +- Show approvals inline with reviewer avatars and counts. +- Show conflict title, compact explanation, `Fix with Kilo`, and a bounded scrollable file list. +- Show Update branch when the branch is behind. +- Show a small shared split button for merge methods, auto-merge, and direct merge. +- Keep the file list capped so the top summary box does not grow without bound. + +## Verification + +- Test merge-tree conflict detection against divergent commits. +- Test PR merge action routing and conflict result handling. +- Run extension typecheck, lint, bundle, architecture checks, i18n checks, and unit tests. +- Verify the PR panel states with isolated VS Code screenshots.