From 190b4a4dc6ed38a71978a82c4cc2d3e0824106e7 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 19 Aug 2026 16:15:49 +0200 Subject: [PATCH 01/14] feat(vscode): add Agent Manager document inspector --- .changeset/rendered-document-inspector.md | 5 + .../kilo-ui/src/components/message-part.css | 2 + .../kilo-ui/src/components/message-part.tsx | 21 +- packages/kilo-vscode/src/KiloProvider.ts | 10 + .../src/agent-manager/AgentManagerProvider.ts | 24 +- .../src/agent-manager/document-reader.ts | 49 +++ .../kilo-vscode/src/agent-manager/types.ts | 20 ++ .../agent-manager/worktree-diff-controller.ts | 36 ++ .../src/diff/DiffViewerProvider.ts | 20 +- packages/kilo-vscode/src/diff/types.ts | 4 + packages/kilo-vscode/src/extension.ts | 1 + .../src/kilo-provider/editor-actions.ts | 10 +- .../tests/unit/agent-manager-arch.test.ts | 2 + .../tests/unit/agent-manager-i18n.test.ts | 2 + .../tests/unit/document-reader.test.ts | 33 ++ .../agent-manager/AgentManagerApp.tsx | 40 ++- .../webview-ui/agent-manager/ClosableTab.tsx | 14 +- .../webview-ui/agent-manager/DiffPanel.tsx | 15 + .../webview-ui/agent-manager/TabBar.tsx | 15 + .../agent-manager/agent-manager.css | 127 +++++++ .../agent-manager/documents/DocumentPanel.tsx | 315 ++++++++++++++++++ .../documents/DocumentPanelHost.tsx | 28 ++ .../agent-manager/documents/state.ts | 172 ++++++++++ .../webview-ui/agent-manager/i18n/ar.ts | 8 + .../webview-ui/agent-manager/i18n/br.ts | 8 + .../webview-ui/agent-manager/i18n/bs.ts | 8 + .../webview-ui/agent-manager/i18n/da.ts | 8 + .../webview-ui/agent-manager/i18n/de.ts | 8 + .../webview-ui/agent-manager/i18n/en.ts | 8 + .../webview-ui/agent-manager/i18n/es.ts | 8 + .../webview-ui/agent-manager/i18n/fa.ts | 8 + .../webview-ui/agent-manager/i18n/fr.ts | 8 + .../webview-ui/agent-manager/i18n/it.ts | 8 + .../webview-ui/agent-manager/i18n/ja.ts | 8 + .../webview-ui/agent-manager/i18n/ko.ts | 8 + .../webview-ui/agent-manager/i18n/nl.ts | 8 + .../webview-ui/agent-manager/i18n/no.ts | 8 + .../webview-ui/agent-manager/i18n/pl.ts | 8 + .../webview-ui/agent-manager/i18n/ru.ts | 8 + .../webview-ui/agent-manager/i18n/th.ts | 8 + .../webview-ui/agent-manager/i18n/tr.ts | 8 + .../webview-ui/agent-manager/i18n/uk.ts | 8 + .../webview-ui/agent-manager/i18n/zh.ts | 8 + .../webview-ui/agent-manager/i18n/zht.ts | 8 + .../agent-manager/side-panel-layout.ts | 1 + .../webview-ui/diff-viewer/DiffViewerApp.tsx | 10 + .../diff-viewer/FullScreenDiffView.tsx | 5 +- .../diff-viewer/MarkdownAnnotationLayer.tsx | 8 +- .../diff-viewer/MarkdownDiffView.tsx | 4 +- packages/kilo-vscode/webview-ui/src/App.tsx | 5 + .../src/components/chat/AssistantMessage.tsx | 1 + .../webview-ui/src/styles/plan-exit.css | 27 ++ .../src/types/messages/extension-messages.ts | 25 ++ .../src/types/messages/webview-messages.ts | 7 + .../opencode/src/kilocode/session/prompt.ts | 1 + packages/ui/src/components/markdown.css | 19 ++ packages/ui/src/i18n/ar.ts | 1 + packages/ui/src/i18n/az.ts | 1 + packages/ui/src/i18n/br.ts | 1 + packages/ui/src/i18n/bs.ts | 1 + packages/ui/src/i18n/da.ts | 1 + packages/ui/src/i18n/de.ts | 1 + packages/ui/src/i18n/en.ts | 1 + packages/ui/src/i18n/es.ts | 1 + packages/ui/src/i18n/fi.ts | 1 + packages/ui/src/i18n/fr.ts | 1 + packages/ui/src/i18n/hi.ts | 1 + packages/ui/src/i18n/id.ts | 1 + packages/ui/src/i18n/it.ts | 1 + packages/ui/src/i18n/ja.ts | 1 + packages/ui/src/i18n/ko.ts | 1 + packages/ui/src/i18n/nl.ts | 1 + packages/ui/src/i18n/no.ts | 1 + packages/ui/src/i18n/pa.ts | 1 + packages/ui/src/i18n/pl.ts | 1 + packages/ui/src/i18n/ru.ts | 1 + packages/ui/src/i18n/sv.ts | 1 + packages/ui/src/i18n/th.ts | 1 + packages/ui/src/i18n/tr.ts | 1 + packages/ui/src/i18n/uk.ts | 1 + packages/ui/src/i18n/ur.ts | 1 + packages/ui/src/i18n/vi.ts | 1 + packages/ui/src/i18n/zh.ts | 1 + packages/ui/src/i18n/zht.ts | 1 + 84 files changed, 1229 insertions(+), 45 deletions(-) create mode 100644 .changeset/rendered-document-inspector.md create mode 100644 packages/kilo-vscode/src/agent-manager/document-reader.ts create mode 100644 packages/kilo-vscode/tests/unit/document-reader.test.ts create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanelHost.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts diff --git a/.changeset/rendered-document-inspector.md b/.changeset/rendered-document-inspector.md new file mode 100644 index 0000000000..a625b494b3 --- /dev/null +++ b/.changeset/rendered-document-inspector.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Add an Agent Manager document inspector that previews Markdown and text files, with inline Markdown review comments that can be sent to the agent. diff --git a/packages/kilo-ui/src/components/message-part.css b/packages/kilo-ui/src/components/message-part.css index c7991e9392..9272fa8496 100644 --- a/packages/kilo-ui/src/components/message-part.css +++ b/packages/kilo-ui/src/components/message-part.css @@ -119,6 +119,7 @@ color: var(--text-base); } } + } [data-slot="message-part-title-filename"] { @@ -154,6 +155,7 @@ direction: rtl; text-align: left; } + } /* Task tool child-session tool list (v1.0.25 style) */ diff --git a/packages/kilo-ui/src/components/message-part.tsx b/packages/kilo-ui/src/components/message-part.tsx index 8300f6943f..0c561d3710 100644 --- a/packages/kilo-ui/src/components/message-part.tsx +++ b/packages/kilo-ui/src/components/message-part.tsx @@ -1427,10 +1427,13 @@ PART_MAPPING["text"] = function TextPartDisplay(props) { if (!exists) { el.classList.remove("file-link-candidate") el.classList.remove("file-link") + el.classList.remove("plan-document-link") el.removeAttribute("data-file-candidate") el.removeAttribute("data-file-path") + el.removeAttribute("data-file-kind") el.removeAttribute("data-file-line") el.removeAttribute("data-file-col") + el.removeAttribute("title") return } // Strip ./ prefix for the click handler — VS Code resolves relative @@ -1440,6 +1443,16 @@ PART_MAPPING["text"] = function TextPartDisplay(props) { el.classList.add("file-link") el.setAttribute("data-file-path", clean) el.removeAttribute("data-file-candidate") + // A plan document gets a document glyph and a localized kind hint instead of + // an inline badge, so the reference stays readable inside a sentence. + el.classList.remove("plan-document-link") + el.removeAttribute("data-file-kind") + el.removeAttribute("title") + if (/(?:^|\/)(?:plans|\.plans?)\/.*\.md$/i.test(clean)) { + el.classList.add("plan-document-link") + el.setAttribute("data-file-kind", "plan") + el.setAttribute("title", i18n.t("ui.patch.action.plan")) + } } const dispatch = (el: HTMLElement, p: string) => { @@ -2053,9 +2066,9 @@ ToolRegistry.register({ animate={props.reveal} onClick={data.openFile ? () => data.openFile!(filepath) : undefined} /> - )} + )} - 0}> + 0}>
{(file) => ( @@ -2941,7 +2954,9 @@ ToolRegistry.register({ { if (!data.openFile) return e.stopPropagation() diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index c288da5bf8..988679918a 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -465,6 +465,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper private createWorktreeHandler: ((baseBranch?: string, branchName?: string) => Promise) | null = null private diffVirtualProvider: import("./DiffVirtualProvider").DiffVirtualProvider | undefined + private diffViewerProvider: import("./diff/DiffViewerProvider").DiffViewerProvider | undefined private remoteService: RemoteStatusService | null = null private unsubscribeRemote: (() => void) | null = null private readonly requirements: AgentRequirementsController @@ -582,6 +583,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper this.diffVirtualProvider = provider } + public setDiffViewerProvider(provider: import("./diff/DiffViewerProvider").DiffViewerProvider): void { + this.diffViewerProvider = provider + } + getTelemetryProperties(): Record { return { appName: "kilo-code", @@ -1604,6 +1609,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper // the live currentSession — see editor-actions.ts's validateFiles case. dir: (sessionID) => this.getWorkspaceDirectory(sessionID ?? this.currentSession?.id), diff: this.diffVirtualProvider, + openMarkdown: (file, sessionID) => { + if (!this.diffViewerProvider) return false + this.diffViewerProvider.openFromCommand({ sessionId: sessionID, file }) + return true + }, storage: this.extensionContext?.globalStorageUri, post: (msg) => this.postMessage(msg), }) diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index 8cfab373fa..ed59d29acb 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -855,39 +855,21 @@ export class AgentManagerProvider implements Disposable { return null } if (m.type === "agentManager.requestDiffBranches") { - void this.sendDiffBranches(m.sessionId, m.scope) + void this.diffs.postBranches(composeDiffId(m.sessionId, normalizeScope(m.scope))) return null } if (m.type === "agentManager.setDiffBaseBranch") { void this.diffs .setBase(composeDiffId(m.sessionId, normalizeScope(m.scope)), m.branch) .catch((err) => this.log("Failed to set diff base:", err instanceof Error ? err.message : String(err))) - .then(() => void this.sendDiffBranches(m.sessionId, m.scope)) + .then(() => void this.diffs.postBranches(composeDiffId(m.sessionId, normalizeScope(m.scope)))) return null } if (m.type === "agentManager.openFile") { this.openWorktreeFile(m.sessionId, m.filePath, m.line, m.column) return null } - } - - private async sendDiffBranches(sessionId: string, scope?: string): Promise { - const id = composeDiffId(sessionId, normalizeScope(scope)) - const result = await this.diffs.branches(id).catch((err) => { - this.log("Failed to list diff branches:", err instanceof Error ? err.message : String(err)) - return undefined - }) - if (!result) return - this.postToWebview({ - type: "agentManager.diffBranches", - sessionId: id, - branches: result.branches, - defaultBranch: result.defaultBranch, - autoBase: result.autoBase, - currentBase: result.currentBase, - isAuto: result.isAuto, - currentBranch: result.currentBranch, - }) + if (m.type === "agentManager.requestDocument") return this.diffs.document(m.sessionId, m.file) } private onBridgeMessage(m: AgentManagerInMessage): Record | null | undefined { diff --git a/packages/kilo-vscode/src/agent-manager/document-reader.ts b/packages/kilo-vscode/src/agent-manager/document-reader.ts new file mode 100644 index 0000000000..7765394bfe --- /dev/null +++ b/packages/kilo-vscode/src/agent-manager/document-reader.ts @@ -0,0 +1,49 @@ +import * as fs from "fs" +import * as path from "path" + +const MAX_TEXT_BYTES = 2_000_000 +const MAX_IMAGE_BYTES = 5_000_000 + +export type DocumentResult = + | { file: string; kind: "text"; content: string } + | { file: string; kind: "image"; mime: string; data: string } + | { error: string } + +function mime(file: string): string | undefined { + const ext = path.extname(file).toLowerCase() + if (ext === ".png") return "image/png" + if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg" + if (ext === ".gif") return "image/gif" + if (ext === ".webp") return "image/webp" + if (ext === ".svg") return "image/svg+xml" + return undefined +} + +export function readDocument(root: string, file: string): DocumentResult { + if (!file) return { error: "Invalid document path." } + + try { + const base = fs.realpathSync(root) + const target = path.isAbsolute(file) ? file : path.resolve(root, file) + const resolved = fs.realpathSync(target) + if (resolved !== base && !resolved.startsWith(base + path.sep)) + return { error: "Document is outside the worktree." } + + const stat = fs.statSync(resolved) + if (!stat.isFile()) return { error: "Document is not a file." } + + const type = mime(resolved) + const limit = type ? MAX_IMAGE_BYTES : MAX_TEXT_BYTES + if (stat.size > limit) return { error: "Document is too large to preview." } + + const relative = path.relative(base, resolved).split(path.sep).join("/") + if (type) return { file: relative, kind: "image", mime: type, data: fs.readFileSync(resolved).toString("base64") } + + const content = fs.readFileSync(resolved) + if (content.includes(0)) return { error: "Binary files cannot be previewed." } + return { file: relative, kind: "text", content: content.toString("utf8") } + } catch (error) { + console.error("[Kilo New] AgentManagerProvider: Cannot read document:", error) + return { error: "Document could not be read." } + } +} diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index edfa7666f6..42743183b9 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -362,6 +362,18 @@ interface WorktreeDiffFileMessage { diff: WorktreeDiffEntry | null } +interface DocumentMessage { + type: "agentManager.document" + sessionId: string + file: string + requestedFile?: string + content?: string + kind?: "text" | "image" + mime?: string + data?: string + error?: string +} + interface RevertWorktreeFileResultMessage { type: "agentManager.revertWorktreeFileResult" sessionId: string @@ -440,6 +452,7 @@ export type AgentManagerOutMessage = | WorktreeDiffNoticeMessage | WorktreeDiffMessage | WorktreeDiffFileMessage + | DocumentMessage | RevertWorktreeFileResultMessage | DiffBranchesMessage | PRStatusOutMessage @@ -787,6 +800,12 @@ interface OpenFileIn { column?: number } +interface RequestDocumentIn { + type: "agentManager.requestDocument" + sessionId: string + file: string +} + // Pass-through messages intercepted for side effects interface GenericOpenFileIn { type: "openFile" @@ -1065,6 +1084,7 @@ export type AgentManagerInMessage = | OpenSessionsIn | VisibleSessionIn | OpenFileIn + | RequestDocumentIn | GenericOpenFileIn | PreviewImageIn | SaveImageIn diff --git a/packages/kilo-vscode/src/agent-manager/worktree-diff-controller.ts b/packages/kilo-vscode/src/agent-manager/worktree-diff-controller.ts index b8a4b269e7..80caa2bbbf 100644 --- a/packages/kilo-vscode/src/agent-manager/worktree-diff-controller.ts +++ b/packages/kilo-vscode/src/agent-manager/worktree-diff-controller.ts @@ -8,6 +8,7 @@ import type { ApplyConflict, GitOps } from "./GitOps" import { shouldStopDiffPolling } from "./delete-worktree" import { remoteRef, type ManagedSession, type WorktreeStateManager } from "./WorktreeStateManager" import { parseDiffId, scopeToSourceId } from "./diff-scope" +import { readDocument } from "./document-reader" import type { AgentManagerOutMessage, WorktreeDiffEntry } from "./types" const LOCAL_DIFF_ID = "local" as const @@ -182,6 +183,41 @@ export class WorktreeDiffController { await this.controller.requestFile(file) } + /** Resolve the base-branch choices for a context and push them to the webview. */ + public async postBranches(id: string): Promise { + const result = await this.branches(id).catch((err) => { + this.ctx.log("Failed to list diff branches:", err instanceof Error ? err.message : String(err)) + return undefined + }) + if (!result) return + this.ctx.post({ + type: "agentManager.diffBranches", + sessionId: id, + branches: result.branches, + defaultBranch: result.defaultBranch, + autoBase: result.autoBase, + currentBase: result.currentBase, + isAuto: result.isAuto, + currentBranch: result.currentBranch, + }) + } + + /** + * Read one file from a worktree for the document inspector. Reuses this + * controller's state/root context because a document read is a worktree file + * read, resolved against the same directory the diff for that context uses. + */ + public document(sessionId: string, file: string): null { + const state = this.ctx.getState() + const worktree = state?.getWorktree(sessionId) + const session = worktree ? undefined : state?.getSession(sessionId) + const root = + worktree?.path ?? (session?.worktreeId ? state?.getWorktree(session.worktreeId)?.path : this.ctx.getRoot()) + if (!root) return null + this.ctx.post({ type: "agentManager.document", sessionId, file, requestedFile: file, ...readDocument(root, file) }) + return null + } + public start(id: string): void { if (this.controller.isPolling && this.controller.currentId === id) return this.ctx.log(`Starting diff polling for ${id}`) diff --git a/packages/kilo-vscode/src/diff/DiffViewerProvider.ts b/packages/kilo-vscode/src/diff/DiffViewerProvider.ts index c17b5a34f4..b517e469a9 100644 --- a/packages/kilo-vscode/src/diff/DiffViewerProvider.ts +++ b/packages/kilo-vscode/src/diff/DiffViewerProvider.ts @@ -55,6 +55,11 @@ export class DiffViewerProvider implements vscode.Disposable { if (this.panel && this.controller) { this.panel.reveal(this.panel.viewColumn ?? vscode.ViewColumn.One) + void this.panel.webview.postMessage({ type: "diffViewer.initialFile", file: this.ctx.initialFile }) + void this.panel.webview.postMessage({ + type: "diffViewer.initialMarkdown", + render: this.ctx.initialMarkdown === true, + }) this.controller.setContext(this.ctx) const nextId = this.catalog.defaultSourceId(this.ctx) if (nextId && nextId !== this.controller.currentId) { @@ -77,7 +82,13 @@ export class DiffViewerProvider implements vscode.Disposable { * the source picker hidden — the view becomes a static "diff of this turn" * rather than the switchable workspace/session viewer. */ - openFromCommand(arg?: { sessionId?: string; turnId?: string; initialSourceId?: string; directory?: string }): void { + openFromCommand(arg?: { + sessionId?: string + turnId?: string + initialSourceId?: string + directory?: string + file?: string + }): void { const sessionId = arg?.sessionId ?? this.sessionIdProvider() const explicit = !!arg && "directory" in arg const dir = explicit ? arg.directory : sessionId ? this.sessionDirectoryProvider(sessionId) : undefined @@ -87,6 +98,8 @@ export class DiffViewerProvider implements vscode.Disposable { sessionId, dir, initialSourceId: turnInitialSourceId ?? arg?.initialSourceId, + initialFile: arg?.file, + initialMarkdown: typeof arg?.file === "string" && /\.(md|mdx|markdown)$/i.test(arg.file), hidePicker: !!turnInitialSourceId, }) } @@ -229,6 +242,11 @@ export class DiffViewerProvider implements vscode.Disposable { workspaceDirectory: this.ctx?.dir ?? getWorkspaceRoot(), }) void this.panel.webview.postMessage({ type: "diffViewer.markdownRender", render: getDiffMarkdownRender() }) + void this.panel.webview.postMessage({ type: "diffViewer.initialFile", file: this.ctx?.initialFile }) + void this.panel.webview.postMessage({ + type: "diffViewer.initialMarkdown", + render: this.ctx?.initialMarkdown === true, + }) const initial = this.ctx ? this.catalog.defaultSourceId(this.ctx) : undefined if (initial) this.swap(initial) } diff --git a/packages/kilo-vscode/src/diff/types.ts b/packages/kilo-vscode/src/diff/types.ts index 3ff72904e1..b8b5c01574 100644 --- a/packages/kilo-vscode/src/diff/types.ts +++ b/packages/kilo-vscode/src/diff/types.ts @@ -3,6 +3,10 @@ export interface PanelContext { sessionId?: string /** Overrides the computed default source on open. */ initialSourceId?: string + /** Select a file when the source first loads. */ + initialFile?: string + /** Render Markdown when the viewer was opened from a Markdown file link. */ + initialMarkdown?: boolean /** * Hides the source picker header in the diff viewer. Used for panels that * open in a fixed view (e.g. a specific turn's diff) diff --git a/packages/kilo-vscode/src/extension.ts b/packages/kilo-vscode/src/extension.ts index 88e498254c..0d1d0b748f 100644 --- a/packages/kilo-vscode/src/extension.ts +++ b/packages/kilo-vscode/src/extension.ts @@ -278,6 +278,7 @@ export function activate(context: vscode.ExtensionContext) { diffViewerProvider.setCommentHandler((comments, autoSend) => { void provider.appendReviewComments(comments, autoSend) }) + provider.setDiffViewerProvider(diffViewerProvider) context.subscriptions.push(diffViewerProvider) // Create diff virtual provider (lightweight single-file diff for permission approval) diff --git a/packages/kilo-vscode/src/kilo-provider/editor-actions.ts b/packages/kilo-vscode/src/kilo-provider/editor-actions.ts index 982c587045..f639b46b7f 100644 --- a/packages/kilo-vscode/src/kilo-provider/editor-actions.ts +++ b/packages/kilo-vscode/src/kilo-provider/editor-actions.ts @@ -14,6 +14,10 @@ type EditorOpenMessage = { sessionID?: string } +function isMarkdownFile(file: string): boolean { + return /\.(md|mdx|markdown)$/i.test(file) +} + function openExternal(url: unknown): void { if (typeof url !== "string") return void vscode.env.openExternal(vscode.Uri.parse(url)) @@ -76,6 +80,7 @@ export function handleEditorAction( opts: { dir: (sessionID?: string) => string diff?: DiffVirtualProvider + openMarkdown?: (file: string, sessionID?: string) => boolean storage?: vscode.Uri post?: (msg: unknown) => void }, @@ -84,7 +89,10 @@ export function handleEditorAction( // Resolve the directory from the session the file reference was rendered // for (when the webview provides it), not whatever session happens to be // current — mirrors the validateFiles case below. - if (message.filePath) openFile(opts.dir(message.sessionID), message.filePath, message.line, message.column) + if (message.filePath) { + if (isMarkdownFile(message.filePath) && opts.openMarkdown?.(message.filePath, message.sessionID)) return true + openFile(opts.dir(message.sessionID), message.filePath, message.line, message.column) + } return true } if (message.type === "openContent") { diff --git a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts index 84c63ad8d3..2eac3c5959 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts @@ -27,6 +27,8 @@ const TSX_FILES = [ path.join(ROOT, "webview-ui/agent-manager/ProjectSelect.tsx"), path.join(ROOT, "webview-ui/agent-manager/sortable-tab.tsx"), path.join(ROOT, "webview-ui/agent-manager/DiffPanel.tsx"), + path.join(ROOT, "webview-ui/agent-manager/documents/DocumentPanel.tsx"), + path.join(ROOT, "webview-ui/agent-manager/documents/DocumentPanelHost.tsx"), path.join(ROOT, "webview-ui/diff-viewer/FullScreenDiffView.tsx"), path.join(ROOT, "webview-ui/diff-viewer/ImageDiffView.tsx"), path.join(ROOT, "webview-ui/diff-viewer/MarkdownDiffView.tsx"), diff --git a/packages/kilo-vscode/tests/unit/agent-manager-i18n.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-i18n.test.ts index 8142d2c20c..bf9cbea424 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-i18n.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-i18n.test.ts @@ -28,6 +28,8 @@ const TSX_FILES = [ path.join(ROOT, "webview-ui/agent-manager/sortable-tab.tsx"), path.join(ROOT, "webview-ui/agent-manager/terminal/SideTerminalPanel.tsx"), path.join(ROOT, "webview-ui/agent-manager/terminal/TerminalDestinationButton.tsx"), + path.join(ROOT, "webview-ui/agent-manager/documents/DocumentPanel.tsx"), + path.join(ROOT, "webview-ui/agent-manager/documents/DocumentPanelHost.tsx"), ] /** diff --git a/packages/kilo-vscode/tests/unit/document-reader.test.ts b/packages/kilo-vscode/tests/unit/document-reader.test.ts new file mode 100644 index 0000000000..3de360d0ba --- /dev/null +++ b/packages/kilo-vscode/tests/unit/document-reader.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "bun:test" +import * as fs from "node:fs" +import * as os from "node:os" +import * as path from "node:path" +import { readDocument } from "../../src/agent-manager/document-reader" + +function workspace() { + return fs.mkdtempSync(path.join(os.tmpdir(), "kilo-document-")) +} + +describe("readDocument", () => { + it("reads text files inside the worktree", () => { + const root = workspace() + fs.writeFileSync(path.join(root, "plan.md"), "# Plan\n") + + expect(readDocument(root, "plan.md")).toEqual({ file: "plan.md", kind: "text", content: "# Plan\n" }) + }) + + it("rejects paths outside the worktree", () => { + const root = workspace() + const outside = path.join(root, "..", "outside.md") + fs.writeFileSync(outside, "secret") + + expect(readDocument(root, "../outside.md")).toEqual({ error: "Document is outside the worktree." }) + }) + + it("rejects binary files", () => { + const root = workspace() + fs.writeFileSync(path.join(root, "data.bin"), Buffer.from([1, 0, 2])) + + expect(readDocument(root, "data.bin")).toEqual({ error: "Binary files cannot be previewed." }) + }) +}) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 10e72057ad..841c629225 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -180,6 +180,8 @@ import { setTabWidths } from "./tab-widths" import { clampPanelWidth, createPanelResize, maxPanelWidth, minPanelWidth, SidePanel } from "./side-panel-layout" import { SubagentPanel } from "./SubagentPanel" import { createSubagentTabs } from "./subagent-tabs" +import { DocumentPanelHost } from "./documents/DocumentPanelHost" +import { createDocumentInspector } from "./documents/state" import { buildShortcutCategories } from "./shortcuts" import { tracker } from "./telemetry" import { createChatFocus, createPromptFocus, hasQuestionOption } from "./focus" @@ -296,6 +298,7 @@ const AgentManagerContent: Component = () => { const [sidePanel, setSidePanel] = createSignal(null) const diffOpen = () => sidePanel() === SidePanel.Diff const prOpen = () => sidePanel() === SidePanel.PR + const documentsOpen = () => sidePanel() === SidePanel.Documents const activePR = createMemo(() => { const selected = selection() if (!selected || selected === LOCAL) return undefined @@ -308,8 +311,7 @@ const AgentManagerContent: Component = () => { const diffLoading = diffs.diffLoading const setDiffLoading = diffs.setDiffLoading const diffNotices = diffs.diffNotices - // Diff, PR, terminal, and subagent views share one inspector width, restored - // from webview state so the user's divider position survives panel reloads. + // Diff, PR, terminal, and subagent views share one inspector width. const [panelWidth, setPanelWidth] = createSignal(clampPanelWidth(persisted?.sidePanelWidth, window.innerWidth)) const resizeSide = createPanelResize(setPanelWidth, () => window.innerWidth) const showSideTerminal = () => { @@ -323,6 +325,17 @@ const AgentManagerContent: Component = () => { const reviewComposer = createReviewComposer() const [reviewActive, setReviewActive] = createSignal(false) const [reviewDiffStyle, setReviewDiffStyle] = createSignal<"unified" | "split">("unified") + const documentInspector = createDocumentInspector( + vscode, + selection, + documentsOpen, + () => { + setHistory(false) + setReviewActive(false) + setSidePanel(SidePanel.Documents) + }, + () => setSidePanel(null), + ) const subagents = createSubagentTabs({ current: session.currentSessionID, sync: (id, parentID) => session.syncSession(id, parentID, "inspector"), @@ -335,17 +348,14 @@ const AgentManagerContent: Component = () => { hide: () => setSidePanel(null), }) const markdown = createMarkdownRender(vscode) - // Per-worktree git stats (diff additions/deletions, commits missing from origin) const worktreeStats = () => registry.active().worktreeStats() const prStatuses = () => registry.active().prStatuses() - const runStatuses = () => registry.active().runStatuses() const setRunStatuses: Setter> = (v) => registry.active().setRunStatuses(v) const runScriptConfigured = () => registry.active().runScriptConfigured() const setRunScriptConfigured = (v: Parameters>[0]) => registry.active().setRunScriptConfigured(v) - // Local repo git stats (branch name, diff additions/deletions, commits) const localStats = () => registry.active().localStats() const projectLive = createProjectLive({ @@ -495,15 +505,9 @@ const AgentManagerContent: Component = () => { ) onCleanup(() => clearTimeout(pendingDeleteTimer)) - // Per-context tab memory lives in the active project's store: maps sidebar - // selection ("local" or a worktree id) -> last active session/pending ID const tabMemory = () => registry.active().tabMemory.all() - const reviewOpen = createMemo(() => { - const sel = selection() - if (sel === null) return false - return reviewOpenByContext()[sel] === true - }) + const reviewOpen = createMemo(() => selection() !== null && reviewOpenByContext()[selection()!] === true) const setReviewOpenForContext = (context: string, open: boolean) => { setReviewOpenByContext((prev) => { @@ -2249,6 +2253,8 @@ const AgentManagerContent: Component = () => { setSidePanel((prev) => (prev === SidePanel.Diff ? null : SidePanel.Diff)) } + const toggleDocumentsPanel = documentInspector.toggle + const renderTabById = (id: string) => renderTab(id, { terms, @@ -2445,6 +2451,9 @@ const AgentManagerContent: Component = () => { prStatus={() => activePR()?.pr} prOpen={prOpen} onTogglePR={togglePRPanel} + documentsOpen={documentsOpen} + documentsAvailable={documentInspector.available} + onToggleDocuments={toggleDocumentsPanel} terminalDestination={sideCtl.destination} terminalDestinationActive={() => sidePanel() === SidePanel.Terminal} terminalKeybind={() => kb().showTerminal ?? ""} @@ -2662,6 +2671,7 @@ const AgentManagerContent: Component = () => { if (id) vscode.postMessage({ type: "agentManager.openFile", sessionId: id, filePath: file, line }) }} + onOpenDocument={documentInspector.open} onRevertFile={metrics.use("revert_file", "side_review", revertCtl.revert)} revertingFiles={revertCtl.reverting()} activeTerminalId={terms.activeId()} @@ -2696,6 +2706,12 @@ const AgentManagerContent: Component = () => { onClosePanel={() => setSidePanel(null)} /> + setSidePanel(null)} + activeTerminalId={terms.activeId()} + visible={documentsOpen} + /> tooltip: Value icon: Value + /** Renders instead of `icon`, for tabs that need a per-filetype glyph. */ + iconNode?: Value iconStatus?: Value<"success" | "failure" | undefined> class?: string focused?: boolean @@ -71,8 +73,15 @@ export const ClosableTabChrome: Component = (props) => { > - }> - + }> + + + } + > + {value(props.iconNode!)} {label()} @@ -133,6 +142,7 @@ export const SortableClosableTab: Component< label={props.label} tooltip={props.tooltip} icon={props.icon} + iconNode={props.iconNode} iconStatus={props.iconStatus} class={props.class} focused={props.focused} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx index eff3750a77..93ee1a45a7 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx @@ -91,6 +91,7 @@ interface DiffPanelProps { onExpand?: () => void onRequestDiff?: (file: string) => void onOpenFile?: (relativePath: string, line?: number) => void + onOpenDocument?: (relativePath: string) => void onRevertFile?: (file: string) => void revertingFiles?: Set activeTerminalId?: string @@ -660,6 +661,20 @@ export const DiffPanel: Component = (props) => { /> + + + { + e.stopPropagation() + props.onOpenDocument?.(diff.file) + }} + /> + + PRStatus | undefined prOpen: () => boolean onTogglePR: () => void + documentsOpen: () => boolean + documentsAvailable: () => boolean + onToggleDocuments: () => void terminalDestination: () => TerminalDestination terminalDestinationActive: () => boolean terminalKeybind: () => string @@ -232,6 +235,18 @@ export const TabBar: Component = (props) => ( )} + + + + + + active: Accessor + getData: (file: string) => DocumentData | undefined + comments: ReviewComment[] + onCommentsChange: (comments: ReviewComment[]) => void + onSelect: (id: string) => void + onClose: (id: string) => void + onCloseOthers: (id: string) => void + onReorder: (from: string, to: string) => void + onOpenFile: (file: string, line?: number) => void + onClosePanel: () => void + activeTerminalId?: string + visible: Accessor +} + +function pathName(file: string): string { + return file.slice(file.lastIndexOf("/") + 1) +} + +function virtualDiff(file: string, content: string): WorktreeFileDiff { + return { + file, + before: "", + after: content, + additions: lineCount(content), + deletions: 0, + status: "added", + } +} + +export const DocumentPanel: Component = (props) => { + const { t } = useLanguage() + const code = useCodeComponent() + const [source, setSource] = createSignal(false) + const [draft, setDraft] = createSignal(null) + const [editing, setEditing] = createSignal(null) + const composer: ReviewComposer = createReviewComposer() + let draftMeta: AnnotationMeta | null = null + let editMeta: AnnotationMeta | null = null + let nextId = 0 + + const selected = createMemo(() => { + const id = props.active() + return props.tabs().find((tab) => tab.id === id) + }) + const data = () => { + const tab = selected() + return tab ? props.getData(tab.file) : undefined + } + const file = () => selected()?.file ?? "" + const content = () => data()?.content ?? "" + const diff = () => virtualDiff(file(), content()) + const labels = (): AnnotationLabels => ({ + commentOnLine: (line) => t("agentManager.review.commentOnLine", { line }), + editCommentOnLine: (line) => t("agentManager.review.editCommentOnLine", { line }), + placeholder: t("agentManager.review.commentPlaceholder"), + cancel: t("common.cancel"), + comment: t("agentManager.review.commentAction"), + send: t("prompt.action.send"), + save: t("common.save"), + sendToChat: t("agentManager.review.sendToChat"), + edit: t("common.edit"), + delete: t("common.delete"), + }) + + const updateComments = (next: ReviewComment[]) => props.onCommentsChange(next) + const comments = () => props.comments.filter((item) => item.file === file()) + const addComment = (path: string, side: AnnotationSide, line: number, text: string, selectedText: string) => { + updateComments([ + ...props.comments, + { id: `doc-${++nextId}-${Date.now()}`, file: path, side, line, comment: text, selectedText }, + ]) + setDraft(null) + draftMeta = null + composer.draft = null + } + const sendComment = (path: string, side: AnnotationSide, line: number, text: string, selectedText: string) => { + sendReviewComments( + [{ id: `doc-${++nextId}-${Date.now()}`, file: path, side, line, comment: text, selectedText }], + props.activeTerminalId, + ) + setDraft(null) + draftMeta = null + composer.draft = null + } + const updateComment = (id: string, text: string) => { + updateComments(props.comments.map((item) => (item.id === id ? { ...item, comment: text } : item))) + setEditing(null) + editMeta = null + composer.edit = null + } + const deleteComment = (id: string) => { + updateComments(props.comments.filter((item) => item.id !== id)) + setEditing(null) + editMeta = null + composer.edit = null + } + const cancelDraft = () => { + setDraft(null) + draftMeta = null + composer.draft = null + } + const annotations = (): DiffLineAnnotation[] => { + const result = buildFileAnnotations(file(), comments(), editing(), draft(), draftMeta, editMeta) + draftMeta = result.draftMeta + editMeta = result.editMeta + composer.draft = draft() ? draftMeta : null + composer.edit = editing() ? editMeta : null + return result.annotations + } + const renderAnnotation = (annotation: DiffLineAnnotation) => + buildReviewAnnotation(annotation, { + diffs: [diff()], + editing: editing(), + setEditing: (id) => setEditing(id), + addComment, + sendComment, + updateComment, + deleteComment, + cancelDraft, + labels: labels(), + activeTerminalId: props.activeTerminalId, + }) + const gutter = (range: SelectedLineRange) => { + if (draft()) return + const side: AnnotationSide = "additions" + const next = { file: file(), side, line: range.start, endLine: range.end } + draftMeta = { type: "draft", comment: null, ...next } + composer.draft = draftMeta + setDraft(next) + } + const sendAll = () => { + if (comments().length === 0) return + sendReviewComments(comments(), props.activeTerminalId) + updateComments(props.comments.filter((item) => item.file !== file())) + } + + createEffect( + on( + () => [file(), content(), props.comments] as const, + ([path, text, current]) => { + if (!path) return + const max = lineCount(text) + const valid = current.filter((item) => item.file !== path || (item.line >= 1 && item.line <= max)) + if (valid.length !== current.length) updateComments(valid) + const currentDraft = draft() + if (currentDraft && currentDraft.file === path && currentDraft.line > max) cancelDraft() + }, + { defer: true }, + ), + ) + + const close = (id: string, focus: { restore: () => void }) => { + props.onClose(id) + if (props.tabs().length > 0) focus.restore() + } + + return ( +
+
+
+ + {t("agentManager.documents.title")} + {props.tabs().length} +
+
+ + + setSource((value) => !value)} + /> + + + props.onOpenFile(file())} + /> + + + +
+
+ props.tabs().map((tab) => tab.id)} + active={props.active} + label={t("agentManager.documents.tabs")} + overlay={(id) => props.tabs().find((tab) => tab.id === id)?.file ?? ""} + onSelect={props.onSelect} + onReorder={props.onReorder} + renderTab={(id, api) => { + const tab = props.tabs().find((item) => item.id === id)! + return ( + } + showKeybind={false} + active={props.active() === id} + role="tab" + selected={props.active() === id} + tabIndex={props.active() === id ? 0 : -1} + onKeyDown={(event) => api.focus.key(id, event)} + onSelect={() => props.onSelect(id)} + onMiddleClick={(event) => { + if (event.button !== 1) return + event.preventDefault() + close(id, api.focus) + }} + onClose={() => close(id, api.focus)} + onCloseOthers={() => props.onCloseOthers(id)} + /> + ) + }} + /> + {t("agentManager.documents.empty")}
}> + +
{t("agentManager.documents.loading")}
+
+ {(error) =>
{error()}
}
+ +
+ {file()} +
+
+ +
+ + } + > + props.onOpenFile(file(), event.lineNumber)} + /> + +
+
+
+ 0}> + + + + ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanelHost.tsx b/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanelHost.tsx new file mode 100644 index 0000000000..48df0518d0 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanelHost.tsx @@ -0,0 +1,28 @@ +import type { Accessor, Component } from "solid-js" +import { DocumentPanel } from "./DocumentPanel" +import { createDocumentInspector } from "./state" + +interface Props { + inspector: ReturnType + onClosePanel: () => void + activeTerminalId?: string + visible: Accessor +} + +export const DocumentPanelHost: Component = (props) => ( + +) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts b/packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts new file mode 100644 index 0000000000..212ff82dba --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts @@ -0,0 +1,172 @@ +import { createSignal, onCleanup, onMount, type Accessor } from "solid-js" +import type { useVSCode } from "../../src/context/vscode" +import type { ReviewComment } from "../../diff-viewer/review-comments" +import type { AgentManagerDocumentMessage } from "../../src/types/messages" + +export interface DocumentTab { + id: string + file: string +} + +export interface DocumentData { + file: string + content?: string + kind?: "text" | "image" + mime?: string + data?: string + error?: string + loading: boolean +} + +function key(context: string, file: string): string { + return `${context}:${file}` +} + +export function isMarkdownPath(file: string): boolean { + return /\.(md|mdx|markdown)$/i.test(file) +} + +export function createDocuments(vscode: ReturnType, context: Accessor) { + const [tabs, setTabs] = createSignal>({}) + const [active, setActive] = createSignal>({}) + const [data, setData] = createSignal>({}) + + const current = () => context() ?? "" + const list = () => tabs()[current()] ?? [] + const selected = () => active()[current()] + const document = (file: string) => data()[key(current(), file)] + + const request = (file: string) => { + const ctx = current() + if (!ctx) return + const id = key(ctx, file) + setData((prev) => ({ ...prev, [id]: { ...(prev[id] ?? { file }), file, loading: true, error: undefined } })) + vscode.postMessage({ type: "agentManager.requestDocument", sessionId: ctx, file }) + } + + const open = (file: string) => { + const ctx = current() + if (!ctx || !file) return + setTabs((prev) => { + const list = prev[ctx] ?? [] + if (list.some((tab) => tab.file === file)) return prev + return { ...prev, [ctx]: [...list, { id: key(ctx, file), file }] } + }) + setActive((prev) => ({ ...prev, [ctx]: key(ctx, file) })) + request(file) + } + + const select = (id: string) => { + const ctx = current() + if (!ctx) return + const tab = (tabs()[ctx] ?? []).find((item) => item.id === id) + if (!tab) return + setActive((prev) => ({ ...prev, [ctx]: id })) + if (!document(tab.file)) request(tab.file) + } + + const close = (id: string) => { + const ctx = current() + const list = tabs()[ctx] ?? [] + const index = list.findIndex((tab) => tab.id === id) + if (index < 0) return + const next = list.filter((tab) => tab.id !== id) + setTabs((prev) => ({ ...prev, [ctx]: next })) + if (active()[ctx] !== id) return + const target = next[Math.min(index, next.length - 1)] + setActive((prev) => ({ ...prev, [ctx]: target?.id })) + } + + const closeOthers = (id: string) => { + const ctx = current() + const tab = (tabs()[ctx] ?? []).find((item) => item.id === id) + if (!tab) return + setTabs((prev) => ({ ...prev, [ctx]: [tab] })) + setActive((prev) => ({ ...prev, [ctx]: id })) + } + + const reorder = (from: string, to: string) => { + const ctx = current() + const list = tabs()[ctx] ?? [] + const fromIndex = list.findIndex((tab) => tab.id === from) + const toIndex = list.findIndex((tab) => tab.id === to) + if (fromIndex < 0 || toIndex < 0 || fromIndex === toIndex) return + const next = [...list] + const [item] = next.splice(fromIndex, 1) + next.splice(toIndex, 0, item!) + setTabs((prev) => ({ ...prev, [ctx]: next })) + } + + const onMessage = (message: AgentManagerDocumentMessage) => { + const id = key(message.sessionId, message.requestedFile ?? message.file) + setData((prev) => ({ + ...prev, + [id]: { + file: message.file, + content: message.content, + kind: message.kind, + mime: message.mime, + data: message.data, + error: message.error, + loading: false, + }, + })) + } + + return { tabs: list, active: selected, document, open, select, close, closeOthers, reorder, onMessage, request } +} + +export function createDocumentComments(context: Accessor) { + const [byContext, setByContext] = createSignal>({}) + const comments = () => { + const ctx = context() + return ctx ? (byContext()[ctx] ?? []) : [] + } + const setComments = (value: ReviewComment[]) => { + const ctx = context() + if (!ctx) return + setByContext((prev) => ({ ...prev, [ctx]: value })) + } + return { comments, setComments } +} + +export function createDocumentInspector( + vscode: ReturnType, + context: Accessor, + isOpen: Accessor, + openPanel: () => void, + closePanel: () => void, +) { + const documents = createDocuments(vscode, context) + const comments = createDocumentComments(context) + const open = (file?: string) => (openPanel(), file ? documents.open(file) : undefined) + onMount(() => { + const handler = (event: Event) => handleDocumentOpen(event, open) + const message = vscode.onMessage((item) => { + if (item.type === "agentManager.document") documents.onMessage(item) + }) + window.addEventListener("kilo:open-file", handler) + onCleanup(() => { + window.removeEventListener("kilo:open-file", handler) + message() + }) + }) + // The toolbar button is a way back to already-open documents, not a way to + // open an empty panel: documents arrive from a file reference or a diff row. + // Tabs are keyed per worktree, so this hides itself on a worktree with none, + // and stays visible while the panel is open so it can still be toggled shut. + const available = () => documents.tabs().length > 0 || isOpen() + const openFile = (file: string, line?: number) => { + const sessionId = context() + if (sessionId) vscode.postMessage({ type: "agentManager.openFile", sessionId, filePath: file, line }) + } + const toggle = () => (isOpen() ? closePanel() : open()) + return { documents, comments, open, openFile, toggle, available } +} + +export function handleDocumentOpen(event: Event, open: (file: string) => void): void { + const file = (event as CustomEvent<{ filePath?: unknown }>).detail?.filePath + if (typeof file !== "string" || !file) return + event.preventDefault() + open(file) +} 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 a4d8180b12..94940d15e0 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts @@ -198,6 +198,14 @@ export const dict = { "agentManager.review.imageUnavailable": "معاينة الصورة غير متاحة في لقطة الجلسة هذه.", "agentManager.review.endOfLongDiff": "لقد وصلت إلى النهاية!", + "agentManager.documents.title": "المستندات", + "agentManager.documents.toggle": "إظهار/إخفاء لوحة المستندات", + "agentManager.documents.tabs": "المستندات المفتوحة", + "agentManager.documents.empty": "افتح ملفًا من الدردشة أو diff لمعاينته هنا.", + "agentManager.documents.loading": "جارٍ تحميل المستند...", + "agentManager.documents.preview": "معاينة المستند", + "agentManager.documents.source": "عرض التعليمات البرمجية المصدر", + "agentManager.documents.comments": "{{count}} تعليقًا", "agentManager.import.pullRequest": "طلب سحب", "agentManager.import.pastePrUrl": "الصق رابط PR...", "agentManager.import.open": "فتح", 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 a3c8066462..730b56eee9 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts @@ -202,6 +202,14 @@ export const dict = { "A visualização da imagem não está disponível para este instantâneo da sessão.", "agentManager.review.endOfLongDiff": "Você chegou ao fim!", + "agentManager.documents.title": "Documentos", + "agentManager.documents.toggle": "Mostrar/ocultar painel de documentos", + "agentManager.documents.tabs": "Documentos abertos", + "agentManager.documents.empty": "Abra um arquivo pelo chat ou pelo diff para pré-visualizá-lo aqui.", + "agentManager.documents.loading": "Carregando documento...", + "agentManager.documents.preview": "Visualizar documento", + "agentManager.documents.source": "Mostrar código-fonte", + "agentManager.documents.comments": "{{count}} comentários", "agentManager.import.pullRequest": "Solicitação de extração", "agentManager.import.pastePrUrl": "Cole a URL do PR...", "agentManager.import.open": "Abrir", 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 e42a222d5c..362e2823eb 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts @@ -201,6 +201,14 @@ export const dict = { "agentManager.review.imageUnavailable": "Pregled slike nije dostupan za ovaj snimak sesije.", "agentManager.review.endOfLongDiff": "Došli ste do kraja!", + "agentManager.documents.title": "Dokumenti", + "agentManager.documents.toggle": "Prikaži/sakrij panel dokumenata", + "agentManager.documents.tabs": "Otvoreni dokumenti", + "agentManager.documents.empty": "Otvorite datoteku iz chata ili diff da biste je ovdje pregledali.", + "agentManager.documents.loading": "Učitavanje dokumenta...", + "agentManager.documents.preview": "Pregled dokumenta", + "agentManager.documents.source": "Prikaži izvorni kod", + "agentManager.documents.comments": "{{count}} komentara", "agentManager.import.pullRequest": "Zahtjev za povlačenje", "agentManager.import.pastePrUrl": "Zalijepite PR URL...", "agentManager.import.open": "Otvori", 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 22f6811774..848c913da8 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts @@ -203,6 +203,14 @@ export const dict = { "Forhåndsvisning af billedet er ikke tilgængelig for dette snapshot af sessionen.", "agentManager.review.endOfLongDiff": "Du nåede slutningen!", + "agentManager.documents.title": "Dokumenter", + "agentManager.documents.toggle": "Slå dokumentpanelet til/fra", + "agentManager.documents.tabs": "Åbnede dokumenter", + "agentManager.documents.empty": "Åbn en fil fra chatten eller diff for at få vist en forhåndsvisning her.", + "agentManager.documents.loading": "Indlæser dokument...", + "agentManager.documents.preview": "Forhåndsvis dokument", + "agentManager.documents.source": "Vis kildekode", + "agentManager.documents.comments": "{{count}} kommentarer", "agentManager.import.pullRequest": "Pull request", "agentManager.import.pastePrUrl": "Indsæt PR URL...", "agentManager.import.open": "Åbn", 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 7f70f0feac..33eaaf1b23 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts @@ -203,6 +203,14 @@ export const dict = { "agentManager.review.imageUnavailable": "Für diesen Sitzungssnapshot ist keine Bildvorschau verfügbar.", "agentManager.review.endOfLongDiff": "Du hast das Ende erreicht!", + "agentManager.documents.title": "Dokumente", + "agentManager.documents.toggle": "Dokumentenbereich ein-/ausblenden", + "agentManager.documents.tabs": "Geöffnete Dokumente", + "agentManager.documents.empty": "Öffnen Sie eine Datei im Chat oder im Diff, um sie hier in der Vorschau anzuzeigen.", + "agentManager.documents.loading": "Dokument wird geladen...", + "agentManager.documents.preview": "Dokument in der Vorschau anzeigen", + "agentManager.documents.source": "Quelltext anzeigen", + "agentManager.documents.comments": "{{count}} Kommentare", "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "PR-URL einfügen...", "agentManager.import.open": "Öffnen", 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 cd455fab94..f3d8d5e9d5 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts @@ -203,6 +203,14 @@ export const dict = { "agentManager.review.imageUnreadable": "This image could not be rendered.", "agentManager.review.imageUnavailable": "Image preview is unavailable for this session snapshot.", "agentManager.review.endOfLongDiff": "You made it to the end!", + "agentManager.documents.title": "Documents", + "agentManager.documents.toggle": "Toggle document panel", + "agentManager.documents.tabs": "Open documents", + "agentManager.documents.empty": "Open a file from chat or the diff to preview it here.", + "agentManager.documents.loading": "Loading document...", + "agentManager.documents.preview": "Preview document", + "agentManager.documents.source": "Show source", + "agentManager.documents.comments": "{{count}} comments", "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "Paste PR URL...", 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 b0ab933273..6aa1a52ff8 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts @@ -203,6 +203,14 @@ export const dict = { "La vista previa de la imagen no está disponible para esta instantánea de la sesión.", "agentManager.review.endOfLongDiff": "¡Llegaste al final!", + "agentManager.documents.title": "Documentos", + "agentManager.documents.toggle": "Mostrar u ocultar el panel de documentos", + "agentManager.documents.tabs": "Documentos abiertos", + "agentManager.documents.empty": "Abre un archivo desde el chat o el diff para previsualizarlo aquí.", + "agentManager.documents.loading": "Cargando documento...", + "agentManager.documents.preview": "Previsualizar documento", + "agentManager.documents.source": "Mostrar código fuente", + "agentManager.documents.comments": "{{count}} comentarios", "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "Pegar URL del PR...", "agentManager.import.open": "Abrir", 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 b2c47eaea6..402cc25a76 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts @@ -205,6 +205,14 @@ export const dict = { "agentManager.review.imageUnavailable": "پیش‌نمایش تصویر برای این نمونه جلسه در دسترس نیست.", "agentManager.review.endOfLongDiff": "به انتها رسیدید!", + "agentManager.documents.title": "اسناد", + "agentManager.documents.toggle": "نمایش/پنهان کردن پنل اسناد", + "agentManager.documents.tabs": "اسناد باز", + "agentManager.documents.empty": "برای پیش‌نمایش فایل در اینجا، آن را از چت یا diff باز کنید.", + "agentManager.documents.loading": "در حال بارگذاری سند...", + "agentManager.documents.preview": "پیش‌نمایش سند", + "agentManager.documents.source": "نمایش کد منبع", + "agentManager.documents.comments": "{{count}} نظر", "agentManager.import.pullRequest": "درخواست ادغام", "agentManager.import.pastePrUrl": "URL درخواست PR را وارد کنید...", "agentManager.import.open": "باز کردن", 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 57794271f5..cd71a6c0e5 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts @@ -203,6 +203,14 @@ export const dict = { "agentManager.review.imageUnavailable": "L’aperçu de l’image n’est pas disponible pour cet instantané de session.", "agentManager.review.endOfLongDiff": "Vous êtes arrivé à la fin !", + "agentManager.documents.title": "Documents", + "agentManager.documents.toggle": "Afficher/masquer le panneau des documents", + "agentManager.documents.tabs": "Documents ouverts", + "agentManager.documents.empty": "Ouvrez un fichier depuis le chat ou le diff pour en afficher un aperçu ici.", + "agentManager.documents.loading": "Chargement du document...", + "agentManager.documents.preview": "Prévisualiser le document", + "agentManager.documents.source": "Afficher le code source", + "agentManager.documents.comments": "{{count}} commentaires", "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "Coller l'URL du PR...", "agentManager.import.open": "Ouvrir", 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 12543b3b2d..0073b3cfe9 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts @@ -209,6 +209,14 @@ export const dict = { "L'anteprima dell'immagine non è disponibile per questa istantanea della sessione.", "agentManager.review.endOfLongDiff": "Sei arrivato alla fine!", + "agentManager.documents.title": "Documenti", + "agentManager.documents.toggle": "Mostra/nascondi il pannello dei documenti", + "agentManager.documents.tabs": "Documenti aperti", + "agentManager.documents.empty": "Apri un file dalla chat o dal diff per visualizzarne qui l'anteprima.", + "agentManager.documents.loading": "Caricamento del documento...", + "agentManager.documents.preview": "Visualizza anteprima del documento", + "agentManager.documents.source": "Mostra il codice sorgente", + "agentManager.documents.comments": "{{count}} commenti", "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "Incolla URL PR...", "agentManager.import.open": "Apri", 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 91b66c4aa7..73c418b258 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts @@ -202,6 +202,14 @@ export const dict = { "agentManager.review.imageUnavailable": "このセッションスナップショットでは画像プレビューを利用できません。", "agentManager.review.endOfLongDiff": "最後まで到達しました!", + "agentManager.documents.title": "ドキュメント", + "agentManager.documents.toggle": "ドキュメントパネルを切り替え", + "agentManager.documents.tabs": "開いているドキュメント", + "agentManager.documents.empty": "チャットまたは diff からファイルを開くと、ここでプレビューできます。", + "agentManager.documents.loading": "ドキュメントを読み込み中...", + "agentManager.documents.preview": "ドキュメントをプレビュー", + "agentManager.documents.source": "ソースを表示", + "agentManager.documents.comments": "{{count}}件のコメント", "agentManager.import.pullRequest": "プルリクエスト", "agentManager.import.pastePrUrl": "PR URLを貼り付け...", "agentManager.import.open": "開く", 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 be6304117a..965c0f919c 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts @@ -200,6 +200,14 @@ export const dict = { "agentManager.review.imageUnavailable": "이 세션 스냅샷에서는 이미지 미리보기를 사용할 수 없습니다.", "agentManager.review.endOfLongDiff": "끝까지 도달했습니다!", + "agentManager.documents.title": "문서", + "agentManager.documents.toggle": "문서 패널 전환", + "agentManager.documents.tabs": "열린 문서", + "agentManager.documents.empty": "채팅이나 diff에서 파일을 열어 여기에서 미리 보세요.", + "agentManager.documents.loading": "문서 로드 중...", + "agentManager.documents.preview": "문서 미리 보기", + "agentManager.documents.source": "소스 보기", + "agentManager.documents.comments": "댓글 {{count}}개", "agentManager.import.pullRequest": "풀 리퀘스트", "agentManager.import.pastePrUrl": "PR URL 붙여넣기...", "agentManager.import.open": "열기", 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 c2ef4e5d14..1545c1261a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts @@ -207,6 +207,14 @@ export const dict = { "agentManager.review.imageUnavailable": "Afbeeldingsvoorbeeld is niet beschikbaar voor deze sessiesnapshot.", "agentManager.review.endOfLongDiff": "Je hebt het einde bereikt!", + "agentManager.documents.title": "Documenten", + "agentManager.documents.toggle": "Documentenpaneel tonen/verbergen", + "agentManager.documents.tabs": "Geopende documenten", + "agentManager.documents.empty": "Open een bestand vanuit de chat of diff om het hier te bekijken.", + "agentManager.documents.loading": "Document wordt geladen...", + "agentManager.documents.preview": "Voorbeeld van document bekijken", + "agentManager.documents.source": "Broncode weergeven", + "agentManager.documents.comments": "{{count}} opmerkingen", "agentManager.import.pullRequest": "Pull request", "agentManager.import.pastePrUrl": "Plak PR URL...", "agentManager.import.open": "Openen", 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 d26092e3ae..b67bcd4cf4 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts @@ -200,6 +200,14 @@ export const dict = { "Forhåndsvisning av bildet er ikke tilgjengelig for dette øyeblikksbildet av økten.", "agentManager.review.endOfLongDiff": "Du nådde slutten!", + "agentManager.documents.title": "Dokumenter", + "agentManager.documents.toggle": "Vis/skjul dokumentpanelet", + "agentManager.documents.tabs": "Åpne dokumenter", + "agentManager.documents.empty": "Åpne en fil fra chatten eller diff for å forhåndsvise den her.", + "agentManager.documents.loading": "Laster inn dokument...", + "agentManager.documents.preview": "Forhåndsvis dokument", + "agentManager.documents.source": "Vis kildekode", + "agentManager.documents.comments": "{{count}} kommentarer", "agentManager.import.pullRequest": "Pull request", "agentManager.import.pastePrUrl": "Lim inn PR URL...", "agentManager.import.open": "Åpne", 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 b4f8324807..0ef67c6873 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts @@ -201,6 +201,14 @@ export const dict = { "agentManager.review.imageUnavailable": "Podgląd obrazu jest niedostępny dla tej migawki sesji.", "agentManager.review.endOfLongDiff": "Dotarłeś do końca!", + "agentManager.documents.title": "Dokumenty", + "agentManager.documents.toggle": "Pokaż/ukryj panel dokumentów", + "agentManager.documents.tabs": "Otwarte dokumenty", + "agentManager.documents.empty": "Otwórz plik z czatu lub diff, aby wyświetlić jego podgląd tutaj.", + "agentManager.documents.loading": "Ładowanie dokumentu...", + "agentManager.documents.preview": "Podgląd dokumentu", + "agentManager.documents.source": "Pokaż kod źródłowy", + "agentManager.documents.comments": "{{count}} komentarzy", "agentManager.import.pullRequest": "Prośba o scalenie", "agentManager.import.pastePrUrl": "Wklej URL PR...", "agentManager.import.open": "Otwórz", 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 809b074b9a..01bc640025 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts @@ -202,6 +202,14 @@ export const dict = { "agentManager.review.imageUnavailable": "Предварительный просмотр изображения недоступен для этого снимка сеанса.", "agentManager.review.endOfLongDiff": "Вы дошли до конца!", + "agentManager.documents.title": "Документы", + "agentManager.documents.toggle": "Показать или скрыть панель документов", + "agentManager.documents.tabs": "Открытые документы", + "agentManager.documents.empty": "Откройте файл из чата или diff, чтобы просмотреть его здесь.", + "agentManager.documents.loading": "Загрузка документа...", + "agentManager.documents.preview": "Предпросмотр документа", + "agentManager.documents.source": "Показать исходный код", + "agentManager.documents.comments": "{{count}} комментариев", "agentManager.import.pullRequest": "Запрос на слияние", "agentManager.import.pastePrUrl": "Вставьте URL PR...", "agentManager.import.open": "Открыть", 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 e74d7dbef9..b1f4ee85e4 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts @@ -196,6 +196,14 @@ export const dict = { "agentManager.review.imageUnavailable": "ไม่สามารถแสดงตัวอย่างรูปภาพสำหรับสแนปช็อตของเซสชันนี้ได้", "agentManager.review.endOfLongDiff": "คุณมาถึงท้ายสุดแล้ว!", + "agentManager.documents.title": "เอกสาร", + "agentManager.documents.toggle": "สลับแผงเอกสาร", + "agentManager.documents.tabs": "เอกสารที่เปิดอยู่", + "agentManager.documents.empty": "เปิดไฟล์จากแชทหรือ diff เพื่อดูตัวอย่างที่นี่", + "agentManager.documents.loading": "กำลังโหลดเอกสาร...", + "agentManager.documents.preview": "ดูตัวอย่างเอกสาร", + "agentManager.documents.source": "แสดงซอร์สโค้ด", + "agentManager.documents.comments": "{{count}} ความคิดเห็น", "agentManager.import.pullRequest": "คำขอรวมโค้ด", "agentManager.import.pastePrUrl": "วาง URL ของ PR...", "agentManager.import.open": "เปิด", 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 d6d5c3012a..53d9bdff3e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts @@ -209,6 +209,14 @@ export const dict = { "agentManager.review.imageUnavailable": "Bu oturum anlık görüntüsü için görsel önizlemesi kullanılamıyor.", "agentManager.review.endOfLongDiff": "Sonuna geldiniz!", + "agentManager.documents.title": "Belgeler", + "agentManager.documents.toggle": "Belge panelini aç/kapat", + "agentManager.documents.tabs": "Açık belgeler", + "agentManager.documents.empty": "Burada önizlemek için sohbetten veya diff üzerinden bir dosya açın.", + "agentManager.documents.loading": "Belge yükleniyor...", + "agentManager.documents.preview": "Belgeyi önizle", + "agentManager.documents.source": "Kaynak kodu göster", + "agentManager.documents.comments": "{{count}} yorum", "agentManager.import.pullRequest": "Çekme İsteği", "agentManager.import.pastePrUrl": "PR URL'sini yapıştırın...", "agentManager.import.open": "Aç", 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 9959ad6a40..ff7b02dea1 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts @@ -210,6 +210,14 @@ export const dict = { "agentManager.review.imageUnavailable": "Попередній перегляд зображення недоступний для цього знімка сеансу.", "agentManager.review.endOfLongDiff": "Ви дійшли до кінця!", + "agentManager.documents.title": "Документи", + "agentManager.documents.toggle": "Показати або приховати панель документів", + "agentManager.documents.tabs": "Відкриті документи", + "agentManager.documents.empty": "Відкрийте файл із чату або diff, щоб переглянути його тут.", + "agentManager.documents.loading": "Завантаження документа...", + "agentManager.documents.preview": "Попередній перегляд документа", + "agentManager.documents.source": "Показати вихідний код", + "agentManager.documents.comments": "{{count}} коментарів", "agentManager.import.pullRequest": "Пул-реквест", "agentManager.import.pastePrUrl": "Вставте URL PR...", "agentManager.import.open": "Відкрити", 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 c8456673d0..190c1d268f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts @@ -194,6 +194,14 @@ export const dict = { "agentManager.review.imageUnavailable": "此会话快照无法提供图片预览。", "agentManager.review.endOfLongDiff": "你已经到末尾了!", + "agentManager.documents.title": "文档", + "agentManager.documents.toggle": "切换文档面板", + "agentManager.documents.tabs": "打开的文档", + "agentManager.documents.empty": "从聊天或 diff 中打开文件,即可在此处预览。", + "agentManager.documents.loading": "正在加载文档...", + "agentManager.documents.preview": "预览文档", + "agentManager.documents.source": "显示源代码", + "agentManager.documents.comments": "{{count}} 条评论", "agentManager.import.pullRequest": "拉取请求", "agentManager.import.pastePrUrl": "粘贴 PR URL...", "agentManager.import.open": "打开", 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 dd009844c7..dfc107163a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts @@ -194,6 +194,14 @@ export const dict = { "agentManager.review.imageUnavailable": "此工作階段快照無法預覽圖片。", "agentManager.review.endOfLongDiff": "你已經到最後了!", + "agentManager.documents.title": "文件", + "agentManager.documents.toggle": "切換文件面板", + "agentManager.documents.tabs": "已開啟的文件", + "agentManager.documents.empty": "從聊天或 diff 中開啟檔案,即可在此處預覽。", + "agentManager.documents.loading": "正在載入文件...", + "agentManager.documents.preview": "預覽文件", + "agentManager.documents.source": "顯示原始碼", + "agentManager.documents.comments": "{{count}} 則留言", "agentManager.import.pullRequest": "提取請求", "agentManager.import.pastePrUrl": "貼上 PR URL...", "agentManager.import.open": "開啟", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/side-panel-layout.ts b/packages/kilo-vscode/webview-ui/agent-manager/side-panel-layout.ts index d89341e31f..f1d87f7e33 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/side-panel-layout.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/side-panel-layout.ts @@ -10,6 +10,7 @@ export enum SidePanel { PR = "pr", Terminal = "terminal", Subagents = "subagents", + Documents = "documents", } function viewportWidth(viewport: number): number { diff --git a/packages/kilo-vscode/webview-ui/diff-viewer/DiffViewerApp.tsx b/packages/kilo-vscode/webview-ui/diff-viewer/DiffViewerApp.tsx index 0811537142..fdc7573008 100644 --- a/packages/kilo-vscode/webview-ui/diff-viewer/DiffViewerApp.tsx +++ b/packages/kilo-vscode/webview-ui/diff-viewer/DiffViewerApp.tsx @@ -46,6 +46,7 @@ const DiffViewerContent: Component = () => { const [loadingFiles, setLoadingFiles] = createSignal>(new Set()) const [availableSources, setAvailableSources] = createSignal([]) const [currentSourceId, setCurrentSourceId] = createSignal(undefined) + const [initialFile, setInitialFile] = createSignal(undefined) const [capabilities, setCapabilities] = createSignal(undefined) const [notice, setNotice] = createSignal(undefined) const [branches, setBranches] = createSignal([]) @@ -136,6 +137,14 @@ const DiffViewerContent: Component = () => { setMarkdown(msg.render) return } + if ((msg as { type: string; file?: string }).type === "diffViewer.initialFile") { + setInitialFile((msg as { file?: string }).file) + return + } + if ((msg as { type: string; render?: boolean }).type === "diffViewer.initialMarkdown") { + setMarkdown((msg as { render?: boolean }).render === true) + return + } if (msg.type === "setAvailableSources") { setAvailableSources(msg.descriptors) setCurrentSourceId(msg.currentId) @@ -266,6 +275,7 @@ const DiffViewerContent: Component = () => { onOpenFile={(relativePath) => { post({ type: "openFile", filePath: relativePath }) }} + initialFile={initialFile()} onRevertFile={(file) => { markReverting(file, true) post({ type: "diffViewer.revertFile", file }) diff --git a/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx b/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx index db2373c3e1..1e538cca0a 100644 --- a/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx +++ b/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx @@ -90,6 +90,7 @@ interface FullScreenDiffViewProps { onMarkdownRenderChange?: (render: boolean) => void onRequestDiff?: (file: string) => void onOpenFile?: (relativePath: string, line?: number) => void + initialFile?: string onRevertFile?: (file: string) => void revertingFiles?: Set activeTerminalId?: string @@ -234,7 +235,9 @@ export const FullScreenDiffView: Component = (props) => // Keep active file in sync — pick first if current is stale const current = activeFile() if (!current || !diffs.some((d) => d.file === current)) { - setActiveFile(diffs[0]!.file) + const initial = + props.initialFile && diffs.some((d) => d.file === props.initialFile) ? props.initialFile : undefined + setActiveFile(initial ?? diffs[0]!.file) } // New context: initialize open state from the diff policy. diff --git a/packages/kilo-vscode/webview-ui/diff-viewer/MarkdownAnnotationLayer.tsx b/packages/kilo-vscode/webview-ui/diff-viewer/MarkdownAnnotationLayer.tsx index f0a5c0288e..70324d1ca0 100644 --- a/packages/kilo-vscode/webview-ui/diff-viewer/MarkdownAnnotationLayer.tsx +++ b/packages/kilo-vscode/webview-ui/diff-viewer/MarkdownAnnotationLayer.tsx @@ -145,12 +145,16 @@ export const MarkdownAnnotationLayer: Component = } const paneBox = pane.getBoundingClientRect() + let bottom = 0 for (const anchor of list) { const box = anchor.element.getBoundingClientRect() const row = document.createElement("div") row.className = "am-markdown-target" - row.style.top = `${box.top - paneBox.top}px` - row.style.height = `${Math.max(20, box.height)}px` + const height = Math.max(20, box.height) + const top = Math.max(box.top - paneBox.top, bottom) + row.style.top = `${top}px` + row.style.height = `${height}px` + bottom = top + height if (props.enableGutterUtility) { const button = document.createElement("button") diff --git a/packages/kilo-vscode/webview-ui/diff-viewer/MarkdownDiffView.tsx b/packages/kilo-vscode/webview-ui/diff-viewer/MarkdownDiffView.tsx index 3627809ccd..78533670ec 100644 --- a/packages/kilo-vscode/webview-ui/diff-viewer/MarkdownDiffView.tsx +++ b/packages/kilo-vscode/webview-ui/diff-viewer/MarkdownDiffView.tsx @@ -24,7 +24,7 @@ export function isMarkdownFile(file: string): boolean { return /\.(md|mdx|markdown)$/i.test(file) } -interface PaneProps { +export interface MarkdownPaneProps { title?: string text: string side: AnnotationSide @@ -36,7 +36,7 @@ interface PaneProps { onLineNumberClick: ((event: { annotationSide: AnnotationSide; lineNumber: number }) => void) | undefined } -const MarkdownPane: Component = (props) => { +export const MarkdownPane: Component = (props) => { let pane: HTMLElement | undefined let body: HTMLDivElement | undefined const interactive = () => diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx index a0162ce04e..53cac0212b 100644 --- a/packages/kilo-vscode/webview-ui/src/App.tsx +++ b/packages/kilo-vscode/webview-ui/src/App.tsx @@ -121,6 +121,11 @@ export const DataBridge: Component<{ children: any }> = (props) => { } const open = (filePath: string, line?: number, column?: number, sessionID?: string) => { + const event = new CustomEvent("kilo:open-file", { + cancelable: true, + detail: { filePath, line, column, sessionID }, + }) + if (!window.dispatchEvent(event)) return vscode.postMessage({ type: "openFile", filePath, line, column, sessionID }) } diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx index 3b42083c99..ce9b9f25e7 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx @@ -76,6 +76,7 @@ function PlanExitCard(props: { part: ToolPart }) {
{label()}{" "} + {language.t("ui.patch.action.plan")} {display()} diff --git a/packages/kilo-vscode/webview-ui/src/styles/plan-exit.css b/packages/kilo-vscode/webview-ui/src/styles/plan-exit.css index 0aed607ecd..3c92434f70 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/plan-exit.css +++ b/packages/kilo-vscode/webview-ui/src/styles/plan-exit.css @@ -23,7 +23,27 @@ font-weight: var(--font-weight-medium); } + [data-slot="plan-exit-badge"] { + display: inline-flex; + align-items: center; + padding: 1px 5px; + border: 1px solid var(--border-interactive-base); + border-radius: 999px; + color: var(--text-interactive-base); + font-size: var(--kilo-font-size-11); + font-weight: var(--font-weight-medium); + line-height: var(--line-height-small); + text-transform: uppercase; + letter-spacing: 0.04em; + } + [data-slot="plan-exit-link"] { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 2px 6px; + border: 1px solid transparent; + border-radius: 4px; cursor: pointer; color: var(--text-interactive-base); text-decoration: none; @@ -33,7 +53,14 @@ word-break: break-all; &:hover { + border-color: var(--border-interactive-base); + background: var(--surface-interactive-weak); text-decoration: underline; } + + &:focus-visible { + outline: 1px solid var(--focus-base); + outline-offset: 1px; + } } } 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 fda0c747d5..c63638d8e5 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 @@ -977,6 +977,18 @@ export interface AgentManagerWorktreeDiffFileMessage { diff: WorktreeFileDiff | null } +export interface AgentManagerDocumentMessage { + type: "agentManager.document" + sessionId: string + file: string + requestedFile?: string + content?: string + kind?: "text" | "image" + mime?: string + data?: string + error?: string +} + // Agent Manager: Diff loading state (extension → webview) export interface AgentManagerWorktreeDiffLoadingMessage { type: "agentManager.worktreeDiffLoading" @@ -1132,6 +1144,16 @@ export interface DiffViewerMarkdownRenderMessage { render: boolean } +export interface DiffViewerInitialFileMessage { + type: "diffViewer.initialFile" + file?: string +} + +export interface DiffViewerInitialMarkdownMessage { + type: "diffViewer.initialMarkdown" + render: boolean +} + export interface SetAvailableSourcesMessage { type: "setAvailableSources" descriptors: DiffSourceDescriptor[] @@ -1408,6 +1430,7 @@ export type ExtensionMessage = | WorkspaceDirectoryChangedMessage | AgentManagerWorktreeDiffMessage | AgentManagerWorktreeDiffFileMessage + | AgentManagerDocumentMessage | AgentManagerWorktreeDiffLoadingMessage | AgentManagerWorktreeDiffNoticeMessage | AgentManagerApplyWorktreeDiffResultMessage @@ -1439,6 +1462,8 @@ export type ExtensionMessage = | DiffViewerRevertFileResultMessage | DiffViewerDiffFileMessage | DiffViewerMarkdownRenderMessage + | DiffViewerInitialFileMessage + | DiffViewerInitialMarkdownMessage | SetAvailableSourcesMessage | DiffViewerCapabilitiesMessage | DiffViewerNoticeMessage 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 c271ceaca2..d058873d29 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 @@ -869,6 +869,12 @@ export interface AgentManagerOpenFileRequest { column?: number } +export interface AgentManagerRequestDocumentMessage { + type: "agentManager.requestDocument" + sessionId: string + file: string +} + // Create multiple worktree sessions for the same prompt (multi-version mode) export interface CreateMultiVersionRequest { type: "agentManager.createMultiVersion" @@ -1527,6 +1533,7 @@ export type WebviewMessage = | CopyToClipboardRequest | ShowExistingLocalTerminalRequest | AgentManagerOpenFileRequest + | AgentManagerRequestDocumentMessage | CreateMultiVersionRequest | SetTabOrderRequest | SetWorktreeOrderRequest diff --git a/packages/opencode/src/kilocode/session/prompt.ts b/packages/opencode/src/kilocode/session/prompt.ts index bd9b2b5c71..4b997d2a5f 100644 --- a/packages/opencode/src/kilocode/session/prompt.ts +++ b/packages/opencode/src/kilocode/session/prompt.ts @@ -451,6 +451,7 @@ export namespace KiloSessionPrompt { info, "Use the chosen plan path as the main plan file. Do not write or edit other files unless the user explicitly asks and your permissions allow it.", "Project/user instructions about plan location (for example plans/ or .plans/) are authorized when permissions allow them; they do not conflict with this reminder. When finalizing, call plan_exit with the path of the plan file you wrote.", + "In the visible final response, cite the saved plan path as an inline code span so the client can open it as a document. Cite other user-facing files you create the same way instead of pasting the full file into chat.", supportsPlanFollowup() ? "When the plan is implementation-ready, write the main plan file and call plan_exit. Do not ask the user to choose between finalizing and refining in chat; the client follow-up after plan_exit asks whether to implement the saved plan or keep refining." : 'Before creating or updating the plan file, or calling plan_exit, ask the user to choose exactly one of: "Finalize and save the plan" or "Continue refining". If the user chooses to finalize, write the main plan file, then call plan_exit.', diff --git a/packages/ui/src/components/markdown.css b/packages/ui/src/components/markdown.css index 4d8955bd2a..0a2b844187 100644 --- a/packages/ui/src/components/markdown.css +++ b/packages/ui/src/components/markdown.css @@ -263,6 +263,25 @@ text-decoration-style: solid; } } + + /* A plan reference reads as a document, not a boxed chip: a small glyph in + front of the path, keeping the same dotted-underline affordance every + other validated file link already uses. No border, no badge, no fill. */ + &.plan-document-link::before { + content: ""; + display: inline-block; + width: 0.95em; + height: 0.95em; + margin-inline-end: 0.3em; + vertical-align: -0.15em; + background-color: currentColor; + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z'/%3E%3Cpath d='M14 2v5h6'/%3E%3Cpath d='M9 13h6'/%3E%3Cpath d='M9 17h4'/%3E%3C/svg%3E"); + mask-repeat: no-repeat; + mask-size: contain; + -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z'/%3E%3Cpath d='M14 2v5h6'/%3E%3Cpath d='M9 13h6'/%3E%3Cpath d='M9 17h4'/%3E%3C/svg%3E"); + -webkit-mask-repeat: no-repeat; + -webkit-mask-size: contain; + } /* kilocode_change end */ } diff --git a/packages/ui/src/i18n/ar.ts b/packages/ui/src/i18n/ar.ts index a91cb5bad2..e4f82a4ed8 100644 --- a/packages/ui/src/i18n/ar.ts +++ b/packages/ui/src/i18n/ar.ts @@ -211,6 +211,7 @@ export const dict = { "ui.patch.action.created": "تم الإنشاء", "ui.patch.action.moved": "منقول", "ui.patch.action.patched": "تم تطبيق رقعة", + "ui.patch.action.plan": "خطة", // kilocode_change "ui.question.subtitle.answered": "تمت الإجابة عن {{count}}", "ui.question.answer.none": "(لا توجد إجابة)", diff --git a/packages/ui/src/i18n/az.ts b/packages/ui/src/i18n/az.ts index 65bf6016b0..0a635b9201 100644 --- a/packages/ui/src/i18n/az.ts +++ b/packages/ui/src/i18n/az.ts @@ -188,6 +188,7 @@ export const dict: Record = { "ui.patch.action.created": "Yaradıldı", "ui.patch.action.moved": "Köçürüldü", "ui.patch.action.patched": "Yamaq tətbiq edildi", + "ui.patch.action.plan": "Plan", // kilocode_change "ui.question.subtitle.answered": "{{count}} cavablandı", "ui.question.answer.none": "(cavab yoxdur)", "ui.question.review.notAnswered": "(cavablanmayıb)", diff --git a/packages/ui/src/i18n/br.ts b/packages/ui/src/i18n/br.ts index 332ff798a2..d007beff11 100644 --- a/packages/ui/src/i18n/br.ts +++ b/packages/ui/src/i18n/br.ts @@ -215,6 +215,7 @@ export const dict = { "ui.patch.action.created": "Criado", "ui.patch.action.moved": "Movido", "ui.patch.action.patched": "Patch aplicado", + "ui.patch.action.plan": "Plano", // kilocode_change "ui.question.subtitle.answered": "{{count}} respondidas", "ui.question.subtitle.dismissed": "{{count}} dismissed", // kilocode_change diff --git a/packages/ui/src/i18n/bs.ts b/packages/ui/src/i18n/bs.ts index 83173fb671..5ab12ecec4 100644 --- a/packages/ui/src/i18n/bs.ts +++ b/packages/ui/src/i18n/bs.ts @@ -203,6 +203,7 @@ export const dict = { "ui.patch.action.created": "Kreirano", "ui.patch.action.moved": "Premješteno", "ui.patch.action.patched": "Primijenjeno", + "ui.patch.action.plan": "Plan", // kilocode_change "ui.question.subtitle.answered": "Odgovoreno: {{count}}", "ui.question.answer.none": "(nema odgovora)", diff --git a/packages/ui/src/i18n/da.ts b/packages/ui/src/i18n/da.ts index 8b3c3aa09d..cbdb268416 100644 --- a/packages/ui/src/i18n/da.ts +++ b/packages/ui/src/i18n/da.ts @@ -191,6 +191,7 @@ export const dict = { "ui.patch.action.created": "Oprettet", "ui.patch.action.moved": "Flyttet", "ui.patch.action.patched": "Patchet", + "ui.patch.action.plan": "Plan", // kilocode_change "ui.question.subtitle.answered": "{{count}} besvaret", "ui.question.subtitle.dismissed": "{{count}} dismissed", // kilocode_change diff --git a/packages/ui/src/i18n/de.ts b/packages/ui/src/i18n/de.ts index c5e8708281..b1cffac666 100644 --- a/packages/ui/src/i18n/de.ts +++ b/packages/ui/src/i18n/de.ts @@ -181,6 +181,7 @@ export const dict = { "ui.patch.action.created": "Erstellt", "ui.patch.action.moved": "Verschoben", "ui.patch.action.patched": "Gepatched", + "ui.patch.action.plan": "Plan", // kilocode_change "ui.question.subtitle.answered": "{{count}} beantwortet", "ui.question.answer.none": "(keine Antwort)", diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index ba503f61f1..fe48472b9a 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -227,6 +227,7 @@ export const dict: Record = { "ui.patch.action.created": "Created", "ui.patch.action.moved": "Moved", "ui.patch.action.patched": "Patched", + "ui.patch.action.plan": "Plan", // kilocode_change "ui.question.subtitle.answered": "{{count}} answered", "ui.question.subtitle.dismissed": "{{count}} dismissed", // kilocode_change diff --git a/packages/ui/src/i18n/es.ts b/packages/ui/src/i18n/es.ts index 58d87ea16a..a66be5cf76 100644 --- a/packages/ui/src/i18n/es.ts +++ b/packages/ui/src/i18n/es.ts @@ -199,6 +199,7 @@ export const dict = { "ui.patch.action.created": "Creado", "ui.patch.action.moved": "Movido", "ui.patch.action.patched": "Parcheado", + "ui.patch.action.plan": "Plan", // kilocode_change "ui.question.subtitle.answered": "{{count}} respondidas", "ui.question.answer.none": "(sin respuesta)", diff --git a/packages/ui/src/i18n/fi.ts b/packages/ui/src/i18n/fi.ts index 35bf61fdef..e938d8c1bf 100644 --- a/packages/ui/src/i18n/fi.ts +++ b/packages/ui/src/i18n/fi.ts @@ -170,6 +170,7 @@ export const dict: Record = { "ui.patch.action.created": "Luotu", "ui.patch.action.moved": "Siirretty", "ui.patch.action.patched": "Paikattu", + "ui.patch.action.plan": "Suunnitelma", // kilocode_change "ui.question.subtitle.answered": "Vastatut kysymykset: {{count}}", "ui.question.answer.none": "(ei vastausta)", "ui.question.review.notAnswered": "(ei vastattu)", diff --git a/packages/ui/src/i18n/fr.ts b/packages/ui/src/i18n/fr.ts index bc4fbad5b0..3a766418fa 100644 --- a/packages/ui/src/i18n/fr.ts +++ b/packages/ui/src/i18n/fr.ts @@ -201,6 +201,7 @@ export const dict = { "ui.patch.action.created": "Créé", "ui.patch.action.moved": "Déplacé", "ui.patch.action.patched": "Correctif appliqué", + "ui.patch.action.plan": "Plan", // kilocode_change "ui.question.subtitle.answered": "Réponses : {{count}}", "ui.question.answer.none": "(pas de réponse)", diff --git a/packages/ui/src/i18n/hi.ts b/packages/ui/src/i18n/hi.ts index 94edecffbf..e63a252ea5 100644 --- a/packages/ui/src/i18n/hi.ts +++ b/packages/ui/src/i18n/hi.ts @@ -190,6 +190,7 @@ export const dict: Record = { "ui.patch.action.created": "बनाया गया", "ui.patch.action.moved": "ले जाया गया", "ui.patch.action.patched": "पैच किया गया", + "ui.patch.action.plan": "योजना", // kilocode_change "ui.question.subtitle.answered": "{{count}} के उत्तर दिए गए", "ui.question.answer.none": "(कोई जवाब नहीं)", "ui.question.review.notAnswered": "(उत्तर नहीं दिया गया)", diff --git a/packages/ui/src/i18n/id.ts b/packages/ui/src/i18n/id.ts index 7ea583106b..8dd26dfb17 100644 --- a/packages/ui/src/i18n/id.ts +++ b/packages/ui/src/i18n/id.ts @@ -209,6 +209,7 @@ export const dict: Record = { "ui.patch.action.created": "Dibuat", "ui.patch.action.moved": "Dipindahkan", "ui.patch.action.patched": "Ditambal", + "ui.patch.action.plan": "Rencana", // kilocode_change "ui.question.subtitle.answered": "{{count}} dijawab", "ui.question.answer.none": "(tidak ada jawaban)", diff --git a/packages/ui/src/i18n/it.ts b/packages/ui/src/i18n/it.ts index f0603e340d..2d0e7c5824 100644 --- a/packages/ui/src/i18n/it.ts +++ b/packages/ui/src/i18n/it.ts @@ -193,6 +193,7 @@ export const dict: Record = { "ui.patch.action.created": "Creato", "ui.patch.action.moved": "Spostato", "ui.patch.action.patched": "Modificato", + "ui.patch.action.plan": "Piano", // kilocode_change "ui.question.subtitle.answered": "{{count}} risposte", "ui.question.answer.none": "(nessuna risposta)", "ui.question.review.notAnswered": "(senza risposta)", diff --git a/packages/ui/src/i18n/ja.ts b/packages/ui/src/i18n/ja.ts index 78d5dce0d0..60c194110d 100644 --- a/packages/ui/src/i18n/ja.ts +++ b/packages/ui/src/i18n/ja.ts @@ -194,6 +194,7 @@ export const dict = { "ui.patch.action.created": "作成済み", "ui.patch.action.moved": "移動済み", "ui.patch.action.patched": "パッチ適用済み", + "ui.patch.action.plan": "計画", // kilocode_change "ui.question.subtitle.answered": "{{count}}件回答済み", "ui.question.answer.none": "(回答なし)", diff --git a/packages/ui/src/i18n/ko.ts b/packages/ui/src/i18n/ko.ts index 8cf4e0fe04..080b33c710 100644 --- a/packages/ui/src/i18n/ko.ts +++ b/packages/ui/src/i18n/ko.ts @@ -171,6 +171,7 @@ export const dict = { "ui.patch.action.created": "생성됨", "ui.patch.action.moved": "이동됨", "ui.patch.action.patched": "패치됨", + "ui.patch.action.plan": "계획", // kilocode_change "ui.question.subtitle.answered": "{{count}}개 답변됨", "ui.question.answer.none": "(답변 없음)", diff --git a/packages/ui/src/i18n/nl.ts b/packages/ui/src/i18n/nl.ts index 1f2d324437..fca188d67a 100644 --- a/packages/ui/src/i18n/nl.ts +++ b/packages/ui/src/i18n/nl.ts @@ -188,6 +188,7 @@ export const dict: Record = { "ui.patch.action.created": "Gemaakt", "ui.patch.action.moved": "Verplaatst", "ui.patch.action.patched": "Bijgewerkt", + "ui.patch.action.plan": "Plan", // kilocode_change "ui.question.subtitle.answered": "{{count}} beantwoord", "ui.question.answer.none": "(geen antwoord)", "ui.question.review.notAnswered": "(niet beantwoord)", diff --git a/packages/ui/src/i18n/no.ts b/packages/ui/src/i18n/no.ts index a94a2b25b6..d8362c62ba 100644 --- a/packages/ui/src/i18n/no.ts +++ b/packages/ui/src/i18n/no.ts @@ -174,6 +174,7 @@ export const dict: Record = { "ui.patch.action.created": "Opprettet", "ui.patch.action.moved": "Flyttet", "ui.patch.action.patched": "Oppdatert", + "ui.patch.action.plan": "Plan", // kilocode_change "ui.question.subtitle.answered": "{{count}} besvart", "ui.question.answer.none": "(ingen svar)", diff --git a/packages/ui/src/i18n/pa.ts b/packages/ui/src/i18n/pa.ts index 874abacb04..770a8fbf13 100644 --- a/packages/ui/src/i18n/pa.ts +++ b/packages/ui/src/i18n/pa.ts @@ -189,6 +189,7 @@ export const dict: Record = { "ui.patch.action.created": "بنایا گیا", "ui.patch.action.moved": "منتقل ہو گیا", "ui.patch.action.patched": "پیچ کیتا گیا", + "ui.patch.action.plan": "ਯੋਜਨਾ", // kilocode_change "ui.question.subtitle.answered": "{{count}} جواب دتا گیا", "ui.question.answer.none": "(کوئی جواب نئیں)", "ui.question.review.notAnswered": "(جواب نئیں دتا گیا)", diff --git a/packages/ui/src/i18n/pl.ts b/packages/ui/src/i18n/pl.ts index dfd773969a..2a22c018fb 100644 --- a/packages/ui/src/i18n/pl.ts +++ b/packages/ui/src/i18n/pl.ts @@ -203,6 +203,7 @@ export const dict = { "ui.patch.action.created": "Utworzono", "ui.patch.action.moved": "Przeniesiono", "ui.patch.action.patched": "Załatano", + "ui.patch.action.plan": "Plan", // kilocode_change "ui.question.subtitle.answered": "Liczba odpowiedzi: {{count}}", "ui.question.answer.none": "(brak odpowiedzi)", diff --git a/packages/ui/src/i18n/ru.ts b/packages/ui/src/i18n/ru.ts index d67ec21163..a8c702ba0b 100644 --- a/packages/ui/src/i18n/ru.ts +++ b/packages/ui/src/i18n/ru.ts @@ -202,6 +202,7 @@ export const dict = { "ui.patch.action.created": "Создано", "ui.patch.action.moved": "Перемещено", "ui.patch.action.patched": "Изменено", + "ui.patch.action.plan": "План", // kilocode_change "ui.question.subtitle.answered": "Получено ответов: {{count}}", "ui.question.answer.none": "(нет ответа)", diff --git a/packages/ui/src/i18n/sv.ts b/packages/ui/src/i18n/sv.ts index bbff73961c..16d32736c1 100644 --- a/packages/ui/src/i18n/sv.ts +++ b/packages/ui/src/i18n/sv.ts @@ -188,6 +188,7 @@ export const dict: Record = { "ui.patch.action.created": "Skapad", "ui.patch.action.moved": "Flyttad", "ui.patch.action.patched": "Patchad", + "ui.patch.action.plan": "Plan", // kilocode_change "ui.question.subtitle.answered": "{{count}} besvarade", "ui.question.answer.none": "(inget svar)", "ui.question.review.notAnswered": "(ej besvarad)", diff --git a/packages/ui/src/i18n/th.ts b/packages/ui/src/i18n/th.ts index 961ebf6b18..f27d63037e 100644 --- a/packages/ui/src/i18n/th.ts +++ b/packages/ui/src/i18n/th.ts @@ -195,6 +195,7 @@ export const dict = { "ui.patch.action.created": "สร้าง", "ui.patch.action.moved": "ย้าย", "ui.patch.action.patched": "แพตช์", + "ui.patch.action.plan": "แผน", // kilocode_change "ui.question.subtitle.answered": "ตอบแล้ว {{count}} ข้อ", "ui.question.answer.none": "(ไม่มีคำตอบ)", diff --git a/packages/ui/src/i18n/tr.ts b/packages/ui/src/i18n/tr.ts index b8578e67bd..ba516a4e04 100644 --- a/packages/ui/src/i18n/tr.ts +++ b/packages/ui/src/i18n/tr.ts @@ -201,6 +201,7 @@ export const dict = { "ui.patch.action.created": "Oluşturuldu", "ui.patch.action.moved": "Taşındı", "ui.patch.action.patched": "Yamalandı", + "ui.patch.action.plan": "Plan", // kilocode_change "ui.question.subtitle.answered": "{{count}} yanıtlandı", "ui.question.answer.none": "(yanıt yok)", diff --git a/packages/ui/src/i18n/uk.ts b/packages/ui/src/i18n/uk.ts index a49abafbac..38333e9ed2 100644 --- a/packages/ui/src/i18n/uk.ts +++ b/packages/ui/src/i18n/uk.ts @@ -218,6 +218,7 @@ export const dict: Record = { "ui.patch.action.created": "Створено", "ui.patch.action.moved": "Переміщено", "ui.patch.action.patched": "Застосовано патч", + "ui.patch.action.plan": "План", // kilocode_change "ui.question.subtitle.answered": "{{count}} відповідей", "ui.question.answer.none": "(немає відповіді)", diff --git a/packages/ui/src/i18n/ur.ts b/packages/ui/src/i18n/ur.ts index 0019290f89..38a83c7bdb 100644 --- a/packages/ui/src/i18n/ur.ts +++ b/packages/ui/src/i18n/ur.ts @@ -189,6 +189,7 @@ export const dict: Record = { "ui.patch.action.created": "بنا دیا گیا", "ui.patch.action.moved": "منتقل کر دیا گیا", "ui.patch.action.patched": "پیچ کیا گیا", + "ui.patch.action.plan": "منصوبہ", // kilocode_change "ui.question.subtitle.answered": "{{count}} کے جواب دیے گئے", "ui.question.answer.none": "(کوئی جواب نہیں)", "ui.question.review.notAnswered": "(جواب نہیں دیا گیا)", diff --git a/packages/ui/src/i18n/vi.ts b/packages/ui/src/i18n/vi.ts index 7019868f60..7c43a8475b 100644 --- a/packages/ui/src/i18n/vi.ts +++ b/packages/ui/src/i18n/vi.ts @@ -188,6 +188,7 @@ export const dict: Record = { "ui.patch.action.created": "Đã tạo", "ui.patch.action.moved": "Đã di chuyển", "ui.patch.action.patched": "Đã vá", + "ui.patch.action.plan": "Kế hoạch", // kilocode_change "ui.question.subtitle.answered": "{{count}} đã trả lời", "ui.question.answer.none": "(không có câu trả lời)", "ui.question.review.notAnswered": "(chưa trả lời)", diff --git a/packages/ui/src/i18n/zh.ts b/packages/ui/src/i18n/zh.ts index eaca27f0b0..e3a84304be 100644 --- a/packages/ui/src/i18n/zh.ts +++ b/packages/ui/src/i18n/zh.ts @@ -197,6 +197,7 @@ export const dict = { "ui.patch.action.created": "已创建", "ui.patch.action.moved": "已移动", "ui.patch.action.patched": "已应用补丁", + "ui.patch.action.plan": "计划", // kilocode_change "ui.question.subtitle.answered": "已回答 {{count}} 个", "ui.question.answer.none": "(无答案)", diff --git a/packages/ui/src/i18n/zht.ts b/packages/ui/src/i18n/zht.ts index 27b7f8b9ac..43eddffe14 100644 --- a/packages/ui/src/i18n/zht.ts +++ b/packages/ui/src/i18n/zht.ts @@ -197,6 +197,7 @@ export const dict = { "ui.patch.action.created": "已建立", "ui.patch.action.moved": "已移動", "ui.patch.action.patched": "已套用修補", + "ui.patch.action.plan": "計畫", // kilocode_change "ui.question.subtitle.answered": "已回答 {{count}} 題", "ui.question.answer.none": "(無答案)", From 65a025b8dc43f82d31b04de16d51b1f4f0575bb6 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 19 Aug 2026 16:28:56 +0200 Subject: [PATCH 02/14] fix(vscode): keep document inspector lint compliant --- .../webview-ui/agent-manager/AgentManagerApp.tsx | 13 +++++-------- .../webview-ui/agent-manager/documents/state.ts | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 841c629225..745b3c7d83 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -298,7 +298,6 @@ const AgentManagerContent: Component = () => { const [sidePanel, setSidePanel] = createSignal(null) const diffOpen = () => sidePanel() === SidePanel.Diff const prOpen = () => sidePanel() === SidePanel.PR - const documentsOpen = () => sidePanel() === SidePanel.Documents const activePR = createMemo(() => { const selected = selection() if (!selected || selected === LOCAL) return undefined @@ -311,7 +310,7 @@ const AgentManagerContent: Component = () => { const diffLoading = diffs.diffLoading const setDiffLoading = diffs.setDiffLoading const diffNotices = diffs.diffNotices - // Diff, PR, terminal, and subagent views share one inspector width. + // Diff, PR, terminal, and subagent views share one inspector width. const [panelWidth, setPanelWidth] = createSignal(clampPanelWidth(persisted?.sidePanelWidth, window.innerWidth)) const resizeSide = createPanelResize(setPanelWidth, () => window.innerWidth) const showSideTerminal = () => { @@ -328,7 +327,7 @@ const AgentManagerContent: Component = () => { const documentInspector = createDocumentInspector( vscode, selection, - documentsOpen, + () => sidePanel() === SidePanel.Documents, () => { setHistory(false) setReviewActive(false) @@ -2253,8 +2252,6 @@ const AgentManagerContent: Component = () => { setSidePanel((prev) => (prev === SidePanel.Diff ? null : SidePanel.Diff)) } - const toggleDocumentsPanel = documentInspector.toggle - const renderTabById = (id: string) => renderTab(id, { terms, @@ -2451,9 +2448,9 @@ const AgentManagerContent: Component = () => { prStatus={() => activePR()?.pr} prOpen={prOpen} onTogglePR={togglePRPanel} - documentsOpen={documentsOpen} + documentsOpen={documentInspector.isOpen} documentsAvailable={documentInspector.available} - onToggleDocuments={toggleDocumentsPanel} + onToggleDocuments={documentInspector.toggle} terminalDestination={sideCtl.destination} terminalDestinationActive={() => sidePanel() === SidePanel.Terminal} terminalKeybind={() => kb().showTerminal ?? ""} @@ -2710,7 +2707,7 @@ const AgentManagerContent: Component = () => { inspector={documentInspector} onClosePanel={() => setSidePanel(null)} activeTerminalId={terms.activeId()} - visible={documentsOpen} + visible={documentInspector.isOpen} /> (isOpen() ? closePanel() : open()) - return { documents, comments, open, openFile, toggle, available } + return { documents, comments, open, openFile, toggle, available, isOpen } } export function handleDocumentOpen(event: Event, open: (file: string) => void): void { From 5aa2cb8a117cd80829edce2d1aa3c54a57cc92bc Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 19 Aug 2026 16:34:05 +0200 Subject: [PATCH 03/14] fix(vscode): reduce Agent Manager app lint size --- .../agent-manager/AgentManagerApp.tsx | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 745b3c7d83..86640e35e9 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -374,9 +374,6 @@ const AgentManagerContent: Component = () => { /** Namespace key so worktree/local ids from different projects never collide. */ const nsKey = (sel: string) => `${currentProjectId() ?? "single"}:${sel}` - // Per-sidebar-context terminal state. `terms.activeId` holds the id of the focused - // terminal tab, if any — takes precedence over session/pending/review when deriving - // the visible tab. Contexts are project-keyed: every project reuses LOCAL and ids like "0". const terms = createTerminalState(() => { const sel = selection() return sel === null ? null : nsKey(sel) @@ -949,9 +946,6 @@ const AgentManagerContent: Component = () => { if (el instanceof HTMLElement) scrollIntoView(el) } - // Sidebar previous/next + numeric-shortcut nav. Multi-project mode traverses - // every expanded project and atomically activates via activateSelection; - // single-project mode keeps the legacy in-process path. const projectNav = createProjectNav( { multiProject, @@ -1254,8 +1248,6 @@ const AgentManagerContent: Component = () => { if (["t", "w", "n", "d", "e", "f"].includes(e.key.toLowerCase()) && !e.shiftKey) { e.preventDefault() } - // Prevent browser defaults for shift variants (new terminal, close worktree, - // advanced/new/open worktree, open PR, terminal cycling) if (["t", "m", "w", "n", "o", "r", "[", "]"].includes(e.key.toLowerCase()) && e.shiftKey) { e.preventDefault() } @@ -1270,10 +1262,6 @@ const AgentManagerContent: Component = () => { } window.addEventListener("keydown", preventDefaults, true) - // Cmd/Ctrl+/ toggles the terminal even when VS Code's webview keybinding - // forwarding drops the key before it reaches the workbench (reported with - // the prompt input focused). When forwarding does work, the extension - // echoes the shortcut back as an action message and sideCtl dedupes it. const shortcut = (e: KeyboardEvent) => sideCtl.press(e) window.addEventListener("keydown", shortcut, true) @@ -1639,8 +1627,6 @@ const AgentManagerContent: Component = () => { } }) - // Diff context = sidebar selection (worktree id or LOCAL), stable across - // session tab switches inside the context so the git scopes don't refetch. const diffCtx = createMemo(() => selection() ?? undefined) // Active session within the diff context. The Session scope follows it, so @@ -1690,8 +1676,6 @@ const AgentManagerContent: Component = () => { /> ) - // Start/stop diff watch when the panel opens/closes, the review tab opens, - // or the composite id (context, scope, active session) changes. createEffect(() => { const panel = diffOpen() const active = reviewActive() @@ -2085,7 +2069,6 @@ const AgentManagerContent: Component = () => { closeReviewTab() } - // Drag-and-drop handlers for tab reordering const tabLookup = createMemo(() => new Map(activeTabs().map((s) => [s.id, s]))) const tabIds = createMemo(() => { const ids = activeTabs().map((s) => s.id) From d220415356bad2e5274bd0bbb2c3fedd19254442 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 19 Aug 2026 18:04:44 +0200 Subject: [PATCH 04/14] fix(vscode): address document inspector review findings --- .../src/agent-manager/AgentManagerProvider.ts | 2 +- .../kilo-vscode/src/agent-manager/types.ts | 2 + .../agent-manager/worktree-diff-controller.ts | 25 +++++--- .../src/diff/DiffViewerProvider.ts | 14 ++-- .../agent-manager/AgentManagerApp.tsx | 1 + .../agent-manager/documents/DocumentPanel.tsx | 21 +++++- .../agent-manager/documents/state.ts | 64 +++++++++++++------ .../diff-viewer/FullScreenDiffView.tsx | 15 +++-- .../src/types/messages/extension-messages.ts | 1 + .../src/types/messages/webview-messages.ts | 1 + 10 files changed, 101 insertions(+), 45 deletions(-) diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index ed59d29acb..283671df1a 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -869,7 +869,7 @@ export class AgentManagerProvider implements Disposable { this.openWorktreeFile(m.sessionId, m.filePath, m.line, m.column) return null } - if (m.type === "agentManager.requestDocument") return this.diffs.document(m.sessionId, m.file) + if (m.type === "agentManager.requestDocument") return this.diffs.document(m.sessionId, m.file, m.contextKey) } private onBridgeMessage(m: AgentManagerInMessage): Record | null | undefined { diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index 42743183b9..7baa490ce8 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -365,6 +365,7 @@ interface WorktreeDiffFileMessage { interface DocumentMessage { type: "agentManager.document" sessionId: string + contextKey?: string file: string requestedFile?: string content?: string @@ -804,6 +805,7 @@ interface RequestDocumentIn { type: "agentManager.requestDocument" sessionId: string file: string + contextKey?: string } // Pass-through messages intercepted for side effects diff --git a/packages/kilo-vscode/src/agent-manager/worktree-diff-controller.ts b/packages/kilo-vscode/src/agent-manager/worktree-diff-controller.ts index 80caa2bbbf..ba3278a61b 100644 --- a/packages/kilo-vscode/src/agent-manager/worktree-diff-controller.ts +++ b/packages/kilo-vscode/src/agent-manager/worktree-diff-controller.ts @@ -207,14 +207,23 @@ export class WorktreeDiffController { * controller's state/root context because a document read is a worktree file * read, resolved against the same directory the diff for that context uses. */ - public document(sessionId: string, file: string): null { - const state = this.ctx.getState() - const worktree = state?.getWorktree(sessionId) - const session = worktree ? undefined : state?.getSession(sessionId) - const root = - worktree?.path ?? (session?.worktreeId ? state?.getWorktree(session.worktreeId)?.path : this.ctx.getRoot()) - if (!root) return null - this.ctx.post({ type: "agentManager.document", sessionId, file, requestedFile: file, ...readDocument(root, file) }) + public document(sessionId: string, file: string, contextKey?: string): null { + void this.ready("stateReady rejected, continuing document resolve:").then(() => { + const state = this.ctx.getState() + const worktree = sessionId === LOCAL_DIFF_ID ? undefined : state?.getWorktree(sessionId) + const session = worktree || sessionId === LOCAL_DIFF_ID ? undefined : state?.getSession(sessionId) + const root = + sessionId === LOCAL_DIFF_ID + ? this.ctx.getRoot() + : (worktree?.path ?? + (session?.worktreeId + ? state?.getWorktree(session.worktreeId)?.path + : session + ? this.ctx.getRoot() + : undefined)) + const result = root ? readDocument(root, file) : { error: "The document context is no longer available." } + this.ctx.post({ type: "agentManager.document", sessionId, file, requestedFile: file, contextKey, ...result }) + }) return null } diff --git a/packages/kilo-vscode/src/diff/DiffViewerProvider.ts b/packages/kilo-vscode/src/diff/DiffViewerProvider.ts index b517e469a9..854b405dd9 100644 --- a/packages/kilo-vscode/src/diff/DiffViewerProvider.ts +++ b/packages/kilo-vscode/src/diff/DiffViewerProvider.ts @@ -56,10 +56,8 @@ export class DiffViewerProvider implements vscode.Disposable { if (this.panel && this.controller) { this.panel.reveal(this.panel.viewColumn ?? vscode.ViewColumn.One) void this.panel.webview.postMessage({ type: "diffViewer.initialFile", file: this.ctx.initialFile }) - void this.panel.webview.postMessage({ - type: "diffViewer.initialMarkdown", - render: this.ctx.initialMarkdown === true, - }) + if (this.ctx.initialMarkdown !== undefined) + void this.panel.webview.postMessage({ type: "diffViewer.initialMarkdown", render: this.ctx.initialMarkdown }) this.controller.setContext(this.ctx) const nextId = this.catalog.defaultSourceId(this.ctx) if (nextId && nextId !== this.controller.currentId) { @@ -99,7 +97,7 @@ export class DiffViewerProvider implements vscode.Disposable { dir, initialSourceId: turnInitialSourceId ?? arg?.initialSourceId, initialFile: arg?.file, - initialMarkdown: typeof arg?.file === "string" && /\.(md|mdx|markdown)$/i.test(arg.file), + ...(typeof arg?.file === "string" && /\.(md|mdx|markdown)$/i.test(arg.file) ? { initialMarkdown: true } : {}), hidePicker: !!turnInitialSourceId, }) } @@ -243,10 +241,8 @@ export class DiffViewerProvider implements vscode.Disposable { }) void this.panel.webview.postMessage({ type: "diffViewer.markdownRender", render: getDiffMarkdownRender() }) void this.panel.webview.postMessage({ type: "diffViewer.initialFile", file: this.ctx?.initialFile }) - void this.panel.webview.postMessage({ - type: "diffViewer.initialMarkdown", - render: this.ctx?.initialMarkdown === true, - }) + if (this.ctx?.initialMarkdown !== undefined) + void this.panel.webview.postMessage({ type: "diffViewer.initialMarkdown", render: this.ctx.initialMarkdown }) const initial = this.ctx ? this.catalog.defaultSourceId(this.ctx) : undefined if (initial) this.swap(initial) } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 86640e35e9..a6f3c9441c 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -327,6 +327,7 @@ const AgentManagerContent: Component = () => { const documentInspector = createDocumentInspector( vscode, selection, + currentProjectId, () => sidePanel() === SidePanel.Documents, () => { setHistory(false) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx index 6a5e1d5dbd..76b59b462a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx @@ -36,7 +36,7 @@ export interface DocumentPanelProps { onClose: (id: string) => void onCloseOthers: (id: string) => void onReorder: (from: string, to: string) => void - onOpenFile: (file: string, line?: number) => void + onOpenFile: (file: string, line?: number, column?: number) => void onClosePanel: () => void activeTerminalId?: string visible: Accessor @@ -166,9 +166,10 @@ export const DocumentPanel: Component = (props) => { createEffect( on( - () => [file(), content(), props.comments] as const, + () => [file(), content(), props.comments, data()?.loading, data()?.error] as const, ([path, text, current]) => { if (!path) return + if (data()?.loading || data()?.error || data()?.content === undefined) return const max = lineCount(text) const valid = current.filter((item) => item.file !== path || (item.line >= 1 && item.line <= max)) if (valid.length !== current.length) updateComments(valid) @@ -178,6 +179,20 @@ export const DocumentPanel: Component = (props) => { { defer: true }, ), ) + createEffect( + on( + () => [file(), props.tabs()] as const, + () => { + setDraft(null) + setEditing(null) + draftMeta = null + editMeta = null + composer.draft = null + composer.edit = null + }, + { defer: true }, + ), + ) const close = (id: string, focus: { restore: () => void }) => { props.onClose(id) @@ -218,7 +233,7 @@ export const DocumentPanel: Component = (props) => { size="small" variant="ghost" label={t("agentManager.diff.openFile")} - onClick={() => props.onOpenFile(file())} + onClick={() => props.onOpenFile(file(), selected()?.line, selected()?.column)} /> diff --git a/packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts b/packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts index 6ee0490015..cc0aee6c1a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts @@ -6,6 +6,8 @@ import type { AgentManagerDocumentMessage } from "../../src/types/messages" export interface DocumentTab { id: string file: string + line?: number + column?: number } export interface DocumentData { @@ -26,7 +28,11 @@ export function isMarkdownPath(file: string): boolean { return /\.(md|mdx|markdown)$/i.test(file) } -export function createDocuments(vscode: ReturnType, context: Accessor) { +export function createDocuments( + vscode: ReturnType, + context: Accessor, + session: Accessor = context, +) { const [tabs, setTabs] = createSignal>({}) const [active, setActive] = createSignal>({}) const [data, setData] = createSignal>({}) @@ -36,24 +42,28 @@ export function createDocuments(vscode: ReturnType, context: A const selected = () => active()[current()] const document = (file: string) => data()[key(current(), file)] - const request = (file: string) => { + const request = (file: string, sessionId = session() ?? "") => { const ctx = current() if (!ctx) return const id = key(ctx, file) setData((prev) => ({ ...prev, [id]: { ...(prev[id] ?? { file }), file, loading: true, error: undefined } })) - vscode.postMessage({ type: "agentManager.requestDocument", sessionId: ctx, file }) + vscode.postMessage({ type: "agentManager.requestDocument", sessionId, file, contextKey: ctx }) } - const open = (file: string) => { + const open = (file: string, sessionId = current(), line?: number, column?: number) => { const ctx = current() if (!ctx || !file) return setTabs((prev) => { const list = prev[ctx] ?? [] - if (list.some((tab) => tab.file === file)) return prev - return { ...prev, [ctx]: [...list, { id: key(ctx, file), file }] } + const id = key(ctx, file) + if (list.some((tab) => tab.id === id)) { + return { ...prev, [ctx]: list.map((tab) => (tab.id === id ? { ...tab, line, column } : tab)) } + } + return { ...prev, [ctx]: [...list, { id, file, line, column }] } }) setActive((prev) => ({ ...prev, [ctx]: key(ctx, file) })) - request(file) + request(file, sessionId) + return true } const select = (id: string) => { @@ -98,7 +108,7 @@ export function createDocuments(vscode: ReturnType, context: A } const onMessage = (message: AgentManagerDocumentMessage) => { - const id = key(message.sessionId, message.requestedFile ?? message.file) + const id = key(message.contextKey ?? message.sessionId, message.requestedFile ?? message.file) setData((prev) => ({ ...prev, [id]: { @@ -133,13 +143,24 @@ export function createDocumentComments(context: Accessor) { export function createDocumentInspector( vscode: ReturnType, context: Accessor, + project: Accessor, isOpen: Accessor, openPanel: () => void, closePanel: () => void, ) { - const documents = createDocuments(vscode, context) - const comments = createDocumentComments(context) - const open = (file?: string) => (openPanel(), file ? documents.open(file) : undefined) + const scope = () => `${project() ?? "single"}:${context() ?? ""}` + const documents = createDocuments(vscode, scope, context) + const comments = createDocumentComments(scope) + const open = (file?: string, sessionId?: string, line?: number, column?: number) => { + if (!file) { + openPanel() + return true + } + const sid = sessionId ?? context() + if (!sid || !documents.open(file, sid, line, column)) return false + openPanel() + return true + } onMount(() => { const handler = (event: Event) => handleDocumentOpen(event, open) const message = vscode.onMessage((item) => { @@ -156,17 +177,24 @@ export function createDocumentInspector( // Tabs are keyed per worktree, so this hides itself on a worktree with none, // and stays visible while the panel is open so it can still be toggled shut. const available = () => documents.tabs().length > 0 || isOpen() - const openFile = (file: string, line?: number) => { + const openFile = (file: string, line?: number, column?: number) => { const sessionId = context() - if (sessionId) vscode.postMessage({ type: "agentManager.openFile", sessionId, filePath: file, line }) + if (sessionId) vscode.postMessage({ type: "agentManager.openFile", sessionId, filePath: file, line, column }) } const toggle = () => (isOpen() ? closePanel() : open()) - return { documents, comments, open, openFile, toggle, available, isOpen } + return { documents, comments, open, openFile, toggle, available, isOpen, scope } } -export function handleDocumentOpen(event: Event, open: (file: string) => void): void { - const file = (event as CustomEvent<{ filePath?: unknown }>).detail?.filePath +export function handleDocumentOpen( + event: Event, + open: (file: string, sessionId?: string, line?: number, column?: number) => boolean, +): void { + const detail = (event as CustomEvent<{ filePath?: unknown; sessionID?: unknown; line?: unknown; column?: unknown }>) + .detail + const file = detail?.filePath if (typeof file !== "string" || !file) return - event.preventDefault() - open(file) + const sessionId = typeof detail.sessionID === "string" ? detail.sessionID : undefined + const line = typeof detail.line === "number" ? detail.line : undefined + const column = typeof detail.column === "number" ? detail.column : undefined + if (open(file, sessionId, line, column)) event.preventDefault() } diff --git a/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx b/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx index 1e538cca0a..3d2e11faf7 100644 --- a/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx +++ b/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx @@ -161,6 +161,7 @@ export const FullScreenDiffView: Component = (props) => // Initialize each worktree with every file expanded, then preserve manual // collapse state while adding and removing files from live summaries. let initializedKey: string | undefined + let initialFileKey: string | undefined let known = new Set() let rootRef: HTMLDivElement | undefined const [scroller, setScroller] = createSignal() @@ -221,7 +222,7 @@ export const FullScreenDiffView: Component = (props) => // vs when we just prune stale entries from the open list. createEffect( on( - () => [props.sessionKey, props.diffs] as const, + () => [props.sessionKey, props.diffs, props.initialFile] as const, ([key, diffs]) => { if (diffs.length === 0) { // No diffs yet — clear active file only for a new key; keep current @@ -234,11 +235,13 @@ export const FullScreenDiffView: Component = (props) => // Keep active file in sync — pick first if current is stale const current = activeFile() - if (!current || !diffs.some((d) => d.file === current)) { - const initial = - props.initialFile && diffs.some((d) => d.file === props.initialFile) ? props.initialFile : undefined - setActiveFile(initial ?? diffs[0]!.file) - } + const initial = + props.initialFile && diffs.some((d) => d.file === props.initialFile) ? props.initialFile : undefined + const initialKey = initial ? `${key}:${initial}` : undefined + if (initial && initial !== current && initialFileKey !== initialKey) { + initialFileKey = initialKey + setActiveFile(initial) + } else if (!current || !diffs.some((d) => d.file === current)) setActiveFile(diffs[0]!.file) // New context: initialize open state from the diff policy. if (key !== initializedKey) { 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 c63638d8e5..2a93c8a60e 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 @@ -980,6 +980,7 @@ export interface AgentManagerWorktreeDiffFileMessage { export interface AgentManagerDocumentMessage { type: "agentManager.document" sessionId: string + contextKey?: string file: string requestedFile?: string content?: string 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 d058873d29..8d80003571 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 @@ -873,6 +873,7 @@ export interface AgentManagerRequestDocumentMessage { type: "agentManager.requestDocument" sessionId: string file: string + contextKey?: string } // Create multiple worktree sessions for the same prompt (multi-version mode) From 056db466132115ae632d27f1863115fc078562cc Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Wed, 19 Aug 2026 19:28:16 +0200 Subject: [PATCH 05/14] test(vscode): cover document worktree isolation --- .../unit/agent-manager-documents.test.ts | 72 +++++++++++++++++++ .../agent-manager/documents/state.ts | 2 +- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 packages/kilo-vscode/tests/unit/agent-manager-documents.test.ts diff --git a/packages/kilo-vscode/tests/unit/agent-manager-documents.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-documents.test.ts new file mode 100644 index 0000000000..78e422ffe9 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/agent-manager-documents.test.ts @@ -0,0 +1,72 @@ +import { describe, expect, it } from "bun:test" +import { createRoot, createSignal } from "solid-js" +import { createDocumentComments, createDocuments } from "../../webview-ui/agent-manager/documents/state" +import type { AgentManagerDocumentMessage } from "../../webview-ui/src/types/messages" + +describe("Agent Manager document state", () => { + it("keeps tabs, content, and comments scoped when switching worktrees and projects", () => { + createRoot((dispose) => { + const sent: unknown[] = [] + const vscode = { postMessage: (message: unknown) => sent.push(message) } as Parameters[0] + const [scope, setScope] = createSignal("project-a:wt-a") + const [session, setSession] = createSignal("ses-a") + const docs = createDocuments(vscode, scope, session) + const comments = createDocumentComments(scope) + + docs.open("plans/plan.md") + expect(sent[0]).toMatchObject({ sessionId: "ses-a", contextKey: "project-a:wt-a" }) + docs.onMessage({ + type: "agentManager.document", + sessionId: "ses-a", + contextKey: "project-a:wt-a", + requestedFile: "plans/plan.md", + file: "plans/plan.md", + kind: "text", + content: "# Worktree A", + } satisfies AgentManagerDocumentMessage) + comments.setComments([{ id: "a", file: "plans/plan.md", side: "additions", line: 1, comment: "A" }]) + + setScope("project-a:wt-b") + setSession("ses-b") + expect(docs.tabs()).toEqual([]) + expect(docs.document("plans/plan.md")).toBeUndefined() + expect(comments.comments()).toEqual([]) + + docs.onMessage({ + type: "agentManager.document", + sessionId: "ses-a", + contextKey: "project-a:wt-a", + requestedFile: "plans/late.md", + file: "plans/late.md", + kind: "text", + content: "# Late A", + } satisfies AgentManagerDocumentMessage) + expect(docs.tabs()).toEqual([]) + expect(docs.document("plans/late.md")).toBeUndefined() + + docs.open("plans/plan.md") + expect(sent[1]).toMatchObject({ sessionId: "ses-b", contextKey: "project-a:wt-b" }) + docs.onMessage({ + type: "agentManager.document", + sessionId: "ses-b", + contextKey: "project-a:wt-b", + requestedFile: "plans/plan.md", + file: "plans/plan.md", + kind: "text", + content: "# Worktree B", + } satisfies AgentManagerDocumentMessage) + comments.setComments([{ id: "b", file: "plans/plan.md", side: "additions", line: 1, comment: "B" }]) + + setScope("project-a:wt-a") + setSession("ses-a") + expect(docs.tabs()).toHaveLength(1) + expect(docs.document("plans/plan.md")?.content).toBe("# Worktree A") + expect(comments.comments().map((item) => item.comment)).toEqual(["A"]) + + setScope("project-b:wt-a") + expect(docs.tabs()).toEqual([]) + expect(comments.comments()).toEqual([]) + dispose() + }) + }) +}) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts b/packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts index cc0aee6c1a..10c46fec80 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts @@ -50,7 +50,7 @@ export function createDocuments( vscode.postMessage({ type: "agentManager.requestDocument", sessionId, file, contextKey: ctx }) } - const open = (file: string, sessionId = current(), line?: number, column?: number) => { + const open = (file: string, sessionId = session() ?? "", line?: number, column?: number) => { const ctx = current() if (!ctx || !file) return setTabs((prev) => { From 9fbb7a1aacc818afad90ff10857927bdb48a53ab Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 20 Aug 2026 10:21:47 +0200 Subject: [PATCH 06/14] fix(vscode): open review comment files directly --- .../src/components/chat/ReviewComments.tsx | 62 +++---------------- 1 file changed, 7 insertions(+), 55 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ReviewComments.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ReviewComments.tsx index ccc0960498..8a2d3afce8 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ReviewComments.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ReviewComments.tsx @@ -1,13 +1,8 @@ import { For, Show, type Component } from "solid-js" import { Button } from "@kilocode/kilo-ui/button" -import { Dialog } from "@kilocode/kilo-ui/dialog" import { Icon } from "@kilocode/kilo-ui/icon" -import { IconButton } from "@kilocode/kilo-ui/icon-button" -import { Tooltip } from "@kilocode/kilo-ui/tooltip" -import { useDialog } from "@kilocode/kilo-ui/context/dialog" import { useLanguage } from "../../context/language" import { useVSCode } from "../../context/vscode" -import { useWorktreeMode } from "../../context/worktree-mode" import type { ReviewComment } from "../../types/messages" import { fileName } from "./prompt-input-utils" @@ -22,58 +17,15 @@ interface ReviewCommentsProps { export const ReviewComments: Component = (props) => { const language = useLanguage() const vscode = useVSCode() - const worktree = useWorktreeMode() - const dialog = useDialog() const side = (item: ReviewComment) => (item.side === "deletions" ? "-" : "+") - const title = (item: ReviewComment) => `${fileName(item.file)} ${side(item)}${item.line}` const open = (item: ReviewComment) => { - if (worktree && props.sessionID) { - vscode.postMessage({ - type: "agentManager.openFile", - sessionId: props.sessionID, - filePath: item.file, - line: item.line, - }) - dialog.close() - return - } - vscode.postMessage({ type: "openFile", filePath: item.file, line: item.line, column: 1 }) - dialog.close() - } - - const show = (item: ReviewComment) => { - dialog.show(() => ( - -
-
- {title(item)} - - open(item)} - /> - -
- -
- {language.t("agentManager.review.metaFile")} - {item.file} - {language.t("agentManager.review.metaLine")} - L{item.line} - {language.t("agentManager.review.metaComment")} - {item.comment} -
- - -
{item.selectedText}
-
-
-
- )) + const event = new CustomEvent("kilo:open-file", { + cancelable: true, + detail: { filePath: item.file, line: item.line, column: 1, sessionID: props.sessionID }, + }) + if (window.dispatchEvent(event)) + vscode.postMessage({ type: "openFile", filePath: item.file, line: item.line, column: 1 }) } return ( @@ -96,7 +48,7 @@ export const ReviewComments: Component = (props) => { {(item) => (
- From 9026260650cf5609e6a02de9b89ae691434a3ae4 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 20 Aug 2026 10:57:54 +0200 Subject: [PATCH 08/14] fix(vscode): preserve document review keyboard flow --- .../agent-manager/documents/DocumentPanel.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx index f0642096d2..8dc8f26280 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx @@ -112,6 +112,11 @@ export const DocumentPanel: Component = (props) => { const updateComments = (next: ReviewComment[]) => props.onCommentsChange(next) const comments = () => props.comments.filter((item) => item.file === file()) + const focusRoot = () => { + requestAnimationFrame(() => { + requestAnimationFrame(() => rootRef?.focus()) + }) + } const addComment = (path: string, side: AnnotationSide, line: number, text: string, selectedText: string) => { updateComments([ ...props.comments, @@ -120,6 +125,7 @@ export const DocumentPanel: Component = (props) => { setDraft(null) draftMeta = null composer.draft = null + focusRoot() } const sendComment = (path: string, side: AnnotationSide, line: number, text: string, selectedText: string) => { sendReviewComments( @@ -129,23 +135,27 @@ export const DocumentPanel: Component = (props) => { setDraft(null) draftMeta = null composer.draft = null + focusRoot() } const updateComment = (id: string, text: string) => { updateComments(props.comments.map((item) => (item.id === id ? { ...item, comment: text } : item))) setEditing(null) editMeta = null composer.edit = null + focusRoot() } const deleteComment = (id: string) => { updateComments(props.comments.filter((item) => item.id !== id)) setEditing(null) editMeta = null composer.edit = null + focusRoot() } const cancelDraft = () => { setDraft(null) draftMeta = null composer.draft = null + focusRoot() } const annotations = (): DiffLineAnnotation[] => { const result = buildFileAnnotations(file(), comments(), editing(), draft(), draftMeta, editMeta) @@ -183,10 +193,6 @@ export const DocumentPanel: Component = (props) => { focusRoot() } - const focusRoot = () => { - requestAnimationFrame(() => rootRef?.focus()) - } - createEffect( on( () => [file(), content(), props.comments, data()?.loading, data()?.error] as const, From 55333d4a12a1af8045f07c0dc0d66b79201bf3a7 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 20 Aug 2026 11:07:09 +0200 Subject: [PATCH 09/14] fix(vscode): restore prompt focus after document review --- .../kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx | 1 + .../kilo-vscode/webview-ui/agent-manager/agent-manager.css | 1 + .../webview-ui/agent-manager/documents/DocumentPanel.tsx | 4 +++- .../webview-ui/agent-manager/documents/DocumentPanelHost.tsx | 2 ++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 79a78a08ba..d6a7eabc16 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -2707,6 +2707,7 @@ const AgentManagerContent: Component = () => { setSidePanel(null)} + onSendAll={focusCtl.focus} activeTerminalId={terms.activeId()} visible={documentInspector.isOpen} /> 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 d9269755c8..2fcfc501db 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -1952,6 +1952,7 @@ body.am-wt-dragging-active * { } .am-diff-panel:focus, +.am-document-panel:focus, .am-review-layout:focus { outline: none; } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx index 8dc8f26280..f4fd7c90da 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx @@ -38,6 +38,7 @@ export interface DocumentPanelProps { onReorder: (from: string, to: string) => void onOpenFile: (file: string, line?: number, column?: number) => void onClosePanel: () => void + onSendAll?: () => void activeTerminalId?: string visible: Accessor } @@ -190,7 +191,8 @@ export const DocumentPanel: Component = (props) => { if (props.comments.length === 0) return sendReviewComments(props.comments, props.activeTerminalId) updateComments([]) - focusRoot() + if (props.onSendAll) props.onSendAll() + else focusRoot() } createEffect( diff --git a/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanelHost.tsx b/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanelHost.tsx index 48df0518d0..2717e1434e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanelHost.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanelHost.tsx @@ -5,6 +5,7 @@ import { createDocumentInspector } from "./state" interface Props { inspector: ReturnType onClosePanel: () => void + onSendAll?: () => void activeTerminalId?: string visible: Accessor } @@ -22,6 +23,7 @@ export const DocumentPanelHost: Component = (props) => ( onReorder={props.inspector.documents.reorder} onOpenFile={props.inspector.openFile} onClosePanel={props.onClosePanel} + onSendAll={props.onSendAll} activeTerminalId={props.activeTerminalId} visible={props.visible} /> From 8e26de0f182ee50954fb02ed19a94c52e5d1ca68 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 20 Aug 2026 12:12:19 +0200 Subject: [PATCH 10/14] fix(vscode): preserve review comment session fallback --- .../webview-ui/src/components/chat/ReviewComments.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ReviewComments.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ReviewComments.tsx index e73b88ddd8..56412887c4 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ReviewComments.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ReviewComments.tsx @@ -42,7 +42,13 @@ export const ReviewComments: Component = (props) => { detail: { filePath: item.file, line: item.line, column: 1, sessionID: props.sessionID }, }) if (window.dispatchEvent(event)) - vscode.postMessage({ type: "openFile", filePath: item.file, line: item.line, column: 1 }) + vscode.postMessage({ + type: "openFile", + filePath: item.file, + line: item.line, + column: 1, + sessionID: props.sessionID, + }) dialog.close() } From 3be279fec53da257a6027ed5fd86970bd4e7f6af Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 20 Aug 2026 13:10:03 +0200 Subject: [PATCH 11/14] feat(vscode): reuse document viewer for sidebar files --- packages/kilo-vscode/esbuild.js | 1 + .../kilo-vscode/src/DocumentViewerProvider.ts | 151 ++++++++++++++++++ packages/kilo-vscode/src/KiloProvider.ts | 13 +- .../agent-manager/worktree-diff-controller.ts | 2 +- .../document-reader.ts | 0 packages/kilo-vscode/src/extension.ts | 16 ++ .../tests/unit/agent-manager-arch.test.ts | 3 +- .../unit/agent-manager-documents.test.ts | 2 +- .../tests/unit/agent-manager-i18n.test.ts | 3 +- .../tests/unit/document-reader.test.ts | 2 +- .../agent-manager/AgentManagerApp.tsx | 2 +- .../documents/DocumentPanelHost.tsx | 4 +- .../documents/DocumentPanel.tsx | 16 +- .../webview-ui/documents/index.tsx | 87 ++++++++++ .../{agent-manager => }/documents/state.ts | 34 ++-- .../src/types/messages/extension-messages.ts | 24 +++ .../src/types/messages/webview-messages.ts | 28 ++++ 17 files changed, 358 insertions(+), 30 deletions(-) create mode 100644 packages/kilo-vscode/src/DocumentViewerProvider.ts rename packages/kilo-vscode/src/{agent-manager => documents}/document-reader.ts (100%) rename packages/kilo-vscode/webview-ui/{agent-manager => }/documents/DocumentPanel.tsx (96%) create mode 100644 packages/kilo-vscode/webview-ui/documents/index.tsx rename packages/kilo-vscode/webview-ui/{agent-manager => }/documents/state.ts (86%) diff --git a/packages/kilo-vscode/esbuild.js b/packages/kilo-vscode/esbuild.js index 2fa1876afd..ec5b59f98b 100644 --- a/packages/kilo-vscode/esbuild.js +++ b/packages/kilo-vscode/esbuild.js @@ -275,6 +275,7 @@ function getWebviewsConfig() { kiloclaw: "webview-ui/kiloclaw/index.tsx", marketplace: "webview-ui/marketplace/index.tsx", "diff-viewer": "webview-ui/diff-viewer/index.tsx", + documents: "webview-ui/documents/index.tsx", "diff-virtual": "webview-ui/diff-virtual/index.tsx", webview: "webview-ui/src/index.tsx", }, diff --git a/packages/kilo-vscode/src/DocumentViewerProvider.ts b/packages/kilo-vscode/src/DocumentViewerProvider.ts new file mode 100644 index 0000000000..8390ded5ff --- /dev/null +++ b/packages/kilo-vscode/src/DocumentViewerProvider.ts @@ -0,0 +1,151 @@ +import * as vscode from "vscode" +import type { ReviewCommentEntry } from "./shared/review-comments" +import { readDocument } from "./documents/document-reader" +import { buildWebviewHtml } from "./utils" +import type { KiloConnectionService } from "./services/cli-backend" + +interface Context { + sessionId?: string + directory?: string +} + +type Comment = ReviewCommentEntry + +export interface DocumentViewerOptions { + onComments: (comments: Comment[], autoSend: boolean) => void +} + +export class DocumentViewerProvider implements vscode.Disposable { + public static readonly viewType = "kilo-code.new.DocumentsPanel" + private panel: vscode.WebviewPanel | undefined + private pending: { file: string; sessionId?: string; directory?: string; line?: number; column?: number } | undefined + private readonly contexts = new Map() + private currentKey = "" + private readonly disposables: vscode.Disposable[] = [] + + constructor( + private readonly extensionUri: vscode.Uri, + private readonly connection: KiloConnectionService, + private readonly options: DocumentViewerOptions, + ) {} + + openFromCommand(input: { + file: string + sessionId?: string + directory?: string + line?: number + column?: number + }): void { + const contextKey = this.contextKey(input.sessionId, input.directory) + this.contexts.set(contextKey, { sessionId: input.sessionId, directory: input.directory }) + this.currentKey = contextKey + const next = { ...input, contextKey } + if (this.panel) { + this.panel.reveal(this.panel.viewColumn ?? vscode.ViewColumn.One) + void this.panel.webview.postMessage({ type: "document.open", ...next }) + return + } + this.pending = input + this.createPanel() + } + + dispose(): void { + this.panel?.dispose() + for (const item of this.disposables) item.dispose() + this.disposables.length = 0 + this.contexts.clear() + } + + private contextKey(sessionId?: string, directory?: string): string { + return `${sessionId ?? "local"}:${directory ?? ""}` + } + + private createPanel(): void { + const panel = vscode.window.createWebviewPanel( + DocumentViewerProvider.viewType, + "Documents", + vscode.ViewColumn.One, + { + enableScripts: true, + retainContextWhenHidden: true, + localResourceRoots: [this.extensionUri], + }, + ) + panel.iconPath = { + light: vscode.Uri.joinPath(this.extensionUri, "assets", "icons", "kilo-light.svg"), + dark: vscode.Uri.joinPath(this.extensionUri, "assets", "icons", "kilo-dark.svg"), + } + panel.webview.html = this.html(panel.webview) + this.panel = panel + this.disposables.push( + panel.webview.onDidReceiveMessage((message) => this.message(message as Record)), + panel.onDidDispose(() => { + this.panel = undefined + this.pending = undefined + this.currentKey = "" + }), + ) + } + + private message(message: Record): void { + if (message.type === "webviewReady") return this.ready() + if (message.type === "document.request") { + return this.request(message) + } + if (message.type === "document.sendComments" && Array.isArray(message.comments)) { + this.options.onComments(message.comments as Comment[], message.autoSend === true) + return + } + if (message.type === "document.openFile" && typeof message.file === "string") { + const context = this.contexts.get(this.currentKey) + this.openFromCommand({ + file: message.file, + sessionId: context?.sessionId, + directory: context?.directory, + line: typeof message.line === "number" ? message.line : undefined, + column: typeof message.column === "number" ? message.column : undefined, + }) + return + } + if (message.type === "document.close") this.panel?.dispose() + } + + private ready(): void { + if (!this.pending || !this.panel) return + const input = this.pending + this.pending = undefined + void this.panel.webview.postMessage({ + type: "document.open", + ...input, + contextKey: this.contextKey(input.sessionId, input.directory), + }) + } + + private request(message: Record): void { + const file = typeof message.file === "string" ? message.file : undefined + const contextKey = typeof message.contextKey === "string" ? message.contextKey : undefined + if (!file || !contextKey) return + const context = this.contexts.get(contextKey) + const result = context?.directory + ? readDocument(context.directory, file) + : { error: "The document context is no longer available." } + this.panel?.webview.postMessage({ + type: "document.result", + sessionId: context?.sessionId ?? "", + contextKey, + requestedFile: file, + ...result, + }) + } + + private html(webview: vscode.Webview): string { + return buildWebviewHtml(webview, { + scriptUri: webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, "dist", "documents.js")), + styleUri: webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, "dist", "documents.css")), + iconsBaseUri: webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, "assets", "icons")), + workerUri: webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, "dist", "shiki-worker.js")), + title: "Documents", + port: this.connection.getServerInfo()?.port, + }) + } +} diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index d9e144a530..58db489f1d 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -465,6 +465,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper private diffVirtualProvider: import("./DiffVirtualProvider").DiffVirtualProvider | undefined private diffViewerProvider: import("./diff/DiffViewerProvider").DiffViewerProvider | undefined + private documentViewerProvider: import("./DocumentViewerProvider").DocumentViewerProvider | undefined private remoteService: RemoteStatusService | null = null private unsubscribeRemote: (() => void) | null = null @@ -566,6 +567,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper this.diffViewerProvider = provider } + public setDocumentViewerProvider(provider: import("./DocumentViewerProvider").DocumentViewerProvider): void { + this.documentViewerProvider = provider + } + getTelemetryProperties(): Record { return { appName: "kilo-code", @@ -1598,8 +1603,12 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper dir: (sessionID) => this.getWorkspaceDirectory(sessionID ?? this.currentSession?.id), diff: this.diffVirtualProvider, openMarkdown: (file, sessionID) => { - if (!this.diffViewerProvider) return false - this.diffViewerProvider.openFromCommand({ sessionId: sessionID, file }) + if (!this.documentViewerProvider) return false + this.documentViewerProvider.openFromCommand({ + sessionId: sessionID, + directory: this.getWorkspaceDirectory(sessionID ?? this.currentSession?.id), + file, + }) return true }, storage: this.extensionContext?.globalStorageUri, diff --git a/packages/kilo-vscode/src/agent-manager/worktree-diff-controller.ts b/packages/kilo-vscode/src/agent-manager/worktree-diff-controller.ts index ba3278a61b..d4ca705992 100644 --- a/packages/kilo-vscode/src/agent-manager/worktree-diff-controller.ts +++ b/packages/kilo-vscode/src/agent-manager/worktree-diff-controller.ts @@ -8,7 +8,7 @@ import type { ApplyConflict, GitOps } from "./GitOps" import { shouldStopDiffPolling } from "./delete-worktree" import { remoteRef, type ManagedSession, type WorktreeStateManager } from "./WorktreeStateManager" import { parseDiffId, scopeToSourceId } from "./diff-scope" -import { readDocument } from "./document-reader" +import { readDocument } from "../documents/document-reader" import type { AgentManagerOutMessage, WorktreeDiffEntry } from "./types" const LOCAL_DIFF_ID = "local" as const diff --git a/packages/kilo-vscode/src/agent-manager/document-reader.ts b/packages/kilo-vscode/src/documents/document-reader.ts similarity index 100% rename from packages/kilo-vscode/src/agent-manager/document-reader.ts rename to packages/kilo-vscode/src/documents/document-reader.ts diff --git a/packages/kilo-vscode/src/extension.ts b/packages/kilo-vscode/src/extension.ts index 4ca34b3791..1de4f7f6d6 100644 --- a/packages/kilo-vscode/src/extension.ts +++ b/packages/kilo-vscode/src/extension.ts @@ -4,6 +4,7 @@ import { AgentManagerProvider } from "./agent-manager/AgentManagerProvider" import { VscodeHost } from "./agent-manager/vscode-host" import { KiloClawProvider } from "./kiloclaw/KiloClawProvider" import { DiffViewerProvider } from "./diff/DiffViewerProvider" +import { DocumentViewerProvider } from "./DocumentViewerProvider" import { DiffSourceCatalog } from "./diff/sources/catalog" import { DiffVirtualProvider } from "./DiffVirtualProvider" import { SettingsEditorProvider } from "./SettingsEditorProvider" @@ -281,6 +282,12 @@ export function activate(context: vscode.ExtensionContext) { provider.setDiffViewerProvider(diffViewerProvider) context.subscriptions.push(diffViewerProvider) + const documentViewerProvider = new DocumentViewerProvider(context.extensionUri, connectionService, { + onComments: (comments, autoSend) => void provider.appendReviewComments(comments, autoSend), + }) + provider.setDocumentViewerProvider(documentViewerProvider) + context.subscriptions.push(documentViewerProvider) + // Create diff virtual provider (lightweight single-file diff for permission approval) const diffVirtualProvider = new DiffVirtualProvider(context.extensionUri) provider.setDiffVirtualProvider(diffVirtualProvider) @@ -326,6 +333,15 @@ export function activate(context: vscode.ExtensionContext) { }), ) + context.subscriptions.push( + vscode.window.registerWebviewPanelSerializer(DocumentViewerProvider.viewType, { + deserializeWebviewPanel(panel: vscode.WebviewPanel) { + panel.dispose() + return Promise.resolve() + }, + }), + ) + context.subscriptions.push( vscode.window.registerWebviewPanelSerializer(DiffViewerProvider.viewType, { deserializeWebviewPanel(panel: vscode.WebviewPanel) { diff --git a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts index fb9112ae3b..5f0f1df1d5 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts @@ -27,8 +27,7 @@ const TSX_FILES = [ path.join(ROOT, "webview-ui/agent-manager/ProjectSelect.tsx"), path.join(ROOT, "webview-ui/agent-manager/sortable-tab.tsx"), path.join(ROOT, "webview-ui/agent-manager/DiffPanel.tsx"), - path.join(ROOT, "webview-ui/agent-manager/documents/DocumentPanel.tsx"), - path.join(ROOT, "webview-ui/agent-manager/documents/DocumentPanelHost.tsx"), + path.join(ROOT, "webview-ui/documents/DocumentPanel.tsx"), path.join(ROOT, "webview-ui/diff-viewer/FullScreenDiffView.tsx"), path.join(ROOT, "webview-ui/diff-viewer/ImageDiffView.tsx"), path.join(ROOT, "webview-ui/diff-viewer/MarkdownDiffView.tsx"), diff --git a/packages/kilo-vscode/tests/unit/agent-manager-documents.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-documents.test.ts index 78e422ffe9..4d73a993e9 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-documents.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-documents.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "bun:test" import { createRoot, createSignal } from "solid-js" -import { createDocumentComments, createDocuments } from "../../webview-ui/agent-manager/documents/state" +import { createDocumentComments, createDocuments } from "../../webview-ui/documents/state" import type { AgentManagerDocumentMessage } from "../../webview-ui/src/types/messages" describe("Agent Manager document state", () => { diff --git a/packages/kilo-vscode/tests/unit/agent-manager-i18n.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-i18n.test.ts index bf9cbea424..50f7958210 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-i18n.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-i18n.test.ts @@ -28,8 +28,7 @@ const TSX_FILES = [ path.join(ROOT, "webview-ui/agent-manager/sortable-tab.tsx"), path.join(ROOT, "webview-ui/agent-manager/terminal/SideTerminalPanel.tsx"), path.join(ROOT, "webview-ui/agent-manager/terminal/TerminalDestinationButton.tsx"), - path.join(ROOT, "webview-ui/agent-manager/documents/DocumentPanel.tsx"), - path.join(ROOT, "webview-ui/agent-manager/documents/DocumentPanelHost.tsx"), + path.join(ROOT, "webview-ui/documents/DocumentPanel.tsx"), ] /** diff --git a/packages/kilo-vscode/tests/unit/document-reader.test.ts b/packages/kilo-vscode/tests/unit/document-reader.test.ts index 3de360d0ba..114674c00c 100644 --- a/packages/kilo-vscode/tests/unit/document-reader.test.ts +++ b/packages/kilo-vscode/tests/unit/document-reader.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "bun:test" import * as fs from "node:fs" import * as os from "node:os" import * as path from "node:path" -import { readDocument } from "../../src/agent-manager/document-reader" +import { readDocument } from "../../src/documents/document-reader" function workspace() { return fs.mkdtempSync(path.join(os.tmpdir(), "kilo-document-")) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 0ffdb78674..f520353b59 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -180,7 +180,7 @@ import { setTabWidths } from "./tab-widths" import { clampPanelWidth, createPanelResize, maxPanelWidth, minPanelWidth, SidePanel } from "./side-panel-layout" import { SubagentPanel } from "./SubagentPanel" import { DocumentPanelHost } from "./documents/DocumentPanelHost" -import { createDocumentInspector } from "./documents/state" +import { createDocumentInspector } from "../documents/state" import { createSubagentController } from "./subagent-tabs" import { buildShortcutCategories } from "./shortcuts" import { tracker } from "./telemetry" diff --git a/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanelHost.tsx b/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanelHost.tsx index 2717e1434e..4c96ca0f91 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanelHost.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanelHost.tsx @@ -1,6 +1,6 @@ import type { Accessor, Component } from "solid-js" -import { DocumentPanel } from "./DocumentPanel" -import { createDocumentInspector } from "./state" +import { DocumentPanel } from "../../documents/DocumentPanel" +import { createDocumentInspector } from "../../documents/state" interface Props { inspector: ReturnType diff --git a/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx b/packages/kilo-vscode/webview-ui/documents/DocumentPanel.tsx similarity index 96% rename from packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx rename to packages/kilo-vscode/webview-ui/documents/DocumentPanel.tsx index f4fd7c90da..c3dfcf0a17 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/documents/DocumentPanel.tsx +++ b/packages/kilo-vscode/webview-ui/documents/DocumentPanel.tsx @@ -1,9 +1,9 @@ import { Dynamic } from "solid-js/web" import { Component, Show, Accessor, createMemo, createSignal, createEffect, on } from "solid-js" -import { MarkdownPane } from "../../diff-viewer/MarkdownDiffView" +import { MarkdownPane } from "../diff-viewer/MarkdownDiffView" import { isMarkdownPath, type DocumentData, type DocumentTab } from "./state" -import { InspectorTabStrip } from "../InspectorTabStrip" -import { SortableClosableTab } from "../ClosableTab" +import { InspectorTabStrip } from "../agent-manager/InspectorTabStrip" +import { SortableClosableTab } from "../agent-manager/ClosableTab" import { useCodeComponent } from "@kilocode/kilo-ui/context/code" import { FileIcon } from "@kilocode/kilo-ui/file-icon" import { Icon } from "@kilocode/kilo-ui/icon" @@ -11,8 +11,8 @@ import { IconButton } from "@kilocode/kilo-ui/icon-button" import { Button } from "@kilocode/kilo-ui/button" import { Tooltip, TooltipKeybind } from "@kilocode/kilo-ui/tooltip" import type { DiffLineAnnotation, AnnotationSide, SelectedLineRange } from "@pierre/diffs" -import type { WorktreeFileDiff } from "../../src/types/messages" -import type { ReviewComment } from "../../diff-viewer/review-comments" +import type { WorktreeFileDiff } from "../src/types/messages" +import type { ReviewComment } from "../diff-viewer/review-comments" import { buildFileAnnotations, buildReviewAnnotation, @@ -22,9 +22,9 @@ import { type AnnotationMeta, type ReviewComposer, type ReviewDraft, -} from "../../diff-viewer/review-annotations" -import { lineCount } from "../../diff-viewer/review-comments" -import { useLanguage } from "../../src/context/language" +} from "../diff-viewer/review-annotations" +import { lineCount } from "../diff-viewer/review-comments" +import { useLanguage } from "../src/context/language" export interface DocumentPanelProps { tabs: Accessor diff --git a/packages/kilo-vscode/webview-ui/documents/index.tsx b/packages/kilo-vscode/webview-ui/documents/index.tsx new file mode 100644 index 0000000000..c1726ae1ea --- /dev/null +++ b/packages/kilo-vscode/webview-ui/documents/index.tsx @@ -0,0 +1,87 @@ +import { onCleanup, onMount, type Component, createSignal } from "solid-js" +import { render } from "solid-js/web" +import "@kilocode/kilo-ui/styles" +import "../src/styles/chat.css" +import "../agent-manager/agent-manager.css" +import "../agent-manager/agent-manager-review.css" +import { ProviderShell } from "../src/context/provider-shell" +import { useVSCode } from "../src/context/vscode" +import { DocumentPanel } from "./DocumentPanel" +import { createDocumentComments, createDocuments, type DocumentMessage } from "./state" + +const App: Component = () => { + const vscode = useVSCode() + const [scope, setScope] = createSignal(null) + const [session, setSession] = createSignal(null) + const docs = createDocuments(vscode, scope, session, (id, file, contextKey) => + vscode.postMessage({ type: "document.request", sessionId: id, file, contextKey }), + ) + const comments = createDocumentComments(scope) + const [visible, setVisible] = createSignal(true) + + const open = (message: { sessionId?: string; contextKey: string; file: string; line?: number; column?: number }) => { + setScope(message.contextKey) + setSession(message.sessionId ?? null) + docs.open(message.file, message.sessionId ?? "", message.line, message.column) + setVisible(true) + } + + onMount(() => { + const message = vscode.onMessage((item) => { + if (item.type === "document.open") { + open(item) + return + } + if (item.type === "document.result") docs.onMessage(item as DocumentMessage) + if (item.type === "appendReviewComments" && Array.isArray(item.comments)) { + comments.setComments([...comments.comments(), ...item.comments]) + if (item.autoSend) + vscode.postMessage({ type: "document.sendComments", comments: item.comments, autoSend: true }) + } + }) + const review = (event: MessageEvent) => { + if (event.data?.type !== "appendReviewComments" || !Array.isArray(event.data.comments)) return + vscode.postMessage({ + type: "document.sendComments", + comments: event.data.comments, + autoSend: !!event.data.autoSend, + }) + } + window.addEventListener("message", review) + onCleanup(() => { + message() + window.removeEventListener("message", review) + }) + }) + + return ( + vscode.postMessage({ type: "document.openFile", file, line, column })} + onClosePanel={() => { + setVisible(false) + vscode.postMessage({ type: "document.close" }) + }} + visible={visible} + /> + ) +} + +const root = document.getElementById("root") +if (!root) throw new Error("Root element not found") +render( + () => ( + + + + ), + root, +) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts b/packages/kilo-vscode/webview-ui/documents/state.ts similarity index 86% rename from packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts rename to packages/kilo-vscode/webview-ui/documents/state.ts index 10c46fec80..c78307898c 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/documents/state.ts +++ b/packages/kilo-vscode/webview-ui/documents/state.ts @@ -1,11 +1,23 @@ import { createSignal, onCleanup, onMount, type Accessor } from "solid-js" -import type { useVSCode } from "../../src/context/vscode" -import type { ReviewComment } from "../../diff-viewer/review-comments" -import type { AgentManagerDocumentMessage } from "../../src/types/messages" +import type { useVSCode } from "../src/context/vscode" +import type { ReviewComment } from "../diff-viewer/review-comments" +export interface DocumentMessage { + type?: "document.result" | "agentManager.document" + sessionId: string + contextKey?: string + file: string + requestedFile?: string + content?: string + kind?: "text" | "image" + mime?: string + data?: string + error?: string +} export interface DocumentTab { id: string file: string + sessionId?: string line?: number column?: number } @@ -32,6 +44,8 @@ export function createDocuments( vscode: ReturnType, context: Accessor, session: Accessor = context, + send: (sessionId: string, file: string, contextKey: string) => void = (sessionId, file, contextKey) => + vscode.postMessage({ type: "agentManager.requestDocument", sessionId, file, contextKey }), ) { const [tabs, setTabs] = createSignal>({}) const [active, setActive] = createSignal>({}) @@ -42,12 +56,12 @@ export function createDocuments( const selected = () => active()[current()] const document = (file: string) => data()[key(current(), file)] - const request = (file: string, sessionId = session() ?? "") => { + const request = (file: string, sessionId = session() ?? "", contextKey = current()) => { const ctx = current() if (!ctx) return const id = key(ctx, file) setData((prev) => ({ ...prev, [id]: { ...(prev[id] ?? { file }), file, loading: true, error: undefined } })) - vscode.postMessage({ type: "agentManager.requestDocument", sessionId, file, contextKey: ctx }) + send(sessionId, file, contextKey) } const open = (file: string, sessionId = session() ?? "", line?: number, column?: number) => { @@ -57,9 +71,9 @@ export function createDocuments( const list = prev[ctx] ?? [] const id = key(ctx, file) if (list.some((tab) => tab.id === id)) { - return { ...prev, [ctx]: list.map((tab) => (tab.id === id ? { ...tab, line, column } : tab)) } + return { ...prev, [ctx]: list.map((tab) => (tab.id === id ? { ...tab, sessionId, line, column } : tab)) } } - return { ...prev, [ctx]: [...list, { id, file, line, column }] } + return { ...prev, [ctx]: [...list, { id, file, sessionId, line, column }] } }) setActive((prev) => ({ ...prev, [ctx]: key(ctx, file) })) request(file, sessionId) @@ -72,7 +86,7 @@ export function createDocuments( const tab = (tabs()[ctx] ?? []).find((item) => item.id === id) if (!tab) return setActive((prev) => ({ ...prev, [ctx]: id })) - if (!document(tab.file)) request(tab.file) + if (!document(tab.file)) request(tab.file, tab.sessionId) } const close = (id: string) => { @@ -107,7 +121,7 @@ export function createDocuments( setTabs((prev) => ({ ...prev, [ctx]: next })) } - const onMessage = (message: AgentManagerDocumentMessage) => { + const onMessage = (message: DocumentMessage) => { const id = key(message.contextKey ?? message.sessionId, message.requestedFile ?? message.file) setData((prev) => ({ ...prev, @@ -164,7 +178,7 @@ export function createDocumentInspector( onMount(() => { const handler = (event: Event) => handleDocumentOpen(event, open) const message = vscode.onMessage((item) => { - if (item.type === "agentManager.document") documents.onMessage(item) + if (item.type === "document.result" || item.type === "agentManager.document") documents.onMessage(item) }) window.addEventListener("kilo:open-file", handler) onCleanup(() => { 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 d999b21d85..854d73974f 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 @@ -309,6 +309,28 @@ export interface AppendReviewCommentsMessage { autoSend?: boolean } +export interface DocumentResultMessage { + type: "document.result" + sessionId: string + contextKey?: string + file: string + requestedFile?: string + content?: string + kind?: "text" | "image" + mime?: string + data?: string + error?: string +} + +export interface DocumentOpenMessage { + type: "document.open" + sessionId?: string + contextKey: string + file: string + line?: number + column?: number +} + export interface AppendReviewCommentsToTerminalMessage { type: "appendReviewCommentsToTerminal" comments: ReviewCommentEntry[] @@ -1307,6 +1329,8 @@ export interface AgentManagerFocusContextRequestedMessage { } export type ExtensionMessage = + | DocumentResultMessage + | DocumentOpenMessage | AgentManagerFocusContextRequestedMessage | ReadyMessage | FontSizeChangedMessage 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 9af631dd3c..cd9f19f776 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 @@ -874,6 +874,30 @@ export interface AgentManagerRequestDocumentMessage { contextKey?: string } +export interface DocumentRequestMessage { + type: "document.request" + sessionId?: string + file: string + contextKey?: string +} + +export interface DocumentOpenFileMessage { + type: "document.openFile" + file: string + line?: number + column?: number +} + +export interface DocumentCloseMessage { + type: "document.close" +} + +export interface DocumentSendCommentsMessage { + type: "document.sendComments" + comments: ReviewCommentEntry[] + autoSend?: boolean +} + // Create multiple worktree sessions for the same prompt (multi-version mode) export interface CreateMultiVersionRequest { type: "agentManager.createMultiVersion" @@ -1403,6 +1427,10 @@ export interface DismissAgentMigrationBannerMessage { } export type WebviewMessage = + | DocumentRequestMessage + | DocumentOpenFileMessage + | DocumentCloseMessage + | DocumentSendCommentsMessage | SendMessageRequest | AbortRequest | RevertSessionRequest From eda1e9c80f712d1af48191e58d61ba5ed6ecea07 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 20 Aug 2026 13:27:57 +0200 Subject: [PATCH 12/14] fix(vscode): register documents webview with knip --- packages/kilo-vscode/knip.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/kilo-vscode/knip.json b/packages/kilo-vscode/knip.json index 18a96ea110..497095ed5c 100644 --- a/packages/kilo-vscode/knip.json +++ b/packages/kilo-vscode/knip.json @@ -3,6 +3,7 @@ "entry": [ "src/extension.ts", "webview-ui/agent-manager/index.tsx", + "webview-ui/documents/index.tsx", "webview-ui/diff-viewer/index.tsx", "webview-ui/diff-virtual/index.tsx", "webview-ui/kiloclaw/index.tsx", From ba1c3c60e157736b1691940633cf38a03f210def Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 20 Aug 2026 13:55:13 +0200 Subject: [PATCH 13/14] fix(vscode): avoid duplicate document review sends --- packages/kilo-vscode/src/DocumentViewerProvider.ts | 14 +++++++------- .../kilo-vscode/webview-ui/documents/index.tsx | 5 ----- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/kilo-vscode/src/DocumentViewerProvider.ts b/packages/kilo-vscode/src/DocumentViewerProvider.ts index 8390ded5ff..41f1409900 100644 --- a/packages/kilo-vscode/src/DocumentViewerProvider.ts +++ b/packages/kilo-vscode/src/DocumentViewerProvider.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode" import type { ReviewCommentEntry } from "./shared/review-comments" import { readDocument } from "./documents/document-reader" +import { openRelativeFile } from "./review-utils" import { buildWebviewHtml } from "./utils" import type { KiloConnectionService } from "./services/cli-backend" @@ -98,13 +99,12 @@ export class DocumentViewerProvider implements vscode.Disposable { } if (message.type === "document.openFile" && typeof message.file === "string") { const context = this.contexts.get(this.currentKey) - this.openFromCommand({ - file: message.file, - sessionId: context?.sessionId, - directory: context?.directory, - line: typeof message.line === "number" ? message.line : undefined, - column: typeof message.column === "number" ? message.column : undefined, - }) + openRelativeFile( + context?.directory, + message.file, + typeof message.line === "number" ? message.line : undefined, + typeof message.column === "number" ? message.column : undefined, + ) return } if (message.type === "document.close") this.panel?.dispose() diff --git a/packages/kilo-vscode/webview-ui/documents/index.tsx b/packages/kilo-vscode/webview-ui/documents/index.tsx index c1726ae1ea..bde3e404a4 100644 --- a/packages/kilo-vscode/webview-ui/documents/index.tsx +++ b/packages/kilo-vscode/webview-ui/documents/index.tsx @@ -33,11 +33,6 @@ const App: Component = () => { return } if (item.type === "document.result") docs.onMessage(item as DocumentMessage) - if (item.type === "appendReviewComments" && Array.isArray(item.comments)) { - comments.setComments([...comments.comments(), ...item.comments]) - if (item.autoSend) - vscode.postMessage({ type: "document.sendComments", comments: item.comments, autoSend: true }) - } }) const review = (event: MessageEvent) => { if (event.data?.type !== "appendReviewComments" || !Array.isArray(event.data.comments)) return From 386af9a636623967eb2b58b7162db648b52b1070 Mon Sep 17 00:00:00 2001 From: "kilo-maintainer[bot]" Date: Thu, 20 Aug 2026 11:58:01 +0000 Subject: [PATCH 14/14] chore: update kilo-vscode visual regression baselines --- .../labs-tool-call-lab/search-previews-chromium-linux.png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/labs-tool-call-lab/search-previews-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/labs-tool-call-lab/search-previews-chromium-linux.png index 0178a46d92..2a0fedc6d1 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/labs-tool-call-lab/search-previews-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/labs-tool-call-lab/search-previews-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5a9f984fc6d004e151c6e82bd095e9038423d8fa6443a91bf1834c44f0af62ce -size 748576 +oid sha256:96a537574ebee768dab6914ffb449665d6930d2d577bf1dc42eb38d975e9be10 +size 749704