diff --git a/bun.lock b/bun.lock index 8673ba655b8..037d8115239 100644 --- a/bun.lock +++ b/bun.lock @@ -30,7 +30,7 @@ }, "packages/app": { "name": "@opencode-ai/app", - "version": "1.0.14", + "version": "7.2.7", "dependencies": { "@kilocode/kilo-i18n": "workspace:*", "@kilocode/kilo-ui": "workspace:*", @@ -86,7 +86,7 @@ }, "packages/desktop": { "name": "@opencode-ai/desktop", - "version": "7.2.6", + "version": "7.2.7", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -119,7 +119,7 @@ }, "packages/desktop-electron": { "name": "@opencode-ai/desktop-electron", - "version": "1.0.14", + "version": "7.2.7", "dependencies": { "@opencode-ai/app": "workspace:*", "@opencode-ai/ui": "workspace:*", @@ -170,7 +170,7 @@ }, "packages/kilo-docs": { "name": "@kilocode/kilo-docs", - "version": "7.2.6", + "version": "7.2.7", "dependencies": { "@docsearch/css": "^4", "@docsearch/js": "^4", @@ -199,7 +199,7 @@ }, "packages/kilo-gateway": { "name": "@kilocode/kilo-gateway", - "version": "7.2.6", + "version": "7.2.7", "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/openai": "3.0.48", @@ -234,7 +234,7 @@ }, "packages/kilo-i18n": { "name": "@kilocode/kilo-i18n", - "version": "7.2.6", + "version": "7.2.7", "devDependencies": { "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", @@ -247,7 +247,7 @@ }, "packages/kilo-telemetry": { "name": "@kilocode/kilo-telemetry", - "version": "7.2.6", + "version": "7.2.7", "dependencies": { "@kilocode/kilo-gateway": "workspace:*", "@opentelemetry/api": "1.9.0", @@ -267,7 +267,7 @@ }, "packages/kilo-ui": { "name": "@kilocode/kilo-ui", - "version": "7.2.6", + "version": "7.2.7", "dependencies": { "@kobalte/core": "0.13.11", "@opencode-ai/util": "workspace:*", @@ -302,7 +302,7 @@ }, "packages/kilo-vscode": { "name": "kilo-code", - "version": "7.2.6", + "version": "7.2.7", "dependencies": { "@anthropic-ai/sdk": "^0.39.0", "@kilocode/kilo-i18n": "workspace:*", @@ -355,7 +355,7 @@ }, "packages/opencode": { "name": "@kilocode/cli", - "version": "7.2.6", + "version": "7.2.7", "bin": { "kilo": "./bin/kilo", "kilocode": "./bin/kilo", @@ -498,7 +498,7 @@ }, "packages/plugin": { "name": "@kilocode/plugin", - "version": "7.2.6", + "version": "7.2.7", "dependencies": { "@kilocode/sdk": "workspace:*", "zod": "catalog:", @@ -522,7 +522,7 @@ }, "packages/script": { "name": "@opencode-ai/script", - "version": "7.2.6", + "version": "7.2.7", "dependencies": { "semver": "^7.6.3", }, @@ -533,7 +533,7 @@ }, "packages/sdk/js": { "name": "@kilocode/sdk", - "version": "7.2.6", + "version": "7.2.7", "dependencies": { "cross-spawn": "catalog:", }, @@ -548,7 +548,7 @@ }, "packages/storybook": { "name": "@opencode-ai/storybook", - "version": "7.2.6", + "version": "7.2.7", "devDependencies": { "@opencode-ai/ui": "workspace:*", "@solidjs/meta": "catalog:", @@ -571,7 +571,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "7.2.6", + "version": "7.2.7", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "catalog:", @@ -621,7 +621,7 @@ }, "packages/util": { "name": "@opencode-ai/util", - "version": "7.2.6", + "version": "7.2.7", "dependencies": { "zod": "catalog:", }, diff --git a/package.json b/package.json index 1f8fa91a60b..0a87234fe23 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,6 @@ "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "solid-js@1.9.10": "patches/solid-js@1.9.10.patch" }, - "version": "7.2.6", + "version": "7.2.7", "peerDependencies": {} } diff --git a/packages/app/package.json b/packages/app/package.json index 26f53103105..d6d32e08ef7 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/app", - "version": "1.0.14", + "version": "7.2.7", "description": "", "type": "module", "exports": { diff --git a/packages/desktop-electron/package.json b/packages/desktop-electron/package.json index 32d751d8dbf..bc3d526bb88 100644 --- a/packages/desktop-electron/package.json +++ b/packages/desktop-electron/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop-electron", "private": true, - "version": "1.0.14", + "version": "7.2.7", "type": "module", "license": "MIT", "homepage": "https://opencode.ai", diff --git a/packages/desktop/package.json b/packages/desktop/package.json index dae829fbe66..6c56c10b1d0 100644 --- a/packages/desktop/package.json +++ b/packages/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/desktop", "private": true, - "version": "7.2.6", + "version": "7.2.7", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index edfc1202b84..80c85deac7b 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 = "1.4.3" +version = "7.2.7" 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/v1.4.3/opencode-darwin-arm64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.7/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.4.3/opencode-darwin-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.7/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v1.4.3/opencode-linux-arm64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.7/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/v1.4.3/opencode-linux-x64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.7/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/v1.4.3/opencode-windows-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.2.7/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/kilo-docs/package.json b/packages/kilo-docs/package.json index 5b0251756db..c14a6f8907f 100644 --- a/packages/kilo-docs/package.json +++ b/packages/kilo-docs/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-docs", - "version": "7.2.6", + "version": "7.2.7", "private": true, "scripts": { "dev": "next dev --webpack --port 3002", diff --git a/packages/kilo-gateway/package.json b/packages/kilo-gateway/package.json index 02bc8e372b1..2667a3f6014 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.2.6", + "version": "7.2.7", "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 f3cccf8fa76..04429dfa5c3 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.2.6", + "version": "7.2.7", "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 c798fb0959d..1c5923f6ddd 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.2.6", + "version": "7.2.7", "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 84bb7b37f8b..d0304fa115d 100644 --- a/packages/kilo-ui/package.json +++ b/packages/kilo-ui/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-ui", - "version": "7.2.6", + "version": "7.2.7", "type": "module", "license": "MIT", "exports": { diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index f1401fdf148..6332171d680 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.2.6", + "version": "7.2.7", "icon": "assets/icons/logo-outline-black.png", "galleryBanner": { "color": "#FFFFFF", diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 7e4ed60bf3d..63d56523321 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -85,7 +85,6 @@ import { handleQuestionReject, fetchAndSendPendingQuestions, } from "./kilo-provider/handlers/question" -import { fetchAndSendPendingSuggestions, routeSuggestionWebviewMessage } from "./kilo-provider/handlers/suggestion" import { buildActionContext, @@ -507,7 +506,6 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper await Promise.all([ fetchAndSendPendingPermissions(this.permissionCtx), fetchAndSendPendingQuestions(this.questionCtx), - fetchAndSendPendingSuggestions(this.questionCtx), ]) } } @@ -561,7 +559,6 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } } - await routeSuggestionWebviewMessage(this.questionCtx, message) switch (message.type) { case "webviewReady": console.log("[Kilo New] KiloProvider: ✅ webviewReady received") diff --git a/packages/kilo-vscode/src/kilo-provider-utils.ts b/packages/kilo-vscode/src/kilo-provider-utils.ts index 2bc1b2fe085..1f9bf18fd3f 100644 --- a/packages/kilo-vscode/src/kilo-provider-utils.ts +++ b/packages/kilo-vscode/src/kilo-provider-utils.ts @@ -307,24 +307,8 @@ export type WebviewMessage = } } | { type: "todoUpdated"; sessionID: string; items: unknown[] } - | { - type: "questionRequest" - question: { id: string; sessionID: string; questions: unknown[]; blocking?: boolean; tool?: unknown } - } + | { type: "questionRequest"; question: { id: string; sessionID: string; questions: unknown[]; tool?: unknown } } | { type: "questionResolved"; requestID: string } - | { - type: "suggestionRequest" - suggestion: { - id: string - sessionID: string - text: string - actions: unknown[] - blocking?: boolean - tool?: unknown - } - } - | { type: "suggestionResolved"; requestID: string } - | { type: "suggestionError"; requestID: string } | { type: "permissionResolved"; permissionID: string } | { type: "permissionError"; permissionID: string } | { type: "sessionCreated"; session: ReturnType; draftID?: string } @@ -428,7 +412,6 @@ export function mapSSEEventToWebviewMessage(event: Event, sessionID: string | un id: event.properties.id, sessionID: event.properties.sessionID, questions: event.properties.questions, - blocking: event.properties.blocking, tool: event.properties.tool, }, } @@ -438,24 +421,6 @@ export function mapSSEEventToWebviewMessage(event: Event, sessionID: string | un type: "questionResolved", requestID: event.properties.requestID, } - case "suggestion.shown": - return { - type: "suggestionRequest", - suggestion: { - id: event.properties.id, - sessionID: event.properties.sessionID, - text: event.properties.text, - actions: event.properties.actions, - blocking: event.properties.blocking, - tool: event.properties.tool, - }, - } - case "suggestion.accepted": - case "suggestion.dismissed": - return { - type: "suggestionResolved", - requestID: event.properties.requestID, - } case "session.error": { return { type: "sessionError", diff --git a/packages/kilo-vscode/src/kilo-provider/handlers/question.ts b/packages/kilo-vscode/src/kilo-provider/handlers/question.ts index 69d37960006..90e82bad7fc 100644 --- a/packages/kilo-vscode/src/kilo-provider/handlers/question.ts +++ b/packages/kilo-vscode/src/kilo-provider/handlers/question.ts @@ -41,7 +41,6 @@ export async function fetchAndSendPendingQuestions(ctx: QuestionContext): Promis id: q.id, sessionID: q.sessionID, questions: q.questions, - blocking: q.blocking, tool: q.tool, }, }) diff --git a/packages/kilo-vscode/src/kilo-provider/handlers/suggestion.ts b/packages/kilo-vscode/src/kilo-provider/handlers/suggestion.ts deleted file mode 100644 index f265144085c..00000000000 --- a/packages/kilo-vscode/src/kilo-provider/handlers/suggestion.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Suggestion handlers — extracted from KiloProvider. - * - * Manages suggestion accept and dismiss flows plus recovery after SSE reconnects. - * No vscode dependency. - */ - -import type { KiloClient, SuggestionRequest } from "@kilocode/sdk/v2/client" -import { recoveryDirs } from "./permission-handler" - -export type RecoverableSuggestion = SuggestionRequest - -export interface SuggestionContext { - readonly client: KiloClient | null - readonly currentSessionId: string | undefined - readonly trackedSessionIds: Set - readonly sessionDirectories: ReadonlyMap - postMessage(msg: unknown): void - getWorkspaceDirectory(sessionId?: string): string -} - -export function recoverableSuggestions(items: RecoverableSuggestion[], tracked: Set, seen: Set) { - return items.filter((item) => { - if (seen.has(item.id)) return false - seen.add(item.id) - return tracked.has(item.sessionID) - }) -} - -/** - * Route suggestion-related webview messages. - * Extracted from the main message handler to stay within the complexity limit. - */ -export async function routeSuggestionWebviewMessage( - ctx: SuggestionContext, - message: { type: string; requestID?: string; sessionID?: string; index?: number }, -): Promise { - switch (message.type) { - case "suggestionAccept": - await handleSuggestionAccept(ctx, message.requestID!, message.index!, message.sessionID) - break - case "suggestionDismiss": - await handleSuggestionDismiss(ctx, message.requestID!, message.sessionID) - break - } -} - -export async function handleSuggestionAccept( - ctx: SuggestionContext, - requestID: string, - index: number, - sessionID?: string, -): Promise { - if (!ctx.client) { - ctx.postMessage({ type: "suggestionError", requestID }) - return - } - - try { - await ctx.client.suggestion.accept( - { requestID, index, directory: ctx.getWorkspaceDirectory(sessionID ?? ctx.currentSessionId) }, - { throwOnError: true }, - ) - } catch (error) { - console.error("[Kilo New] KiloProvider: Failed to accept suggestion:", error) - ctx.postMessage({ type: "suggestionError", requestID }) - } -} - -export async function handleSuggestionDismiss( - ctx: SuggestionContext, - requestID: string, - sessionID?: string, -): Promise { - if (!ctx.client) { - ctx.postMessage({ type: "suggestionError", requestID }) - return - } - - try { - await ctx.client.suggestion.dismiss( - { requestID, directory: ctx.getWorkspaceDirectory(sessionID ?? ctx.currentSessionId) }, - { throwOnError: true }, - ) - } catch (error) { - console.error("[Kilo New] KiloProvider: Failed to dismiss suggestion:", error) - ctx.postMessage({ type: "suggestionError", requestID }) - } -} - -export async function fetchAndSendPendingSuggestions(ctx: SuggestionContext): Promise { - if (!ctx.client) return - try { - const dirs = recoveryDirs(ctx.getWorkspaceDirectory(), ctx.sessionDirectories) - - const seen = new Set() - for (const dir of dirs) { - const { data } = await ctx.client.suggestion.list({ directory: dir }) - if (!data) continue - for (const suggestion of recoverableSuggestions(data, ctx.trackedSessionIds, seen)) { - ctx.postMessage({ - type: "suggestionRequest", - suggestion, - }) - } - } - } catch (error) { - console.error("[Kilo New] KiloProvider: Failed to fetch pending suggestions:", error) - } -} diff --git a/packages/kilo-vscode/src/services/cli-backend/connection-service.ts b/packages/kilo-vscode/src/services/cli-backend/connection-service.ts index 0a237f5c29d..e68a5e2953c 100644 --- a/packages/kilo-vscode/src/services/cli-backend/connection-service.ts +++ b/packages/kilo-vscode/src/services/cli-backend/connection-service.ts @@ -405,7 +405,6 @@ export class KiloConnectionService { if (error) throw new Error(`Failed to reject question ${q.id}: ${String(error)}`) } } - await drainSuggestions(this.client, dir) await drainNetworkWaits(this.client, dir) } for (const listener of this.clearPendingPromptsListeners) { @@ -634,14 +633,3 @@ export class KiloConnectionService { this.startHealthPoll(config.baseUrl, config.password) } } - -async function drainSuggestions(client: KiloClient, directory: string): Promise { - const { data, error: err } = await client.suggestion.list({ directory }) - if (err) throw new Error(`Failed to list suggestions for ${directory}: ${String(err)}`) - if (data) { - for (const s of data) { - const { error } = await client.suggestion.dismiss({ requestID: s.id, directory }) - if (error) throw new Error(`Failed to dismiss suggestion ${s.id}: ${String(error)}`) - } - } -} diff --git a/packages/kilo-vscode/src/services/cli-backend/connection-utils.ts b/packages/kilo-vscode/src/services/cli-backend/connection-utils.ts index cd91d811192..25eb285ecd3 100644 --- a/packages/kilo-vscode/src/services/cli-backend/connection-utils.ts +++ b/packages/kilo-vscode/src/services/cli-backend/connection-utils.ts @@ -41,17 +41,6 @@ export function resolveEventSessionId( case "question.replied": case "question.rejected": return event.properties.sessionID - default: - return resolveSuggestionSessionId(event) - } -} - -function resolveSuggestionSessionId(event: Event): string | undefined { - switch (event.type) { - case "suggestion.shown": - case "suggestion.accepted": - case "suggestion.dismissed": - return event.properties.sessionID default: // session.network.* events are not yet in the SDK Event type union // (pending SDK regeneration). Handle them via string comparison. diff --git a/packages/kilo-vscode/tests/unit/connection-utils.test.ts b/packages/kilo-vscode/tests/unit/connection-utils.test.ts index c916af225a9..0715975f3d8 100644 --- a/packages/kilo-vscode/tests/unit/connection-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/connection-utils.test.ts @@ -149,30 +149,6 @@ describe("resolveEventSessionId", () => { expect(resolveEventSessionId(e, noLookup)).toBe("s11") }) - it("returns sessionID from suggestion.shown", () => { - const e = event({ - type: "suggestion.shown", - properties: { id: "sug_1", sessionID: "s12", text: "Review?", actions: [] }, - }) - expect(resolveEventSessionId(e, noLookup)).toBe("s12") - }) - - it("returns sessionID from suggestion.accepted", () => { - const e = event({ - type: "suggestion.accepted", - properties: { sessionID: "s13", requestID: "sug_1", index: 0, action: { label: "Start", prompt: "x" } }, - }) - expect(resolveEventSessionId(e, noLookup)).toBe("s13") - }) - - it("returns sessionID from suggestion.dismissed", () => { - const e = event({ - type: "suggestion.dismissed", - properties: { sessionID: "s14", requestID: "sug_2" }, - }) - expect(resolveEventSessionId(e, noLookup)).toBe("s14") - }) - it("returns undefined for unknown event types (global events)", () => { const e = event({ type: "server.connected", properties: {} }) expect(resolveEventSessionId(e, noLookup)).toBeUndefined() diff --git a/packages/kilo-vscode/tests/unit/kilo-provider-utils.test.ts b/packages/kilo-vscode/tests/unit/kilo-provider-utils.test.ts index ebd96663c76..5d9f38aae33 100644 --- a/packages/kilo-vscode/tests/unit/kilo-provider-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/kilo-provider-utils.test.ts @@ -26,9 +26,6 @@ import type { EventQuestionAsked, EventQuestionReplied, EventQuestionRejected, - EventSuggestionShown, - EventSuggestionAccepted, - EventSuggestionDismissed, EventSessionCreated, EventSessionUpdated, EventServerConnected, @@ -447,43 +444,6 @@ describe("mapSSEEventToWebviewMessage", () => { } }) - it("maps suggestion.shown to suggestionRequest", () => { - const event: EventSuggestionShown = { - type: "suggestion.shown", - properties: { - id: "sug-1", - sessionID: "sess-1", - text: "Review changes?", - actions: [{ label: "Start", prompt: "/local-review-uncommitted" }], - }, - } - const msg = mapSSEEventToWebviewMessage(event, "sess-1") - expect(msg?.type).toBe("suggestionRequest") - }) - - it("maps suggestion.accepted to suggestionResolved", () => { - const event: EventSuggestionAccepted = { - type: "suggestion.accepted", - properties: { - sessionID: "sess-1", - requestID: "sug-1", - index: 0, - action: { label: "Start", prompt: "/local-review-uncommitted" }, - }, - } - const msg = mapSSEEventToWebviewMessage(event, "sess-1") - expect(msg?.type).toBe("suggestionResolved") - }) - - it("maps suggestion.dismissed to suggestionResolved", () => { - const event: EventSuggestionDismissed = { - type: "suggestion.dismissed", - properties: { sessionID: "sess-1", requestID: "sug-2" }, - } - const msg = mapSSEEventToWebviewMessage(event, "sess-1") - expect(msg?.type).toBe("suggestionResolved") - }) - it("maps session.created to sessionCreated with ISO dates", () => { const event: EventSessionCreated = { type: "session.created", diff --git a/packages/kilo-vscode/tests/unit/suggestion-recovery.test.ts b/packages/kilo-vscode/tests/unit/suggestion-recovery.test.ts deleted file mode 100644 index 81f535afa1d..00000000000 --- a/packages/kilo-vscode/tests/unit/suggestion-recovery.test.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { describe, expect, it } from "bun:test" -import { - fetchAndSendPendingSuggestions, - recoverableSuggestions, - type RecoverableSuggestion, - type SuggestionContext, -} from "../../src/kilo-provider/handlers/suggestion" - -function pending(id: string, sessionID: string): RecoverableSuggestion { - return { - id, - sessionID, - text: "Review changes?", - actions: [{ label: "Start", prompt: "/local-review-uncommitted" }], - } -} - -type Items = Record - -function suggestionClient(itemsPerDir: Items, queries: string[]) { - return { - suggestion: { - list: async (args?: { directory?: string }) => { - const dir = args?.directory ?? "" - queries.push(dir) - return { data: itemsPerDir[dir] ?? [] } - }, - accept: async () => ({ data: true }), - dismiss: async () => ({ data: true }), - }, - } -} - -function ctx(opts: { tracked: string[]; dirs?: Map; itemsPerDir?: Items }) { - const messages: unknown[] = [] - const queries: string[] = [] - const sdk = suggestionClient(opts.itemsPerDir ?? {}, queries) as unknown as SuggestionContext["client"] - - const fake: SuggestionContext = { - client: sdk, - currentSessionId: undefined, - trackedSessionIds: new Set(opts.tracked), - sessionDirectories: opts.dirs ?? new Map(), - postMessage: (msg) => messages.push(msg), - getWorkspaceDirectory: () => "/workspace", - } - - return { fake, messages, queries } -} - -describe("recoverableSuggestions", () => { - it("filters out untracked suggestions and deduplicates by id", () => { - const seen = new Set() - const list = [pending("s1", "tracked"), pending("s1", "tracked"), pending("s2", "other")] - expect(recoverableSuggestions(list, new Set(["tracked"]), seen)).toEqual([pending("s1", "tracked")]) - }) -}) - -describe("fetchAndSendPendingSuggestions", () => { - it("forwards suggestions from tracked sessions", async () => { - const dirs = new Map([["s1", "/wt"]]) - const { fake, messages, queries } = ctx({ - tracked: ["s1"], - dirs, - itemsPerDir: { "/wt": [pending("sug-1", "s1")] }, - }) - - await fetchAndSendPendingSuggestions(fake) - - expect(queries).toContain("/workspace") - expect(queries).toContain("/wt") - expect(messages).toEqual([{ type: "suggestionRequest", suggestion: pending("sug-1", "s1") }]) - }) - - it("does nothing when client is null", async () => { - const messages: unknown[] = [] - const fake: SuggestionContext = { - client: null, - currentSessionId: undefined, - trackedSessionIds: new Set(["s1"]), - sessionDirectories: new Map(), - postMessage: (msg) => messages.push(msg), - getWorkspaceDirectory: () => "/workspace", - } - - await fetchAndSendPendingSuggestions(fake) - expect(messages).toHaveLength(0) - }) -}) diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/suggest-bar-review-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/suggest-bar-review-chromium-linux.png deleted file mode 100644 index 27e9b8983a1..00000000000 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/suggest-bar-review-chromium-linux.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:eee5f5d61cf2ac9ad0b987a8b220d76c503be0d11447766e6669da4684698c82 -size 6130 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 b4541bde844..02035c8cd1e 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx @@ -19,7 +19,6 @@ import type { import { useData } from "@kilocode/kilo-ui/context/data" import { useSession } from "../../context/session" import { QuestionDock } from "./QuestionDock" -import { SuggestBar } from "./SuggestBar" // Tools that the upstream message-part renderer suppresses (returns null for). // We render these ourselves via ToolRegistry when they complete, @@ -42,22 +41,6 @@ function isRenderable(part: SDKPart): boolean { return !!PART_MAPPING[part.type] } -/** - * Match a tool part to an active request (question or suggestion) by tool name - * and callID/messageID. Returns the matched request or undefined. - */ -function matchToolRequest( - part: SDKPart, - name: string, - requests: T[], -): T | undefined { - if (part.type !== "tool") return undefined - const tp = part as unknown as ToolPart - if (tp.tool !== name) return undefined - if (tp.state?.status !== "pending" && tp.state?.status !== "running") return undefined - return requests.find((r) => r.tool?.callID === tp.callID && r.tool?.messageID === tp.messageID) -} - interface AssistantMessageProps { message: SDKAssistantMessage showAssistantCopyPartID?: string | null @@ -104,40 +87,36 @@ export const AssistantMessage: Component = (props) => { part.type === "tool" && UPSTREAM_SUPPRESSED_TOOLS.has((part as SDKPart & { tool: string }).tool) // Active question tool parts render the interactive QuestionDock inline - const activeQuestion = createMemo(() => matchToolRequest(part, "question", session.questions())) - - // Active suggestion tool parts render the interactive SuggestBar inline - const activeSuggestion = createMemo(() => matchToolRequest(part, "suggest", session.suggestions())) + const activeQuestion = createMemo(() => { + if (part.type !== "tool") return undefined + const tp = part as unknown as ToolPart + if (tp.tool !== "question") return undefined + if (tp.state?.status !== "pending" && tp.state?.status !== "running") return undefined + return session.questions().find((q) => q.tool?.callID === tp.callID && q.tool?.messageID === tp.messageID) + }) return ( - +
+ - - + /> } > - {(req) => } + } > diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index 1f769cae8f7..eee0d6a844a 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -55,16 +55,13 @@ export const ChatView: Component = (props) => { // not once per accessor call (questionRequest, permissionRequest, blocked all read these). const familyPermissions = createMemo(() => session.scopedPermissions(id())) const familyQuestions = createMemo(() => session.scopedQuestions(id())) - const familySuggestions = createMemo(() => session.scopedSuggestions(id())) + // Non-tool questions (standalone, not from the question tool) render inline in // the message list since they don't have an associated tool part in the conversation. // Tool-linked questions render inline at their tool part position via AssistantMessage. const standaloneQuestions = createMemo(() => familyQuestions().filter((q) => !q.tool)) - const standaloneSuggestions = createMemo(() => familySuggestions().filter((s) => !s.tool)) const permissionRequest = () => familyPermissions().find((p) => p.sessionID === id()) ?? familyPermissions()[0] - const blocked = () => familyPermissions().length > 0 || familyQuestions().some((q) => q.blocking !== false) - // Session is busy only because a suggestion tool call is pending — prompt should behave as idle - const suggesting = () => !blocked() && familySuggestions().length > 0 + const blocked = () => familyPermissions().length > 0 || familyQuestions().length > 0 const dock = () => !props.readonly || !!permissionRequest() // When a bottom-dock permission disappears while the session is busy, @@ -133,7 +130,6 @@ export const ChatView: Component = (props) => { onSelectSession={props.onSelectSession} onShowHistory={props.onShowHistory} questions={standaloneQuestions} - suggestions={standaloneSuggestions} readonly={props.readonly} />
@@ -219,12 +215,7 @@ export const ChatView: Component = (props) => {
- +
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx index a057a1fab47..8828ca0020f 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx @@ -23,9 +23,8 @@ import { AccountSwitcher } from "../shared/AccountSwitcher" import { KiloNotifications } from "./KiloNotifications" import { WorkingIndicator } from "../shared/WorkingIndicator" import { QuestionDock } from "./QuestionDock" -import { SuggestBar } from "./SuggestBar" import { activeUserMessageID as getActiveUserMessageID } from "../../context/session-queue" -import type { QuestionRequest, SuggestionRequest } from "../../types/messages" +import type { QuestionRequest } from "../../types/messages" const KiloLogo = (): JSX.Element => { const iconsBaseUri = (window as { ICONS_BASE_URI?: string }).ICONS_BASE_URI || "" @@ -45,8 +44,6 @@ interface MessageListProps { onShowHistory?: () => void /** Non-tool question requests to render inline at the bottom of the message list */ questions?: () => QuestionRequest[] - /** Non-tool suggestion requests to render inline at the bottom of the message list */ - suggestions?: () => SuggestionRequest[] /** When true (subagent viewer), replace the welcome screen with an initializing indicator */ readonly?: boolean } @@ -176,7 +173,6 @@ export const MessageList: Component = (props) => { {(req) => } - {(req) => } diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx index 8fe805e6547..b5a2ff32182 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx @@ -50,8 +50,6 @@ function mergeReviewComments(current: ReviewComment[], incoming: ReviewComment[] interface PromptInputProps { blocked?: () => boolean - /** When true, session is busy only because a suggestion is pending — treat as idle for input */ - suggesting?: () => boolean boxId?: string pendingSessionID?: string } @@ -270,7 +268,7 @@ export const PromptInput: Component = (props) => { window.addEventListener("compactSession", onCompact) onCleanup(() => window.removeEventListener("compactSession", onCompact)) - const isBusy = () => session.status() !== "idle" && !props.suggesting?.() + const isBusy = () => session.status() !== "idle" const isDisabled = () => !server.isConnected() const hasInput = () => text().trim().length > 0 || imageAttach.images().length > 0 || reviewComments().length > 0 const canSend = () => hasInput() && !isDisabled() && !terminal.pending() && !props.blocked?.() diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/SuggestBar.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/SuggestBar.tsx deleted file mode 100644 index 2eac7976986..00000000000 --- a/packages/kilo-vscode/webview-ui/src/components/chat/SuggestBar.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { Button } from "@kilocode/kilo-ui/button" -import { Icon } from "@kilocode/kilo-ui/icon" -import { IconButton } from "@kilocode/kilo-ui/icon-button" -import type { Component } from "solid-js" -import { For, Show } from "solid-js" -import { useLanguage } from "../../context/language" -import { useSession } from "../../context/session" -import type { SuggestionRequest } from "../../types/messages" - -export const SuggestBar: Component<{ request: SuggestionRequest }> = (props) => { - const session = useSession() - const language = useLanguage() - - const error = () => session.suggestionErrors().has(props.request.id) - const responding = () => session.respondingSuggestions().has(props.request.id) - - const accept = (index: number) => { - if (responding()) return - session.acceptSuggestion(props.request.id, index) - } - - const dismiss = () => { - if (responding()) return - session.dismissSuggestion(props.request.id) - } - - return ( -
-
- - - - {props.request.text} -
- -
- - Failed — try again -
-
-
- - {(action, index) => ( - - )} - - -
-
- ) -} diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/WorkingIndicator.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/WorkingIndicator.tsx index 4d4c64fe677..eb4067751d2 100644 --- a/packages/kilo-vscode/webview-ui/src/components/shared/WorkingIndicator.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/shared/WorkingIndicator.tsx @@ -83,8 +83,7 @@ export const WorkingIndicator: Component = () => { .permissions() .filter((p) => p.sessionID === id && !(p.tool && ["todowrite", "todoread"].includes(p.toolName))) const questions = session.questions().filter((q) => q.sessionID === id) - const suggestions = session.suggestions().filter((s) => s.sessionID === id) - return perms.length > 0 || questions.length > 0 || suggestions.length > 0 + return perms.length > 0 || questions.length > 0 } const isRetrying = () => session.statusInfo().type === "retry" diff --git a/packages/kilo-vscode/webview-ui/src/context/session.tsx b/packages/kilo-vscode/webview-ui/src/context/session.tsx index ea21f0730a4..ed1c38b7ef3 100644 --- a/packages/kilo-vscode/webview-ui/src/context/session.tsx +++ b/packages/kilo-vscode/webview-ui/src/context/session.tsx @@ -22,7 +22,6 @@ import type { SessionStatusInfo, PermissionRequest, QuestionRequest, - SuggestionRequest, TodoItem, ModelSelection, ContextUsage, @@ -114,14 +113,10 @@ interface SessionContextValue { // Pending question requests (unscoped — all tracked sessions) questions: Accessor questionErrors: Accessor> - suggestions: Accessor - suggestionErrors: Accessor> - respondingSuggestions: Accessor> // Scoped permissions/questions — filtered to a session's family (self + subagents) scopedPermissions: (sessionID: string | undefined) => PermissionRequest[] scopedQuestions: (sessionID: string | undefined) => QuestionRequest[] - scopedSuggestions: (sessionID: string | undefined) => SuggestionRequest[] // Model selection (global, extension-lifetime) selected: Accessor @@ -196,8 +191,6 @@ interface SessionContextValue { ) => void replyToQuestion: (requestID: string, answers: string[][]) => void rejectQuestion: (requestID: string) => void - acceptSuggestion: (requestID: string, index: number) => void - dismissSuggestion: (requestID: string) => void createSession: () => void clearCurrentSession: () => void loadSessions: () => void @@ -257,9 +250,6 @@ export const SessionProvider: ParentComponent = (props) => { // Tracks question IDs that failed so the UI can reset sending state const [questionErrors, setQuestionErrors] = createSignal>(new Set()) - const [suggestions, setSuggestions] = createSignal([]) - const [suggestionErrors, setSuggestionErrors] = createSignal>(new Set()) - const [respondingSuggestions, setRespondingSuggestions] = createSignal>(new Set()) // Tracks whether the user has explicitly set a model override per agent (to // prevent the default-sync effect from overwriting it). @@ -653,9 +643,6 @@ export const SessionProvider: ParentComponent = (props) => { // Handle messages from extension onMount(() => { const unsubscribe = vscode.onMessage((message: ExtensionMessage) => { - // Route suggestion messages (extracted to stay within complexity limit) - routeSuggestionMessage(message) - switch (message.type) { case "sessionCreated": handleSessionCreated(message.session, message.draftID) @@ -696,10 +683,7 @@ export const SessionProvider: ParentComponent = (props) => { case "clearPendingPrompts": setPermissions([]) setQuestions([]) - setSuggestions([]) setRespondingPermissions(new Set()) - setSuggestionErrors(new Set()) - setRespondingSuggestions(new Set()) break case "sessionsLoaded": @@ -1016,60 +1000,6 @@ export const SessionProvider: ParentComponent = (props) => { setQuestionErrors((prev) => new Set(prev).add(requestID)) } - function handleSuggestionRequest(suggestion: SuggestionRequest) { - setSuggestions((prev) => { - const idx = prev.findIndex((item) => item.id === suggestion.id) - if (idx === -1) return [...prev, suggestion] - const next = prev.slice() - next[idx] = suggestion - return next - }) - } - - function handleSuggestionResolved(requestID: string) { - setSuggestions((prev) => prev.filter((item) => item.id !== requestID)) - setRespondingSuggestions((prev) => { - if (!prev.has(requestID)) return prev - const next = new Set(prev) - next.delete(requestID) - return next - }) - setSuggestionErrors((prev) => { - if (!prev.has(requestID)) return prev - const next = new Set(prev) - next.delete(requestID) - return next - }) - } - - function handleSuggestionError(requestID: string) { - setRespondingSuggestions((prev) => { - if (!prev.has(requestID)) return prev - const next = new Set(prev) - next.delete(requestID) - return next - }) - setSuggestionErrors((prev) => new Set(prev).add(requestID)) - } - - /** - * Route suggestion-related extension messages. - * Extracted from the main message handler to stay within the complexity limit. - */ - function routeSuggestionMessage(message: ExtensionMessage) { - switch (message.type) { - case "suggestionRequest": - handleSuggestionRequest(message.suggestion) - break - case "suggestionResolved": - handleSuggestionResolved(message.requestID) - break - case "suggestionError": - handleSuggestionError(message.requestID) - break - } - } - /** * Handle a failed send: remove the optimistic message from the store * and show a toast. The PromptInput restores the draft text separately @@ -1186,12 +1116,6 @@ export const SessionProvider: ParentComponent = (props) => { return questions().filter((q) => family.has(q.sessionID)) } - function scopedSuggestions(sessionID: string | undefined): SuggestionRequest[] { - if (!sessionID) return [] - const family = sessionFamily(sessionID) - return suggestions().filter((item) => family.has(item.sessionID)) - } - function handleTodoUpdated(sessionID: string, items: TodoItem[]) { setStore("todos", sessionID, items) } @@ -1272,24 +1196,6 @@ export const SessionProvider: ParentComponent = (props) => { return next }) } - const gone = suggestions() - .filter((item) => item.sessionID === sessionID) - .map((item) => item.id) - if (gone.length > 0) { - setSuggestions((prev) => prev.filter((item) => item.sessionID !== sessionID)) - setSuggestionErrors((prev) => { - const next = new Set(prev) - for (const id of gone) next.delete(id) - if (next.size === prev.size) return prev - return next - }) - setRespondingSuggestions((prev) => { - const next = new Set(prev) - for (const id of gone) next.delete(id) - if (next.size === prev.size) return prev - return next - }) - } setPermissions((prev) => removeSessionPermissions(prev, sessionID)) setStatusMap( produce((map) => { @@ -1502,8 +1408,6 @@ export const SessionProvider: ParentComponent = (props) => { } const sid = currentSessionID() - const suggestion = scopedSuggestions(sid)[0] - if (suggestion) dismissSuggestion(suggestion.id) if (sid) addOptimistic(sid, messageID, text, files) const agent = selectedAgentName() !== defaultAgent() ? selectedAgentName() : undefined @@ -1557,8 +1461,6 @@ export const SessionProvider: ParentComponent = (props) => { const messageID = Identifier.ascending("message") const sid = currentSessionID() - const suggestion = scopedSuggestions(sid)[0] - if (suggestion) dismissSuggestion(suggestion.id) if (sid) addOptimistic(sid, messageID, `/${command} ${args}`.trim(), files) @@ -1646,15 +1548,6 @@ export const SessionProvider: ParentComponent = (props) => { }) } - function clearSuggestionError(requestID: string) { - setSuggestionErrors((prev) => { - if (!prev.has(requestID)) return prev - const next = new Set(prev) - next.delete(requestID) - return next - }) - } - function replyToQuestion(requestID: string, answers: string[][]) { clearQuestionError(requestID) const question = questions().find((item) => item.id === requestID) @@ -1678,29 +1571,6 @@ export const SessionProvider: ParentComponent = (props) => { }) } - function acceptSuggestion(requestID: string, index: number) { - clearSuggestionError(requestID) - setRespondingSuggestions((prev) => new Set(prev).add(requestID)) - const sid = suggestions().find((s) => s.id === requestID)?.sessionID ?? currentSessionID() ?? "" - vscode.postMessage({ - type: "suggestionAccept", - requestID, - sessionID: sid, - index, - }) - } - - function dismissSuggestion(requestID: string) { - clearSuggestionError(requestID) - setRespondingSuggestions((prev) => new Set(prev).add(requestID)) - const sid = suggestions().find((s) => s.id === requestID)?.sessionID ?? currentSessionID() ?? "" - vscode.postMessage({ - type: "suggestionDismiss", - requestID, - sessionID: sid, - }) - } - function createSession() { if (!server.isConnected()) { console.warn("[Kilo New] Cannot create session: not connected") @@ -1945,12 +1815,8 @@ export const SessionProvider: ParentComponent = (props) => { respondingPermissions, questions, questionErrors, - suggestions, - suggestionErrors, - respondingSuggestions, scopedPermissions, scopedQuestions, - scopedSuggestions, selected, selectModel, hasModelOverride, @@ -2012,8 +1878,6 @@ export const SessionProvider: ParentComponent = (props) => { respondToPermission, replyToQuestion, rejectQuestion, - acceptSuggestion, - dismissSuggestion, createSession, clearCurrentSession, loadSessions, diff --git a/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx b/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx index a9a0f10e8b4..43eed3f123a 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx @@ -33,13 +33,7 @@ import { dict as appEn } from "../i18n/en" import { dict as amEn } from "../../agent-manager/i18n/en" import { dict as kiloEn } from "@kilocode/kilo-i18n/en" import { resolveTemplate } from "../context/language-utils" -import type { - Config, - KilocodeNotification, - PermissionRequest, - QuestionRequest, - SuggestionRequest, -} from "../types/messages" +import type { Config, KilocodeNotification, PermissionRequest, QuestionRequest } from "../types/messages" // Merged English dictionary (same merge order as the real LanguageProvider) const dict: Record = { ...appEn, ...amEn, ...uiEn, ...kiloEn } @@ -126,13 +120,11 @@ export function mockSessionValue(overrides?: { id?: string permissions?: PermissionRequest[] questions?: QuestionRequest[] - suggestions?: SuggestionRequest[] status?: string }) { const id = overrides?.id ?? "story-session-001" const permissions = overrides?.permissions ?? [] const qs = overrides?.questions ?? [] - const suggestions = overrides?.suggestions ?? [] const status = (overrides?.status ?? "idle") as "idle" | "busy" return { @@ -162,12 +154,8 @@ export function mockSessionValue(overrides?: { respondingPermissions: () => new Set(), questions: () => qs, questionErrors: () => new Set(), - suggestions: () => suggestions, - suggestionErrors: () => new Set(), - respondingSuggestions: () => new Set(), scopedPermissions: (sid?: string) => (sid ? permissions.filter((p) => p.sessionID === sid) : permissions), scopedQuestions: (sid?: string) => (sid ? qs.filter((q) => q.sessionID === sid) : qs), - scopedSuggestions: (sid?: string) => (sid ? suggestions.filter((item) => item.sessionID === sid) : suggestions), selected: () => ({ providerID: "kilo", modelID: "anthropic/claude-sonnet-4-6" }), selectModel: noop, hasModelOverride: () => false, @@ -198,14 +186,11 @@ export function mockSessionValue(overrides?: { currentVariant: () => undefined, selectVariant: noop, sendMessage: noop, - sendCommand: noop, abort: noop, compact: noop, respondToPermission: noop, replyToQuestion: noop, rejectQuestion: noop, - acceptSuggestion: noop, - dismissSuggestion: noop, createSession: noop, clearCurrentSession: noop, loadSessions: noop, @@ -226,7 +211,6 @@ interface StoryProvidersProps { data?: any permissions?: PermissionRequest[] questions?: QuestionRequest[] - suggestions?: SuggestionRequest[] notifications?: KilocodeNotification[] status?: string sessionID?: string @@ -258,7 +242,6 @@ export const StoryProviders: ParentComponent = (props) => { id: props.sessionID, permissions: props.permissions, questions: props.questions, - suggestions: props.suggestions, status: props.status, }) const notifications = mockNotificationsValue(props.notifications) diff --git a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx index 1fd60ef1de4..93c64ab4ade 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx @@ -12,10 +12,9 @@ import { StoryProviders, mockSessionValue } from "./StoryProviders" import { ChatView } from "../components/chat/ChatView" import { TaskHeader } from "../components/chat/TaskHeader" import { QuestionDock } from "../components/chat/QuestionDock" -import { SuggestBar } from "../components/chat/SuggestBar" import { SessionContext } from "../context/session" import { ServerContext } from "../context/server" -import type { QuestionRequest, SuggestionRequest, TodoItem } from "../types/messages" +import type { QuestionRequest, TodoItem } from "../types/messages" const SESSION_ID = "story-session-chat-001" @@ -67,14 +66,6 @@ const multiQuestion: QuestionRequest = { tool: { messageID: "asst-msg-001", callID: "call-question-002" }, } -const reviewSuggestion: SuggestionRequest = { - id: "s-review-001", - sessionID: SESSION_ID, - text: "Start a code review of uncommitted changes?", - actions: [{ label: "Start review", description: "Run a local review now", prompt: "/local-review-uncommitted" }], - tool: { messageID: "asst-msg-002", callID: "call-suggest-001" }, -} - // --------------------------------------------------------------------------- // Meta // --------------------------------------------------------------------------- @@ -181,17 +172,6 @@ export const QuestionDockManyOptions: Story = { ), } -export const SuggestBarReview: Story = { - name: "SuggestBar — review suggestion", - render: () => ( - -
- -
-
- ), -} - // --------------------------------------------------------------------------- // TaskHeader with todos // --------------------------------------------------------------------------- diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index a40375411cc..e05bb9ded1f 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -19,6 +19,5 @@ @import "./notifications.css"; @import "./tool-overrides.css"; @import "./question-dock.css"; -@import "./suggest-bar.css"; @import "./settings.css"; @import "./high-contrast.css"; diff --git a/packages/kilo-vscode/webview-ui/src/styles/suggest-bar.css b/packages/kilo-vscode/webview-ui/src/styles/suggest-bar.css deleted file mode 100644 index d624fadf5fa..00000000000 --- a/packages/kilo-vscode/webview-ui/src/styles/suggest-bar.css +++ /dev/null @@ -1,49 +0,0 @@ -/* ============================================ - Suggest Bar - ============================================ */ - -/* Strip the card border from tool-part-wrapper when it contains a suggest bar */ -[data-component="tool-part-wrapper"]:has([data-component="suggest-bar"]) { - border: none !important; - border-radius: 0; - overflow: visible; -} - -[data-component="suggest-bar"] { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin: 0; - padding: 10px 12px; - background: color-mix(in srgb, var(--background-base) 88%, var(--vscode-textLink-foreground) 12%); - - [data-slot="suggest-bar-copy"] { - display: flex; - align-items: center; - gap: 8px; - min-width: 0; - flex: 1; - } - - [data-slot="suggest-bar-icon"] { - display: inline-flex; - align-items: center; - color: var(--text-info, var(--vscode-textLink-foreground)); - flex-shrink: 0; - } - - [data-slot="suggest-bar-text"] { - min-width: 0; - color: var(--text-base); - font-size: 12px; - line-height: 1.4; - } - - [data-slot="suggest-bar-actions"] { - display: flex; - align-items: center; - gap: 6px; - flex-shrink: 0; - } -} diff --git a/packages/kilo-vscode/webview-ui/src/types/messages.ts b/packages/kilo-vscode/webview-ui/src/types/messages.ts index 51d142bc302..7a2254bfa5f 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages.ts @@ -218,25 +218,6 @@ export interface QuestionRequest { id: string sessionID: string questions: QuestionInfo[] - blocking?: boolean - tool?: { - messageID: string - callID: string - } -} - -export interface SuggestionAction { - label: string - description?: string - prompt: string -} - -export interface SuggestionRequest { - id: string - sessionID: string - text: string - actions: SuggestionAction[] - blocking?: boolean tool?: { messageID: string callID: string @@ -785,21 +766,6 @@ export interface QuestionErrorMessage { requestID: string } -export interface SuggestionRequestMessage { - type: "suggestionRequest" - suggestion: SuggestionRequest -} - -export interface SuggestionResolvedMessage { - type: "suggestionResolved" - requestID: string -} - -export interface SuggestionErrorMessage { - type: "suggestionError" - requestID: string -} - export interface BrowserSettings { enabled: boolean useSystemChrome: boolean @@ -1530,9 +1496,6 @@ export type ExtensionMessage = | QuestionRequestMessage | QuestionResolvedMessage | QuestionErrorMessage - | SuggestionRequestMessage - | SuggestionResolvedMessage - | SuggestionErrorMessage | BrowserSettingsLoadedMessage | ClaudeCompatSettingLoadedMessage | ConfigLoadedMessage @@ -1844,19 +1807,6 @@ export interface QuestionRejectRequest { sessionID?: string } -export interface SuggestionAcceptRequest { - type: "suggestionAccept" - requestID: string - sessionID: string - index: number -} - -export interface SuggestionDismissRequest { - type: "suggestionDismiss" - requestID: string - sessionID: string -} - export interface DeleteSessionRequest { type: "deleteSession" sessionID: string @@ -2493,8 +2443,6 @@ export type WebviewMessage = | SetLanguageRequest | QuestionReplyRequest | QuestionRejectRequest - | SuggestionAcceptRequest - | SuggestionDismissRequest | DeleteSessionRequest | RenameSessionRequest | RequestAutocompleteSettingsMessage diff --git a/packages/opencode/package.json b/packages/opencode/package.json index e46570bd4e7..ae86e02959a 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.2.6", + "version": "7.2.7", "name": "@kilocode/cli", "type": "module", "license": "MIT", diff --git a/packages/opencode/src/agent/agent.ts b/packages/opencode/src/agent/agent.ts index 47d06340219..d967031edd5 100644 --- a/packages/opencode/src/agent/agent.ts +++ b/packages/opencode/src/agent/agent.ts @@ -102,7 +102,6 @@ export namespace Agent { "*": "ask", ...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])), }, - suggest: "deny", // kilocode_change question: "deny", plan_enter: "deny", plan_exit: "deny", @@ -131,7 +130,6 @@ export namespace Agent { defaults, Permission.fromConfig({ question: "allow", - suggest: "allow", // kilocode_change plan_enter: "allow", }), user, diff --git a/packages/opencode/src/cli/cmd/tui/context/sync.tsx b/packages/opencode/src/cli/cmd/tui/context/sync.tsx index be9ab690deb..c82bcc2b054 100644 --- a/packages/opencode/src/cli/cmd/tui/context/sync.tsx +++ b/packages/opencode/src/cli/cmd/tui/context/sync.tsx @@ -9,7 +9,6 @@ import type { Command, PermissionRequest, QuestionRequest, - SuggestionRequest, // kilocode_change SessionNetworkWait, // kilocode_change LspStatus, McpStatus, @@ -28,7 +27,6 @@ import type { Snapshot } from "@/snapshot" import { useExit } from "./exit" import { useArgs } from "./args" import { batch, onMount } from "solid-js" -import { handleSuggestionEvent } from "@/kilocode/suggestion/tui/sync" // kilocode_change import { Log } from "@/util/log" import { useToast } from "@tui/ui/toast" // kilocode_change import type { Path } from "@kilocode/sdk" @@ -54,9 +52,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ [sessionID: string]: QuestionRequest[] } // kilocode_change start - suggestion: { - [sessionID: string]: SuggestionRequest[] - } network: { [sessionID: string]: SessionNetworkWait[] } @@ -103,7 +98,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ permission: {}, question: {}, // kilocode_change start - suggestion: {}, network: {}, // kilocode_change end command: [], @@ -149,9 +143,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ delete draft.session_diff[sessionID] delete draft.session_status[sessionID] delete draft.todo[sessionID] - delete draft.permission[sessionID] - delete draft.question[sessionID] - delete draft.suggestion[sessionID] delete draft.network[sessionID] }), ) @@ -265,15 +256,6 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ break } - // kilocode_change start - case "suggestion.accepted": - case "suggestion.dismissed": - case "suggestion.shown": { - handleSuggestionEvent(event, store, setStore) - break - } - // kilocode_change end - case "session.network.restored": { const requests = store.network[event.properties.sessionID] if (!requests) break @@ -306,6 +288,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({ break } // kilocode_change end + case "todo.updated": setStore("todo", event.properties.sessionID, event.properties.todos) break diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx index 09c286b60ea..022ab7ae185 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/index.tsx @@ -21,7 +21,7 @@ import { Spinner } from "@tui/component/spinner" import { selectedForeground, useTheme } from "@tui/context/theme" import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers, TextAttributes, RGBA } from "@opentui/core" import { Prompt, type PromptRef } from "@tui/component/prompt" -import type { AssistantMessage, Part, Provider, ToolPart, UserMessage, TextPart, ReasoningPart } from "@kilocode/sdk/v2" // kilocode_change +import type { AssistantMessage, Part, Provider, ToolPart, UserMessage, TextPart, ReasoningPart } from "@kilocode/sdk/v2" // kilocode_change // kilocode_change import { useLocal } from "@tui/context/local" import { Locale } from "@/util/locale" import type { Tool } from "@/tool/tool" @@ -69,8 +69,6 @@ import { Filesystem } from "@/util/filesystem" import { Global } from "@/global" import { PermissionPrompt } from "./permission" import { QuestionPrompt } from "./question" -import { Suggest } from "@/kilocode/suggestion/tui/render" // kilocode_change -import { SuggestPrompt } from "@/kilocode/suggestion/tui/prompt" // kilocode_change import { NetworkPrompt } from "./network" // kilocode_change import { DialogExportOptions } from "../../ui/dialog-export-options" import * as Model from "../../util/model" @@ -135,42 +133,18 @@ export function Session() { return children().flatMap((x) => sync.data.question[x.id] ?? []) }) // kilocode_change start - const suggestions = createMemo(() => { - if (session()?.parentID) return [] - return children().flatMap((x) => sync.data.suggestion[x.id] ?? []) - }) const network = createMemo(() => { if (session()?.parentID) return [] return children().flatMap((x) => sync.data.network[x.id] ?? []) }) - const blockingQuestions = createMemo(() => questions().filter((q) => q.blocking !== false)) - const nonBlockingQuestions = createMemo(() => questions().filter((q) => q.blocking === false)) - const question = createMemo(() => blockingQuestions()[0] ?? nonBlockingQuestions()[0]) - const blockingSuggestions = createMemo(() => suggestions().filter((s) => s.blocking !== false)) - const nonBlockingSuggestions = createMemo(() => suggestions().filter((s) => s.blocking === false)) - const suggestion = createMemo(() => blockingSuggestions()[0] ?? nonBlockingSuggestions()[0]) const visible = createMemo( - () => - !session()?.parentID && - permissions().length === 0 && - blockingQuestions().length === 0 && - blockingSuggestions().length === 0 && - network().length === 0, + () => !session()?.parentID && permissions().length === 0 && questions().length === 0 && network().length === 0, ) const networkVisible = createMemo( - () => - permissions().length === 0 && - blockingQuestions().length === 0 && - blockingSuggestions().length === 0 && - network().length > 0, - ) - const disabled = createMemo( - () => - permissions().length > 0 || - blockingQuestions().length > 0 || - blockingSuggestions().length > 0 || - network().length > 0, + () => permissions().length === 0 && questions().length === 0 && network().length > 0, ) + const disabled = createMemo(() => permissions().length > 0 || questions().length > 0 || network().length > 0) + // kilocode_change end const pending = createMemo(() => { return messages().findLast((x) => x.role === "assistant" && !x.time.completed)?.id @@ -179,7 +153,6 @@ export function Session() { const lastAssistant = createMemo(() => { return messages().findLast((x) => x.role === "assistant") }) - // kilocode_change end // kilocode_change start - ring terminal bell on task completion createEffect( @@ -214,7 +187,7 @@ export function Session() { ) createEffect( on( - () => [route.sessionID, suggestions().length + network().length] as const, // kilocode_change + () => [route.sessionID, network().length] as const, ([id, len], prev) => { if (!prev || prev[0] !== id) return if (len > prev[1] && bellEnabled()) bell() @@ -1258,32 +1231,15 @@ export function Session() { {/* kilocode_change start */} - - {(request) => ( - prompt?.focused ?? false} - /> - )} - - - {/* kilocode_change end */} - {/* kilocode_change start */} - - {(request) => ( - prompt?.focused ?? false} - /> - )} - + {/* kilocode_change start */} + 0}> + + {/* kilocode_change end */} + {/* kilocode_change end */} - {/* kilocode_change end */} {/* kilocode_change start */} @@ -1701,11 +1657,6 @@ function ToolPart(props: { last: boolean; part: ToolPart; message: AssistantMess - {/* kilocode_change start */} - - - - {/* kilocode_change end */} diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx index 98456003550..51fdde8fbde 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/session/question.tsx @@ -11,11 +11,7 @@ import { useTextareaKeybindings } from "../../component/textarea-keybindings" import { useDialog } from "../../ui/dialog" // kilocode_change start -export function QuestionPrompt(props: { - request: QuestionRequest - nonBlocking?: boolean - inputFocused?: () => boolean -}) { +export function QuestionPrompt(props: { request: QuestionRequest }) { // kilocode_change end const sdk = useSDK() const { theme } = useTheme() @@ -132,10 +128,6 @@ export function QuestionPrompt(props: { // Skip processing if a dialog (e.g., command palette) is open if (dialog.stack.length > 0) return - // kilocode_change start - avoid intrusive key capture for non-blocking review suggestions - if (props.nonBlocking && props.inputFocused?.()) return - // kilocode_change end - // When editing custom answer textarea if (store.editing && !confirm()) { if (evt.name === "escape") { diff --git a/packages/opencode/src/cli/cmd/tui/routes/session/suggest.tsx b/packages/opencode/src/cli/cmd/tui/routes/session/suggest.tsx deleted file mode 100644 index 2e01ca68b7e..00000000000 --- a/packages/opencode/src/cli/cmd/tui/routes/session/suggest.tsx +++ /dev/null @@ -1,2 +0,0 @@ -// kilocode_change - new file -export { SuggestPrompt } from "../../../../../kilocode/suggestion/tui/prompt" diff --git a/packages/opencode/src/command/index.ts b/packages/opencode/src/command/index.ts index 0a61e9756d4..ceeced6e951 100644 --- a/packages/opencode/src/command/index.ts +++ b/packages/opencode/src/command/index.ts @@ -202,10 +202,4 @@ export namespace Command { export async function list() { return runPromise((svc) => svc.list()) } - - // kilocode_change start - export async function get(name: string) { - return runPromise((svc) => svc.get(name)) - } - // kilocode_change end } diff --git a/packages/opencode/src/id/id.ts b/packages/opencode/src/id/id.ts index e18271264aa..9e324962bf7 100644 --- a/packages/opencode/src/id/id.ts +++ b/packages/opencode/src/id/id.ts @@ -8,7 +8,6 @@ export namespace Identifier { message: "msg", permission: "per", question: "que", - suggestion: "sug", // kilocode_change user: "usr", part: "prt", pty: "pty", diff --git a/packages/opencode/src/kilo-sessions/remote-sender.ts b/packages/opencode/src/kilo-sessions/remote-sender.ts index 931ada68db4..de57cefd27f 100644 --- a/packages/opencode/src/kilo-sessions/remote-sender.ts +++ b/packages/opencode/src/kilo-sessions/remote-sender.ts @@ -5,7 +5,6 @@ import { Instance } from "@/project/instance" import { Session } from "@/session" import { SessionPrompt } from "@/session/prompt" import { Question } from "@/question" -import { Suggestion } from "@/kilocode/suggestion" // kilocode_change import { Permission } from "@/permission" import { PermissionID } from "@/permission/schema" import { SessionID } from "@/session/schema" @@ -25,11 +24,6 @@ const PermissionData = z.object({ message: z.string().optional(), }) -const SuggestionData = z.object({ - requestID: z.string(), - index: z.number().int().nonnegative(), -}) - // kilocode_change start — lazy init to avoid circular dependency // (Server → RemoteRoutes → RemoteSender → SessionPrompt at module load time) let _remotePromptInput: ReturnType | undefined @@ -39,6 +33,7 @@ function getRemotePromptInput() { })) } // kilocode_change end + function normalizeModel(model: string | undefined) { if (!model) return undefined return { @@ -119,24 +114,11 @@ export namespace RemoteSender { } } - // Replay pending suggestions/questions/permissions so a newly-subscribed web client + // Replay pending questions/permissions so a newly-subscribed web client // sees state that was asked before it connected — analogous to the Cloud // Agent's `connected` event carrying pending question/permission fields. async function replay(sessionId: string) { - const [suggestions, questions, permissions] = await Promise.all([ - Suggestion.list(), - Question.list(), - Permission.list(), - ]) - for (const suggestion of suggestions) { - if (suggestion.sessionID !== sessionId) continue - options.conn.send({ - type: "event", - sessionId, - event: "suggestion.shown", - data: suggestion, - }) - } + const [questions, permissions] = await Promise.all([Question.list(), Permission.list()]) for (const q of questions) { if (q.sessionID !== sessionId) continue options.conn.send({ @@ -307,37 +289,6 @@ export namespace RemoteSender { dispatchQuick(msg, dir, () => Question.reject(QuestionID.make(parsed.data.requestID))) return } - if (msg.command === "suggestion_accept") { - const parsed = SuggestionData.safeParse(msg.data) - if (!parsed.success) { - options.conn.send({ - type: "response", - id: msg.id, - error: "invalid suggestion_accept data: " + parsed.error.message, - }) - return - } - const dir = msg.sessionId ? directoryFor(msg.sessionId) : Promise.resolve(options.directory) - dispatchQuick(msg, dir, async () => { - const ok = await Suggestion.accept(parsed.data) - if (!ok) throw new Error("suggestion not found or invalid action index") - }) - return - } - if (msg.command === "suggestion_dismiss") { - const parsed = z.object({ requestID: z.string() }).safeParse(msg.data) - if (!parsed.success) { - options.conn.send({ - type: "response", - id: msg.id, - error: "invalid suggestion_dismiss data: " + parsed.error.message, - }) - return - } - const dir = msg.sessionId ? directoryFor(msg.sessionId) : Promise.resolve(options.directory) - dispatchQuick(msg, dir, () => Suggestion.dismiss(parsed.data.requestID)) - return - } if (msg.command === "permission_respond") { const parsed = PermissionData.safeParse(msg.data) if (!parsed.success) { diff --git a/packages/opencode/src/kilocode/agent/index.ts b/packages/opencode/src/kilocode/agent/index.ts index 39a1df8009f..700c6c2c41f 100644 --- a/packages/opencode/src/kilocode/agent/index.ts +++ b/packages/opencode/src/kilocode/agent/index.ts @@ -231,7 +231,6 @@ export function patchAgents( defaults, Permission.fromConfig({ question: "allow", - suggest: "allow", // kilocode_change plan_exit: "allow", bash: readOnlyBash, ...kilo.mcpRules, @@ -290,7 +289,6 @@ export function patchAgents( defaults, Permission.fromConfig({ question: "allow", - suggest: "allow", // kilocode_change plan_enter: "allow", }), user, @@ -314,7 +312,6 @@ export function patchAgents( glob: "allow", list: "allow", question: "allow", - suggest: "allow", // kilocode_change task: "allow", todoread: "allow", todowrite: "allow", diff --git a/packages/opencode/src/kilocode/plan-followup.ts b/packages/opencode/src/kilocode/plan-followup.ts index 49b239eefc2..8171a4716b7 100644 --- a/packages/opencode/src/kilocode/plan-followup.ts +++ b/packages/opencode/src/kilocode/plan-followup.ts @@ -115,7 +115,6 @@ export async function generateHandover(input: { export namespace PlanFollowup { const log = Log.create({ service: "plan.followup" }) - export const PLAN_PREFIX = "Implement the following plan:" export const ANSWER_NEW_SESSION = "Start new session" export const ANSWER_CONTINUE = "Continue here" diff --git a/packages/opencode/src/kilocode/review/review.ts b/packages/opencode/src/kilocode/review/review.ts index 5d6c8e90eeb..3e1aad0e144 100644 --- a/packages/opencode/src/kilocode/review/review.ts +++ b/packages/opencode/src/kilocode/review/review.ts @@ -351,51 +351,27 @@ export namespace Review { } /** - * Get uncommitted changes (staged + unstaged + untracked) + * Get uncommitted changes (staged + unstaged) * Implements SCOPE-01 * - * Uses: git diff HEAD for tracked changes, plus git ls-files for untracked files + * Uses: git diff HEAD to capture both staged and unstaged changes */ export async function getUncommittedChanges(): Promise { log.info("getting uncommitted changes") - // git diff HEAD shows all uncommitted changes (staged + unstaged) for tracked files + // git diff HEAD shows all uncommitted changes (staged + unstaged) // Using -c core.quotepath=false to handle unicode filenames const result = await $`git -c core.quotepath=false diff HEAD`.cwd(Instance.directory).quiet().nothrow() - let raw = result.exitCode === 0 ? result.stdout.toString() : "" - if (result.exitCode !== 0) { log.warn("git diff failed", { exitCode: result.exitCode, stderr: result.stderr.toString(), }) + return { files: [], raw: "" } } - // Also include untracked files — git diff HEAD misses brand-new files - const untracked = await $`git ls-files --others --exclude-standard -z`.cwd(Instance.directory).quiet().nothrow() - if (untracked.exitCode === 0) { - const paths = untracked.stdout.toString().split("\0").filter(Boolean) - // Process in batches to avoid spawning hundreds of git processes - const batch = 20 - for (let i = 0; i < paths.length; i += batch) { - const chunk = paths.slice(i, i + batch) - const diffs = await Promise.all( - chunk.map((p) => - // --no-index exits 1 when files differ, which is expected - $`git -c core.quotepath=false diff --no-index -- /dev/null ${p}` - .cwd(Instance.directory) - .quiet() - .nothrow() - .then((fd) => fd.stdout.toString()), - ), - ) - for (const out of diffs) { - if (out) raw += out - } - } - } - + const raw = result.stdout.toString() const parsed = parseDiff(raw) log.info("parsed uncommitted changes", { diff --git a/packages/opencode/src/kilocode/server/instance.ts b/packages/opencode/src/kilocode/server/instance.ts index 0f7adc5f49c..63062f94c55 100644 --- a/packages/opencode/src/kilocode/server/instance.ts +++ b/packages/opencode/src/kilocode/server/instance.ts @@ -12,7 +12,6 @@ import { KilocodeRoutes } from "../../server/routes/kilocode" import { PermissionKilocodeRoutes } from "../permission/routes" import { RemoteRoutes } from "../../server/routes/remote" import { NetworkRoutes } from "../../server/routes/network" -import { SuggestionRoutes } from "../suggestion/routes" import { createKiloRoutes } from "@kilocode/kilo-gateway" import { Auth } from "../../auth" import { errors } from "../../server/error" @@ -28,7 +27,6 @@ export function register(app: Hono): Hono { return app .route("/permission", PermissionKilocodeRoutes()) .route("/network", NetworkRoutes()) - .route("/suggestion", SuggestionRoutes()) .route("/telemetry", TelemetryRoutes()) .route("/remote", RemoteRoutes()) .route("/commit-message", CommitMessageRoutes()) diff --git a/packages/opencode/src/kilocode/soul.txt b/packages/opencode/src/kilocode/soul.txt index 39443253ef7..70d52164ec1 100644 --- a/packages/opencode/src/kilocode/soul.txt +++ b/packages/opencode/src/kilocode/soul.txt @@ -12,16 +12,3 @@ You are Kilo, a highly skilled software engineer with extensive knowledge in man # Code - When making changes to code, always consider the context in which the code is being used. Ensure that your changes are compatible with the existing codebase and that they follow the project's coding standards and best practices. - -## Suggestions - -- Use the `question` tool only when you need an actual answer from the user. -- If the `suggest` tool is available, use it ONLY to offer a local code review — never for other actions like committing, pushing, running tests, or any other next step. -- When you have completed implementation work and you are at least 90% confident the task is done, use `suggest` to offer a code review of uncommitted changes. -- Only suggest review when the user's request appears fully addressed. Do not suggest it after every edit or partial implementation turn. -- Do not repeat a review suggestion that was already dismissed in this conversation. -- Keep suggestion text concise, use at most 1-2 actions, and make each accepted action prompt self-contained. -- When suggesting a code review, choose the right command for the action prompt: - - `/local-review-uncommitted` — for reviewing uncommitted working-tree changes (staged, unstaged, and untracked files). - - `/local-review` — for reviewing all committed changes on the current branch vs its base branch. - - Prefer `/local-review-uncommitted` when the work you just did has not been committed yet. diff --git a/packages/opencode/src/kilocode/suggestion/index.ts b/packages/opencode/src/kilocode/suggestion/index.ts deleted file mode 100644 index 5f03bb677f3..00000000000 --- a/packages/opencode/src/kilocode/suggestion/index.ts +++ /dev/null @@ -1,194 +0,0 @@ -import { Bus } from "../../bus" -import { BusEvent } from "../../bus/bus-event" -import { Identifier } from "../../id/id" -import { Instance } from "../../project/instance" -import { Log } from "../../util/log" -import z from "zod" - -export namespace Suggestion { - const log = Log.create({ service: "suggestion" }) - - export const Action = z - .object({ - label: z.string().describe("Button or option label (1-5 words)"), - description: z.string().optional().describe("Brief explanation of what this action does"), - prompt: z.string().describe("Synthetic user prompt to inject when this action is accepted"), - }) - .meta({ - ref: "SuggestionAction", - }) - export type Action = z.infer - - export const Info = z - .object({ - text: z.string().describe("Suggestion text shown to the user"), - actions: z.array(Action).min(1).max(2).describe("Available actions the user can take"), - }) - .meta({ - ref: "SuggestionInfo", - }) - export type Info = z.infer - - export const Request = z - .object({ - id: Identifier.schema("suggestion"), - sessionID: Identifier.schema("session"), - text: z.string().describe("Suggestion text shown to the user"), - actions: z.array(Action).min(1).max(2).describe("Available actions the user can take"), - blocking: z.boolean().optional().describe("Whether this suggestion blocks prompt input (default: true)"), - tool: z - .object({ - messageID: z.string(), - callID: z.string(), - }) - .optional(), - }) - .meta({ - ref: "SuggestionRequest", - }) - export type Request = z.infer - - export const Accept = z.object({ - index: z.number().int().nonnegative().describe("Zero-based action index to accept"), - }) - export type Accept = z.infer - - export const Event = { - Shown: BusEvent.define("suggestion.shown", Request), - Accepted: BusEvent.define( - "suggestion.accepted", - z.object({ - sessionID: z.string(), - requestID: z.string(), - index: z.number().int().nonnegative(), - action: Action, - }), - ), - Dismissed: BusEvent.define( - "suggestion.dismissed", - z.object({ - sessionID: z.string(), - requestID: z.string(), - }), - ), - } - - const state = Instance.state(async () => { - const pending: Record< - string, - { - info: Request - resolve: (action: Action) => void - reject: (error: any) => void - } - > = {} - - return { - pending, - } - }) - - export async function show(input: { - sessionID: string - text: string - actions: Action[] - blocking?: boolean - tool?: { messageID: string; callID: string } - }): Promise { - const s = await state() - const id = Identifier.ascending("suggestion") - - log.info("shown", { id, actions: input.actions.length }) - - return new Promise((resolve, reject) => { - const info: Request = { - id, - sessionID: input.sessionID, - text: input.text, - actions: input.actions, - blocking: input.blocking, - tool: input.tool, - } - s.pending[id] = { - info, - resolve, - reject, - } - Bus.publish(Event.Shown, info) - }) - } - - export async function accept(input: { requestID: string; index: number }): Promise { - const s = await state() - const existing = s.pending[input.requestID] - if (!existing) { - log.warn("accept for unknown request", { requestID: input.requestID }) - return false - } - - const action = existing.info.actions[input.index] - if (!action) { - log.warn("accept for invalid action index", { requestID: input.requestID, index: input.index }) - delete s.pending[input.requestID] - existing.reject(new Error(`Invalid action index: ${input.index}`)) - return false - } - - delete s.pending[input.requestID] - - log.info("accepted", { requestID: input.requestID, index: input.index, label: action.label }) - - Bus.publish(Event.Accepted, { - sessionID: existing.info.sessionID, - requestID: existing.info.id, - index: input.index, - action, - }) - - existing.resolve(action) - return true - } - - export async function dismiss(requestID: string): Promise { - const s = await state() - const existing = s.pending[requestID] - if (!existing) { - log.warn("dismiss for unknown request", { requestID }) - return - } - delete s.pending[requestID] - - log.info("dismissed", { requestID }) - - Bus.publish(Event.Dismissed, { - sessionID: existing.info.sessionID, - requestID: existing.info.id, - }) - - existing.reject(new DismissedError()) - } - - export class DismissedError extends Error { - constructor() { - super("The user dismissed this suggestion") - } - } - - export async function dismissAll(sessionID: string): Promise { - const s = await state() - for (const [id, entry] of Object.entries(s.pending)) { - if (entry.info.sessionID !== sessionID) continue - delete s.pending[id] - log.info("dismissed", { requestID: id }) - Bus.publish(Event.Dismissed, { - sessionID: entry.info.sessionID, - requestID: entry.info.id, - }) - entry.reject(new DismissedError()) - } - } - - export async function list() { - return state().then((state) => Object.values(state.pending).map((item) => item.info)) - } -} diff --git a/packages/opencode/src/kilocode/suggestion/routes.ts b/packages/opencode/src/kilocode/suggestion/routes.ts deleted file mode 100644 index c47af53b81c..00000000000 --- a/packages/opencode/src/kilocode/suggestion/routes.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { errors } from "../../server/error" -import { NotFoundError } from "../../storage/db" -import { lazy } from "../../util/lazy" -import { Hono } from "hono" -import { describeRoute, resolver, validator } from "hono-openapi" -import z from "zod" -import { Suggestion } from "./index" - -export const SuggestionRoutes = lazy(() => - new Hono() - .get( - "/", - describeRoute({ - summary: "List pending suggestions", - description: "Get all pending suggestion requests across all sessions.", - operationId: "suggestion.list", - responses: { - 200: { - description: "List of pending suggestions", - content: { - "application/json": { - schema: resolver(Suggestion.Request.array()), - }, - }, - }, - }, - }), - async (c) => { - const suggestions = await Suggestion.list() - return c.json(suggestions) - }, - ) - .post( - "/:requestID/accept", - describeRoute({ - summary: "Accept suggestion request", - description: "Accept a suggestion request from the AI assistant.", - operationId: "suggestion.accept", - responses: { - 200: { - description: "Suggestion accepted successfully", - content: { - "application/json": { - schema: resolver(z.boolean()), - }, - }, - }, - ...errors(400, 404), - }, - }), - validator( - "param", - z.object({ - requestID: z.string(), - }), - ), - validator("json", Suggestion.Accept), - async (c) => { - const params = c.req.valid("param") - const json = c.req.valid("json") - const ok = await Suggestion.accept({ - requestID: params.requestID, - index: json.index, - }) - if (!ok) throw new NotFoundError({ message: `Suggestion not found: ${params.requestID}` }) - return c.json(true) - }, - ) - .post( - "/:requestID/dismiss", - describeRoute({ - summary: "Dismiss suggestion request", - description: "Dismiss a suggestion request from the AI assistant.", - operationId: "suggestion.dismiss", - responses: { - 200: { - description: "Suggestion dismissed successfully", - content: { - "application/json": { - schema: resolver(z.boolean()), - }, - }, - }, - ...errors(400, 404), - }, - }), - validator( - "param", - z.object({ - requestID: z.string(), - }), - ), - async (c) => { - const params = c.req.valid("param") - await Suggestion.dismiss(params.requestID) - return c.json(true) - }, - ), -) diff --git a/packages/opencode/src/kilocode/suggestion/tool.ts b/packages/opencode/src/kilocode/suggestion/tool.ts deleted file mode 100644 index 9f77227eba5..00000000000 --- a/packages/opencode/src/kilocode/suggestion/tool.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Command } from "../../command" -import { Flag } from "../../flag/flag" -import { Log } from "../../util/log" -import z from "zod" -import DESCRIPTION from "./tool.txt" -import { Tool } from "../../tool/tool" -import { Suggestion } from "./index" - -const log = Log.create({ service: "tool.suggest" }) - -const Params = z.object({ - suggest: z.string().describe("Short suggestion text shown to the user"), - actions: z.array(Suggestion.Action).min(1).max(2).describe("Available actions the user can take"), -}) - -type Meta = { - accepted?: Suggestion.Action - dismissed: boolean - truncated: boolean -} - -/** - * If prompt starts with `/`, treat it as a slash-command reference. - * Resolve the command template and return its content so the LLM can - * act on it in the current turn — without injecting a synthetic user - * message or trying to dispatch a command on the same session (which - * would deadlock). - */ -async function resolve(prompt: string): Promise { - if (!prompt.startsWith("/")) return prompt - - const name = prompt.slice(1).split(/\s/, 1)[0] - if (!name) return prompt - - const args = prompt.slice(1 + name.length).trim() - - const cmd = await Command.get(name) - if (!cmd) { - log.warn("unknown command in suggestion action", { name }) - return prompt - } - - try { - const template = await cmd.template - log.info("resolved command template", { name, length: template.length }) - return args ? `${template}\n\n${args}` : template - } catch (err) { - log.warn("failed to resolve command template", { name, err }) - return prompt - } -} - -export const SuggestTool = Tool.define("suggest", { - description: DESCRIPTION, - parameters: Params, - async execute(params, ctx) { - const promise = Suggestion.show({ - sessionID: ctx.sessionID, - text: params.suggest, - actions: params.actions, - blocking: Flag.KILO_CLIENT !== "vscode", - tool: ctx.callID ? { messageID: ctx.messageID, callID: ctx.callID } : undefined, - }) - - const listener = () => - Suggestion.list().then((items: Suggestion.Request[]) => { - const match = items.find((item: Suggestion.Request) => item.tool?.callID === ctx.callID) - if (match) return Suggestion.dismiss(match.id) - }) - ctx.abort.addEventListener("abort", listener, { once: true }) - - const action = await promise - .catch((error) => { - if (error instanceof Suggestion.DismissedError) return undefined - throw error - }) - .finally(() => { - ctx.abort.removeEventListener("abort", listener) - }) - - if (!action) { - const metadata: Meta = { - accepted: undefined, - dismissed: true, - truncated: false, - } - return { - title: "Suggestion dismissed", - output: "User dismissed the suggestion.", - metadata, - } - } - - const resolved = await resolve(action.prompt) - - const metadata: Meta = { - accepted: action, - dismissed: false, - truncated: false, - } - - return { - title: `User accepted: ${action.label}`, - output: `User accepted the suggestion "${action.label}". Carry out the following request now:\n\n${resolved}`, - metadata, - } - }, -}) diff --git a/packages/opencode/src/kilocode/suggestion/tool.txt b/packages/opencode/src/kilocode/suggestion/tool.txt deleted file mode 100644 index fc86ed21b17..00000000000 --- a/packages/opencode/src/kilocode/suggestion/tool.txt +++ /dev/null @@ -1,17 +0,0 @@ -Use this tool to suggest a local code review to the user after completing implementation work. - -This tool is ONLY for suggesting code review. Do NOT use it to suggest running tests, committing, pushing, or any other action. - -Guidelines: -- Only suggest review when you are at least 90% confident the user's request is fully addressed -- Do not suggest review after every edit or partial implementation turn -- Do not repeat a review suggestion that was already dismissed in this conversation -- Keep the suggestion text concise and actionable -- Provide 1-2 actions maximum -- Make each action prompt self-contained so it can be injected as a synthetic user message -- If you need a real answer from the user, use the `question` tool instead - -Choosing the right review command for the action prompt: -- Use `/local-review-uncommitted` as the action prompt for uncommitted working-tree changes (staged, unstaged, and untracked files) -- Use `/local-review` as the action prompt for committed branch-level changes -- Prefer `/local-review-uncommitted` when the work you just did has not been committed yet \ No newline at end of file diff --git a/packages/opencode/src/kilocode/suggestion/tui/prompt.tsx b/packages/opencode/src/kilocode/suggestion/tui/prompt.tsx deleted file mode 100644 index 91614f3fbf4..00000000000 --- a/packages/opencode/src/kilocode/suggestion/tui/prompt.tsx +++ /dev/null @@ -1,173 +0,0 @@ -/** @jsxImportSource @opentui/solid */ - -import { useKeyboard } from "@opentui/solid" -import type { SuggestionRequest } from "@kilocode/sdk/v2" -import { createMemo, createSignal, For } from "solid-js" -import { SplitBorder } from "../../../cli/cmd/tui/component/border" -import { useKeybind } from "../../../cli/cmd/tui/context/keybind" -import { useSDK } from "../../../cli/cmd/tui/context/sdk" -import { tint, useTheme } from "../../../cli/cmd/tui/context/theme" -import { useDialog } from "../../../cli/cmd/tui/ui/dialog" - -const dismiss = { - label: "Dismiss", - description: "Dismiss this suggestion and continue", -} - -export function SuggestPrompt(props: { - request: SuggestionRequest - nonBlocking?: boolean - inputFocused?: () => boolean -}) { - const sdk = useSDK() - const { theme } = useTheme() - const keybind = useKeybind() - const dialog = useDialog() - - const options = createMemo(() => [...props.request.actions, dismiss]) - const [selected, setSelected] = createSignal(0) - const [busy, setBusy] = createSignal(false) - - function accept(index: number) { - if (busy()) return - setBusy(true) - sdk.client.suggestion - .accept({ - requestID: props.request.id, - index, - }) - .catch(() => { - setBusy(false) - }) - } - - function reject() { - if (busy()) return - setBusy(true) - sdk.client.suggestion - .dismiss({ - requestID: props.request.id, - }) - .catch(() => { - setBusy(false) - }) - } - - function choose(index: number) { - if (index >= props.request.actions.length) { - reject() - return - } - accept(index) - } - - useKeyboard((evt) => { - if (dialog.stack.length > 0) return - if (props.nonBlocking && props.inputFocused?.()) return - - const total = options().length - const max = Math.min(total, 9) - const digit = Number(evt.name) - - if (!Number.isNaN(digit) && digit >= 1 && digit <= max) { - evt.preventDefault() - const index = digit - 1 - setSelected(index) - choose(index) - return - } - - if (evt.name === "up" || evt.name === "k") { - evt.preventDefault() - setSelected((selected() - 1 + total) % total) - return - } - - if (evt.name === "down" || evt.name === "j") { - evt.preventDefault() - setSelected((selected() + 1) % total) - return - } - - if (evt.name === "return") { - evt.preventDefault() - choose(selected()) - return - } - - if (evt.name === "escape" || keybind.match("app_exit", evt)) { - evt.preventDefault() - reject() - } - }) - - const note = createMemo(() => (busy() ? "Waiting..." : undefined)) - - return ( - - - - {props.request.text} - - - - - {(opt, i) => { - const active = () => i() === selected() - const muted = () => i() === props.request.actions.length - return ( - setSelected(i())} - onMouseDown={() => setSelected(i())} - onMouseUp={() => choose(i())} - > - - - - {`${i() + 1}.`} - - - - {opt.label} - - - - - {opt.description} - - - ) - }} - - - - - - - {"↑↓"} select - - - enter choose - - - esc dismiss - - - {note()} - - - ) -} diff --git a/packages/opencode/src/kilocode/suggestion/tui/render.tsx b/packages/opencode/src/kilocode/suggestion/tui/render.tsx deleted file mode 100644 index 638794f15d1..00000000000 --- a/packages/opencode/src/kilocode/suggestion/tui/render.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/** @jsxImportSource @opentui/solid */ - -import { createMemo, Show, type JSX } from "solid-js" -import { useTheme } from "../../../cli/cmd/tui/context/theme" -import type { ToolPart as MessageToolPart } from "@kilocode/sdk/v2" - -type InlineProps = { - icon: string - complete: unknown - pending: string - part: MessageToolPart - children: JSX.Element -} - -type BlockProps = { - title: string - part?: MessageToolPart - children: JSX.Element -} - -export function Suggest(props: { - input: { - suggest?: string - } - metadata: { - accepted?: { - label: string - } - dismissed?: boolean - } - part: MessageToolPart - InlineTool: (props: InlineProps) => JSX.Element - BlockTool: (props: BlockProps) => JSX.Element -}) { - const { theme } = useTheme() - const accepted = createMemo(() => props.metadata.accepted) - const dismissed = createMemo(() => props.metadata.dismissed === true) - - if (accepted() || dismissed()) { - return props.BlockTool({ - title: "# Suggestion", - part: props.part, - children: ( - - {props.input.suggest} - - Accepted: {accepted()?.label} - - - Dismissed - - - ), - }) - } - - return props.InlineTool({ - icon: "→", - pending: "Suggesting next step...", - complete: props.part.state.status === "completed", - part: props.part, - children: props.input.suggest ?? "Suggested next step", - }) -} diff --git a/packages/opencode/src/kilocode/suggestion/tui/sync.ts b/packages/opencode/src/kilocode/suggestion/tui/sync.ts deleted file mode 100644 index 48a0e93d1f5..00000000000 --- a/packages/opencode/src/kilocode/suggestion/tui/sync.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Binary } from "@opencode-ai/util/binary" -import type { SuggestionRequest } from "@kilocode/sdk/v2" - -type RemovedEvent = { - type: "suggestion.accepted" | "suggestion.dismissed" - properties: { - sessionID: string - requestID: string - } -} - -type ShownEvent = { - type: "suggestion.shown" - properties: SuggestionRequest -} - -type Event = RemovedEvent | ShownEvent - -type Store = { - suggestion: { - [sessionID: string]: SuggestionRequest[] - } -} - -type SetStore = { - (key: "suggestion", sessionID: string, value: SuggestionRequest[]): void -} - -export function handleSuggestionEvent(event: Event, store: Store, setStore: SetStore) { - if (event.type !== "suggestion.shown") { - const info = event.properties - const requests = store.suggestion[info.sessionID] - if (!requests) return - const match = Binary.search(requests, info.requestID, (r) => r.id) - if (!match.found) return - setStore("suggestion", info.sessionID, requests.toSpliced(match.index, 1)) - return - } - - const request = event.properties - const requests = store.suggestion[request.sessionID] - if (!requests) { - setStore("suggestion", request.sessionID, [request]) - return - } - const match = Binary.search(requests, request.id, (r) => r.id) - if (match.found) { - const next = [...requests] - next[match.index] = request - setStore("suggestion", request.sessionID, next) - return - } - setStore("suggestion", request.sessionID, [ - ...requests.slice(0, match.index), - request, - ...requests.slice(match.index), - ]) -} diff --git a/packages/opencode/src/kilocode/tool/registry.ts b/packages/opencode/src/kilocode/tool/registry.ts index c6d38c324e7..f868f581296 100644 --- a/packages/opencode/src/kilocode/tool/registry.ts +++ b/packages/opencode/src/kilocode/tool/registry.ts @@ -26,11 +26,6 @@ export namespace KiloToolRegistry { return true } - /** Suggest tool is only registered for cli and vscode clients */ - export function suggest(tool: Tool.Def): Tool.Def[] { - return ["cli", "vscode"].includes(Flag.KILO_CLIENT) ? [tool] : [] - } - /** Kilo-specific tools to append to the builtin list */ export function extra( tools: { codebase: Tool.Def; recall: Tool.Def }, diff --git a/packages/opencode/src/question/index.ts b/packages/opencode/src/question/index.ts index e425a27867b..615c699ce91 100644 --- a/packages/opencode/src/question/index.ts +++ b/packages/opencode/src/question/index.ts @@ -37,7 +37,6 @@ export namespace Question { id: QuestionID.zod, sessionID: SessionID.zod, questions: z.array(Info).describe("Questions to ask"), - blocking: z.boolean().optional().describe("Whether this question blocks prompt input (default: true)"), // kilocode_change tool: z .object({ messageID: MessageID.zod, @@ -98,7 +97,6 @@ export namespace Question { readonly ask: (input: { sessionID: SessionID questions: Info[] - blocking?: boolean // kilocode_change tool?: { messageID: MessageID; callID: string } }) => Effect.Effect readonly reply: (input: { requestID: QuestionID; answers: Answer[] }) => Effect.Effect @@ -134,7 +132,6 @@ export namespace Question { const ask = Effect.fn("Question.ask")(function* (input: { sessionID: SessionID questions: Info[] - blocking?: boolean // kilocode_change tool?: { messageID: MessageID; callID: string } }) { const pending = (yield* InstanceState.get(state)).pending @@ -146,7 +143,6 @@ export namespace Question { id, sessionID: input.sessionID, questions: input.questions, - blocking: input.blocking, // kilocode_change tool: input.tool, } pending.set(id, { info, deferred }) @@ -209,7 +205,6 @@ export namespace Question { export async function ask(input: { sessionID: SessionID questions: Info[] - blocking?: boolean // kilocode_change tool?: { messageID: MessageID; callID: string } }): Promise { return runPromise((s) => s.ask(input)) diff --git a/packages/opencode/src/server/routes/suggestion.ts b/packages/opencode/src/server/routes/suggestion.ts deleted file mode 100644 index e3f4d44e464..00000000000 --- a/packages/opencode/src/server/routes/suggestion.ts +++ /dev/null @@ -1,2 +0,0 @@ -// kilocode_change - new file -export { SuggestionRoutes } from "../../kilocode/suggestion/routes" diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index 4c4db59adc7..72c4fcd6d37 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -19,7 +19,6 @@ import { SessionSummary } from "./summary" import type { Provider } from "@/provider/provider" import { Question } from "@/question" import { KiloSessionProcessor } from "@/kilocode/session/processor" // kilocode_change -import { Suggestion } from "@/kilocode/suggestion" // kilocode_change import { errorMessage } from "@/util/error" import { isRecord } from "@/util/record" @@ -209,11 +208,7 @@ export namespace SessionProcessor { }, }) // kilocode_change start - if ( - error instanceof Permission.RejectedError || - error instanceof Question.RejectedError || - error instanceof Suggestion.DismissedError - ) { + if (error instanceof Permission.RejectedError || error instanceof Question.RejectedError) { // kilocode_change end ctx.blocked = ctx.shouldBreak } @@ -362,11 +357,6 @@ export namespace SessionProcessor { case "tool-result": { yield* completeToolCall(value.toolCallId, value.output) - // kilocode_change start - if (value.output.metadata?.dismissed === true) { - ctx.blocked = ctx.shouldBreak - } - // kilocode_change end return } diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index 19c7c816d96..7faa83bd66b 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -3,7 +3,6 @@ import os from "os" import fs from "fs/promises" import { KiloSessionPrompt } from "@/kilocode/session/prompt" // kilocode_change import { KiloSession } from "@/kilocode/session" // kilocode_change -import { Suggestion } from "@/kilocode/suggestion" // kilocode_change import z from "zod" import { SessionID, MessageID, PartID } from "./schema" import { MessageV2 } from "./message-v2" @@ -1288,11 +1287,6 @@ NOTE: At any point in time through this workflow you should feel free to ask the } if (input.noReply === true) return message - // kilocode_change start — dismiss pending suggestions and cancel the session - // before starting a new loop to avoid the runner ignoring the new work - yield* Effect.promise(() => Suggestion.dismissAll(input.sessionID)) - yield* state.cancel(input.sessionID) - // kilocode_change end return yield* loop({ sessionID: input.sessionID }) }, ) diff --git a/packages/opencode/src/suggestion/index.ts b/packages/opencode/src/suggestion/index.ts deleted file mode 100644 index 04c2bcbf868..00000000000 --- a/packages/opencode/src/suggestion/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -// kilocode_change - new file -export { Suggestion } from "../kilocode/suggestion/index" diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 115f3a21848..13f45255f48 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -1,6 +1,5 @@ import { PlanExitTool } from "./plan" import { QuestionTool } from "./question" -import { SuggestTool } from "../kilocode/suggestion/tool" // kilocode_change import { BashTool } from "./bash" import { EditTool } from "./edit" import { GlobTool } from "./glob" @@ -162,7 +161,6 @@ export namespace ToolRegistry { question: Tool.init(question), lsp: Tool.init(LspTool), plan: Tool.init(PlanExitTool), - suggest: Tool.init(SuggestTool), // kilocode_change }) const kilo = yield* KiloToolRegistry.build() // kilocode_change @@ -187,7 +185,6 @@ export namespace ToolRegistry { tool.patch, ...(Flag.KILO_EXPERIMENTAL_LSP_TOOL ? [tool.lsp] : []), // kilocode_change ...(KiloToolRegistry.plan() ? [tool.plan] : []), // kilocode_change - ...KiloToolRegistry.suggest(tool.suggest), // kilocode_change ...KiloToolRegistry.extra(kilo, cfg), // kilocode_change ], task: tool.task, diff --git a/packages/opencode/src/tool/suggest.ts b/packages/opencode/src/tool/suggest.ts deleted file mode 100644 index 76debcc98ca..00000000000 --- a/packages/opencode/src/tool/suggest.ts +++ /dev/null @@ -1,2 +0,0 @@ -// kilocode_change - new file -export { SuggestTool } from "../kilocode/suggestion/tool" diff --git a/packages/opencode/test/kilocode/sessions/remote-sender.test.ts b/packages/opencode/test/kilocode/sessions/remote-sender.test.ts index 85ebd29537f..798f694cf1a 100644 --- a/packages/opencode/test/kilocode/sessions/remote-sender.test.ts +++ b/packages/opencode/test/kilocode/sessions/remote-sender.test.ts @@ -6,7 +6,6 @@ import type { RemoteProtocol } from "../../../src/kilo-sessions/remote-protocol" import { SessionPrompt } from "../../../src/session/prompt" import { Question } from "../../../src/question" import { Permission } from "../../../src/permission" -import { Suggestion } from "../../../src/kilocode/suggestion" // kilocode_change function fakeConn() { const sent: any[] = [] @@ -491,51 +490,6 @@ describe("RemoteSender", () => { expect(sent[0].error).toContain("boom") }) - test("suggestion_accept sends response after work completes", async () => { - const { conn, sent } = fakeConn() - const accept = spyOn(Suggestion, "accept").mockResolvedValue(true) - const sender = RemoteSender.create({ - conn, - directory: "/tmp/test", - log: nolog, - subscribe: fakeBus().subscribe, - provide: async (input: { directory: string; init?: () => Promise; fn: () => R }) => input.fn(), - }) - - sender.handle({ - type: "command", - id: "req_suggestion_accept", - command: "suggestion_accept", - data: { requestID: "sug_1", index: 1 }, - }) - - await new Promise((r) => setTimeout(r, 10)) - - expect(accept).toHaveBeenCalledWith({ requestID: "sug_1", index: 1 }) - expect(sent).toContainEqual({ type: "response", id: "req_suggestion_accept", result: {} }) - }) - - test("suggestion_dismiss with invalid data sends error response", () => { - const { conn, sent } = fakeConn() - const sender = RemoteSender.create({ - conn, - directory: "/tmp/test", - log: nolog, - subscribe: fakeBus().subscribe, - provide: async () => ({}) as any, - }) - - sender.handle({ - type: "command", - id: "req_suggestion_dismiss_bad", - command: "suggestion_dismiss", - data: { nope: true }, - }) - - expect(sent).toHaveLength(1) - expect(sent[0].error).toContain("invalid suggestion_dismiss data") - }) - test("question_reject sends response after work completes", async () => { const { conn, sent } = fakeConn() let provideCalled = false @@ -859,7 +813,6 @@ describe("RemoteSender", () => { const { conn, sent } = fakeConn() const bus = fakeBus() - spyOn(Suggestion, "list").mockResolvedValue([]) spyOn(Question, "list").mockResolvedValue([ { id: "question_1", sessionID: "ses_target", questions: [{ type: "text", text: "Continue?" }] } as any, { id: "question_2", sessionID: "ses_other", questions: [{ type: "text", text: "Unrelated?" }] } as any, @@ -891,7 +844,6 @@ describe("RemoteSender", () => { const { conn, sent } = fakeConn() const bus = fakeBus() - spyOn(Suggestion, "list").mockResolvedValue([]) spyOn(Question, "list").mockResolvedValue([]) spyOn(Permission, "list").mockResolvedValue([ { @@ -944,9 +896,6 @@ describe("RemoteSender", () => { const { conn, sent } = fakeConn() const bus = fakeBus() - spyOn(Suggestion, "list").mockResolvedValue([ - { id: "sug_1", sessionID: "ses_other", text: "Review?", actions: [] } as any, - ]) spyOn(Question, "list").mockResolvedValue([{ id: "question_1", sessionID: "ses_other", questions: [] } as any]) spyOn(Permission, "list").mockResolvedValue([ { @@ -974,53 +923,6 @@ describe("RemoteSender", () => { expect(events).toHaveLength(0) }) - test("subscribe replays pending suggestion for the subscribed session", async () => { - const { conn, sent } = fakeConn() - const bus = fakeBus() - - spyOn(Suggestion, "list").mockResolvedValue([ - { - id: "sug_1", - sessionID: "ses_target", - text: "Review?", - actions: [{ label: "Start", prompt: "/local-review-uncommitted" }], - } as any, - { - id: "sug_2", - sessionID: "ses_other", - text: "Ignore", - actions: [{ label: "Skip", prompt: "skip" }], - } as any, - ]) - spyOn(Question, "list").mockResolvedValue([]) - spyOn(Permission, "list").mockResolvedValue([]) - - const sender = RemoteSender.create({ - conn, - directory: "/tmp/test", - log: nolog, - subscribe: bus.subscribe, - provide: async (input: any) => input.fn(), - }) - - sender.handle({ type: "subscribe", sessionId: "ses_target" }) - await new Promise((r) => setTimeout(r, 10)) - - const suggestionEvents = sent.filter((m: any) => m.event === "suggestion.shown") - expect(suggestionEvents).toHaveLength(1) - expect(suggestionEvents[0]).toEqual({ - type: "event", - sessionId: "ses_target", - event: "suggestion.shown", - data: { - id: "sug_1", - sessionID: "ses_target", - text: "Review?", - actions: [{ label: "Start", prompt: "/local-review-uncommitted" }], - }, - }) - }) - test("system message is handled without error", () => { const { conn, sent } = fakeConn() const sender = RemoteSender.create({ diff --git a/packages/opencode/test/kilocode/suggestion/suggestion.test.ts b/packages/opencode/test/kilocode/suggestion/suggestion.test.ts deleted file mode 100644 index 39e4d02ec92..00000000000 --- a/packages/opencode/test/kilocode/suggestion/suggestion.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { describe, expect, test } from "bun:test" -import { Instance } from "../../../src/project/instance" -import { Suggestion } from "../../../src/kilocode/suggestion" -import { tmpdir } from "../../fixture/fixture" - -describe("suggestion", () => { - test("show adds pending request with blocking flag", async () => { - await using tmp = await tmpdir({ git: true }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const pending = Suggestion.show({ - sessionID: "ses_test", - text: "Run review?", - blocking: false, - actions: [{ label: "Start", description: "Run it", prompt: "/local-review-uncommitted" }], - }) - - const list = await Suggestion.list() - expect(list).toHaveLength(1) - expect(list[0]?.blocking).toBe(false) - expect(list[0]?.text).toBe("Run review?") - - await Suggestion.dismiss(list[0]!.id) - await expect(pending).rejects.toBeInstanceOf(Suggestion.DismissedError) - }, - }) - }) - - test("accept resolves selected action and removes pending request", async () => { - await using tmp = await tmpdir({ git: true }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const ask = Suggestion.show({ - sessionID: "ses_test", - text: "Next step?", - actions: [ - { label: "Review", description: "Start review", prompt: "/local-review-uncommitted" }, - { label: "Test", description: "Run tests", prompt: "Run the relevant tests now." }, - ], - }) - - const list = await Suggestion.list() - await Suggestion.accept({ requestID: list[0]!.id, index: 1 }) - - await expect(ask).resolves.toEqual({ - label: "Test", - description: "Run tests", - prompt: "Run the relevant tests now.", - }) - await expect(Suggestion.list()).resolves.toEqual([]) - }, - }) - }) - - test("dismiss rejects pending request and removes it", async () => { - await using tmp = await tmpdir({ git: true }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const ask = Suggestion.show({ - sessionID: "ses_test", - text: "Review changes?", - actions: [{ label: "Start", prompt: "/local-review-uncommitted" }], - }) - - const list = await Suggestion.list() - await Suggestion.dismiss(list[0]!.id) - - await expect(ask).rejects.toBeInstanceOf(Suggestion.DismissedError) - await expect(Suggestion.list()).resolves.toEqual([]) - }, - }) - }) -}) diff --git a/packages/opencode/test/kilocode/suggestion/tool.test.ts b/packages/opencode/test/kilocode/suggestion/tool.test.ts deleted file mode 100644 index 74311ca5c7a..00000000000 --- a/packages/opencode/test/kilocode/suggestion/tool.test.ts +++ /dev/null @@ -1,162 +0,0 @@ -import { afterEach, beforeEach, describe, expect, test, spyOn } from "bun:test" -import { Command } from "../../../src/command" -import { Suggestion } from "../../../src/kilocode/suggestion" -import { SuggestTool } from "../../../src/kilocode/suggestion/tool" - -const ctx = { - sessionID: "ses_test", - messageID: "msg_assistant", - callID: "call_suggest", - agent: "code", - abort: AbortSignal.any([]), - messages: [ - { - info: { - id: "msg_user", - role: "user", - sessionID: "ses_test", - time: { created: 1 }, - agent: "code", - model: { providerID: "openai", modelID: "gpt-4" }, - }, - parts: [], - }, - ], - metadata: () => {}, - ask: async () => {}, -} - -describe("tool.suggest", () => { - let show: ReturnType - let cmdGet: ReturnType - - beforeEach(() => { - show = spyOn(Suggestion, "show") - cmdGet = spyOn(Command, "get") - }) - - afterEach(() => { - show.mockRestore() - cmdGet.mockRestore() - }) - - test("returns dismissal result when suggestion is dismissed", async () => { - const tool = await SuggestTool.init() - show.mockRejectedValueOnce(new Suggestion.DismissedError()) - - const result = await tool.execute( - { - suggest: "Run review?", - actions: [{ label: "Start", prompt: "/local-review-uncommitted" }], - }, - ctx as any, - ) - - expect(result.title).toBe("Suggestion dismissed") - expect(result.output).toBe("User dismissed the suggestion.") - expect(result.metadata.dismissed).toBe(true) - }) - - test("resolves command template for slash-command action prompt", async () => { - const tool = await SuggestTool.init() - show.mockResolvedValueOnce({ - label: "Start review", - description: "Run a local review now", - prompt: "/local-review-uncommitted", - }) - cmdGet.mockResolvedValueOnce({ - name: "local-review-uncommitted", - description: "local review (uncommitted changes)", - template: Promise.resolve("Review these uncommitted changes:\n\n## Files Changed\n..."), - hints: [], - }) - - const result = await tool.execute( - { - suggest: "Run review?", - actions: [{ label: "Start review", prompt: "/local-review-uncommitted" }], - }, - ctx as any, - ) - - expect(result.title).toBe("User accepted: Start review") - expect(result.output).toContain("Review these uncommitted changes:") - expect(result.output).toContain("Carry out the following request now") - expect(result.metadata.dismissed).toBe(false) - expect(result.metadata.accepted).toEqual({ - label: "Start review", - description: "Run a local review now", - prompt: "/local-review-uncommitted", - }) - expect(cmdGet).toHaveBeenCalledWith("local-review-uncommitted") - }) - - test("returns plain-text prompt directly for non-command actions", async () => { - const tool = await SuggestTool.init() - show.mockResolvedValueOnce({ - label: "Run tests", - prompt: "Run the test suite and fix any failures", - }) - - const result = await tool.execute( - { - suggest: "Tests might need running", - actions: [{ label: "Run tests", prompt: "Run the test suite and fix any failures" }], - }, - ctx as any, - ) - - expect(result.title).toBe("User accepted: Run tests") - expect(result.output).toContain("Run the test suite and fix any failures") - expect(result.output).toContain("Carry out the following request now") - expect(result.metadata.dismissed).toBe(false) - expect(cmdGet).not.toHaveBeenCalled() - }) - - test("falls back to raw prompt when command is not found", async () => { - const tool = await SuggestTool.init() - show.mockResolvedValueOnce({ - label: "Unknown cmd", - prompt: "/nonexistent-command", - }) - cmdGet.mockResolvedValueOnce(undefined) - - const result = await tool.execute( - { - suggest: "Try this?", - actions: [{ label: "Unknown cmd", prompt: "/nonexistent-command" }], - }, - ctx as any, - ) - - expect(result.title).toBe("User accepted: Unknown cmd") - expect(result.output).toContain("/nonexistent-command") - expect(result.metadata.dismissed).toBe(false) - }) - - test("falls back to raw prompt when template resolution fails", async () => { - const tool = await SuggestTool.init() - show.mockResolvedValueOnce({ - label: "Start review", - prompt: "/local-review-uncommitted", - }) - cmdGet.mockResolvedValueOnce({ - name: "local-review-uncommitted", - description: "local review (uncommitted changes)", - template: Promise.reject(new Error("git not found")), - hints: [], - }) - - const result = await tool.execute( - { - suggest: "Run review?", - actions: [{ label: "Start review", prompt: "/local-review-uncommitted" }], - }, - ctx as any, - ) - - expect(result.title).toBe("User accepted: Start review") - expect(result.output).toContain("/local-review-uncommitted") - expect(result.metadata.dismissed).toBe(false) - }) -}) diff --git a/packages/opencode/test/question/question.test.ts b/packages/opencode/test/question/question.test.ts index 712db3f6cee..adfeda395a9 100644 --- a/packages/opencode/test/question/question.test.ts +++ b/packages/opencode/test/question/question.test.ts @@ -72,34 +72,6 @@ test("ask - adds to pending list", async () => { }) }) -// kilocode_change start - review follow-up uses non-blocking question prompts -test("ask - preserves blocking flag", async () => { - await using tmp = await tmpdir({ git: true }) - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const askPromise = Question.ask({ - sessionID: SessionID.make("ses_test"), - blocking: false, - questions: [ - { - question: "Proceed with review suggestion?", - header: "Code review", - options: [{ label: "Start", description: "Run review" }], - }, - ], - }) - - const pending = await Question.list() - expect(pending[0]?.blocking).toBe(false) - - await Question.reject(pending[0].id) - await expect(askPromise).rejects.toBeInstanceOf(Question.RejectedError) - }, - }) -}) -// kilocode_change end - // reply tests test("reply - resolves the pending ask with answers", async () => { diff --git a/packages/opencode/test/session/prompt-effect.test.ts b/packages/opencode/test/session/prompt-effect.test.ts index 499669ebd60..8e496f60e3b 100644 --- a/packages/opencode/test/session/prompt-effect.test.ts +++ b/packages/opencode/test/session/prompt-effect.test.ts @@ -844,8 +844,10 @@ it.live( 3_000, ) -it.live( +// kilocode_change start - skip flaky test, tracked in #8990 +it.live.skip( "prompt submitted during an active run is included in the next LLM input", + // kilocode_change end () => provideTmpdirServer( Effect.fnUntraced(function* ({ llm }) { diff --git a/packages/opencode/test/suggestion/suggestion.test.ts b/packages/opencode/test/suggestion/suggestion.test.ts deleted file mode 100644 index 10b911a24ff..00000000000 --- a/packages/opencode/test/suggestion/suggestion.test.ts +++ /dev/null @@ -1,2 +0,0 @@ -// kilocode_change - new file -// Moved to test/kilocode/suggestion/suggestion.test.ts. diff --git a/packages/opencode/test/tool/registry.test.ts b/packages/opencode/test/tool/registry.test.ts index 2c6fca70aae..6cd8ad75211 100644 --- a/packages/opencode/test/tool/registry.test.ts +++ b/packages/opencode/test/tool/registry.test.ts @@ -32,37 +32,6 @@ describe("tool.registry", () => { }) // kilocode_change end - // kilocode_change start - test("suggest is registered for cli and vscode only", async () => { - const original = process.env["KILO_CLIENT"] - const originalQuestion = process.env["KILO_ENABLE_QUESTION_TOOL"] - const originalConfig = process.env["KILO_CONFIG_DIR"] - try { - for (const client of ["cli", "vscode", "desktop", "app"]) { - process.env["KILO_CLIENT"] = client - process.env["KILO_ENABLE_QUESTION_TOOL"] = client === "vscode" ? "true" : "false" - await using tmp = await tmpdir({ git: true }) - process.env["KILO_CONFIG_DIR"] = tmp.path - await Instance.provide({ - directory: tmp.path, - fn: async () => { - const ids = await ToolRegistry.ids() - if (client === "cli" || client === "vscode") expect(ids).toContain("suggest") - else expect(ids).not.toContain("suggest") - }, - }) - } - } finally { - if (original === undefined) delete process.env["KILO_CLIENT"] - else process.env["KILO_CLIENT"] = original - if (originalQuestion === undefined) delete process.env["KILO_ENABLE_QUESTION_TOOL"] - else process.env["KILO_ENABLE_QUESTION_TOOL"] = originalQuestion - if (originalConfig === undefined) delete process.env["KILO_CONFIG_DIR"] - else process.env["KILO_CONFIG_DIR"] = originalConfig - } - }) - // kilocode_change end - test("loads tools from .opencode/tool (singular)", async () => { await using tmp = await tmpdir({ init: async (dir) => { diff --git a/packages/opencode/test/tool/suggest.test.ts b/packages/opencode/test/tool/suggest.test.ts deleted file mode 100644 index 448fbb3e940..00000000000 --- a/packages/opencode/test/tool/suggest.test.ts +++ /dev/null @@ -1,2 +0,0 @@ -// kilocode_change - new file -// Moved to test/kilocode/suggestion/tool.test.ts. diff --git a/packages/plugin/package.json b/packages/plugin/package.json index e917c8bd5d5..a0c26f8d945 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/plugin", - "version": "7.2.6", + "version": "7.2.7", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/script/package.json b/packages/script/package.json index 661798c4109..a0235d8a451 100644 --- a/packages/script/package.json +++ b/packages/script/package.json @@ -12,6 +12,6 @@ "exports": { ".": "./src/index.ts" }, - "version": "7.2.6", + "version": "7.2.7", "peerDependencies": {} } diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 2b2f6a4eef9..3767924037f 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/sdk", - "version": "7.2.6", + "version": "7.2.7", "type": "module", "license": "MIT", "scripts": { diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 0da83ed7a12..f024b56d07d 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -203,11 +203,6 @@ import type { SessionUpdateResponses, SessionViewedResponses, SubtaskPartInput, - SuggestionAcceptErrors, - SuggestionAcceptResponses, - SuggestionDismissErrors, - SuggestionDismissResponses, - SuggestionListResponses, TelemetryCaptureErrors, TelemetryCaptureResponses, TextPartInput, @@ -4521,109 +4516,6 @@ export class Network extends HeyApiClient { } } -export class Suggestion extends HeyApiClient { - /** - * List pending suggestions - * - * Get all pending suggestion requests across all sessions. - */ - public list( - parameters?: { - directory?: string - workspace?: string - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).get({ - url: "/suggestion", - ...options, - ...params, - }) - } - - /** - * Accept suggestion request - * - * Accept a suggestion request from the AI assistant. - */ - public accept( - parameters: { - requestID: string - directory?: string - workspace?: string - index?: number - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "requestID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - { in: "body", key: "index" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/suggestion/{requestID}/accept", - ...options, - ...params, - headers: { - "Content-Type": "application/json", - ...options?.headers, - ...params.headers, - }, - }) - } - - /** - * Dismiss suggestion request - * - * Dismiss a suggestion request from the AI assistant. - */ - public dismiss( - parameters: { - requestID: string - directory?: string - workspace?: string - }, - options?: Options, - ) { - const params = buildClientParams( - [parameters], - [ - { - args: [ - { in: "path", key: "requestID" }, - { in: "query", key: "directory" }, - { in: "query", key: "workspace" }, - ], - }, - ], - ) - return (options?.client ?? this.client).post({ - url: "/suggestion/{requestID}/dismiss", - ...options, - ...params, - }) - } -} - export class Telemetry extends HeyApiClient { /** * Capture telemetry event @@ -5829,11 +5721,6 @@ export class KiloClient extends HeyApiClient { return (this._network ??= new Network({ client: this.client })) } - private _suggestion?: Suggestion - get suggestion(): Suggestion { - return (this._suggestion ??= new Suggestion({ client: this.client })) - } - private _telemetry?: Telemetry get telemetry(): Telemetry { return (this._telemetry ??= new Telemetry({ client: this.client })) diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index a055d14ab74..fd35d28c50d 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -418,10 +418,6 @@ export type QuestionRequest = { * Questions to ask */ questions: Array - /** - * Whether this question blocks prompt input (default: true) - */ - blocking?: boolean tool?: { messageID: string callID: string @@ -509,65 +505,6 @@ export type EventSessionIdle = { } } -export type SuggestionAction = { - /** - * Button or option label (1-5 words) - */ - label: string - /** - * Brief explanation of what this action does - */ - description?: string - /** - * Synthetic user prompt to inject when this action is accepted - */ - prompt: string -} - -export type SuggestionRequest = { - id: string - sessionID: string - /** - * Suggestion text shown to the user - */ - text: string - /** - * Available actions the user can take - */ - actions: Array - /** - * Whether this suggestion blocks prompt input (default: true) - */ - blocking?: boolean - tool?: { - messageID: string - callID: string - } -} - -export type EventSuggestionShown = { - type: "suggestion.shown" - properties: SuggestionRequest -} - -export type EventSuggestionAccepted = { - type: "suggestion.accepted" - properties: { - sessionID: string - requestID: string - index: number - action: SuggestionAction - } -} - -export type EventSuggestionDismissed = { - type: "suggestion.dismissed" - properties: { - sessionID: string - requestID: string - } -} - export type EventSessionCompacted = { type: "session.compacted" properties: { @@ -1147,9 +1084,6 @@ export type Event = | EventTodoUpdated | EventSessionStatus | EventSessionIdle - | EventSuggestionShown - | EventSuggestionAccepted - | EventSuggestionDismissed | EventSessionCompacted | EventKiloSessionsRemoteStatusChanged | EventWorkspaceReady @@ -5757,98 +5691,6 @@ export type NetworkRejectResponses = { export type NetworkRejectResponse = NetworkRejectResponses[keyof NetworkRejectResponses] -export type SuggestionListData = { - body?: never - path?: never - query?: { - directory?: string - workspace?: string - } - url: "/suggestion" -} - -export type SuggestionListResponses = { - /** - * List of pending suggestions - */ - 200: Array -} - -export type SuggestionListResponse = SuggestionListResponses[keyof SuggestionListResponses] - -export type SuggestionAcceptData = { - body?: { - /** - * Zero-based action index to accept - */ - index: number - } - path: { - requestID: string - } - query?: { - directory?: string - workspace?: string - } - url: "/suggestion/{requestID}/accept" -} - -export type SuggestionAcceptErrors = { - /** - * Bad request - */ - 400: BadRequestError - /** - * Not found - */ - 404: NotFoundError -} - -export type SuggestionAcceptError = SuggestionAcceptErrors[keyof SuggestionAcceptErrors] - -export type SuggestionAcceptResponses = { - /** - * Suggestion accepted successfully - */ - 200: boolean -} - -export type SuggestionAcceptResponse = SuggestionAcceptResponses[keyof SuggestionAcceptResponses] - -export type SuggestionDismissData = { - body?: never - path: { - requestID: string - } - query?: { - directory?: string - workspace?: string - } - url: "/suggestion/{requestID}/dismiss" -} - -export type SuggestionDismissErrors = { - /** - * Bad request - */ - 400: BadRequestError - /** - * Not found - */ - 404: NotFoundError -} - -export type SuggestionDismissError = SuggestionDismissErrors[keyof SuggestionDismissErrors] - -export type SuggestionDismissResponses = { - /** - * Suggestion dismissed successfully - */ - 200: boolean -} - -export type SuggestionDismissResponse = SuggestionDismissResponses[keyof SuggestionDismissResponses] - export type TelemetryCaptureData = { body?: { /** diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index d94e7d72b2d..65c2bf4f9f7 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -5247,206 +5247,6 @@ ] } }, - "/suggestion": { - "get": { - "operationId": "suggestion.list", - "parameters": [ - { - "in": "query", - "name": "directory", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - } - ], - "summary": "List pending suggestions", - "description": "Get all pending suggestion requests across all sessions.", - "responses": { - "200": { - "description": "List of pending suggestions", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/SuggestionRequest" - } - } - } - } - } - }, - "x-codeSamples": [ - { - "lang": "js", - "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.suggestion.list({\n ...\n})" - } - ] - } - }, - "/suggestion/{requestID}/accept": { - "post": { - "operationId": "suggestion.accept", - "parameters": [ - { - "in": "query", - "name": "directory", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, - { - "in": "path", - "name": "requestID", - "schema": { - "type": "string" - }, - "required": true - } - ], - "summary": "Accept suggestion request", - "description": "Accept a suggestion request from the AI assistant.", - "responses": { - "200": { - "description": "Suggestion accepted successfully", - "content": { - "application/json": { - "schema": { - "type": "boolean" - } - } - } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotFoundError" - } - } - } - } - }, - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "index": { - "description": "Zero-based action index to accept", - "type": "integer", - "minimum": 0, - "maximum": 9007199254740991 - } - }, - "required": ["index"] - } - } - } - }, - "x-codeSamples": [ - { - "lang": "js", - "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.suggestion.accept({\n ...\n})" - } - ] - } - }, - "/suggestion/{requestID}/dismiss": { - "post": { - "operationId": "suggestion.dismiss", - "parameters": [ - { - "in": "query", - "name": "directory", - "schema": { - "type": "string" - } - }, - { - "in": "query", - "name": "workspace", - "schema": { - "type": "string" - } - }, - { - "in": "path", - "name": "requestID", - "schema": { - "type": "string" - }, - "required": true - } - ], - "summary": "Dismiss suggestion request", - "description": "Dismiss a suggestion request from the AI assistant.", - "responses": { - "200": { - "description": "Suggestion dismissed successfully", - "content": { - "application/json": { - "schema": { - "type": "boolean" - } - } - } - }, - "400": { - "description": "Bad request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/BadRequestError" - } - } - } - }, - "404": { - "description": "Not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/NotFoundError" - } - } - } - } - }, - "x-codeSamples": [ - { - "lang": "js", - "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.suggestion.dismiss({\n ...\n})" - } - ] - } - }, "/provider": { "get": { "operationId": "provider.list", @@ -11178,7 +10978,6 @@ }, "required": ["name", "data"] }, - "Event.session.error": { "type": "object", "properties": { @@ -13284,18 +13083,6 @@ { "$ref": "#/components/schemas/Event.lsp.updated" }, - { - "$ref": "#/components/schemas/Event.file.edited" - }, - { - "$ref": "#/components/schemas/Event.suggestion.shown" - }, - { - "$ref": "#/components/schemas/Event.suggestion.accepted" - }, - { - "$ref": "#/components/schemas/Event.suggestion.dismissed" - }, { "$ref": "#/components/schemas/Event.tui.prompt.append" }, diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 6ed8e6d395e..0ff7deb293f 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -26,7 +26,7 @@ "typescript": "catalog:", "vite": "catalog:" }, - "version": "7.2.6", + "version": "7.2.7", "dependencies": {}, "peerDependencies": {} } diff --git a/packages/ui/package.json b/packages/ui/package.json index 8690b6e21c4..53c4e09b90a 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/ui", - "version": "7.2.6", + "version": "7.2.7", "type": "module", "license": "MIT", "exports": { diff --git a/packages/util/package.json b/packages/util/package.json index 839ffff7048..3889aaed7fd 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@opencode-ai/util", - "version": "7.2.6", + "version": "7.2.7", "private": true, "type": "module", "license": "MIT", diff --git a/script/upstream/package.json b/script/upstream/package.json index c3d8f0a4aa4..bf4332ac793 100644 --- a/script/upstream/package.json +++ b/script/upstream/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/upstream-merge", - "version": "7.2.6", + "version": "7.2.7", "private": true, "type": "module", "description": "Scripts for automating upstream opencode merges into Kilo", diff --git a/sdks/vscode/package.json b/sdks/vscode/package.json index 2945cc3912d..4de95a81058 100644 --- a/sdks/vscode/package.json +++ b/sdks/vscode/package.json @@ -2,7 +2,7 @@ "name": "opencode", "displayName": "opencode", "description": "opencode for VS Code", - "version": "7.2.6", + "version": "7.2.7", "publisher": "sst-dev", "repository": { "type": "git",