diff --git a/.changeset/agent-manager-modal-mode-shortcut.md b/.changeset/agent-manager-modal-mode-shortcut.md deleted file mode 100644 index 4efdaa9bc43..00000000000 --- a/.changeset/agent-manager-modal-mode-shortcut.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Fix Agent Manager mode shortcuts in the New Worktree dialog so the selected mode and its matching model stay in sync. diff --git a/.changeset/bright-files-count.md b/.changeset/bright-files-count.md deleted file mode 100644 index 84e4aa4fa2b..00000000000 --- a/.changeset/bright-files-count.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Show aggregate added and removed line counts for multi-file patch tool calls. diff --git a/.changeset/bright-project-dnd.md b/.changeset/bright-project-dnd.md deleted file mode 100644 index 750341b6225..00000000000 --- a/.changeset/bright-project-dnd.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Restore Agent Manager sections and worktree drag-and-drop when multiple projects are shown, with ordering and section moves scoped to the owning project. diff --git a/.changeset/clean-vscode-sidebar-edge.md b/.changeset/clean-vscode-sidebar-edge.md deleted file mode 100644 index dd798951ac5..00000000000 --- a/.changeset/clean-vscode-sidebar-edge.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Remove the duplicate border along the Kilo Code sidebar edge. diff --git a/.changeset/compaction-threshold-boundary.md b/.changeset/compaction-threshold-boundary.md deleted file mode 100644 index 2e610dbefb0..00000000000 --- a/.changeset/compaction-threshold-boundary.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Prevent configured compaction thresholds from interrupting active tool sequences. diff --git a/.changeset/explain-tool-auto-approval-tui.md b/.changeset/explain-tool-auto-approval-tui.md deleted file mode 100644 index df3e2f5cd03..00000000000 --- a/.changeset/explain-tool-auto-approval-tui.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": minor ---- - -Show why a tool call was auto-approved or denied in the TUI, and record the denial reason on the tool call metadata (visible in `kilo export`) alongside the existing auto-approval reason. diff --git a/.changeset/fair-subagents-route.md b/.changeset/fair-subagents-route.md deleted file mode 100644 index a1d2775fc90..00000000000 --- a/.changeset/fair-subagents-route.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Keep config-defined subagents routable when an installed primary agent uses the same name. diff --git a/.changeset/fast-local-recall.md b/.changeset/fast-local-recall.md deleted file mode 100644 index a2cc81e647c..00000000000 --- a/.changeset/fast-local-recall.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Speed up local session recall searches across large conversation histories. diff --git a/.changeset/faster-macos-speech.md b/.changeset/faster-macos-speech.md deleted file mode 100644 index 2d9bd9cc258..00000000000 --- a/.changeset/faster-macos-speech.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Start voice input faster on macOS with native AVFoundation capture. diff --git a/.changeset/fix-agent-manager-focus.md b/.changeset/fix-agent-manager-focus.md deleted file mode 100644 index f8d5d6c65e3..00000000000 --- a/.changeset/fix-agent-manager-focus.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Restore keyboard focus to the prompt or pending question when switching Agent Manager worktrees and sessions. diff --git a/.changeset/fix-multi-project-navigation.md b/.changeset/fix-multi-project-navigation.md new file mode 100644 index 00000000000..4eddfc5eb17 --- /dev/null +++ b/.changeset/fix-multi-project-navigation.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Fix multi-project Agent Manager keyboard navigation and Cmd/Ctrl shortcut selection when worktrees are grouped in sections. diff --git a/.changeset/grep-signal-controls.md b/.changeset/grep-signal-controls.md deleted file mode 100644 index 917e6ed2f59..00000000000 --- a/.changeset/grep-signal-controls.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Add bounded, context-aware signal-to-noise controls to grep searches. diff --git a/.changeset/instant-worktree-hover.md b/.changeset/instant-worktree-hover.md deleted file mode 100644 index e95e1af87ba..00000000000 --- a/.changeset/instant-worktree-hover.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Show and hide Agent Manager worktree hover cards instantly. diff --git a/.changeset/jetbrains-attachment-button-sync.md b/.changeset/jetbrains-attachment-button-sync.md deleted file mode 100644 index dc86abba24e..00000000000 --- a/.changeset/jetbrains-attachment-button-sync.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Keep the JetBrains prompt send/stop button in sync when attachments are added or removed while a session is busy. diff --git a/.changeset/jetbrains-diff-view-fixes.md b/.changeset/jetbrains-diff-view-fixes.md deleted file mode 100644 index 9fc6246c69b..00000000000 --- a/.changeset/jetbrains-diff-view-fixes.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Fix JetBrains diff views to show compact workspace-relative file paths and keep added-file content visible in large branch diffs. diff --git a/.changeset/jetbrains-session-load-crop.md b/.changeset/jetbrains-session-load-crop.md deleted file mode 100644 index 4e515cb3041..00000000000 --- a/.changeset/jetbrains-session-load-crop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Fix JetBrains chat transcripts rendering cropped when opening existing sessions. diff --git a/.changeset/project-local-navigation-hints.md b/.changeset/project-local-navigation-hints.md new file mode 100644 index 00000000000..f6acca80ecd --- /dev/null +++ b/.changeset/project-local-navigation-hints.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Show previous and next navigation hints using each project's own Agent Manager sidebar order. diff --git a/.changeset/prompt-training-indicator-height.md b/.changeset/prompt-training-indicator-height.md deleted file mode 100644 index e1842cbfef4..00000000000 --- a/.changeset/prompt-training-indicator-height.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Keep the prompt controls at a consistent height when the model selector shows the prompt-training indicator. diff --git a/.changeset/quick-model-search.md b/.changeset/quick-model-search.md deleted file mode 100644 index 5b01ee44f97..00000000000 --- a/.changeset/quick-model-search.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Update model search results instantly and keep the active match visible while typing. diff --git a/.changeset/quiet-agent-sections.md b/.changeset/quiet-agent-sections.md deleted file mode 100644 index b6d8d079c04..00000000000 --- a/.changeset/quiet-agent-sections.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Allow Agent Manager sessions to move their worktree between sections or ungroup it through the `agent_manager` tool. diff --git a/.changeset/quiet-metadata-generators.md b/.changeset/quiet-metadata-generators.md deleted file mode 100644 index 67bb87bcbd9..00000000000 --- a/.changeset/quiet-metadata-generators.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Keep Kilo's persona out of generated conversation titles and Agent Manager branch names. diff --git a/.changeset/quiet-speech-shortcut.md b/.changeset/quiet-speech-shortcut.md deleted file mode 100644 index aa18416e3b6..00000000000 --- a/.changeset/quiet-speech-shortcut.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Bind voice input to Cmd/Ctrl+K in Kilo prompt and review comment fields, with hold-to-talk and release-to-send support. diff --git a/.changeset/quiet-terminals-focus.md b/.changeset/quiet-terminals-focus.md deleted file mode 100644 index 22715b263ae..00000000000 --- a/.changeset/quiet-terminals-focus.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Preserve prompt or Agent Manager terminal focus independently for each session when switching sessions. diff --git a/.changeset/quiet-terminals-recover.md b/.changeset/quiet-terminals-recover.md deleted file mode 100644 index 99c87f0de2f..00000000000 --- a/.changeset/quiet-terminals-recover.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Start a fresh shell in the same Agent Manager terminal tab when the user types after the terminal ends. diff --git a/.changeset/remove-project-selection-indicator.md b/.changeset/remove-project-selection-indicator.md new file mode 100644 index 00000000000..50aee2c720f --- /dev/null +++ b/.changeset/remove-project-selection-indicator.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Remove the redundant selected-project indicator from Agent Manager. diff --git a/.changeset/skill-shell-inline-docs.md b/.changeset/skill-shell-inline-docs.md deleted file mode 100644 index a0e1e864dfb..00000000000 --- a/.changeset/skill-shell-inline-docs.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Stop treating `` !`cmd` `` shown as an inline code example in skill documentation as a live command, so it no longer triggers a shell permission prompt. diff --git a/.changeset/tui-paste-expand-placeholder.md b/.changeset/tui-paste-expand-placeholder.md deleted file mode 100644 index 07b963ca0bb..00000000000 --- a/.changeset/tui-paste-expand-placeholder.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": minor ---- - -A second identical large paste expands its collapsed prompt placeholder. \ No newline at end of file diff --git a/.changeset/vscode-skills-settings-narrow-overflow.md b/.changeset/vscode-skills-settings-narrow-overflow.md deleted file mode 100644 index bbdb87be496..00000000000 --- a/.changeset/vscode-skills-settings-narrow-overflow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Fix skill folder path and URL rows clipping and pushing the remove (×) button off-screen in narrow Skills settings panels. Long paths and URLs now truncate within their row, and hovering a truncated value shows the full path or URL in a tooltip. diff --git a/.changeset/workflow-model-variants.md b/.changeset/workflow-model-variants.md deleted file mode 100644 index 48b090358ce..00000000000 --- a/.changeset/workflow-model-variants.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@kilocode/cli": minor -"kilo-code": minor ---- - -Configure a model and reasoning variant for each workflow from Agent Behaviour settings. diff --git a/.changeset/worktree-rename-focus.md b/.changeset/worktree-rename-focus.md new file mode 100644 index 00000000000..fef1fe11e23 --- /dev/null +++ b/.changeset/worktree-rename-focus.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Keep multi-project worktree rename inputs focused while selection updates are still settling. diff --git a/bun.lock b/bun.lock index e2b68afdf4f..67811a9f0ef 100644 --- a/bun.lock +++ b/bun.lock @@ -32,7 +32,7 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "7.4.18", + "version": "7.4.20", "bin": { "opencode": "./bin/opencode", }, @@ -127,7 +127,7 @@ }, "packages/effect-drizzle-sqlite": { "name": "@opencode-ai/effect-drizzle-sqlite", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -141,7 +141,7 @@ }, "packages/effect-sqlite-node": { "name": "@opencode-ai/effect-sqlite-node", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "effect": "catalog:", }, @@ -153,7 +153,7 @@ }, "packages/http-recorder": { "name": "@opencode-ai/http-recorder", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@effect/platform-node": "4.0.0-beta.74", "@effect/platform-node-shared": "4.0.0-beta.74", @@ -174,7 +174,7 @@ }, "packages/kilo-console": { "name": "@kilocode/kilo-console", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@kilocode/kilo-indexing": "workspace:*", "@kilocode/kilo-web-ui": "workspace:*", @@ -197,7 +197,7 @@ }, "packages/kilo-docs": { "name": "@kilocode/kilo-docs", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@docsearch/css": "^4", "@docsearch/js": "^4", @@ -227,7 +227,7 @@ }, "packages/kilo-gateway": { "name": "@kilocode/kilo-gateway", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/anthropic": "3.0.82", @@ -263,7 +263,7 @@ }, "packages/kilo-i18n": { "name": "@kilocode/kilo-i18n", - "version": "7.4.18", + "version": "7.4.20", "devDependencies": { "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", @@ -273,7 +273,7 @@ }, "packages/kilo-indexing": { "name": "@kilocode/kilo-indexing", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@aws-sdk/client-bedrock-runtime": "3.1005.0", "@aws-sdk/credential-provider-ini": "3.972.31", @@ -309,7 +309,7 @@ }, "packages/kilo-memory": { "name": "@kilocode/kilo-memory", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "effect": "catalog:", "zod": "catalog:", @@ -323,7 +323,7 @@ }, "packages/kilo-sandbox": { "name": "@kilocode/sandbox", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@anthropic-ai/sandbox-runtime": "catalog:", "effect": "catalog:", @@ -338,7 +338,7 @@ }, "packages/kilo-telemetry": { "name": "@kilocode/kilo-telemetry", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@kilocode/kilo-gateway": "workspace:*", "posthog-node": "4.4.0", @@ -352,7 +352,7 @@ }, "packages/kilo-ui": { "name": "@kilocode/kilo-ui", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "0.13.11", @@ -390,7 +390,7 @@ }, "packages/kilo-vscode": { "name": "kilo-code", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@anthropic-ai/sdk": "^0.39.0", "@kilocode/kilo-gateway": "workspace:*", @@ -459,7 +459,7 @@ }, "packages/kilo-web-ui": { "name": "@kilocode/kilo-web-ui", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@kilocode/kilo-ui": "workspace:*", "@kobalte/core": "catalog:", @@ -476,7 +476,7 @@ }, "packages/llm": { "name": "@opencode-ai/llm", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", @@ -494,7 +494,7 @@ }, "packages/opencode": { "name": "@kilocode/cli", - "version": "7.4.18", + "version": "7.4.20", "bin": { "kilo": "./bin/kilo", "kilocode": "./bin/kilo", @@ -661,7 +661,7 @@ }, "packages/plugin": { "name": "@kilocode/plugin", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@kilocode/sdk": "workspace:*", "effect": "catalog:", @@ -689,7 +689,7 @@ }, "packages/plugin-atomic-chat": { "name": "@kilocode/plugin-atomic-chat", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@kilocode/plugin": "workspace:*", }, @@ -703,7 +703,7 @@ }, "packages/script": { "name": "@opencode-ai/script", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "semver": "^7.6.3", }, @@ -714,7 +714,7 @@ }, "packages/sdk/js": { "name": "@kilocode/sdk", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "cross-spawn": "catalog:", }, @@ -729,7 +729,7 @@ }, "packages/server": { "name": "@opencode-ai/server", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@opencode-ai/core": "workspace:*", "drizzle-orm": "catalog:", @@ -743,7 +743,7 @@ }, "packages/storybook": { "name": "@opencode-ai/storybook", - "version": "7.4.18", + "version": "7.4.20", "devDependencies": { "@opencode-ai/ui": "workspace:*", "@solidjs/meta": "catalog:", @@ -766,7 +766,7 @@ }, "packages/tui": { "name": "@opencode-ai/tui", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@kilocode/plugin": "workspace:*", "@kilocode/sdk": "workspace:*", @@ -793,7 +793,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "7.4.18", + "version": "7.4.20", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "catalog:", diff --git a/package.json b/package.json index cb7a4a91b07..e55aa0cf85f 100644 --- a/package.json +++ b/package.json @@ -173,6 +173,6 @@ "pacote@21.5.1": "patches/pacote@21.5.1.patch", "mammoth@1.12.0": "patches/mammoth@1.12.0.patch" }, - "version": "7.4.18", + "version": "7.4.20", "peerDependencies": {} } diff --git a/packages/core/package.json b/packages/core/package.json index e07c580a5de..a5e0eb65ff1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.4.18", + "version": "7.4.20", "name": "@opencode-ai/core", "type": "module", "license": "MIT", diff --git a/packages/core/src/kilocode/ripgrep-grep.ts b/packages/core/src/kilocode/ripgrep-grep.ts deleted file mode 100644 index f92cc210f99..00000000000 --- a/packages/core/src/kilocode/ripgrep-grep.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { Match } from "../filesystem/schema" - -export interface Options { - readonly context?: number - readonly literal?: boolean - readonly ignoreCase?: boolean -} - -export type GrepMatch = Match & { - readonly context: boolean - readonly textTruncated: boolean -} - -export const flags = (input: Options) => [ - ...(input.literal ? ["--fixed-strings"] : []), - ...(input.ignoreCase ? ["--ignore-case"] : []), - ...(input.context ? [`--context=${input.context}`] : []), -] - -export const stop = (limit: number) => { - let matches = 0 - return (row: { readonly context: boolean }) => !row.context && ++matches > limit -} - -export const select = < - A extends { - readonly context: boolean - readonly path: { readonly text: string } - readonly line_number: number - }, ->( - input: { readonly limit: number; readonly context?: number }, - items: readonly A[], -) => { - let count = 0 - const overflow = items.findIndex((row) => !row.context && ++count > input.limit) - const selected = items.slice(0, overflow === -1 ? items.length : overflow) - const matches = selected.filter((row) => !row.context) - return selected.filter( - (row) => - !row.context || - matches.some( - (match) => - match.path.text === row.path.text && Math.abs(match.line_number - row.line_number) <= (input.context ?? 0), - ), - ) -} - -export const decorate = (match: Match, context: boolean, textTruncated: boolean): GrepMatch => ({ - ...match, - context, - textTruncated, -}) diff --git a/packages/core/src/ripgrep.ts b/packages/core/src/ripgrep.ts index 5ee202a861d..e1545195606 100644 --- a/packages/core/src/ripgrep.ts +++ b/packages/core/src/ripgrep.ts @@ -6,7 +6,6 @@ import path from "path" import { LayerNode } from "./effect/layer-node" import { Entry, Match } from "./filesystem/schema" import { FSUtil } from "./fs-util" -import * as KiloGrep from "./kilocode/ripgrep-grep" // kilocode_change import * as SpawnValidation from "./kilocode/spawn-validation" // kilocode_change import { AppProcess, collectStream, waitForAbort } from "./process" import { NonNegativeInt, PositiveInt, RelativePath } from "./schema" @@ -24,7 +23,7 @@ const MAX_RECORD_BYTES = 64 * 1024 const MAX_SUBMATCHES = 100 const RawMatch = Schema.Struct({ - type: Schema.Literals(["match", "context"]), // kilocode_change - retain requested context records + type: Schema.Literal("match"), data: Schema.Struct({ path: Schema.Struct({ text: Schema.String }), lines: Schema.Struct({ text: Schema.String }), @@ -40,7 +39,7 @@ const RawMatch = Schema.Struct({ }), }) -type RawMatchData = (typeof RawMatch.Type)["data"] & { readonly context: boolean } // kilocode_change +type RawMatchData = (typeof RawMatch.Type)["data"] export class Error extends Schema.TaggedErrorClass()("Ripgrep.Error", { message: Schema.String, @@ -72,8 +71,7 @@ export interface GlobInput { readonly validate?: Effect.Effect // kilocode_change - bind approved searches at spawn } -export interface GrepInput extends KiloGrep.Options { - // kilocode_change +export interface GrepInput { readonly cwd: string readonly pattern: string readonly file?: string @@ -86,7 +84,7 @@ export interface GrepInput extends KiloGrep.Options { export interface Interface { readonly find: (input: FindInput) => Effect.Effect readonly glob: (input: GlobInput) => Effect.Effect, Error> // kilocode_change - readonly grep: (input: GrepInput) => Effect.Effect, Error | InvalidPatternError> // kilocode_change + readonly grep: (input: GrepInput) => Effect.Effect, Error | InvalidPatternError> // kilocode_change } // kilocode_change start - retain truncation state through model-facing tools @@ -118,7 +116,6 @@ export const layer = Layer.effect( readonly parse: (line: string) => Effect.Effect readonly pattern?: string readonly onItem?: (item: A) => Effect.Effect - readonly stop?: (item: A) => boolean // kilocode_change - stop bounded searches at the overflow match readonly validate?: Effect.Effect // kilocode_change - spawn-bound target validation }) => { const program = Effect.scoped( @@ -139,13 +136,6 @@ export const layer = Layer.effect( Effect.forkScoped, ) let observed = 0 - let stopped = false // kilocode_change - const take = input.stop // kilocode_change start - ? Stream.takeUntil((row) => { - stopped = input.stop?.(row) ?? false - return stopped - }) - : Stream.take(input.limit + 1) // kilocode_change end const rows = yield* Stream.decodeText(handle.stdout).pipe( Stream.splitLines, Stream.filter((line) => line.length > 0), @@ -155,12 +145,11 @@ export const layer = Layer.effect( if (!input.onItem || observed++ >= input.limit) return Effect.void return input.onItem(row) }), - take, // kilocode_change + Stream.take(input.limit + 1), Stream.runCollect, Effect.map((chunk) => [...chunk]), ) - if (stopped) return { items: rows, truncated: true, partial: false } // kilocode_change - const truncated = input.stop ? false : rows.length > input.limit // kilocode_change - custom stop predicates own truncation + const truncated = rows.length > input.limit if (truncated) return { items: rows.slice(0, input.limit), truncated, partial: false } const code = yield* handle.exitCode @@ -260,13 +249,11 @@ export const layer = Layer.effect( grep: (input) => run({ ...input, - stop: KiloGrep.stop(input.limit), // kilocode_change args: [ "--no-config", "--json", "--hidden", "--no-messages", - ...KiloGrep.flags(input), // kilocode_change ...(input.include ? [`--glob=${input.include}`] : []), "--glob=!**/.git/**", "--", @@ -282,19 +269,13 @@ export const layer = Layer.effect( }) ).pipe( Effect.flatMap((json) => { - if ( - !json || - typeof json !== "object" || - !("type" in json) || - (json.type !== "match" && json.type !== "context") // kilocode_change - ) + if (!json || typeof json !== "object" || !("type" in json) || json.type !== "match") return Effect.succeed(undefined) return Schema.decodeUnknownEffect(RawMatch)(json).pipe( Effect.map((match) => ({ ...match.data, path: { text: match.data.path.text.replace(/^\.[\\/]/, "") }, submatches: match.data.submatches.slice(0, MAX_SUBMATCHES), - context: match.type === "context", // kilocode_change })), Effect.mapError((cause) => failure("Invalid ripgrep match output", cause)), ) @@ -304,13 +285,13 @@ export const layer = Layer.effect( // kilocode_change start - retain spawn metadata after mapping matches Effect.map((result) => ({ ...result, - items: KiloGrep.select(input, result.items).map((match) => { + items: result.items.map((match) => { const relative = match.path.text .replace(/^(?:\.[\\/])+/u, "") .replace(/^[\\/]+/u, "") .replaceAll("\\", "/") const absolute = path.resolve(input.cwd, relative) - const item = new Match({ + return new Match({ entry: new Entry({ path: RelativePath.make(relative), type: "file", @@ -325,7 +306,6 @@ export const layer = Layer.effect( end: submatch.end, })), }) - return KiloGrep.decorate(item, match.context, match.lines.text.length > 2_000) }), })), // kilocode_change end diff --git a/packages/core/src/v1/config/permission.ts b/packages/core/src/v1/config/permission.ts index 36d2468bbec..6d6caa02abd 100644 --- a/packages/core/src/v1/config/permission.ts +++ b/packages/core/src/v1/config/permission.ts @@ -25,6 +25,7 @@ const InputObject = Schema.StructWithRest( bash: Schema.optional(Rule), task: Schema.optional(Rule), external_directory: Schema.optional(Rule), + markdown_source: Schema.optional(Rule), // kilocode_change - explicitly authorize external agent/command sources todowrite: Schema.optional(Action), question: Schema.optional(Action), webfetch: Schema.optional(Action), diff --git a/packages/core/test/pty/pty-session.test.ts b/packages/core/test/pty/pty-session.test.ts index f74abf88a80..a238ffb579c 100644 --- a/packages/core/test/pty/pty-session.test.ts +++ b/packages/core/test/pty/pty-session.test.ts @@ -80,17 +80,19 @@ const attachCollecting = Effect.fn("PtySessionTest.attachCollecting")(function* return { attachment, output, ended } }) -const waitForOutput = (output: Queue.Queue, text: string) => - Effect.gen(function* () { - let received = "" +const waitForOutput = (output: Queue.Queue, text: string) => { + let received = "" + const pull = Effect.gen(function* () { while (!received.includes(text)) received += yield* Queue.take(output) return received - }).pipe( + }) + return pull.pipe( Effect.timeoutOrElse({ duration: PTY_TEST_TIMEOUT, // kilocode_change - orElse: () => Effect.fail(new Error(`timeout waiting for output containing ${JSON.stringify(text)}`)), + orElse: () => Effect.fail(new Error(`timeout waiting for output containing ${JSON.stringify(text)}, received ${JSON.stringify(received)}`)), }), ) +} describe("pty", () => { it.live("returns typed not found errors for missing sessions", () => @@ -143,11 +145,13 @@ describe("pty", () => { ) // (script terminals forward raw output to xterm without transcoding). + // The child must outlive its output: an immediate exit can race bun-pty's + // reader thread and drop trailing bytes under load, so print then sleep. ptyTest("round-trips non-ASCII output byte-identically", () => Effect.gen(function* () { const pty = yield* Pty.Service const marker = "café-über-北京-🚀" - const info = yield* createPty("sh", ["-c", "printf 'caf\\303\\251-\\303\\274ber-\\345\\214\\227\\344\\272\\254-\\360\\237\\232\\200\\n'"]) + const info = yield* createPty("sh", ["-c", "printf 'caf\\303\\251-\\303\\274ber-\\345\\214\\227\\344\\272\\254-\\360\\237\\232\\200\\n'; sleep 5"]) const attached = yield* attachCollecting(info.id) expect(yield* waitForOutput(attached.output, marker)).toContain(marker) }), diff --git a/packages/effect-drizzle-sqlite/package.json b/packages/effect-drizzle-sqlite/package.json index 7303d76d7ca..907db1f9a39 100644 --- a/packages/effect-drizzle-sqlite/package.json +++ b/packages/effect-drizzle-sqlite/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.4.18", + "version": "7.4.20", "name": "@opencode-ai/effect-drizzle-sqlite", "type": "module", "license": "MIT", diff --git a/packages/effect-sqlite-node/package.json b/packages/effect-sqlite-node/package.json index 2b297b5ecb3..4af05664a09 100644 --- a/packages/effect-sqlite-node/package.json +++ b/packages/effect-sqlite-node/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.4.18", + "version": "7.4.20", "name": "@opencode-ai/effect-sqlite-node", "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index 5321bca1b32..50fe224ed2a 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "kilo" name = "Kilo" description = "The open source coding agent." -version = "7.4.18" +version = "7.4.20" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/Kilo-Org/kilocode" @@ -11,26 +11,26 @@ name = "Kilo" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.18/opencode-darwin-arm64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.20/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.18/opencode-darwin-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.20/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.18/opencode-linux-arm64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.20/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.18/opencode-linux-x64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.20/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.18/opencode-windows-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.20/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/http-recorder/package.json b/packages/http-recorder/package.json index ff408eb4302..e931dce3e17 100644 --- a/packages/http-recorder/package.json +++ b/packages/http-recorder/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.4.18", + "version": "7.4.20", "name": "@opencode-ai/http-recorder", "description": "Record and replay Effect HTTP client traffic with deterministic cassettes", "type": "module", diff --git a/packages/kilo-console/package.json b/packages/kilo-console/package.json index c9097a403ce..2a024ace453 100755 --- a/packages/kilo-console/package.json +++ b/packages/kilo-console/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-console", - "version": "7.4.18", + "version": "7.4.20", "private": true, "type": "module", "scripts": { diff --git a/packages/kilo-docs/package.json b/packages/kilo-docs/package.json index 5f296eedeaf..f1c085a55f5 100644 --- a/packages/kilo-docs/package.json +++ b/packages/kilo-docs/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-docs", - "version": "7.4.18", + "version": "7.4.20", "private": true, "scripts": { "dev": "next dev --webpack --port 3002", diff --git a/packages/kilo-docs/pages/customize/custom-subagents.md b/packages/kilo-docs/pages/customize/custom-subagents.md index 99c889db7f5..5c00ea8d77c 100644 --- a/packages/kilo-docs/pages/customize/custom-subagents.md +++ b/packages/kilo-docs/pages/customize/custom-subagents.md @@ -93,6 +93,20 @@ Define agents as markdown files with YAML frontmatter. Place them in: The **filename** (without `.md`) becomes the agent name. +If `.kilo/agents/` is a symlink to a directory outside the project, allow that exact source in your global `~/.config/kilo/kilo.jsonc`: + +```jsonc +{ + "permission": { + "markdown_source": { + "/path/to/shared/agents/*": "allow" + } + } +} +``` + +Project configuration cannot grant this permission. External agent files remain untrusted: `{env:...}` substitutions are blocked and `{file:...}` substitutions remain confined to the project. + ```markdown --- description: Reviews code for quality and best practices diff --git a/packages/kilo-docs/pages/customize/workflows.md b/packages/kilo-docs/pages/customize/workflows.md index cb55800c87d..9d6da762ff3 100644 --- a/packages/kilo-docs/pages/customize/workflows.md +++ b/packages/kilo-docs/pages/customize/workflows.md @@ -17,6 +17,20 @@ Workflows are Markdown files stored as **slash commands** in `.kilo/commands/`: - **Global commands**: `~/.config/kilo/commands/` (available in all projects) - **Project commands**: `[project]/.kilo/commands/` (project-specific) +If `.kilo/commands/` is a symlink to a directory outside the project, allow that exact source in your global `~/.config/kilo/kilo.jsonc`: + +```jsonc +{ + "permission": { + "markdown_source": { + "/path/to/shared/commands/*": "allow" + } + } +} +``` + +Project configuration cannot grant this permission. External command files remain untrusted: `{env:...}` substitutions are blocked and `{file:...}` substitutions remain confined to the project. + ### Basic Setup 1. Create a `.md` file with step-by-step instructions diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/labs-tool-call-lab/search-previews-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/labs-tool-call-lab/search-previews-chromium-linux.png index 6a4e3e8ba0b..e571fdb6845 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/labs-tool-call-lab/search-previews-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/labs-tool-call-lab/search-previews-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd794db4d2c038984f9e753ba70b8b0473d97a16713a9ff7255f0d2cf505d41a -size 758306 +oid sha256:033960b68a915d0b05ce539784998d0f84603f206689dbbd14005771d112faa4 +size 762429 diff --git a/packages/kilo-gateway/package.json b/packages/kilo-gateway/package.json index e55bbbdd5df..bb5e6796df1 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.4.18", + "version": "7.4.20", "type": "module", "license": "MIT", "description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration", diff --git a/packages/kilo-gateway/src/api/models.ts b/packages/kilo-gateway/src/api/models.ts index d0bb964ca51..8188c1f825b 100644 --- a/packages/kilo-gateway/src/api/models.ts +++ b/packages/kilo-gateway/src/api/models.ts @@ -1,6 +1,7 @@ import { z } from "zod" import { getKiloUrlFromToken } from "../auth/token.js" import { getDefaultHeaders, buildKiloHeaders } from "../headers.js" +import { resolveKiloGatewayBaseUrl } from "./url.js" import { KILO_API_BASE, KILO_OPENROUTER_BASE, MODELS_FETCH_TIMEOUT_MS, PROMPTS, AI_SDK_PROVIDERS } from "./constants.js" export type KiloModelsResult = { @@ -121,6 +122,16 @@ export type KiloImageModelsResult = { error?: { kind: "unauthorized" | "network" | "schema" | "http"; status?: number } } +export type KiloTranscriptionModel = { + id: string + name: string +} + +export type KiloTranscriptionModelsResult = { + models: KiloTranscriptionModel[] + error?: { kind: "unauthorized" | "network" | "schema" | "http"; status?: number } +} + /** * Fetch image-capable models from Kilo API (OpenRouter-compatible endpoint). * Uses the same raw fetch as {@link fetchKiloModels} but keeps only models @@ -145,6 +156,52 @@ export async function fetchKiloImageModels(options?: { return { models } } +export async function fetchKiloTranscriptionModels(options?: { + kilocodeToken?: string + kilocodeOrganizationId?: string + baseURL?: string +}): Promise { + const token = options?.kilocodeToken + const organizationId = options?.kilocodeOrganizationId + const url = new URL("transcription-models", resolveKiloGatewayBaseUrl({ baseURL: options?.baseURL, token })) + const response = await fetch(url, { + headers: { + ...getDefaultHeaders(), + ...buildKiloHeaders(undefined, { kilocodeOrganizationId: organizationId }), + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + signal: AbortSignal.timeout(MODELS_FETCH_TIMEOUT_MS), + }).catch((err: unknown) => err as Error) + + if (response instanceof Error) return { models: [], error: { kind: "network" } } + if (!response.ok) { + const kind = response.status === 401 || response.status === 403 ? "unauthorized" : "http" + return { models: [], error: { kind, status: response.status } } + } + + const json = await response.json().catch(() => null) + if (!json || !Array.isArray(json.data)) return { models: [], error: { kind: "schema" } } + + const data: unknown[] = json.data + const models = data.filter(isTranscriptionModel).map((model) => ({ + id: model.id, + name: model.name, + })) + if (models.length === 0) return { models: [], error: { kind: "schema" } } + return { models } +} + +type TranscriptionModelResponse = { + id: string + name: string +} + +function isTranscriptionModel(value: unknown): value is TranscriptionModelResponse { + if (!value || typeof value !== "object") return false + const model = value as Record + return typeof model.id === "string" && typeof model.name === "string" +} + /** * Shared raw fetch + validate used by both {@link fetchKiloModels} and {@link fetchKiloImageModels}. */ diff --git a/packages/kilo-gateway/src/index.ts b/packages/kilo-gateway/src/index.ts index cee10c2f781..4df0e8a7681 100644 --- a/packages/kilo-gateway/src/index.ts +++ b/packages/kilo-gateway/src/index.ts @@ -41,6 +41,9 @@ export { fetchKiloImageModels, type KiloImageModel, type KiloImageModelsResult, + fetchKiloTranscriptionModels, + type KiloTranscriptionModel, + type KiloTranscriptionModelsResult, } from "./api/models.js" export { EMPTY_KILO_EMBEDDING_MODEL_CATALOG, diff --git a/packages/kilo-gateway/test/api/models.test.ts b/packages/kilo-gateway/test/api/models.test.ts index 656d27eaf05..0833ec11f30 100644 --- a/packages/kilo-gateway/test/api/models.test.ts +++ b/packages/kilo-gateway/test/api/models.test.ts @@ -1,7 +1,7 @@ // Verifies fetchKiloModels typed result and 401 fallback behaviour. import { test, expect } from "bun:test" -import { fetchKiloModels } from "../../src/api/models.js" +import { fetchKiloModels, fetchKiloTranscriptionModels } from "../../src/api/models.js" const VALID_RESPONSE = JSON.stringify({ data: [ @@ -330,3 +330,56 @@ test("keeps image-output models with tools and drops models without tools", asyn expect(result.models["test/model-a"]).toBeDefined() expect(result.models["test/no-tools"]).toBeUndefined() }) + +test("fetches and filters the transcription catalog", async () => { + const orig = globalThis.fetch + const calls: string[] = [] + stubFetch(async (input) => { + calls.push(String(input)) + return new Response( + JSON.stringify({ + data: [ + { + id: "fish-audio/transcribe-1", + name: "Fish Audio: Transcribe 1", + }, + ], + }), + { status: 200, headers: { "content-type": "application/json" } }, + ) + }) + + const result = await fetchKiloTranscriptionModels({ kilocodeToken: "token" }) + + ;(globalThis as any).fetch = orig + + expect(result.error).toBeUndefined() + expect(result.models).toEqual([ + { + id: "fish-audio/transcribe-1", + name: "Fish Audio: Transcribe 1", + }, + ]) + expect(calls[0]).toContain("/api/gateway/transcription-models") +}) + +test("keeps organization catalog errors from silently falling back to personal models", async () => { + const orig = globalThis.fetch + const calls: string[] = [] + const headers: Headers[] = [] + stubFetch(async (input, init) => { + calls.push(String(input)) + headers.push(new Headers(init?.headers)) + return new Response("Forbidden", { status: 403 }) + }) + + const result = await fetchKiloTranscriptionModels({ kilocodeToken: "token", kilocodeOrganizationId: "org-1" }) + + ;(globalThis as any).fetch = orig + + expect(result.models).toEqual([]) + expect(result.error?.kind).toBe("unauthorized") + expect(calls).toHaveLength(1) + expect(calls[0]).toContain("/api/gateway/transcription-models") + expect(headers[0]?.get("X-KILOCODE-ORGANIZATIONID")).toBe("org-1") +}) diff --git a/packages/kilo-i18n/package.json b/packages/kilo-i18n/package.json index b74de939f7d..ec845e49b51 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.4.18", + "version": "7.4.20", "type": "module", "license": "MIT", "description": "Kilo-specific i18n translations and overrides", diff --git a/packages/kilo-indexing/package.json b/packages/kilo-indexing/package.json index 7f2e39eea54..aab1178e92f 100644 --- a/packages/kilo-indexing/package.json +++ b/packages/kilo-indexing/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-indexing", - "version": "7.4.18", + "version": "7.4.20", "type": "module", "license": "MIT", "description": "Standalone indexing engine and host helpers for Kilo Code", diff --git a/packages/kilo-jetbrains/CHANGELOG.md b/packages/kilo-jetbrains/CHANGELOG.md index 58f12c85b35..d000b8245ae 100644 --- a/packages/kilo-jetbrains/CHANGELOG.md +++ b/packages/kilo-jetbrains/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 7.4.18 + +### Patch Changes + +- [#12746](https://github.com/Kilo-Org/kilocode/pull/12746) [`1a506a7`](https://github.com/Kilo-Org/kilocode/commit/1a506a712c43d317a5a34b250df16845b641eff8) - Keep the JetBrains prompt send/stop button in sync when attachments are added or removed while a session is busy. + +- [#12746](https://github.com/Kilo-Org/kilocode/pull/12746) [`64f0373`](https://github.com/Kilo-Org/kilocode/commit/64f0373056b75546a015816dc0f18b1e380ad93f) - Fix JetBrains diff views to show compact workspace-relative file paths and keep added-file content visible in large branch diffs. + +- [#12746](https://github.com/Kilo-Org/kilocode/pull/12746) [`c1f6a75`](https://github.com/Kilo-Org/kilocode/commit/c1f6a75377b438edfc5c3b5dd85ebdc301302e7a) - Fix JetBrains chat transcripts rendering cropped when opening existing sessions. + ## 7.5.0 ### Minor Changes @@ -150,6 +160,7 @@ ## [7.0.12-rc.4] - 2026-08-01 ### Fixed + - Improve large branch diff performance by capping huge inline diff previews, compacting diff tree paths, and allowing horizontal scrolling for long file names. - Reflow existing long chat sessions after they load so transcripts lay out at the correct width without needing to resize the tool window. - Keep the prompt send/stop button synchronized when attachments are added, removed, or cleared while a session is busy. diff --git a/packages/kilo-memory/package.json b/packages/kilo-memory/package.json index 4989cfc0f2c..24b2e775f62 100644 --- a/packages/kilo-memory/package.json +++ b/packages/kilo-memory/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-memory", - "version": "7.4.18", + "version": "7.4.20", "type": "module", "license": "MIT", "description": "Project memory storage, indexing, recall, and command helpers for Kilo Code", diff --git a/packages/kilo-sandbox/package.json b/packages/kilo-sandbox/package.json index 1355fc2e076..0f992f992ab 100644 --- a/packages/kilo-sandbox/package.json +++ b/packages/kilo-sandbox/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/sandbox", - "version": "7.4.18", + "version": "7.4.20", "type": "module", "license": "MIT", "private": true, diff --git a/packages/kilo-telemetry/package.json b/packages/kilo-telemetry/package.json index 63a01315655..4ce691dbb55 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.4.18", + "version": "7.4.20", "type": "module", "license": "MIT", "description": "Telemetry for Kilo CLI - PostHog analytics integration", diff --git a/packages/kilo-telemetry/src/__tests__/telemetry.test.ts b/packages/kilo-telemetry/src/__tests__/telemetry.test.ts index 939bb5dd243..c86a249f51f 100644 --- a/packages/kilo-telemetry/src/__tests__/telemetry.test.ts +++ b/packages/kilo-telemetry/src/__tests__/telemetry.test.ts @@ -85,6 +85,19 @@ describe("TelemetryEvent", () => { }) describe("Telemetry", () => { + test("skips identity updates when disabled", async () => { + const enabled = spyOn(Client, "isEnabled").mockReturnValue(false) + const update = spyOn(Identity, "updateFromKiloAuth").mockResolvedValue() + + try { + await Telemetry.updateIdentity("token") + expect(update).not.toHaveBeenCalled() + } finally { + enabled.mockRestore() + update.mockRestore() + } + }) + test("includes host OS properties", () => { const capture = spyOn(Client, "capture").mockImplementation(() => {}) diff --git a/packages/kilo-telemetry/src/telemetry.ts b/packages/kilo-telemetry/src/telemetry.ts index 496bf1ad4c9..9e819e25519 100644 --- a/packages/kilo-telemetry/src/telemetry.ts +++ b/packages/kilo-telemetry/src/telemetry.ts @@ -105,6 +105,8 @@ export namespace Telemetry { } export async function updateIdentity(token: string | null, accountId?: string): Promise { + if (!isEnabled()) return + const previousId = Identity.getDistinctId() await Identity.updateFromKiloAuth(token, accountId) diff --git a/packages/kilo-ui/package.json b/packages/kilo-ui/package.json index 8724dc548f9..6948cbadbac 100644 --- a/packages/kilo-ui/package.json +++ b/packages/kilo-ui/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-ui", - "version": "7.4.18", + "version": "7.4.20", "type": "module", "license": "MIT", "exports": { diff --git a/packages/kilo-ui/src/components/message-part.tsx b/packages/kilo-ui/src/components/message-part.tsx index 0a61e75f6c6..4281174d125 100644 --- a/packages/kilo-ui/src/components/message-part.tsx +++ b/packages/kilo-ui/src/components/message-part.tsx @@ -2799,18 +2799,9 @@ ToolRegistry.register({ > {(file) => { - const active = createMemo(() => expanded().includes(file.filePath)) - const [visible, setVisible] = createSignal(false) - createEffect(() => { - if (!active()) { - setVisible(false) - return - } - requestAnimationFrame(() => { - if (!active()) return - setVisible(true) - }) - }) + // Diff defers its own expensive render; mounting the container + // here avoids dropping the last item during batch expansion. + const active = createMemo(() => allExpanded().includes(file.filePath)) return ( @@ -2864,7 +2855,7 @@ ToolRegistry.register({ - + {(diff) => (
this.handleExportSessionTranscript(sessionID), copy: (text) => vscode.env.clipboard.writeText(text), openSessions: (ids) => this.trackOpenSessions(ids), + speechToTextModels: () => this.fetchAndSendSpeechToTextModels(), }) ) { return @@ -2701,6 +2704,15 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper this.postMessage(message) } + private async fetchAndSendSpeechToTextModels(): Promise { + const result = await fetchSpeechToTextModels(this.connectionService, this.getWorkspaceDirectory()) + if (!result.ok) { + this.postMessage({ type: "speechToTextModelsLoaded" as const, models: [...SPEECH_TO_TEXT_MODELS] }) + return + } + this.postMessage({ type: "speechToTextModelsLoaded" as const, models: result.models }) + } + /** * Seed sessionStatusMap with current session statuses on connect. * Without this, the Settings panel (which has no tracked sessions) would see @@ -3944,6 +3956,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper disposeGlobal: () => this.disposeGlobal(), fetchAndSendProviders: () => this.fetchAndSendProviders(), fetchAndSendAgents: () => this.fetchAndSendAgents(), + fetchAndSendSpeechToTextModels: () => this.fetchAndSendSpeechToTextModels(), } } diff --git a/packages/kilo-vscode/src/kilo-provider/early-message.ts b/packages/kilo-vscode/src/kilo-provider/early-message.ts index f67a2144fa1..abf79b49a02 100644 --- a/packages/kilo-vscode/src/kilo-provider/early-message.ts +++ b/packages/kilo-vscode/src/kilo-provider/early-message.ts @@ -17,6 +17,7 @@ type Ctx = { exportTranscript: (sessionID: string) => Promise copy: (text: string) => PromiseLike openSessions: (ids: string[]) => void + speechToTextModels: () => Promise } export async function routeEarlyMessage( @@ -64,6 +65,10 @@ export async function routeEarlyMessage( ctx.post(buildThroughputSettingMessage()) return true } + if (message.type === "requestSpeechToTextModels") { + await ctx.speechToTextModels() + return true + } if (message.type === "requestBrowserSettings") { ctx.browserSettings() return true diff --git a/packages/kilo-vscode/src/kilo-provider/handlers/auth.ts b/packages/kilo-vscode/src/kilo-provider/handlers/auth.ts index db6e9e6d7df..07ae712b902 100644 --- a/packages/kilo-vscode/src/kilo-provider/handlers/auth.ts +++ b/packages/kilo-vscode/src/kilo-provider/handlers/auth.ts @@ -15,6 +15,7 @@ export interface AuthContext { disposeGlobal(): Promise fetchAndSendProviders(): Promise fetchAndSendAgents(): Promise + fetchAndSendSpeechToTextModels(): Promise } /** @@ -137,6 +138,11 @@ export async function handleSetOrganization(ctx: AuthContext, organizationId: st } catch (error) { console.error("[Kilo New] KiloProvider: Failed to refresh agents after org switch:", error) } + try { + await ctx.fetchAndSendSpeechToTextModels() + } catch (error) { + console.error("[Kilo New] KiloProvider: Failed to refresh speech-to-text models after org switch:", error) + } } /** Handle profile refresh request. */ diff --git a/packages/kilo-vscode/src/speech-to-text/catalog.ts b/packages/kilo-vscode/src/speech-to-text/catalog.ts new file mode 100644 index 00000000000..fbb24114f74 --- /dev/null +++ b/packages/kilo-vscode/src/speech-to-text/catalog.ts @@ -0,0 +1,62 @@ +import type { KiloConnectionService } from "../services/cli-backend/connection-service" +import { getErrorMessage } from "../kilo-provider-utils" +import { type SpeechToTextModelDef } from "./models" + +const PATH = "/kilo/models/transcriptions" + +type CatalogModel = { + id: string + name: string +} + +export type SpeechToTextCatalogResult = { ok: true; models: SpeechToTextModelDef[] } | { ok: false; error: string } + +export async function fetchSpeechToTextModels( + connection: KiloConnectionService, + dir: string, + signal?: AbortSignal, +): Promise { + const cfg = connection.getServerConfig() + if (!cfg) return fail("Not connected to the Kilo backend") + + const auth = Buffer.from(`kilo:${cfg.password}`).toString("base64") + const url = new URL(PATH, cfg.baseUrl) + if (dir) url.searchParams.set("directory", dir) + + try { + const res = await fetch(url, { signal, headers: { Authorization: `Basic ${auth}` } }) + if (!res.ok) return fail(`Failed to fetch speech-to-text models (HTTP ${res.status})`) + + const models = parseSpeechToTextCatalog(await res.json()) + if (!models) return fail("Invalid speech-to-text model catalog") + return { ok: true, models } + } catch (err) { + return fail(getErrorMessage(err)) + } +} + +function fail(error: string): SpeechToTextCatalogResult { + return { ok: false, error } +} + +export function parseSpeechToTextCatalog(body: unknown): SpeechToTextModelDef[] | undefined { + if (!Array.isArray(body)) return undefined + const models = body.filter(isCatalogModel).map(toModel) + return models.length > 0 ? models : undefined +} + +function isCatalogModel(value: unknown): value is CatalogModel { + if (!value || typeof value !== "object") return false + const model = value as Record + return typeof model.id === "string" && typeof model.name === "string" +} + +function toModel(model: CatalogModel): SpeechToTextModelDef { + const index = model.name.indexOf(":") + const provider = index === -1 ? model.id.split("/", 1)[0] || "Kilo Gateway" : model.name.slice(0, index).trim() + return { + id: model.id, + label: index === -1 ? model.name : model.name.slice(index + 1).trim(), + provider, + } +} diff --git a/packages/kilo-vscode/src/speech-to-text/transcribe.ts b/packages/kilo-vscode/src/speech-to-text/transcribe.ts index 1e978bf028c..bfa0daf590f 100644 --- a/packages/kilo-vscode/src/speech-to-text/transcribe.ts +++ b/packages/kilo-vscode/src/speech-to-text/transcribe.ts @@ -54,7 +54,7 @@ export async function transcribeSpeech( "Content-Type": "application/json", }, body: JSON.stringify({ - model: model.id, + model: input.model || model.id, input_audio: { data: input.data, format: input.format, diff --git a/packages/kilo-vscode/tests/package.json b/packages/kilo-vscode/tests/package.json index 0219e54e36c..9b3ef8d5815 100644 --- a/packages/kilo-vscode/tests/package.json +++ b/packages/kilo-vscode/tests/package.json @@ -1,6 +1,6 @@ { "type": "module", - "version": "7.4.18", + "version": "7.4.20", "dependencies": {}, "devDependencies": {}, "peerDependencies": {} diff --git a/packages/kilo-vscode/tests/unit/agent-manager-focus.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-focus.test.ts index 1c63c08b4fa..c5c33eea67f 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-focus.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-focus.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "bun:test" import { Window } from "happy-dom" -import { focusQuestionOption, hasQuestionOption } from "../../webview-ui/agent-manager/focus" +import { focusQuestionOption, hasQuestionOption, preservesTextFocus } from "../../webview-ui/agent-manager/focus" +import { isTextControl } from "../../webview-ui/src/utils/focus" describe("Agent Manager focus", () => { it("focuses the first enabled question option", () => { @@ -53,4 +54,21 @@ describe("Agent Manager focus", () => { dock.setAttribute("inert", "") expect(hasQuestionOption(root)).toBe(false) }) + + it("preserves focus for an active editable control", () => { + const window = new Window() + const rename = window.document.createElement("input") + rename.className = "am-worktree-rename-input" + const prompt = window.document.createElement("textarea") + prompt.className = "prompt-input" + const editor = window.document.createElement("div") + editor.contentEditable = "plaintext-only" + const button = window.document.createElement("button") + + expect(isTextControl(rename)).toBe(true) + expect(preservesTextFocus(rename)).toBe(true) + expect(preservesTextFocus(prompt)).toBe(false) + expect(isTextControl(editor)).toBe(true) + expect(isTextControl(button)).toBe(false) + }) }) diff --git a/packages/kilo-vscode/tests/unit/navigate.test.ts b/packages/kilo-vscode/tests/unit/navigate.test.ts index be6e5d816e1..3f23206ec69 100644 --- a/packages/kilo-vscode/tests/unit/navigate.test.ts +++ b/packages/kilo-vscode/tests/unit/navigate.test.ts @@ -401,7 +401,7 @@ describe("buildProjectNavOrder", () => { expect(order.map((e) => e.id)).toEqual([localNavId("A"), worktreeNavId("A", "aw2")]) }) - it("renders section members before ungrouped worktrees (matching the project body)", () => { + it("renders ungrouped worktrees before section members (matching the project body)", () => { const order = buildProjectNavOrder([ project({ id: "A", @@ -411,7 +411,76 @@ describe("buildProjectNavOrder", () => { unassigned: [], }), ]) - expect(order.map((e) => e.id)).toEqual([localNavId("A"), worktreeNavId("A", "aw1"), worktreeNavId("A", "aw2")]) + expect(order.map((e) => e.id)).toEqual([localNavId("A"), worktreeNavId("A", "aw2"), worktreeNavId("A", "aw1")]) + }) + + it("follows persisted top-level section order and worktree order", () => { + const order = buildProjectNavOrder([ + project({ + id: "A", + expanded: true, + worktrees: [{ id: "aw1", sectionId: "s1" }, { id: "aw2" }, { id: "aw3", sectionId: "s2" }], + worktreeOrder: ["aw2", "s2", "s1", "aw3", "aw1"], + sections: [ + { id: "s1", collapsed: false }, + { id: "s2", collapsed: false }, + ], + unassigned: [], + }), + ]) + + expect(order.map((e) => e.id)).toEqual([ + localNavId("A"), + worktreeNavId("A", "aw2"), + worktreeNavId("A", "aw3"), + worktreeNavId("A", "aw1"), + ]) + }) + + it("keeps multi-version worktrees adjacent", () => { + const order = buildProjectNavOrder([ + project({ + id: "A", + expanded: true, + worktrees: [{ id: "aw1", groupId: "g" }, { id: "aw2" }, { id: "aw3", groupId: "g" }], + worktreeOrder: ["aw1", "aw2", "aw3"], + sections: [], + unassigned: [], + }), + ]) + + expect(order.map((e) => e.id)).toEqual([ + localNavId("A"), + worktreeNavId("A", "aw1"), + worktreeNavId("A", "aw3"), + worktreeNavId("A", "aw2"), + ]) + }) + + it("matches raw ungrouped order when sections are present", () => { + const order = buildProjectNavOrder([ + project({ + id: "A", + expanded: true, + worktrees: [ + { id: "aw1", groupId: "g" }, + { id: "aw2" }, + { id: "aw3", groupId: "g" }, + { id: "aw4", sectionId: "s1" }, + ], + worktreeOrder: ["aw1", "aw2", "aw3", "s1", "aw4"], + sections: [{ id: "s1", collapsed: false }], + unassigned: [], + }), + ]) + + expect(order.map((e) => e.id)).toEqual([ + localNavId("A"), + worktreeNavId("A", "aw1"), + worktreeNavId("A", "aw2"), + worktreeNavId("A", "aw3"), + worktreeNavId("A", "aw4"), + ]) }) it("excludes unassigned sessions when the sessions section is collapsed", () => { diff --git a/packages/kilo-vscode/tests/unit/project-local-navigation.test.ts b/packages/kilo-vscode/tests/unit/project-local-navigation.test.ts new file mode 100644 index 00000000000..b20bbc5f30a --- /dev/null +++ b/packages/kilo-vscode/tests/unit/project-local-navigation.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from "bun:test" +import { projectAdjacentHint } from "../../webview-ui/agent-manager/project-local-navigation" + +describe("projectAdjacentHint", () => { + it("does not leak a hint to another project with the same raw ID", () => { + expect(projectAdjacentHint("project-a", "project-a", "shared", "local", ["local", "shared"], "prev", "next")).toBe( + "next", + ) + expect(projectAdjacentHint("project-b", "project-a", "shared", "local", ["local", "shared"], "prev", "next")).toBe( + "", + ) + }) + + it("uses the active project's local sidebar order", () => { + expect(projectAdjacentHint("project-a", "project-a", "shared", "local", ["local", "shared"], "prev", "next")).toBe( + "next", + ) + expect(projectAdjacentHint("project-a", "project-a", "local", "shared", ["local", "shared"], "prev", "next")).toBe( + "prev", + ) + expect( + projectAdjacentHint("project-b", "project-b", "shared", "local", ["local", "other", "shared"], "prev", "next"), + ).toBe("") + }) +}) diff --git a/packages/kilo-vscode/tests/unit/speech-to-text-availability.test.ts b/packages/kilo-vscode/tests/unit/speech-to-text-availability.test.ts index 83848168963..1361f8f5166 100644 --- a/packages/kilo-vscode/tests/unit/speech-to-text-availability.test.ts +++ b/packages/kilo-vscode/tests/unit/speech-to-text-availability.test.ts @@ -23,9 +23,11 @@ describe("speech-to-text availability", () => { }) it("normalizes configured and unknown transcription models", () => { - expect(selectedSpeechToTextModel({ experimental: { speech_to_text_model: "google/chirp-3" } })).toBe( - "google/chirp-3", - ) + expect( + selectedSpeechToTextModel({ experimental: { speech_to_text_model: "google/chirp-3" } }, [ + { id: "google/chirp-3", label: "Chirp 3", provider: "Google" }, + ]), + ).toBe("google/chirp-3") expect(selectedSpeechToTextModel({ experimental: { speech_to_text_model: "unknown/model" } })).toBe( DEFAULT_SPEECH_TO_TEXT_MODEL.id, ) diff --git a/packages/kilo-vscode/tests/unit/speech-to-text-catalog.test.ts b/packages/kilo-vscode/tests/unit/speech-to-text-catalog.test.ts new file mode 100644 index 00000000000..a4e0a8029f8 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/speech-to-text-catalog.test.ts @@ -0,0 +1,38 @@ +import { describe, expect, it } from "bun:test" +import { parseSpeechToTextCatalog } from "../../src/speech-to-text/catalog" +import { DEFAULT_SPEECH_TO_TEXT_MODEL } from "../../src/speech-to-text/models" + +describe("speech-to-text discovery", () => { + it("keeps transcription catalog metadata authoritative and exposes additions", () => { + const models = parseSpeechToTextCatalog([ + { + id: "fish-audio/transcribe-1", + name: "Fish Audio: Transcribe 1", + }, + { + id: "openai/gpt-4o-mini-transcribe", + name: "OpenAI: GPT-4o Mini Transcribe", + }, + { + id: "openai/whisper-1", + name: "Whisper 1", + }, + ]) + + expect(models).toEqual([ + { id: "fish-audio/transcribe-1", label: "Transcribe 1", provider: "Fish Audio" }, + { + id: "openai/gpt-4o-mini-transcribe", + label: "GPT-4o Mini Transcribe", + provider: "OpenAI", + }, + { id: "openai/whisper-1", label: "Whisper 1", provider: "openai" }, + ]) + }) + + it("rejects empty or malformed catalogs so callers can use the static fallback", () => { + expect(parseSpeechToTextCatalog([])).toBeUndefined() + expect(parseSpeechToTextCatalog({ data: [] })).toBeUndefined() + expect(DEFAULT_SPEECH_TO_TEXT_MODEL.id).toBe("openai/whisper-large-v3-turbo") + }) +}) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index e4649be8f7f..e73770bc4a2 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -104,7 +104,7 @@ import { focusChatSearch, LOCAL, } from "./navigate" -import { createProjectNav } from "./project-nav" +import { buildProjectNavEntries, createProjectNav } from "./project-nav" import { addPendingTab as addLocalPendingTab, nextTabAfterClose, @@ -893,6 +893,11 @@ const AgentManagerContent: Component = () => { ) /** Map from sidebar item id → 1-based shortcut number (⌘1 for LOCAL, ⌘2 for first worktree, etc.) */ const shortcutMap = createMemo(() => buildShortcutMap(sidebarOrder())) + const projectShortcutMap = createMemo(() => + buildShortcutMap( + buildProjectNavEntries(projectList(), projectStates(), projectLive.sessions()).map((entry) => ({ id: entry.id })), + ), + ) const moveToSection = (ids: string[], sec: string | null) => vscode.postMessage({ type: "agentManager.moveToSection", worktreeIds: ids, sectionId: sec }) @@ -2354,6 +2359,7 @@ const AgentManagerContent: Component = () => { t={t} onSearchRef={(ref) => (sidebarSearchMenu = ref)} onShortcuts={handleShowKeyboardShortcuts} + shortcutMap={projectShortcutMap} /> diff --git a/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx index 083d892d557..ae15fa3cf6b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx @@ -21,6 +21,7 @@ import { useProvider } from "../src/context/provider" import { useConfig } from "../src/context/config" import { canUseSpeechToText, selectedSpeechToTextModel } from "../src/components/speech-to-text/availability" import { useSpeechToText } from "../src/components/speech-to-text/useSpeechToText" +import { useSpeechToTextModels } from "../src/context/speech-to-text-models" import { getDirectory, getFilename, @@ -111,8 +112,9 @@ export const DiffPanel: Component = (props) => { const provider = useProvider() const { config } = useConfig() const speech = useSpeechToText(vscode, server, { t }) + const speechModels = useSpeechToTextModels() const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates()) - const speechModel = () => selectedSpeechToTextModel(config()) + const speechModel = () => selectedSpeechToTextModel(config(), speechModels.models()) const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent) const sendAllKeybind = () => isMac ? t("agentManager.review.sendAllShortcut.mac") : t("agentManager.review.sendAllShortcut.other") diff --git a/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx b/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx index bdcba1c8453..72a68c57ef4 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx @@ -39,6 +39,7 @@ import { import { useLanguage } from "../src/context/language" import { useImageAttachments, type ImageAttachment } from "../src/hooks/useImageAttachments" import { useSpeechToText } from "../src/components/speech-to-text/useSpeechToText" +import { useSpeechToTextModels } from "../src/context/speech-to-text-models" import { createSpeechShortcut } from "../src/components/speech-to-text/shortcut" import { convertToMentionPath } from "../src/utils/path-mentions" import { insertSpacedText } from "../src/components/chat/prompt-input-utils" @@ -131,8 +132,9 @@ export const NewWorktreeDialog: Component<{ const sandboxRequestID = crypto.randomUUID() const sandboxVisible = () => features().sandboxControls && globalConfig().sandbox?.enabled === true const speech = useSpeechToText(vscode, server, { t }) + const speechModels = useSpeechToTextModels() const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates()) - const speechModel = () => selectedSpeechToTextModel(config()) + const speechModel = () => selectedSpeechToTextModel(config(), speechModels.models()) let prior: string | null = null let request: string | undefined const cancel = () => { diff --git a/packages/kilo-vscode/webview-ui/agent-manager/ProjectList.tsx b/packages/kilo-vscode/webview-ui/agent-manager/ProjectList.tsx index cbfc6c08b76..1575e4e4074 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/ProjectList.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/ProjectList.tsx @@ -41,6 +41,7 @@ interface Props { t: LanguageContextValue["t"] onSearchRef: (ref: SidebarSearchMenuRef) => void onShortcuts: () => void + shortcutMap?: () => Map } export const ProjectList: Component = (props) => { @@ -222,6 +223,7 @@ export const ProjectList: Component = (props) => { sessions={props.sessions[project.id]} selectedProject={props.selectedProject} selection={props.selection} + currentSessionID={props.currentSessionID} bindings={props.bindings} t={props.t} onSelectLocal={(projectId) => select({ projectId, kind: "local" })} @@ -229,6 +231,7 @@ export const ProjectList: Component = (props) => { onSelectSession={(projectId, sessionId) => select({ projectId, kind: "session", sessionId })} onNewWorktree={newWorktree} onDefaultBranch={defaultBranch} + shortcutMap={props.shortcutMap} /> )} /> diff --git a/packages/kilo-vscode/webview-ui/agent-manager/ProjectSidebarBody.tsx b/packages/kilo-vscode/webview-ui/agent-manager/ProjectSidebarBody.tsx index 77a479db17c..96d89b75a79 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/ProjectSidebarBody.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/ProjectSidebarBody.tsx @@ -20,6 +20,7 @@ import type { } from "../src/types/messages" import type { LanguageContextValue } from "../src/context/language" import { useVSCode } from "../src/context/vscode" +import { projectAdjacentHint, projectSidebarOrder } from "./project-local-navigation" import SectionHeader from "./SectionHeader" import { WorktreeItem } from "./WorktreeItem" import { UnassignedSessionsSection } from "./UnassignedSessionsSection" @@ -31,6 +32,8 @@ import { ConstrainDragXAxis } from "./constrain-drag-x" import { createProjectStore, type ProjectStore } from "./project/store" import { randomColor } from "./section-colors" +const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent) + interface Props { project: AgentProjectSnapshot state?: AgentManagerStateMessage @@ -44,6 +47,7 @@ interface Props { sessions?: ProjectSessionInfo[] selectedProject?: string selection?: string + currentSessionID?: () => string | undefined bindings: Record t: LanguageContextValue["t"] onSelectLocal: (projectId: string) => void @@ -51,6 +55,7 @@ interface Props { onSelectSession: (projectId: string, sessionId: string) => void onNewWorktree: (projectId: string) => void onDefaultBranch: (projectId: string, selected?: string, detected?: string) => void + shortcutMap?: () => Map } /** Permanent real sidebar body for one expanded project. */ @@ -99,9 +104,23 @@ export const ProjectSidebarBody: Component = (props) => { const members = (sectionId: string) => sorted().filter((wt) => wt.sectionId === sectionId) const ungrouped = createMemo(() => sorted().filter((wt) => !wt.sectionId)) const top = createMemo(() => buildTopLevelItems(sections(), ungrouped(), sorted(), order())) + const sidebarOrder = createMemo(() => + projectSidebarOrder(top(), sorted(), sections(), members, state()?.sessionsCollapsed ? [] : localSessions()), + ) const post = (message: Record) => vscode.postMessage({ ...message, projectId: props.project.id } as never) + const navHint = (id: string) => + projectAdjacentHint( + props.project.id, + props.selectedProject, + id, + props.selection ?? props.currentSessionID?.(), + sidebarOrder(), + props.bindings.previousSession ?? "", + props.bindings.nextSession ?? "", + ) + const scope = (kind: "section" | "worktree", id: string) => `${props.project.id}:${kind}:${id}` const parse = (kind: "section" | "worktree", value: unknown) => { if (typeof value !== "string") return @@ -216,6 +235,7 @@ export const ProjectSidebarBody: Component = (props) => { = (props) => { working={props.working?.(worktree.id) || runs()[worktree.id]?.state === "running"} stale={state()?.staleWorktreeIds?.includes(worktree.id) === true} stats={props.stats?.[worktree.id]} + navHint={navHint(worktree.id)} sessions={sessions(worktree.id).length} grouped={isGrouped(worktree)} groupStart={isGroupStart(worktree, idx(), list)} @@ -288,6 +309,14 @@ export const ProjectSidebarBody: Component = (props) => { + + {(shortcut) => ( + + {isMac ? "⌘" : "Ctrl+"} + {shortcut()} + + )} +
{props.t("agentManager.local")} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/ProjectsSection.tsx b/packages/kilo-vscode/webview-ui/agent-manager/ProjectsSection.tsx index c8b39b253d6..e9cb59c785e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/ProjectsSection.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/ProjectsSection.tsx @@ -48,7 +48,7 @@ export const ProjectsSection: Component = (props) => ( {(id) => { const project = () => props.projects.find((item) => item.id === id)! return ( -
+