diff --git a/.github/workflows/test-vscode.yml b/.github/workflows/test-vscode.yml index 12735b6f725..9ec1df4f442 100644 --- a/.github/workflows/test-vscode.yml +++ b/.github/workflows/test-vscode.yml @@ -35,6 +35,10 @@ jobs: working-directory: packages/kilo-vscode run: bun run test:unit + - name: Check formatting (prettier) + working-directory: packages/kilo-vscode + run: bun run format:check + - name: Check for dead code (knip) working-directory: packages/kilo-vscode run: bun run knip diff --git a/AGENTS.md b/AGENTS.md index 18ae4708973..4a26702d20c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,6 +14,7 @@ Kilo CLI is an open source AI coding agent that generates code from natural lang - **Test**: `bun test` from `packages/opencode/` (NOT from root -- root blocks tests) - **Single test**: `bun test test/tool/tool.test.ts` from `packages/opencode/` - **SDK regen**: After changing server endpoints in `packages/opencode/src/server/`, run `./script/generate.ts` from root to regenerate `packages/sdk/js/` +- **Knip** (unused exports): `bun run knip` from `packages/kilo-vscode/`. CI runs this — all exported types/functions must be imported somewhere. Remove or unexport unused exports before pushing. ## Products diff --git a/bun.lock b/bun.lock index 73e6ae212de..82628087816 100644 --- a/bun.lock +++ b/bun.lock @@ -25,7 +25,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "7.0.43", + "version": "7.0.44", "dependencies": { "@kilocode/kilo-i18n": "workspace:*", "@kilocode/kilo-ui": "workspace:*", @@ -77,7 +77,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "7.0.43", + "version": "7.0.44", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -110,7 +110,7 @@ }, "packages/kilo-docs": { "name": "@kilocode/kilo-docs", - "version": "7.0.43", + "version": "7.0.44", "dependencies": { "@docsearch/css": "^4", "@docsearch/js": "^4", @@ -139,7 +139,7 @@ }, "packages/kilo-gateway": { "name": "@kilocode/kilo-gateway", - "version": "7.0.43", + "version": "7.0.44", "dependencies": { "@clack/prompts": "1.0.0-alpha.1", "@kilocode/plugin": "workspace:*", @@ -171,7 +171,7 @@ }, "packages/kilo-i18n": { "name": "@kilocode/kilo-i18n", - "version": "7.0.43", + "version": "7.0.44", "devDependencies": { "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", @@ -181,7 +181,7 @@ }, "packages/kilo-telemetry": { "name": "@kilocode/kilo-telemetry", - "version": "7.0.43", + "version": "7.0.44", "dependencies": { "@kilocode/kilo-gateway": "workspace:*", "@opentelemetry/api": "1.9.0", @@ -201,7 +201,7 @@ }, "packages/kilo-ui": { "name": "@kilocode/kilo-ui", - "version": "7.0.43", + "version": "7.0.44", "dependencies": { "@kobalte/core": "0.13.11", "@opencode-ai/util": "workspace:*", @@ -230,7 +230,7 @@ }, "packages/kilo-vscode": { "name": "kilo-code", - "version": "7.0.43", + "version": "7.0.44", "dependencies": { "@anthropic-ai/sdk": "^0.39.0", "@kilocode/kilo-i18n": "workspace:*", @@ -281,7 +281,7 @@ }, "packages/opencode": { "name": "@kilocode/cli", - "version": "7.0.43", + "version": "7.0.44", "bin": { "opencode": "./bin/opencode", }, @@ -398,7 +398,7 @@ }, "packages/plugin": { "name": "@kilocode/plugin", - "version": "7.0.43", + "version": "7.0.44", "dependencies": { "@kilocode/sdk": "workspace:*", "zod": "catalog:", @@ -412,14 +412,14 @@ }, "packages/script": { "name": "@opencode-ai/script", - "version": "7.0.43", + "version": "7.0.44", "devDependencies": { "@types/bun": "catalog:", }, }, "packages/sdk/js": { "name": "@kilocode/sdk", - "version": "7.0.43", + "version": "7.0.44", "devDependencies": { "@hey-api/openapi-ts": "0.90.10", "@tsconfig/node22": "catalog:", @@ -453,7 +453,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "7.0.43", + "version": "7.0.44", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "catalog:", @@ -498,7 +498,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "7.0.43", + "version": "7.0.44", "dependencies": { "zod": "catalog:", }, diff --git a/package.json b/package.json index 1de7ab68264..3ea198bbb4a 100644 --- a/package.json +++ b/package.json @@ -108,6 +108,6 @@ "@openrouter/ai-sdk-provider@1.5.4": "patches/@openrouter%2Fai-sdk-provider@1.5.4.patch", "ghostty-web@0.3.0": "patches/ghostty-web@0.3.0.patch" }, - "version": "7.0.43", + "version": "7.0.44", "peerDependencies": {} } diff --git a/packages/app/package.json b/packages/app/package.json index 2d9b8cbe304..01c5d876026 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "7.0.43", + "version": "7.0.44", "description": "", "type": "module", "exports": { diff --git a/packages/desktop/package.json b/packages/desktop/package.json index 0a666dcdead..a2de60f66e5 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "7.0.43", + "version": "7.0.44", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index e6e3fb2e39d..c15796840a4 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "kilo" name = "Kilo" description = "The open source coding agent." -version = "7.0.43" +version = "7.0.44" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/Kilo-Org/kilocode" @@ -11,26 +11,26 @@ name = "Kilo" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.43/opencode-darwin-arm64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.44/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.43/opencode-darwin-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.44/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.43/opencode-linux-arm64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.44/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.43/opencode-linux-x64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.44/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.43/opencode-windows-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.0.44/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/kilo-docs/package.json b/packages/kilo-docs/package.json index 1ac5839060f..14a40917c1d 100644 --- a/packages/kilo-docs/package.json +++ b/packages/kilo-docs/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-docs", - "version": "7.0.43", + "version": "7.0.44", "private": true, "scripts": { "dev": "next dev --webpack --port 3002", diff --git a/packages/kilo-gateway/package.json b/packages/kilo-gateway/package.json index 0012b0e9438..c47bf9e0167 100644 --- a/packages/kilo-gateway/package.json +++ b/packages/kilo-gateway/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-gateway", - "version": "7.0.43", + "version": "7.0.44", "type": "module", "license": "MIT", "description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration", diff --git a/packages/kilo-i18n/package.json b/packages/kilo-i18n/package.json index a43f417d056..d3b45020942 100644 --- a/packages/kilo-i18n/package.json +++ b/packages/kilo-i18n/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-i18n", - "version": "7.0.43", + "version": "7.0.44", "type": "module", "license": "MIT", "description": "Kilo-specific i18n translations and overrides", diff --git a/packages/kilo-telemetry/package.json b/packages/kilo-telemetry/package.json index a0a10c9cbf7..cc78d8b4308 100644 --- a/packages/kilo-telemetry/package.json +++ b/packages/kilo-telemetry/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-telemetry", - "version": "7.0.43", + "version": "7.0.44", "type": "module", "license": "MIT", "description": "Telemetry for Kilo CLI - PostHog analytics integration", diff --git a/packages/kilo-ui/package.json b/packages/kilo-ui/package.json index a8faf63a3cd..0909dac037b 100644 --- a/packages/kilo-ui/package.json +++ b/packages/kilo-ui/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-ui", - "version": "7.0.43", + "version": "7.0.44", "type": "module", "license": "MIT", "exports": { diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index def37e1b85b..725dcb5899a 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -2,7 +2,7 @@ "name": "kilo-code", "displayName": "Kilo Code: AI Coding Agent, Copilot, and Autocomplete", "description": "Open Source AI coding agent that generates code from natural language, automates tasks, and runs terminal commands. Features inline autocomplete, browser automation, automated refactoring, and custom modes for planning, coding, and debugging. Supports 500+ AI models including Claude (Anthropic), Gemini, Grok, GPT, Codex and GLM.", - "version": "7.0.43", + "version": "7.0.44", "icon": "assets/icons/logo-outline-black.png", "galleryBanner": { "color": "#FFFFFF", @@ -690,16 +690,19 @@ }, "scripts": { "prepare:cli-binary": "bun script/local-bin.ts", - "compile": "bun run prepare:cli-binary && bun run rebuild-sdk && bun run check-types && bun run lint && node esbuild.js", + "compile": "bun run prepare:cli-binary && bun run rebuild-sdk && bun run typecheck && bun run lint && node esbuild.js", "watch": "bun run rebuild-sdk && bun run --parallel watch:esbuild watch:tsc", "watch:esbuild": "bun run prepare:cli-binary && node esbuild.js --watch", "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", "watch:cli": "bun script/watch-cli.ts", - "package": "bun run prepare:cli-binary && bun run rebuild-sdk && bun run check-types && bun run lint && node esbuild.js --production", + "package": "bun run prepare:cli-binary && bun run rebuild-sdk && bun run typecheck && bun run lint && node esbuild.js --production", "compile-tests": "tsc -p . --outDir out", "watch-tests": "tsc -p . -w --outDir out", "pretest": "bun run compile-tests && bun run compile && bun run lint", "check-types": "tsc --noEmit", + "check-types:webview": "bun script/typecheck.ts --project webview-ui/tsconfig.json", + "typecheck": "bun run check-types:extension && bun run check-types:webview", + "check-types:extension": "bun script/typecheck.ts", "format": "prettier --write .", "format:check": "prettier --check .", "knip": "knip", diff --git a/packages/kilo-vscode/script/typecheck.ts b/packages/kilo-vscode/script/typecheck.ts new file mode 100644 index 00000000000..ddab32a3e05 --- /dev/null +++ b/packages/kilo-vscode/script/typecheck.ts @@ -0,0 +1,38 @@ +#!/usr/bin/env bun +/** + * Cross-platform typecheck script that runs tsc and filters errors. + * + * Replaces the previous bash/grep pipeline so `bun run typecheck` works + * on Windows without POSIX tools. + * + * Usage: + * bun script/typecheck.ts # check extension + * bun script/typecheck.ts --project webview-ui/tsconfig.json # check webview + * + * Filtering rules: + * - Only lines matching "error TS" are reported + * - Lines starting with ".." (parent node_modules) are excluded + * - For the webview project, "@pierre/diffs" errors are also excluded + */ + +import { $ } from "bun" + +const args = process.argv.slice(2) +const projectIdx = args.indexOf("--project") +const project = projectIdx !== -1 ? args[projectIdx + 1] : undefined +const webview = project?.includes("webview-ui") + +const tscArgs = project ? ["--noEmit", "--project", project] : ["--noEmit"] +const result = await $`tsc ${tscArgs}`.nothrow().quiet() +const output = result.stdout.toString() + result.stderr.toString() + +const errors = output + .split("\n") + .filter((line) => line.includes("error TS")) + .filter((line) => !line.startsWith("..")) + .filter((line) => !webview || !line.includes("@pierre/diffs")) + +if (errors.length > 0) { + console.error(errors.join("\n")) + process.exit(1) +} diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index c68ac330e85..30b08d12066 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -394,6 +394,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper case "openChanges": vscode.commands.executeCommand("kilo-code.new.showChanges") break + case "openSubAgentViewer": + vscode.commands.executeCommand("kilo-code.new.openSubAgentViewer", message.sessionID, message.title) + break case "openFile": if (message.filePath) { this.handleOpenFile(message.filePath, message.line, message.column) diff --git a/packages/kilo-vscode/src/SubAgentViewerProvider.ts b/packages/kilo-vscode/src/SubAgentViewerProvider.ts new file mode 100644 index 00000000000..c50d3acba2d --- /dev/null +++ b/packages/kilo-vscode/src/SubAgentViewerProvider.ts @@ -0,0 +1,110 @@ +import * as vscode from "vscode" +import { KiloProvider } from "./KiloProvider" +import type { KiloConnectionService } from "./services/cli-backend" + +/** + * Opens a read-only editor panel to view a sub-agent session. + * + * Each child session ID maps to at most one panel — calling openPanel() + * again with the same ID reveals the existing panel. + * + * Uses a full KiloProvider so the viewer has backend connectivity + * (messages, parts, SSE events) identical to the sidebar. + */ +export class SubAgentViewerProvider implements vscode.Disposable { + private panels = new Map() + private providers = new Map() + + constructor( + private readonly extensionUri: vscode.Uri, + private readonly connectionService: KiloConnectionService, + private readonly context: vscode.ExtensionContext, + ) {} + + openPanel(sessionID: string, title?: string): void { + const existing = this.panels.get(sessionID) + if (existing) { + existing.reveal(vscode.ViewColumn.One) + return + } + + const label = title ? `Sub-agent: ${title}` : "Sub-agent Viewer" + + const panel = vscode.window.createWebviewPanel("kilo-code.new.SubAgentViewerPanel", label, 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"), + } + + const provider = new KiloProvider(this.extensionUri, this.connectionService, this.context) + provider.resolveWebviewPanel(panel) + + // Once the webview is ready, fetch the session and display it in read-only mode. + const readyDisposable = panel.webview.onDidReceiveMessage(async (msg) => { + if (msg.type !== "webviewReady") return + readyDisposable.dispose() + + // Small delay to let KiloProvider's own webviewReady handler finish first + await new Promise((resolve) => setTimeout(resolve, 50)) + + try { + const client = this.connectionService.getClient() + const { data: session } = await client.session.get({ sessionID }, { throwOnError: true }) + + // Register the session on the provider — this adds it to + // trackedSessionIds for live SSE updates and sends + // sessionCreated to the webview. + provider.registerSession(session) + + // Fetch and send existing messages + const { data: messagesData } = await client.session.messages({ sessionID }, { throwOnError: true }) + const messages = messagesData.map((m) => ({ + ...m.info, + parts: m.parts, + createdAt: new Date(m.info.time.created).toISOString(), + })) + provider.postMessage({ + type: "messagesLoaded", + sessionID, + messages, + }) + + // Navigate to the sub-agent viewer + provider.postMessage({ type: "viewSubAgentSession", sessionID }) + } catch (err) { + console.error("[Kilo New] SubAgentViewerProvider: Failed to load session:", err) + } + }) + + // Listen for closePanel from the webview (back button) + const closeDisposable = panel.webview.onDidReceiveMessage((msg) => { + if (msg.type === "closePanel") { + panel.dispose() + } + }) + + this.panels.set(sessionID, panel) + this.providers.set(sessionID, provider) + + panel.onDidDispose(() => { + console.log("[Kilo New] Sub-agent viewer panel disposed:", sessionID) + closeDisposable.dispose() + provider.dispose() + this.panels.delete(sessionID) + this.providers.delete(sessionID) + }) + } + + dispose(): void { + for (const [, panel] of this.panels) { + panel.dispose() + } + this.panels.clear() + this.providers.clear() + } +} diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index bd78a9a6681..4cc2a8db275 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -22,6 +22,7 @@ import { executeVscodeTask } from "./task-runner" import { formatKeybinding } from "./format-keybinding" import { TelemetryProxy, TelemetryEventName } from "../services/telemetry" import { MAX_MULTI_VERSIONS } from "./constants" +import type { AgentManagerOutMessage, AgentManagerInMessage } from "./types" import { getWorkspaceRoot, hashFileDiffs, openFileInEditor, resolveLocalDiffTarget } from "../review-utils" /** @@ -54,8 +55,8 @@ export class AgentManagerProvider implements vscode.Disposable { private gitOps: GitOps private cachedDiffTarget: { directory: string; baseBranch: string } | undefined private staleWorktreeIds = new Set() - private cachedWorktreeStats: Record | undefined - private cachedLocalStats: Record | undefined + private cachedWorktreeStats: AgentManagerOutMessage | undefined + private cachedLocalStats: AgentManagerOutMessage | undefined private applyingWorktreeId: string | undefined /** Session ID most recently loaded via a `loadMessages` message from the webview. * Updated synchronously — unlike KiloProvider.currentSession which depends on @@ -77,12 +78,12 @@ export class AgentManagerProvider implements vscode.Disposable { getWorkspaceRoot: () => this.getWorkspaceRoot(), getClient: () => this.connectionService.getClient(), onStats: (stats) => { - const msg = { type: "agentManager.worktreeStats", stats } + const msg = { type: "agentManager.worktreeStats" as const, stats } this.cachedWorktreeStats = msg this.postToWebview(msg) }, onLocalStats: (stats) => { - const msg = { type: "agentManager.localStats", stats } + const msg = { type: "agentManager.localStats" as const, stats } this.cachedLocalStats = msg this.postToWebview(msg) }, @@ -184,58 +185,53 @@ export class AgentManagerProvider implements vscode.Disposable { // --------------------------------------------------------------------------- private async onMessage(msg: Record): Promise | null> { - const type = msg.type as string + const m = msg as unknown as AgentManagerInMessage - if (type === "agentManager.createWorktree") { - return this.onCreateWorktree(msg.baseBranch as string | undefined, msg.branchName as string | undefined) + if (m.type === "agentManager.createWorktree") { + return this.onCreateWorktree(m.baseBranch, m.branchName) } - if (type === "agentManager.deleteWorktree" && typeof msg.worktreeId === "string") - return this.onDeleteWorktree(msg.worktreeId) - if (type === "agentManager.removeStaleWorktree" && typeof msg.worktreeId === "string") - return this.onRemoveStaleWorktree(msg.worktreeId) - if (type === "agentManager.promoteSession" && typeof msg.sessionId === "string") - return this.onPromoteSession(msg.sessionId) - if (type === "agentManager.addSessionToWorktree" && typeof msg.worktreeId === "string") - return this.onAddSessionToWorktree(msg.worktreeId) - if (type === "agentManager.closeSession" && typeof msg.sessionId === "string") - return this.onCloseSession(msg.sessionId) - if (type === "agentManager.configureSetupScript") { + if (m.type === "agentManager.deleteWorktree") return this.onDeleteWorktree(m.worktreeId) + if (m.type === "agentManager.removeStaleWorktree") return this.onRemoveStaleWorktree(m.worktreeId) + if (m.type === "agentManager.promoteSession") return this.onPromoteSession(m.sessionId) + if (m.type === "agentManager.addSessionToWorktree") return this.onAddSessionToWorktree(m.worktreeId) + if (m.type === "agentManager.closeSession") return this.onCloseSession(m.sessionId) + if (m.type === "agentManager.configureSetupScript") { void this.configureSetupScript() return null } - if (type === "agentManager.showTerminal" && typeof msg.sessionId === "string") { - this.terminalManager.showTerminal(msg.sessionId, this.state) + if (m.type === "agentManager.showTerminal") { + this.terminalManager.showTerminal(m.sessionId, this.state) return null } - if (type === "agentManager.showLocalTerminal") { + if (m.type === "agentManager.showLocalTerminal") { this.terminalManager.showLocalTerminal() return null } - if (type === "agentManager.openWorktree" && typeof msg.worktreeId === "string") { - this.openWorktreeDirectory(msg.worktreeId) + if (m.type === "agentManager.openWorktree") { + this.openWorktreeDirectory(m.worktreeId) return null } - if (type === "agentManager.showExistingLocalTerminal") { + if (m.type === "agentManager.showExistingLocalTerminal") { this.terminalManager.syncLocalOnSessionSwitch() return null } - if (type === "agentManager.requestRepoInfo") { + if (m.type === "agentManager.requestRepoInfo") { void this.sendRepoInfo() return null } - if (type === "agentManager.createMultiVersion") { - void this.onCreateMultiVersion(msg) + if (m.type === "agentManager.createMultiVersion") { + void this.onCreateMultiVersion(m) return null } - if (type === "agentManager.renameWorktree" && typeof msg.worktreeId === "string" && typeof msg.label === "string") { + if (m.type === "agentManager.renameWorktree") { const state = this.getStateManager() if (state) { - state.updateWorktreeLabel(msg.worktreeId, msg.label) + state.updateWorktreeLabel(m.worktreeId, m.label) this.pushState() } return null } - if (type === "agentManager.requestState") { + if (m.type === "agentManager.requestState") { void this.stateReady ?.then(() => { // When the workspace is not a git repo (or has no folder open), @@ -269,82 +265,73 @@ export class AgentManagerProvider implements vscode.Disposable { }) return null } - if (type === "agentManager.requestBranches") { + if (m.type === "agentManager.requestBranches") { void this.onRequestBranches() return null } - if (type === "agentManager.setTabOrder" && typeof msg.key === "string" && Array.isArray(msg.order)) { - this.state?.setTabOrder(msg.key, msg.order as string[]) + if (m.type === "agentManager.setTabOrder") { + this.state?.setTabOrder(m.key, m.order) return null } - if (type === "agentManager.setSessionsCollapsed" && typeof msg.collapsed === "boolean") { - this.state?.setSessionsCollapsed(msg.collapsed) + if (m.type === "agentManager.setSessionsCollapsed") { + this.state?.setSessionsCollapsed(m.collapsed) return null } - if (type === "agentManager.setReviewDiffStyle" && (msg.style === "unified" || msg.style === "split")) { - this.state?.setReviewDiffStyle(msg.style) + if (m.type === "agentManager.setReviewDiffStyle") { + this.state?.setReviewDiffStyle(m.style) return null } - - if (type === "agentManager.setDefaultBaseBranch") { - const branch = normalizeBaseBranch(msg.branch as string | undefined) + if (m.type === "agentManager.setDefaultBaseBranch") { + const branch = normalizeBaseBranch(m.branch) this.state?.setDefaultBaseBranch(branch) this.pushState() return null } - if (type === "agentManager.requestExternalWorktrees") { + if (m.type === "agentManager.requestExternalWorktrees") { void this.onRequestExternalWorktrees() return null } - if (type === "agentManager.importFromBranch" && typeof msg.branch === "string") { - void this.onImportFromBranch(msg.branch) + if (m.type === "agentManager.importFromBranch") { + void this.onImportFromBranch(m.branch) return null } - if (type === "agentManager.importFromPR" && typeof msg.url === "string") { - void this.onImportFromPR(msg.url) + if (m.type === "agentManager.importFromPR") { + void this.onImportFromPR(m.url) return null } - if ( - type === "agentManager.importExternalWorktree" && - typeof msg.path === "string" && - typeof msg.branch === "string" - ) { - void this.onImportExternalWorktree(msg.path, msg.branch) + if (m.type === "agentManager.importExternalWorktree") { + void this.onImportExternalWorktree(m.path, m.branch) return null } - if (type === "agentManager.importAllExternalWorktrees") { + if (m.type === "agentManager.importAllExternalWorktrees") { void this.onImportAllExternalWorktrees() return null } - - if (type === "agentManager.requestWorktreeDiff" && typeof msg.sessionId === "string") { - void this.onRequestWorktreeDiff(msg.sessionId) + if (m.type === "agentManager.requestWorktreeDiff") { + void this.onRequestWorktreeDiff(m.sessionId) return null } - if (type === "agentManager.applyWorktreeDiff" && typeof msg.worktreeId === "string") { - const selectedFiles = Array.isArray(msg.selectedFiles) + if (m.type === "agentManager.applyWorktreeDiff") { + const selectedFiles = Array.isArray(m.selectedFiles) ? [ ...new Set( - msg.selectedFiles.filter((file): file is string => typeof file === "string").map((file) => file.trim()), + m.selectedFiles.filter((file): file is string => typeof file === "string").map((file) => file.trim()), ), ].filter((file) => file.length > 0) : undefined - void this.onApplyWorktreeDiff(msg.worktreeId, selectedFiles) + void this.onApplyWorktreeDiff(m.worktreeId, selectedFiles) return null } - if (type === "agentManager.startDiffWatch" && typeof msg.sessionId === "string") { - this.startDiffPolling(msg.sessionId) + if (m.type === "agentManager.startDiffWatch") { + this.startDiffPolling(m.sessionId) return null } - if (type === "agentManager.stopDiffWatch") { + if (m.type === "agentManager.stopDiffWatch") { this.stopDiffPolling() return null } - - if (type === "agentManager.openFile" && typeof msg.sessionId === "string" && typeof msg.filePath === "string") { - const line = typeof msg.line === "number" ? msg.line : undefined - const column = typeof msg.column === "number" ? msg.column : undefined - this.openWorktreeFile(msg.sessionId, msg.filePath, line, column) + if (m.type === "agentManager.openFile") { + this.openWorktreeFile(m.sessionId, m.filePath, m.line, m.column) return null } @@ -353,26 +340,24 @@ export class AgentManagerProvider implements vscode.Disposable { // Local sessions fall through to KiloProvider which resolves against workspace root. // Uses activeSessionId (set synchronously by loadMessages) rather than // KiloProvider.currentSession which can be stale during rapid tab switches. - if (type === "openFile" && typeof msg.filePath === "string") { + if (m.type === "openFile") { const sessionId = this.activeSessionId const state = this.getStateManager() if (sessionId && state?.directoryFor(sessionId)) { - const line = typeof msg.line === "number" ? msg.line : undefined - const column = typeof msg.column === "number" ? msg.column : undefined - this.openWorktreeFile(sessionId, msg.filePath, line, column) + this.openWorktreeFile(sessionId, m.filePath, m.line, m.column) return null } } // Track the active session synchronously so worktree-aware file resolution // uses the correct session even before KiloProvider's async session.get completes. - if (type === "loadMessages" && typeof msg.sessionID === "string") { - this.activeSessionId = msg.sessionID - this.terminalManager.syncOnSessionSwitch(msg.sessionID) + if (m.type === "loadMessages") { + this.activeSessionId = m.sessionID + this.terminalManager.syncOnSessionSwitch(m.sessionID) } // After clearSession, clear active tracking and re-register worktree sessions - if (type === "clearSession") { + if (m.type === "clearSession") { this.activeSessionId = undefined void Promise.resolve().then(() => { if (!this.provider || !this.state) return @@ -383,10 +368,10 @@ export class AgentManagerProvider implements vscode.Disposable { } // Track when a user stops/cancels a running session in the agent manager - if (type === "abort" && typeof msg.sessionID === "string") { + if (m.type === "abort") { TelemetryProxy.capture(TelemetryEventName.AGENT_MANAGER_SESSION_STOPPED, { source: PLATFORM, - sessionId: msg.sessionID, + sessionId: m.sessionID, }) } @@ -506,7 +491,8 @@ export class AgentManagerProvider implements vscode.Disposable { let client: KiloClient try { client = this.connectionService.getClient() - } catch { + } catch (err) { + this.log("createSessionInWorktree: client not available:", err) this.postToWebview({ type: "agentManager.worktreeSetup", status: "error", @@ -705,7 +691,8 @@ export class AgentManagerProvider implements vscode.Disposable { let client: KiloClient try { client = this.connectionService.getClient() - } catch { + } catch (err) { + this.log("onAddSessionToWorktree: client not available:", err) this.postToWebview({ type: "error", message: "Not connected to CLI backend" }) return null } @@ -776,19 +763,19 @@ export class AgentManagerProvider implements vscode.Disposable { // --------------------------------------------------------------------------- /** Create N worktree sessions for the same prompt (multi-version mode). */ - private async onCreateMultiVersion(msg: Record): Promise { - const text = (msg.text as string | undefined)?.trim() || undefined + private async onCreateMultiVersion( + msg: Extract, + ): Promise { + const text = msg.text?.trim() || undefined - const worktreeName = (msg.name as string | undefined)?.trim() || undefined - const agent = msg.agent as string | undefined - const files = msg.files as Array<{ mime: string; url: string }> | undefined - const baseBranch = msg.baseBranch as string | undefined - const branchName = (msg.branchName as string | undefined)?.trim() || undefined + const worktreeName = msg.name?.trim() || undefined + const agent = msg.agent + const files = msg.files + const baseBranch = msg.baseBranch + const branchName = msg.branchName?.trim() || undefined // Expand model allocations into per-version model assignments - const rawAllocations = msg.modelAllocations as - | Array<{ providerID: string; modelID: string; count: number }> - | undefined + const rawAllocations = msg.modelAllocations const perVersionModels: Array<{ providerID: string; modelID: string } | undefined> = [] if (rawAllocations && rawAllocations.length > 0) { for (const alloc of rawAllocations) { @@ -806,8 +793,8 @@ export class AgentManagerProvider implements vscode.Disposable { : Math.min(Math.max(Number(msg.versions) || 1, 1), MAX_MULTI_VERSIONS) // Fall back to single model when not in compare mode - const providerID = perVersionModels.length > 0 ? undefined : (msg.providerID as string | undefined) - const modelID = perVersionModels.length > 0 ? undefined : (msg.modelID as string | undefined) + const providerID = perVersionModels.length > 0 ? undefined : msg.providerID + const modelID = perVersionModels.length > 0 ? undefined : msg.modelID // Generate a shared group ID for multi-version worktrees const groupId = versions > 1 ? `grp-${Date.now()}` : undefined @@ -1735,13 +1722,14 @@ export class AgentManagerProvider implements vscode.Disposable { { throwOnError: true }, ) - this.log(`Worktree diff returned ${diffs.length} file(s) for session ${sessionId}`) + const files = diffs ?? [] + this.log(`Worktree diff returned ${files.length} file(s) for session ${sessionId}`) - const hash = hashFileDiffs(diffs) + const hash = hashFileDiffs(files) this.lastDiffHash = hash this.diffSessionId = sessionId - this.postToWebview({ type: "agentManager.worktreeDiff", sessionId, diffs }) + this.postToWebview({ type: "agentManager.worktreeDiff", sessionId, diffs: files }) } catch (err) { this.log("Failed to fetch worktree diff:", err) } finally { @@ -1761,12 +1749,13 @@ export class AgentManagerProvider implements vscode.Disposable { { throwOnError: true }, ) - const hash = hashFileDiffs(diffs) + const files = diffs ?? [] + const hash = hashFileDiffs(files) if (hash === this.lastDiffHash && this.diffSessionId === sessionId) return this.lastDiffHash = hash this.diffSessionId = sessionId - this.postToWebview({ type: "agentManager.worktreeDiff", sessionId, diffs }) + this.postToWebview({ type: "agentManager.worktreeDiff", sessionId, diffs: files }) } catch (err) { this.log("Failed to poll worktree diff:", err) } @@ -1805,7 +1794,7 @@ export class AgentManagerProvider implements vscode.Disposable { this.cachedDiffTarget = undefined } - private postToWebview(message: Record): void { + private postToWebview(message: AgentManagerOutMessage): void { if (this.panel?.webview) void this.panel.webview.postMessage(message) } diff --git a/packages/kilo-vscode/src/agent-manager/GitStatsPoller.ts b/packages/kilo-vscode/src/agent-manager/GitStatsPoller.ts index b1e1d9fec25..4c3358e62a1 100644 --- a/packages/kilo-vscode/src/agent-manager/GitStatsPoller.ts +++ b/packages/kilo-vscode/src/agent-manager/GitStatsPoller.ts @@ -5,7 +5,7 @@ import { remoteRef, type Worktree } from "./WorktreeStateManager" import type { GitOps } from "./GitOps" import { normalizePath } from "./git-import" -interface WorktreeStats { +export interface WorktreeStats { worktreeId: string files: number additions: number @@ -14,7 +14,7 @@ interface WorktreeStats { behind: number } -interface LocalStats { +export interface LocalStats { branch: string files: number additions: number diff --git a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts index d876c04ddd6..8604bab1242 100644 --- a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts +++ b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts @@ -62,7 +62,7 @@ export interface CreateWorktreeResult { startPointWarning?: string } -interface ExternalWorktreeItem { +export interface ExternalWorktreeItem { path: string branch: string } @@ -324,7 +324,7 @@ export class WorktreeManager { // Try metadata.json first (has parentBranch + remote) try { const content = await fs.promises.readFile(path.join(dir, METADATA_FILE), "utf-8") - const data = JSON.parse(content) + const data = JSON.parse(content) as { sessionId?: string; parentBranch?: string; remote?: string } if (data.sessionId) { return { sessionId: data.sessionId, @@ -332,8 +332,8 @@ export class WorktreeManager { remote: data.remote, } } - } catch { - // Fall back to session-id file + } catch (e) { + this.log(`readMetadata: metadata.json unreadable in ${worktreePath}: ${e}`) } // Legacy: plain text session-id file @@ -341,8 +341,8 @@ export class WorktreeManager { const content = await fs.promises.readFile(path.join(dir, SESSION_ID_FILE), "utf-8") const id = content.trim() if (id) return { sessionId: id } - } catch { - // No metadata + } catch (e) { + this.log(`readMetadata: session-id unreadable in ${worktreePath}: ${e}`) } return undefined @@ -440,6 +440,7 @@ export class WorktreeManager { const stat = await fs.promises.stat(gitFile) if (!stat.isFile()) return undefined } catch { + // .git path inaccessible — not a valid worktree return undefined } @@ -538,8 +539,8 @@ export class WorktreeManager { source: "fallback", warning: `Branch "${branch}" not found, falling back to "${fallback}"`, } - } catch { - // continue + } catch (e) { + this.log(`resolveStartPoint: fallback "${fallback}" failed: ${e}`) } } } @@ -556,7 +557,7 @@ export class WorktreeManager { if (this.ops) { const name = await this.ops.resolveRemote(this.root).catch(() => "origin") const remotes = await this.git.getRemotes().catch(() => []) - return remotes.some((r) => r.name === name) ? name : undefined + return remotes.some((r: { name: string }) => r.name === name) ? name : undefined } const remotes = await this.git.getRemotes().catch(() => []) return remotes.some((r) => r.name === "origin") ? "origin" : undefined @@ -571,6 +572,7 @@ export class WorktreeManager { await this.git.raw(["rev-parse", "--verify", `${ref}^{commit}`]) return true } catch { + // ref does not exist return false } } @@ -579,7 +581,9 @@ export class WorktreeManager { const defaults = [] try { defaults.push(await this.defaultBranch()) - } catch {} + } catch (e) { + this.log(`derivedFallbackBranches: failed to determine default branch: ${e}`) + } return defaults } @@ -592,13 +596,17 @@ export class WorktreeManager { try { const attributes = await fs.promises.readFile(path.join(this.root, ".gitattributes"), "utf-8") if (attributes.includes("filter=lfs")) return true - } catch {} + } catch (e) { + this.log(`repoUsesLfs: failed to read .gitattributes: ${e}`) + } // Check .git/info/attributes try { const infoAttributes = await fs.promises.readFile(path.join(gitDir, "info", "attributes"), "utf-8") if (infoAttributes.includes("filter=lfs")) return true - } catch {} + } catch (e) { + this.log(`repoUsesLfs: failed to read info/attributes: ${e}`) + } return false } @@ -608,6 +616,7 @@ export class WorktreeManager { await execWithShellEnv("git", ["lfs", "version"], { cwd: this.root, timeout: 5000 }) return true } catch { + // git-lfs not installed return false } } @@ -625,7 +634,8 @@ export class WorktreeManager { try { const branches = await this.git.branch() return branches.all.includes(name) || branches.all.includes(`remotes/origin/${name}`) - } catch { + } catch (e) { + this.log(`branchExists: failed to list branches: ${e}`) return false } } @@ -824,6 +834,7 @@ export class WorktreeManager { await this.gitExec(args) return true } catch { + // Command failed — caller handles false return return false } } diff --git a/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts b/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts index bf530c6561e..ff6f25e9227 100644 --- a/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts +++ b/packages/kilo-vscode/src/agent-manager/WorktreeStateManager.ts @@ -37,7 +37,7 @@ export function remoteRef(wt: Pick): string return wt.remote ? `${wt.remote}/${wt.parentBranch}` : wt.parentBranch } -interface ManagedSession { +export interface ManagedSession { id: string worktreeId: string | null createdAt: string diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts new file mode 100644 index 00000000000..b3d63bf4aaa --- /dev/null +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -0,0 +1,400 @@ +/** + * Typed message contracts for the Agent Manager extension ↔ webview boundary. + * + * These types must stay in sync with webview-ui/src/types/messages.ts. + * The webview side re-uses the types directly; this file provides the + * extension-side equivalents so onMessage() and postToWebview() are + * type-checked rather than relying on Record casts. + */ + +import type { FileDiff } from "@kilocode/sdk/v2/client" +import type { Worktree, ManagedSession } from "./WorktreeStateManager" +import type { WorktreeStats, LocalStats } from "./GitStatsPoller" +import type { ApplyConflict } from "./GitOps" +import type { BranchListItem } from "./git-import" +import type { ExternalWorktreeItem } from "./WorktreeManager" + +// --------------------------------------------------------------------------- +// Shared payload types +// --------------------------------------------------------------------------- + +type SessionMode = "worktree" | "local" + +export type ApplyDiffStatus = "checking" | "applying" | "success" | "conflict" | "error" + +// --------------------------------------------------------------------------- +// Extension → Webview messages (postToWebview) +// --------------------------------------------------------------------------- + +interface WorktreeStatsMessage { + type: "agentManager.worktreeStats" + stats: WorktreeStats[] +} + +interface LocalStatsMessage { + type: "agentManager.localStats" + stats: LocalStats +} + +interface WorktreeSetupMessage { + type: "agentManager.worktreeSetup" + status: "creating" | "starting" | "ready" | "error" + message: string + sessionId?: string + branch?: string + worktreeId?: string +} + +interface SessionMetaMessage { + type: "agentManager.sessionMeta" + sessionId: string + mode: SessionMode + branch?: string + path?: string + parentBranch?: string +} + +interface StateMessage { + type: "agentManager.state" + worktrees: Worktree[] + sessions: ManagedSession[] + staleWorktreeIds?: string[] + tabOrder?: Record + sessionsCollapsed?: boolean + reviewDiffStyle?: "unified" | "split" + isGitRepo?: boolean + defaultBaseBranch?: string +} + +interface ErrorOutMessage { + type: "error" + message: string +} + +interface SessionAddedMessage { + type: "agentManager.sessionAdded" + sessionId: string + worktreeId: string +} + +interface MultiVersionProgressMessage { + type: "agentManager.multiVersionProgress" + status: "creating" | "done" + total: number + completed: number + groupId?: string +} + +interface SetSessionModelMessage { + type: "agentManager.setSessionModel" + sessionId: string + providerID: string + modelID: string +} + +interface SendInitialMessage { + type: "agentManager.sendInitialMessage" + sessionId: string + worktreeId: string + text?: string + providerID?: string + modelID?: string + agent?: string + files?: Array<{ mime: string; url: string }> +} + +interface BranchesMessage { + type: "agentManager.branches" + branches: (BranchListItem & { isCheckedOut?: boolean })[] + defaultBranch: string +} + +interface ExternalWorktreesMessage { + type: "agentManager.externalWorktrees" + worktrees: ExternalWorktreeItem[] +} + +interface ImportResultMessage { + type: "agentManager.importResult" + success: boolean + message: string +} + +interface KeybindingsMessage { + type: "agentManager.keybindings" + bindings: Record +} + +interface RepoInfoMessage { + type: "agentManager.repoInfo" + branch: string + defaultBranch?: string +} + +interface ApplyWorktreeDiffResultMessage { + type: "agentManager.applyWorktreeDiffResult" + worktreeId: string + status: ApplyDiffStatus + message: string + conflicts?: ApplyConflict[] +} + +interface WorktreeDiffLoadingMessage { + type: "agentManager.worktreeDiffLoading" + sessionId: string + loading: boolean +} + +interface WorktreeDiffMessage { + type: "agentManager.worktreeDiff" + sessionId: string + diffs: FileDiff[] +} + +interface ActionOutMessage { + type: "action" + action: string +} + +/** All messages the Agent Manager extension sends to the webview. */ +export type AgentManagerOutMessage = + | WorktreeStatsMessage + | LocalStatsMessage + | WorktreeSetupMessage + | SessionMetaMessage + | StateMessage + | ErrorOutMessage + | SessionAddedMessage + | MultiVersionProgressMessage + | SetSessionModelMessage + | SendInitialMessage + | BranchesMessage + | ExternalWorktreesMessage + | ImportResultMessage + | KeybindingsMessage + | RepoInfoMessage + | ApplyWorktreeDiffResultMessage + | WorktreeDiffLoadingMessage + | WorktreeDiffMessage + | ActionOutMessage + +// --------------------------------------------------------------------------- +// Webview → Extension messages (onMessage) +// --------------------------------------------------------------------------- + +interface CreateWorktreeIn { + type: "agentManager.createWorktree" + baseBranch?: string + branchName?: string +} + +interface DeleteWorktreeIn { + type: "agentManager.deleteWorktree" + worktreeId: string +} + +interface RemoveStaleWorktreeIn { + type: "agentManager.removeStaleWorktree" + worktreeId: string +} + +interface PromoteSessionIn { + type: "agentManager.promoteSession" + sessionId: string +} + +interface AddSessionToWorktreeIn { + type: "agentManager.addSessionToWorktree" + worktreeId: string +} + +interface CloseSessionIn { + type: "agentManager.closeSession" + sessionId: string +} + +interface ConfigureSetupScriptIn { + type: "agentManager.configureSetupScript" +} + +interface ShowTerminalIn { + type: "agentManager.showTerminal" + sessionId: string +} + +interface ShowLocalTerminalIn { + type: "agentManager.showLocalTerminal" +} + +interface OpenWorktreeIn { + type: "agentManager.openWorktree" + worktreeId: string +} + +interface ShowExistingLocalTerminalIn { + type: "agentManager.showExistingLocalTerminal" +} + +interface RequestRepoInfoIn { + type: "agentManager.requestRepoInfo" +} + +interface CreateMultiVersionIn { + type: "agentManager.createMultiVersion" + text?: string + name?: string + versions?: number + providerID?: string + modelID?: string + agent?: string + files?: Array<{ mime: string; url: string }> + baseBranch?: string + branchName?: string + modelAllocations?: Array<{ providerID: string; modelID: string; count: number }> +} + +interface RenameWorktreeIn { + type: "agentManager.renameWorktree" + worktreeId: string + label: string +} + +interface RequestStateIn { + type: "agentManager.requestState" +} + +interface RequestBranchesIn { + type: "agentManager.requestBranches" +} + +interface SetTabOrderIn { + type: "agentManager.setTabOrder" + key: string + order: string[] +} + +interface SetSessionsCollapsedIn { + type: "agentManager.setSessionsCollapsed" + collapsed: boolean +} + +interface SetReviewDiffStyleIn { + type: "agentManager.setReviewDiffStyle" + style: "unified" | "split" +} + +interface SetDefaultBaseBranchIn { + type: "agentManager.setDefaultBaseBranch" + branch?: string +} + +interface RequestExternalWorktreesIn { + type: "agentManager.requestExternalWorktrees" +} + +interface ImportFromBranchIn { + type: "agentManager.importFromBranch" + branch: string +} + +interface ImportFromPRIn { + type: "agentManager.importFromPR" + url: string +} + +interface ImportExternalWorktreeIn { + type: "agentManager.importExternalWorktree" + path: string + branch: string +} + +interface ImportAllExternalWorktreesIn { + type: "agentManager.importAllExternalWorktrees" +} + +interface RequestWorktreeDiffIn { + type: "agentManager.requestWorktreeDiff" + sessionId: string +} + +interface ApplyWorktreeDiffIn { + type: "agentManager.applyWorktreeDiff" + worktreeId: string + selectedFiles?: string[] +} + +interface StartDiffWatchIn { + type: "agentManager.startDiffWatch" + sessionId: string +} + +interface StopDiffWatchIn { + type: "agentManager.stopDiffWatch" +} + +interface OpenFileIn { + type: "agentManager.openFile" + sessionId: string + filePath: string + line?: number + column?: number +} + +// Pass-through messages intercepted for side effects +interface GenericOpenFileIn { + type: "openFile" + filePath: string + line?: number + column?: number +} + +interface LoadMessagesIn { + type: "loadMessages" + sessionID: string +} + +interface ClearSessionIn { + type: "clearSession" +} + +interface AbortIn { + type: "abort" + sessionID: string +} + +/** All messages the Agent Manager expects from the webview (onMessage input). */ +export type AgentManagerInMessage = + | CreateWorktreeIn + | DeleteWorktreeIn + | RemoveStaleWorktreeIn + | PromoteSessionIn + | AddSessionToWorktreeIn + | CloseSessionIn + | ConfigureSetupScriptIn + | ShowTerminalIn + | ShowLocalTerminalIn + | OpenWorktreeIn + | ShowExistingLocalTerminalIn + | RequestRepoInfoIn + | CreateMultiVersionIn + | RenameWorktreeIn + | RequestStateIn + | RequestBranchesIn + | SetTabOrderIn + | SetSessionsCollapsedIn + | SetReviewDiffStyleIn + | SetDefaultBaseBranchIn + | RequestExternalWorktreesIn + | ImportFromBranchIn + | ImportFromPRIn + | ImportExternalWorktreeIn + | ImportAllExternalWorktreesIn + | RequestWorktreeDiffIn + | ApplyWorktreeDiffIn + | StartDiffWatchIn + | StopDiffWatchIn + | OpenFileIn + | GenericOpenFileIn + | LoadMessagesIn + | ClearSessionIn + | AbortIn diff --git a/packages/kilo-vscode/src/extension.ts b/packages/kilo-vscode/src/extension.ts index 7ef3e12ce3a..97d82fe844d 100644 --- a/packages/kilo-vscode/src/extension.ts +++ b/packages/kilo-vscode/src/extension.ts @@ -3,6 +3,7 @@ import { KiloProvider } from "./KiloProvider" import { AgentManagerProvider } from "./agent-manager/AgentManagerProvider" import { DiffViewerProvider } from "./DiffViewerProvider" import { SettingsEditorProvider } from "./SettingsEditorProvider" +import { SubAgentViewerProvider } from "./SubAgentViewerProvider" import { EXTENSION_DISPLAY_NAME } from "./constants" import { KiloConnectionService } from "./services/cli-backend" import { registerAutocompleteProvider } from "./services/autocomplete" @@ -60,6 +61,10 @@ export function activate(context: vscode.ExtensionContext) { const settingsEditorProvider = new SettingsEditorProvider(context.extensionUri, connectionService, context) context.subscriptions.push(settingsEditorProvider) + // Create sub-agent viewer provider (read-only editor panel for sub-agent sessions) + const subAgentViewerProvider = new SubAgentViewerProvider(context.extensionUri, connectionService, context) + context.subscriptions.push(subAgentViewerProvider) + // Register toolbar button command handlers context.subscriptions.push( vscode.commands.registerCommand("kilo-code.new.plusButtonClicked", () => { @@ -94,6 +99,9 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand("kilo-code.new.showChanges", () => { diffViewerProvider.openPanel() }), + vscode.commands.registerCommand("kilo-code.new.openSubAgentViewer", (sessionID: string, title?: string) => { + subAgentViewerProvider.openPanel(sessionID, title) + }), vscode.commands.registerCommand("kilo-code.new.agentManager.previousSession", () => { agentManagerProvider.postMessage({ type: "action", action: "sessionPrevious" }) }), diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index a682c263276..8f8dd7d775f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -1970,7 +1970,7 @@ const AgentManagerContent: Component = () => { >
- + {t("agentManager.notGitRepo")}
@@ -2377,7 +2377,7 @@ const AgentManagerContent: Component = () => { {(() => { // Show setup overlay: either the transient ready/error state for the selected worktree, // or if the selected worktree is still being set up (from busyWorktrees map) - const overlayState = () => { + const overlayState = (): SetupState | null => { const s = setup() const sel = selection() // Transient ready/error overlay for the selected worktree (or worktree-less setup) @@ -2387,7 +2387,11 @@ const AgentManagerContent: Component = () => { const busy = busyWorktrees().get(sel) if (busy?.reason === "setting-up") { const wt = worktrees().find((w) => w.id === sel) - return { active: true, message: busy.message, branch: busy.branch ?? wt?.branch } + return { + active: true, + message: busy.message ?? "", + branch: busy.branch ?? wt?.branch, + } satisfies SetupState } } return null diff --git a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx index 53434399edf..5f5c47fb9bb 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx @@ -233,7 +233,7 @@ export const WorktreeItem: Component = (props) => {