mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-21 14:07:20 +08:00
Merge origin/main into fix-multi-project-progress-indicators
This commit is contained in:
@@ -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.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Show aggregate added and removed line counts for multi-file patch tool calls.
|
||||
@@ -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.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Remove the duplicate border along the Kilo Code sidebar edge.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Prevent configured compaction thresholds from interrupting active tool sequences.
|
||||
@@ -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.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Keep config-defined subagents routable when an installed primary agent uses the same name.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Speed up local session recall searches across large conversation histories.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Start voice input faster on macOS with native AVFoundation capture.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Restore keyboard focus to the prompt or pending question when switching Agent Manager worktrees and sessions.
|
||||
@@ -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.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Add bounded, context-aware signal-to-noise controls to grep searches.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Show and hide Agent Manager worktree hover cards instantly.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Fix JetBrains chat transcripts rendering cropped when opening existing sessions.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Show previous and next navigation hints using each project's own Agent Manager sidebar order.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep the prompt controls at a consistent height when the model selector shows the prompt-training indicator.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Update model search results instantly and keep the active match visible while typing.
|
||||
@@ -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.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Keep Kilo's persona out of generated conversation titles and Agent Manager branch names.
|
||||
@@ -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.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Preserve prompt or Agent Manager terminal focus independently for each session when switching sessions.
|
||||
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Remove the redundant selected-project indicator from Agent Manager.
|
||||
@@ -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.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
---
|
||||
|
||||
A second identical large paste expands its collapsed prompt placeholder.
|
||||
@@ -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.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Configure a model and reasoning variant for each workflow from Agent Behaviour settings.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep multi-project worktree rename inputs focused while selection updates are still settling.
|
||||
@@ -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:",
|
||||
|
||||
+1
-1
@@ -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": {}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
@@ -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<Error>()("Ripgrep.Error", {
|
||||
message: Schema.String,
|
||||
@@ -72,8 +71,7 @@ export interface GlobInput {
|
||||
readonly validate?: Effect.Effect<void, unknown> // 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 Entry[], Error>
|
||||
readonly glob: (input: GlobInput) => Effect.Effect<SearchResult<Entry>, Error> // kilocode_change
|
||||
readonly grep: (input: GrepInput) => Effect.Effect<SearchResult<KiloGrep.GrepMatch>, Error | InvalidPatternError> // kilocode_change
|
||||
readonly grep: (input: GrepInput) => Effect.Effect<SearchResult<Match>, 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<A | undefined, Error>
|
||||
readonly pattern?: string
|
||||
readonly onItem?: (item: A) => Effect.Effect<void>
|
||||
readonly stop?: (item: A) => boolean // kilocode_change - stop bounded searches at the overflow match
|
||||
readonly validate?: Effect.Effect<void, unknown> // 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<A>((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<RawMatchData>({
|
||||
...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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -80,17 +80,19 @@ const attachCollecting = Effect.fn("PtySessionTest.attachCollecting")(function*
|
||||
return { attachment, output, ended }
|
||||
})
|
||||
|
||||
const waitForOutput = (output: Queue.Queue<string>, text: string) =>
|
||||
Effect.gen(function* () {
|
||||
let received = ""
|
||||
const waitForOutput = (output: Queue.Queue<string>, 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)
|
||||
}),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-console",
|
||||
"version": "7.4.18",
|
||||
"version": "7.4.20",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fd794db4d2c038984f9e753ba70b8b0473d97a16713a9ff7255f0d2cf505d41a
|
||||
size 758306
|
||||
oid sha256:033960b68a915d0b05ce539784998d0f84603f206689dbbd14005771d112faa4
|
||||
size 762429
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<KiloTranscriptionModelsResult> {
|
||||
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<string, unknown>
|
||||
return typeof model.id === "string" && typeof model.name === "string"
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared raw fetch + validate used by both {@link fetchKiloModels} and {@link fetchKiloImageModels}.
|
||||
*/
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(() => {})
|
||||
|
||||
|
||||
@@ -105,6 +105,8 @@ export namespace Telemetry {
|
||||
}
|
||||
|
||||
export async function updateIdentity(token: string | null, accountId?: string): Promise<void> {
|
||||
if (!isEnabled()) return
|
||||
|
||||
const previousId = Identity.getDistinctId()
|
||||
await Identity.updateFromKiloAuth(token, accountId)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.4.18",
|
||||
"version": "7.4.20",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -2799,18 +2799,9 @@ ToolRegistry.register({
|
||||
>
|
||||
<For each={files()}>
|
||||
{(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 (
|
||||
<Accordion.Item value={file.filePath} data-type={file.type}>
|
||||
@@ -2864,7 +2855,7 @@ ToolRegistry.register({
|
||||
</Accordion.Trigger>
|
||||
</StickyAccordionHeader>
|
||||
<Accordion.Content>
|
||||
<Show when={visible() && view(file)}>
|
||||
<Show when={active() && view(file)}>
|
||||
{(diff) => (
|
||||
<div data-component="apply-patch-file-diff">
|
||||
<Dynamic
|
||||
|
||||
@@ -1,5 +1,51 @@
|
||||
# kilo-code
|
||||
|
||||
## 7.4.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#12841](https://github.com/Kilo-Org/kilocode/pull/12841) [`744c5c4`](https://github.com/Kilo-Org/kilocode/commit/744c5c4a6b244d2aac38b7acc08f98a4086544ad) - Discover available speech-to-text models from the Kilo Gateway while retaining offline fallback support and organization model restrictions.
|
||||
|
||||
- [#12842](https://github.com/Kilo-Org/kilocode/pull/12842) [`2613ff9`](https://github.com/Kilo-Org/kilocode/commit/2613ff93c6446eb3a662ed6d4a4b063131a6a371) - Show the first Agent Manager terminal prompt sooner by deferring optional terminal enhancements until after initial paint.
|
||||
|
||||
- [#12839](https://github.com/Kilo-Org/kilocode/pull/12839) [`4af1d1c`](https://github.com/Kilo-Org/kilocode/commit/4af1d1c8abd15881f5c06554cca8e2f6133003ee) - Keep the final file visible when expanding multi-file patch results.
|
||||
|
||||
## 7.4.19
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#12729](https://github.com/Kilo-Org/kilocode/pull/12729) [`ce7984f`](https://github.com/Kilo-Org/kilocode/commit/ce7984fc4247fb2805990ef62e1b1d9f286de9d9) - Configure a model and reasoning variant for each workflow from Agent Behaviour settings.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#12796](https://github.com/Kilo-Org/kilocode/pull/12796) [`20d1648`](https://github.com/Kilo-Org/kilocode/commit/20d1648e7a10f981fde09fc4d5e9de5c89b1dda8) - Fix Agent Manager mode shortcuts in the New Worktree dialog so the selected mode and its matching model stay in sync.
|
||||
|
||||
- [#12725](https://github.com/Kilo-Org/kilocode/pull/12725) [`f239f36`](https://github.com/Kilo-Org/kilocode/commit/f239f36f65a1edb5ef89748933b49b3f21639a45) - Show aggregate added and removed line counts for multi-file patch tool calls.
|
||||
|
||||
- [#12803](https://github.com/Kilo-Org/kilocode/pull/12803) [`9819c1c`](https://github.com/Kilo-Org/kilocode/commit/9819c1c315bd05631862ad6f21bbef21d73b7bdc) - Restore Agent Manager sections and worktree drag-and-drop when multiple projects are shown, with ordering and section moves scoped to the owning project.
|
||||
|
||||
- [#12836](https://github.com/Kilo-Org/kilocode/pull/12836) [`4f7dfe6`](https://github.com/Kilo-Org/kilocode/commit/4f7dfe65cc85ef7d103a9d0d93f86b030f7142e0) - Remove the duplicate border along the Kilo Code sidebar edge.
|
||||
|
||||
- [#12814](https://github.com/Kilo-Org/kilocode/pull/12814) [`cd7d053`](https://github.com/Kilo-Org/kilocode/commit/cd7d053f03fa7b5434b6fcae9e2c68e415b54331) - Start voice input faster on macOS with native AVFoundation capture.
|
||||
|
||||
- [#12795](https://github.com/Kilo-Org/kilocode/pull/12795) [`37559f8`](https://github.com/Kilo-Org/kilocode/commit/37559f8643ef8ecb68ee04eae770c9b47feee88a) - Restore keyboard focus to the prompt or pending question when switching Agent Manager worktrees and sessions.
|
||||
|
||||
- [#12799](https://github.com/Kilo-Org/kilocode/pull/12799) [`7cfaeb2`](https://github.com/Kilo-Org/kilocode/commit/7cfaeb2c8dbe7355b1d92d9a8f62d6bdfb7f6d46) - Show and hide Agent Manager worktree hover cards instantly.
|
||||
|
||||
- [#12805](https://github.com/Kilo-Org/kilocode/pull/12805) [`8490124`](https://github.com/Kilo-Org/kilocode/commit/84901241c0c6dcb735d7e229bd9bce2c0f7e5c36) - Keep the prompt controls at a consistent height when the model selector shows the prompt-training indicator.
|
||||
|
||||
- [#12810](https://github.com/Kilo-Org/kilocode/pull/12810) [`e04f653`](https://github.com/Kilo-Org/kilocode/commit/e04f6531bf3ef2eed5cde6e1d81a4983f276b9ca) - Update model search results instantly and keep the active match visible while typing.
|
||||
|
||||
- [#12815](https://github.com/Kilo-Org/kilocode/pull/12815) [`3d4294e`](https://github.com/Kilo-Org/kilocode/commit/3d4294e3bb50630159233c8063b253dc5f3da8d3) - Allow Agent Manager sessions to move their worktree between sections or ungroup it through the `agent_manager` tool.
|
||||
|
||||
- [#12798](https://github.com/Kilo-Org/kilocode/pull/12798) [`dfc1607`](https://github.com/Kilo-Org/kilocode/commit/dfc16076934decb6578730ea41f9705e2acc0923) - Bind voice input to Cmd/Ctrl+K in Kilo prompt and review comment fields, with hold-to-talk and release-to-send support.
|
||||
|
||||
- [#12801](https://github.com/Kilo-Org/kilocode/pull/12801) [`4810766`](https://github.com/Kilo-Org/kilocode/commit/4810766d058f53084b8a6cb0fe4d1ff42973e89b) - Preserve prompt or Agent Manager terminal focus independently for each session when switching sessions.
|
||||
|
||||
- [#12812](https://github.com/Kilo-Org/kilocode/pull/12812) [`6ae16f9`](https://github.com/Kilo-Org/kilocode/commit/6ae16f9d6272b3bfba998bb516cba7667935d6e4) - Start a fresh shell in the same Agent Manager terminal tab when the user types after the terminal ends.
|
||||
|
||||
- [#12733](https://github.com/Kilo-Org/kilocode/pull/12733) [`63220e0`](https://github.com/Kilo-Org/kilocode/commit/63220e019c048d6df639a4b2fbd4c5c3f124547f) Thanks [@rakshith1928](https://github.com/rakshith1928)! - 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.
|
||||
|
||||
## 7.4.18
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -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.4.18",
|
||||
"version": "7.4.20",
|
||||
"icon": "assets/icons/logo-outline-black.png",
|
||||
"galleryBanner": {
|
||||
"color": "#FFFFFF",
|
||||
|
||||
@@ -169,6 +169,8 @@ import type { ProjectRef, SessionRef, WorktreeRef } from "./agent-manager/projec
|
||||
import { indexingConsentStore, registeredProjects } from "./indexing-consent"
|
||||
import { fetchKiloEmbeddingModelCatalog } from "@kilocode/kilo-gateway"
|
||||
import { fetchImageModels } from "./image-generation/models"
|
||||
import { fetchSpeechToTextModels } from "./speech-to-text/catalog"
|
||||
import { SPEECH_TO_TEXT_MODELS } from "./speech-to-text/models"
|
||||
import { stopSessionProcesses } from "./kilo-provider/background-process"
|
||||
import { sandboxDefault, sandboxSessionMetadata } from "./shared/sandbox-session"
|
||||
import {
|
||||
@@ -1021,6 +1023,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
exportTranscript: (sessionID) => 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<void> {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ type Ctx = {
|
||||
exportTranscript: (sessionID: string) => Promise<void>
|
||||
copy: (text: string) => PromiseLike<void>
|
||||
openSessions: (ids: string[]) => void
|
||||
speechToTextModels: () => Promise<void>
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -15,6 +15,7 @@ export interface AuthContext {
|
||||
disposeGlobal(): Promise<void>
|
||||
fetchAndSendProviders(): Promise<void>
|
||||
fetchAndSendAgents(): Promise<void>
|
||||
fetchAndSendSpeechToTextModels(): Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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. */
|
||||
|
||||
@@ -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<SpeechToTextCatalogResult> {
|
||||
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<string, unknown>
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"version": "7.4.18",
|
||||
"version": "7.4.20",
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"peerDependencies": {}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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("")
|
||||
})
|
||||
})
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
})
|
||||
@@ -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}
|
||||
/>
|
||||
</Show>
|
||||
<Show when={!multiProject()}>
|
||||
|
||||
@@ -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<DiffPanelProps> = (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")
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -41,6 +41,7 @@ interface Props {
|
||||
t: LanguageContextValue["t"]
|
||||
onSearchRef: (ref: SidebarSearchMenuRef) => void
|
||||
onShortcuts: () => void
|
||||
shortcutMap?: () => Map<string, number>
|
||||
}
|
||||
|
||||
export const ProjectList: Component<Props> = (props) => {
|
||||
@@ -222,6 +223,7 @@ export const ProjectList: Component<Props> = (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> = (props) => {
|
||||
onSelectSession={(projectId, sessionId) => select({ projectId, kind: "session", sessionId })}
|
||||
onNewWorktree={newWorktree}
|
||||
onDefaultBranch={defaultBranch}
|
||||
shortcutMap={props.shortcutMap}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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<string, string>
|
||||
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<string, number>
|
||||
}
|
||||
|
||||
/** Permanent real sidebar body for one expanded project. */
|
||||
@@ -99,9 +104,23 @@ export const ProjectSidebarBody: Component<Props> = (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<string, unknown>) =>
|
||||
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) => {
|
||||
<WorktreeItem
|
||||
worktree={worktree}
|
||||
sidebarId={`${props.project.id}:${worktree.id}`}
|
||||
shortcut={props.shortcutMap?.().get(`${props.project.id}:wt:${worktree.id}`)}
|
||||
label={worktree.label || label()}
|
||||
subtitle={worktree.label ? (worktree.label !== worktree.branch ? worktree.branch : undefined) : subtitle()}
|
||||
active={active() && props.selection === worktree.id}
|
||||
@@ -224,6 +244,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> = (props) => {
|
||||
<path d="M10 13.5V16.5" stroke="currentColor" />
|
||||
</svg>
|
||||
</Show>
|
||||
<Show when={props.shortcutMap?.().get(`${props.project.id}:local`)}>
|
||||
{(shortcut) => (
|
||||
<span class="am-shortcut-badge">
|
||||
{isMac ? "⌘" : "Ctrl+"}
|
||||
{shortcut()}
|
||||
</span>
|
||||
)}
|
||||
</Show>
|
||||
<div class="am-local-text">
|
||||
<span class="am-local-label">{props.t("agentManager.local")}</span>
|
||||
<Show when={props.local?.branch}>
|
||||
|
||||
@@ -48,7 +48,7 @@ export const ProjectsSection: Component<ProjectsSectionProps> = (props) => (
|
||||
{(id) => {
|
||||
const project = () => props.projects.find((item) => item.id === id)!
|
||||
return (
|
||||
<div class="am-project" classList={{ "am-project-active": project().active }}>
|
||||
<div class="am-project">
|
||||
<div class="am-project-item" data-project-id={project().id}>
|
||||
<button
|
||||
class="am-project-main"
|
||||
|
||||
@@ -241,25 +241,12 @@ html[data-theme="kilo-vscode"]
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.am-project::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 6px auto 6px 0;
|
||||
width: 2px;
|
||||
border-radius: 0 2px 2px 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.am-project-active::before {
|
||||
background: var(--border-interactive-base);
|
||||
}
|
||||
|
||||
.am-project-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
min-height: 34px;
|
||||
padding: 6px 8px 6px 12px;
|
||||
padding: 6px 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { isTextControl } from "../src/utils/focus"
|
||||
|
||||
const OPTION = '[data-component="question-dock"] button[data-slot="question-option"]'
|
||||
|
||||
/** Keep an active editor, such as the worktree rename input, in control. */
|
||||
export const preservesTextFocus = (active: Element | null): boolean =>
|
||||
active !== null && isTextControl(active) && !active.classList.contains("prompt-input")
|
||||
|
||||
export function createChatFocus(deps: {
|
||||
term: () => string | undefined
|
||||
history: () => boolean
|
||||
@@ -7,6 +13,7 @@ export function createChatFocus(deps: {
|
||||
}) {
|
||||
const focus = (force: boolean) => {
|
||||
if ((!force && !document.hasFocus()) || deps.term() || deps.history() || deps.review()) return
|
||||
if (preservesTextFocus(document.activeElement)) return
|
||||
if (!force && document.activeElement?.matches('[role="tab"]')) return
|
||||
if (!force && document.activeElement?.closest('[data-component="question-dock"]')) return
|
||||
if (focusQuestionOption()) return
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* Returns the action to take: select a session by ID, go to local, or do nothing.
|
||||
*/
|
||||
|
||||
import { sortWorktrees } from "./section-helpers"
|
||||
|
||||
/** Sentinel value for the local repo selection. */
|
||||
export const LOCAL = "local" as const
|
||||
|
||||
@@ -137,8 +139,8 @@ export function focusChatSearch(reset: { history(v: boolean): void; review(v: bo
|
||||
* Multi-project navigation.
|
||||
*
|
||||
* In multi-project mode the sidebar shows an accordion of projects; each
|
||||
* expanded project renders its own Local item, worktrees (sections first,
|
||||
* then ungrouped), and an unassigned-sessions list. Keyboard previous/next
|
||||
* expanded project renders its own Local item, ungrouped worktrees, section
|
||||
* members, and an unassigned-sessions list. Keyboard previous/next
|
||||
* and numeric shortcuts must traverse every expanded project in visual
|
||||
* order, not just the active one.
|
||||
*
|
||||
@@ -160,7 +162,9 @@ export interface NavEntry {
|
||||
export interface ProjectNavInput {
|
||||
id: string
|
||||
expanded: boolean
|
||||
worktrees: { id: string; sectionId?: string }[]
|
||||
worktrees: { id: string; sectionId?: string; groupId?: string }[]
|
||||
/** Persisted top-level order containing worktree and section IDs. */
|
||||
worktreeOrder?: string[]
|
||||
sections: { id: string; collapsed: boolean }[]
|
||||
sessionsCollapsed: boolean
|
||||
/** Visible unassigned (root, no worktree) sessions in render order. */
|
||||
@@ -174,10 +178,10 @@ export const sessionNavId = (projectId: string, sessionId: string) => `${project
|
||||
/**
|
||||
* Build one global visual order across expanded projects.
|
||||
*
|
||||
* For each expanded project (in input order): Local, then worktrees in the
|
||||
* order the multi-project body renders them (each non-collapsed section's
|
||||
* members, then ungrouped), then visible unassigned sessions (when the
|
||||
* sessions section is not collapsed). Collapsed projects contribute nothing.
|
||||
* For each expanded project (in input order): Local, then ungrouped worktrees,
|
||||
* then members of each non-collapsed section in top-level order, then visible
|
||||
* unassigned sessions. This matches `buildTopLevelItems` and the project body.
|
||||
* Collapsed projects contribute nothing.
|
||||
*/
|
||||
export function buildProjectNavOrder(projects: ProjectNavInput[]): NavEntry[] {
|
||||
const order: NavEntry[] = []
|
||||
@@ -185,19 +189,28 @@ export function buildProjectNavOrder(projects: ProjectNavInput[]): NavEntry[] {
|
||||
if (!p.expanded) continue
|
||||
const pid = p.id
|
||||
order.push({ id: localNavId(pid), target: { projectId: pid, kind: "local" } })
|
||||
for (const sec of p.sections) {
|
||||
const worktrees = sortWorktrees(p.worktrees, p.worktreeOrder ?? [])
|
||||
const rank = new Map((p.worktreeOrder ?? []).map((id, index) => [id, index] as const))
|
||||
const ungrouped = worktrees.filter((w) => !w.sectionId)
|
||||
if (p.sections.length > 0) {
|
||||
ungrouped.sort(
|
||||
(a, b) => (rank.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (rank.get(b.id) ?? Number.MAX_SAFE_INTEGER),
|
||||
)
|
||||
}
|
||||
const secs = [...p.sections].sort(
|
||||
(a, b) => (rank.get(a.id) ?? Number.MAX_SAFE_INTEGER) - (rank.get(b.id) ?? Number.MAX_SAFE_INTEGER),
|
||||
)
|
||||
for (const w of ungrouped) {
|
||||
order.push({ id: worktreeNavId(pid, w.id), target: { projectId: pid, kind: "worktree", worktreeId: w.id } })
|
||||
}
|
||||
for (const sec of secs) {
|
||||
if (sec.collapsed) continue
|
||||
for (const w of p.worktrees) {
|
||||
for (const w of worktrees) {
|
||||
if (w.sectionId === sec.id) {
|
||||
order.push({ id: worktreeNavId(pid, w.id), target: { projectId: pid, kind: "worktree", worktreeId: w.id } })
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const w of p.worktrees) {
|
||||
if (!w.sectionId) {
|
||||
order.push({ id: worktreeNavId(pid, w.id), target: { projectId: pid, kind: "worktree", worktreeId: w.id } })
|
||||
}
|
||||
}
|
||||
if (!p.sessionsCollapsed) {
|
||||
for (const s of p.unassigned) {
|
||||
order.push({ id: sessionNavId(pid, s.id), target: { projectId: pid, kind: "session", sessionId: s.id } })
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { adjacentHint } from "./navigate"
|
||||
import { buildSidebarOrder } from "./section-helpers"
|
||||
|
||||
export function projectSidebarOrder(...args: Parameters<typeof buildSidebarOrder>): string[] {
|
||||
return buildSidebarOrder(...args).map((item) => item.id)
|
||||
}
|
||||
|
||||
export function projectAdjacentHint(
|
||||
projectId: string,
|
||||
activeProjectId: string | undefined,
|
||||
itemId: string,
|
||||
activeId: string | undefined,
|
||||
flatIds: string[],
|
||||
prev: string,
|
||||
next: string,
|
||||
): string {
|
||||
if (projectId !== activeProjectId) return ""
|
||||
return adjacentHint(itemId, activeId, flatIds, prev, next)
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import type { AgentManagerStateMessage, AgentProjectSnapshot, ProjectSessionInfo
|
||||
*
|
||||
* Handles previous/next (⌘⌥↑/↓) and numeric-shortcut (⌘1-9) navigation across
|
||||
* the sidebar. In multi-project mode it builds one global visual order across
|
||||
* every expanded project — Local, worktrees (sections first, then ungrouped),
|
||||
* every expanded project — Local, ungrouped worktrees, then section members,
|
||||
* and visible unassigned sessions — using stable project-qualified composite
|
||||
* ids, and activates each target with a single atomic
|
||||
* `agentManager.activateSelection` dispatch. In single-project mode it keeps
|
||||
@@ -48,6 +48,31 @@ export interface ProjectNav {
|
||||
jump: (index: number) => void
|
||||
}
|
||||
|
||||
/** Build the same global order used by keyboard navigation and shortcut badges. */
|
||||
export function buildProjectNavEntries(
|
||||
projects: AgentProjectSnapshot[],
|
||||
states: Record<string, AgentManagerStateMessage>,
|
||||
sessions: Record<string, ProjectSessionInfo[]>,
|
||||
): NavEntry[] {
|
||||
return buildProjectNavOrder(
|
||||
projects.map((p) => {
|
||||
const st = states[p.id]
|
||||
if (!st) {
|
||||
return { id: p.id, expanded: false, worktrees: [], sections: [], sessionsCollapsed: false, unassigned: [] }
|
||||
}
|
||||
return {
|
||||
id: p.id,
|
||||
expanded: p.expanded,
|
||||
worktrees: (st.worktrees ?? []).map((w) => ({ id: w.id, sectionId: w.sectionId, groupId: w.groupId })),
|
||||
worktreeOrder: st.worktreeOrder,
|
||||
sections: (st.sections ?? []).map((s) => ({ id: s.id, collapsed: s.collapsed })),
|
||||
sessionsCollapsed: st.sessionsCollapsed === true,
|
||||
unassigned: (sessions[p.id] ?? []).filter((s) => s.worktreeId === null).map((s) => ({ id: s.id })),
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/** DOM selector for the sidebar element backing a nav target. */
|
||||
export const navSelector = (target: NavTarget): string => {
|
||||
if (target.kind === "local") return `[data-sidebar-id="${target.projectId}:local"]`
|
||||
@@ -74,24 +99,7 @@ export function createProjectNav(
|
||||
if (!deps.multiProject()) return []
|
||||
const states = deps.states()
|
||||
const live = deps.sessions()
|
||||
return buildProjectNavOrder(
|
||||
deps.projects().map((p) => {
|
||||
const st = states[p.id]
|
||||
// Until a project's state payload arrives its body shows a spinner
|
||||
// (no Local/worktrees/sessions) — exclude it from the nav order.
|
||||
if (!st) {
|
||||
return { id: p.id, expanded: false, worktrees: [], sections: [], sessionsCollapsed: false, unassigned: [] }
|
||||
}
|
||||
return {
|
||||
id: p.id,
|
||||
expanded: p.expanded,
|
||||
worktrees: (st.worktrees ?? []).map((w) => ({ id: w.id, sectionId: w.sectionId })),
|
||||
sections: (st.sections ?? []).map((s) => ({ id: s.id, collapsed: s.collapsed })),
|
||||
sessionsCollapsed: st.sessionsCollapsed === true,
|
||||
unassigned: (live[p.id] ?? []).filter((s) => s.worktreeId === null).map((s) => ({ id: s.id })),
|
||||
}
|
||||
}),
|
||||
)
|
||||
return buildProjectNavEntries(deps.projects(), states, live)
|
||||
})
|
||||
|
||||
const currentId = createMemo((): string | undefined => {
|
||||
|
||||
@@ -10,11 +10,11 @@ export type TopLevelItem = { kind: "section"; section: SectionState } | { kind:
|
||||
export type SidebarItem = { type: "local" | "wt" | "session"; id: string }
|
||||
|
||||
/** Apply persisted order while keeping multi-version worktrees adjacent. */
|
||||
export function sortWorktrees(all: WorktreeState[], order: string[]): WorktreeState[] {
|
||||
export function sortWorktrees<T extends { id: string; groupId?: string }>(all: T[], order: string[]): T[] {
|
||||
const ordered = applyTabOrder(all, order)
|
||||
if (ordered.length === 0) return []
|
||||
|
||||
const groups = new Map<string, WorktreeState[]>()
|
||||
const groups = new Map<string, T[]>()
|
||||
for (const wt of ordered) {
|
||||
if (!wt.groupId) continue
|
||||
const group = groups.get(wt.groupId) ?? []
|
||||
@@ -22,7 +22,7 @@ export function sortWorktrees(all: WorktreeState[], order: string[]): WorktreeSt
|
||||
groups.set(wt.groupId, group)
|
||||
}
|
||||
|
||||
const result: WorktreeState[] = []
|
||||
const result: T[] = []
|
||||
const placed = new Set<string>()
|
||||
for (const wt of ordered) {
|
||||
if (placed.has(wt.id)) continue
|
||||
@@ -145,7 +145,7 @@ export function buildSidebarOrder(
|
||||
}
|
||||
|
||||
/** Build a map from sidebar item id → 1-based shortcut number (1 for LOCAL, 2+ for worktrees). */
|
||||
export function buildShortcutMap(order: SidebarItem[]): Map<string, number> {
|
||||
export function buildShortcutMap(order: { id: string }[]): Map<string, number> {
|
||||
const map = new Map<string, number>()
|
||||
for (let i = 0; i < order.length && i < 9; i++) {
|
||||
map.set(order[i]!.id, i + 1)
|
||||
|
||||
@@ -158,39 +158,6 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
})
|
||||
const fit = new FitAddon()
|
||||
term.loadAddon(fit)
|
||||
// Clickable URLs in terminal output (Cmd/Ctrl+click to open).
|
||||
// WebLinksAddon's default handler calls `window.open`, which VS Code
|
||||
// webviews intercept and silently drop — so we pass an explicit
|
||||
// handler that posts an `openExternal` message. The message falls
|
||||
// through `AgentManagerProvider.onMessage` to the underlying
|
||||
// `KiloProvider.handleWebviewMessage` path which already calls
|
||||
// `vscode.env.openExternal` for sidebar + settings links.
|
||||
term.loadAddon(
|
||||
new WebLinksAddon((_event, url) => {
|
||||
vscode.postMessage({ type: "openExternal", url })
|
||||
}),
|
||||
)
|
||||
// OSC 52 clipboard support — lets shell programs (tmux, neovim, etc.)
|
||||
// copy to the system clipboard via escape sequences. Writes always
|
||||
// work in the webview; reads require the clipboard-read permission,
|
||||
// which VS Code does not grant by default, so paste-from-escape
|
||||
// silently falls back to no-op. Acceptable trade-off.
|
||||
term.loadAddon(new ClipboardAddon())
|
||||
// Unicode 15 grapheme-aware width tables. Fixes cell width for
|
||||
// emoji introduced in Unicode 12-15 (🫠 melting face, 🫡 salute,
|
||||
// 🧌 troll, and ~400 others) plus ZWJ grapheme sequences like
|
||||
// 👨👩👧👦 and 🏳️🌈. The older `@xterm/addon-unicode11` (which VS
|
||||
// Code's integrated terminal still uses) stops at Unicode 11
|
||||
// (2018), leaving all post-2020 emoji rendered with wrong width —
|
||||
// the canvas cuts them off in the DOM renderer and cursor math
|
||||
// drifts by one cell per emoji. VS Code hides this visually with
|
||||
// WebGL; in a webview we don't have that fallback, so we fix it
|
||||
// at the buffer-width layer instead. Addon is marked
|
||||
// "experimental" in its README but has been stable on npm since
|
||||
// 2023, is shipped by the same maintainer as the core xterm.js
|
||||
// package, and has no open bugs as of v0.4.0.
|
||||
term.loadAddon(new UnicodeGraphemesAddon())
|
||||
term.unicode.activeVersion = "15-graphemes"
|
||||
term.open(host)
|
||||
// Fit on the next frame — `host` might still have 0px dimensions
|
||||
// during the initial layout pass otherwise.
|
||||
@@ -232,6 +199,8 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
let fallbackTimer: ReturnType<typeof setTimeout> | undefined
|
||||
let streamed = false
|
||||
let socketEnded = false
|
||||
let frame: number | undefined
|
||||
let deferred: number | undefined
|
||||
// The failure line must not depend on event ordering: the stream can
|
||||
// close before the exited snapshot lands (fast failures), or stay open
|
||||
// when a background child outlives the script. Write it exactly once,
|
||||
@@ -358,6 +327,34 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
}
|
||||
const disposeData = term.onData(send)
|
||||
open(props.wsUrl)
|
||||
|
||||
// These addons are not needed to paint the initial prompt. Defer them
|
||||
// until after the first frame so their startup work, especially the
|
||||
// Unicode 15 width tables, does not delay the shell connection.
|
||||
const loadAddons = () => {
|
||||
deferred = undefined
|
||||
if (closed) return
|
||||
|
||||
// Clickable URLs in terminal output (Cmd/Ctrl+click to open).
|
||||
// WebLinksAddon's default handler calls `window.open`, which VS Code
|
||||
// webviews intercept and silently drop, so post an explicit message.
|
||||
term.loadAddon(
|
||||
new WebLinksAddon((_event, url) => {
|
||||
vscode.postMessage({ type: "openExternal", url })
|
||||
}),
|
||||
)
|
||||
// OSC 52 clipboard support for shell programs such as tmux and neovim.
|
||||
term.loadAddon(new ClipboardAddon())
|
||||
// Use grapheme-aware width tables for newer emoji and ZWJ sequences.
|
||||
term.loadAddon(new UnicodeGraphemesAddon())
|
||||
term.unicode.activeVersion = "15-graphemes"
|
||||
term.refresh(0, Math.max(0, term.rows - 1))
|
||||
}
|
||||
frame = requestAnimationFrame(() => {
|
||||
frame = undefined
|
||||
deferred = requestAnimationFrame(loadAddons)
|
||||
})
|
||||
|
||||
const restarted = (url: string) => {
|
||||
open(url)
|
||||
}
|
||||
@@ -525,6 +522,8 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
onCleanup(() => {
|
||||
closed = true
|
||||
if (pendingFrame !== null) cancelAnimationFrame(pendingFrame)
|
||||
if (frame !== undefined) cancelAnimationFrame(frame)
|
||||
if (deferred !== undefined) cancelAnimationFrame(deferred)
|
||||
document.removeEventListener("visibilitychange", onVisibilityChange)
|
||||
window.removeEventListener("focus", onWindowFocus)
|
||||
host.removeEventListener("focusin", onFocusIn)
|
||||
|
||||
@@ -24,6 +24,7 @@ import type { DiffViewerNotice } from "../src/types/messages/extension-messages"
|
||||
import { DiffPickerHeader } from "./DiffPickerHeader"
|
||||
import { BaseBranchPicker } from "./BaseBranchPicker"
|
||||
import { SpeechToTextPrewarm } from "../src/components/speech-to-text/SpeechToTextPrewarm"
|
||||
import { SpeechToTextModelsProvider } from "../src/context/speech-to-text-models"
|
||||
|
||||
const NOTICE_KEYS: Record<DiffViewerNotice, string> = {
|
||||
"snapshots-disabled": "diffViewer.notice.snapshotsDisabled",
|
||||
@@ -306,8 +307,10 @@ export const DiffViewerApp: Component = () => {
|
||||
<ServerProvider>
|
||||
<ProviderProvider>
|
||||
<ConfigProvider>
|
||||
<SpeechToTextPrewarm />
|
||||
<DiffViewerShell />
|
||||
<SpeechToTextModelsProvider>
|
||||
<SpeechToTextPrewarm />
|
||||
<DiffViewerShell />
|
||||
</SpeechToTextModelsProvider>
|
||||
</ConfigProvider>
|
||||
</ProviderProvider>
|
||||
</ServerProvider>
|
||||
|
||||
@@ -27,6 +27,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 { FileTree } from "./FileTree"
|
||||
import { treeOrder } from "./file-tree-utils"
|
||||
import { getDirectory, getFilename, lineCount, sanitizeReviewComments, type ReviewComment } from "./review-comments"
|
||||
@@ -113,8 +114,9 @@ export const FullScreenDiffView: Component<FullScreenDiffViewProps> = (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")
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Tooltip } from "@kilocode/kilo-ui/tooltip"
|
||||
import { FileIcon } from "@kilocode/kilo-ui/file-icon"
|
||||
import { Icon } from "@kilocode/kilo-ui/icon"
|
||||
import { showToast } from "@kilocode/kilo-ui/toast"
|
||||
import { isTextControl } from "../../utils/focus"
|
||||
import { useSession } from "../../context/session"
|
||||
import { useLocalTabs } from "../../context/local-tabs"
|
||||
import { useServer } from "../../context/server"
|
||||
@@ -34,6 +35,7 @@ import { hasGitChangesMention } from "../../hooks/git-changes-context-utils"
|
||||
import { useSlashCommand } from "../../hooks/useSlashCommand"
|
||||
import { useGhostText } from "../../hooks/useGhostText"
|
||||
import { useSpeechToText } from "../speech-to-text/useSpeechToText"
|
||||
import { useSpeechToTextModels } from "../../context/speech-to-text-models"
|
||||
import { createSpeechShortcut } from "../speech-to-text/shortcut"
|
||||
import { useImageAttachments, type ImageAttachment } from "../../hooks/useImageAttachments"
|
||||
import { convertToMentionPath } from "../../utils/path-mentions"
|
||||
@@ -348,6 +350,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
const ghost = useGhostText(vscode, text, () => server.isConnected())
|
||||
const speech = useSpeechToText(vscode, server, language)
|
||||
const speechModels = useSpeechToTextModels()
|
||||
|
||||
const replaceReviewComments = (next: ReviewComment[]) => {
|
||||
setReviewComments(next)
|
||||
@@ -416,8 +419,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const onFocusPrompt = (event: Event) => {
|
||||
const defer = () =>
|
||||
event instanceof CustomEvent && event.detail?.deferFocusToQuestion && props.deferFocusToQuestion?.()
|
||||
const ownsFocus = () => {
|
||||
const active = document.activeElement
|
||||
return active !== textareaRef && isTextControl(active)
|
||||
}
|
||||
const focus = () => {
|
||||
if (defer()) return
|
||||
if (defer() || ownsFocus()) return
|
||||
const ref = textareaRef
|
||||
if (!ref) return
|
||||
ref.focus({ preventScroll: true })
|
||||
@@ -425,7 +432,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
focus()
|
||||
if (!(event instanceof CustomEvent) || !event.detail?.restore) return
|
||||
const restore = () => {
|
||||
if (defer()) return
|
||||
if (defer() || ownsFocus()) return
|
||||
window.focus()
|
||||
focus()
|
||||
}
|
||||
@@ -511,7 +518,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
)
|
||||
const isDisabled = () => !server.isConnected()
|
||||
const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates())
|
||||
const speechModel = () => selectedSpeechToTextModel(config())
|
||||
const speechModel = () => selectedSpeechToTextModel(config(), speechModels.models())
|
||||
const hasInput = () => text().trim().length > 0 || imageAttach.images().length > 0 || reviewComments().length > 0
|
||||
const canSend = () =>
|
||||
!isDisabled() &&
|
||||
|
||||
@@ -7,13 +7,14 @@ import { useConfig } from "../../context/config"
|
||||
import { useLanguage } from "../../context/language"
|
||||
import { useProvider } from "../../context/provider"
|
||||
import { useSession } from "../../context/session"
|
||||
import { useSpeechToTextModels } from "../../context/speech-to-text-models"
|
||||
import { parseModelString } from "../../../../src/shared/provider-model"
|
||||
import { ModelSelectorBase } from "../shared/ModelSelector"
|
||||
import { ThinkingSelectorBase } from "../shared/ThinkingSelector"
|
||||
import SettingsRow from "./SettingsRow"
|
||||
import { DEFAULT_SPEECH_TO_TEXT_MODEL } from "../../../../src/speech-to-text/models"
|
||||
import { hasSpeechToTextAccess, selectedSpeechToTextModel } from "../speech-to-text/availability"
|
||||
import { SPEECH_TO_TEXT_MODEL_OPTIONS } from "../speech-to-text/model-selector"
|
||||
import { speechToTextModelOptions } from "../speech-to-text/model-selector"
|
||||
import { AUTOCOMPLETE_SELECTOR_MODELS, getAutocompleteSelection } from "./autocomplete-model-selector"
|
||||
|
||||
const ModelsTab: Component = () => {
|
||||
@@ -21,6 +22,7 @@ const ModelsTab: Component = () => {
|
||||
const language = useLanguage()
|
||||
const provider = useProvider()
|
||||
const session = useSession()
|
||||
const speechModels = useSpeechToTextModels()
|
||||
|
||||
const autocompleteProvider = () => {
|
||||
const v = settings()["autocomplete.provider"]
|
||||
@@ -42,8 +44,9 @@ const ModelsTab: Component = () => {
|
||||
}
|
||||
|
||||
const subagentModel = createMemo(() => parseModelString(config().subagent_model ?? undefined))
|
||||
const speechModel = createMemo(() => selectedSpeechToTextModel(config()))
|
||||
const speechOption = createMemo(() => SPEECH_TO_TEXT_MODEL_OPTIONS.find((item) => item.value === speechModel()))
|
||||
const speechModel = createMemo(() => selectedSpeechToTextModel(config(), speechModels.models()))
|
||||
const speechOptions = createMemo(() => speechToTextModelOptions(speechModels.models()))
|
||||
const speechOption = createMemo(() => speechOptions().find((item) => item.value === speechModel()))
|
||||
const kiloReady = createMemo(() => hasSpeechToTextAccess(config(), provider.authStates()))
|
||||
const variantKey = createMemo(() => config().subagent_model ?? undefined)
|
||||
const subagentVariants = createMemo(() => Object.keys(provider.findModel(subagentModel())?.variants ?? {}))
|
||||
@@ -190,7 +193,7 @@ const ModelsTab: Component = () => {
|
||||
inactive={kiloReady()}
|
||||
>
|
||||
<Select
|
||||
options={SPEECH_TO_TEXT_MODEL_OPTIONS}
|
||||
options={speechOptions()}
|
||||
current={speechOption()}
|
||||
value={(item) => item.value}
|
||||
label={(item) => `${item.label} (${item.provider})`}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { KILO_PROVIDER_ID } from "../../../../src/shared/provider-model"
|
||||
import { getSpeechToTextModel } from "../../../../src/speech-to-text/models"
|
||||
import {
|
||||
DEFAULT_SPEECH_TO_TEXT_MODEL,
|
||||
SPEECH_TO_TEXT_MODELS,
|
||||
type SpeechToTextModelDef,
|
||||
} from "../../../../src/speech-to-text/models"
|
||||
|
||||
type Cfg = {
|
||||
enabled_providers?: string[]
|
||||
@@ -21,6 +25,10 @@ export function canUseSpeechToText(cfg: Cfg, auth: Readonly<Record<string, AuthS
|
||||
return hasSpeechToTextAccess(cfg, auth)
|
||||
}
|
||||
|
||||
export function selectedSpeechToTextModel(cfg: Cfg): string {
|
||||
return getSpeechToTextModel(cfg.experimental?.speech_to_text_model).id
|
||||
export function selectedSpeechToTextModel(
|
||||
cfg: Cfg,
|
||||
models: readonly SpeechToTextModelDef[] = SPEECH_TO_TEXT_MODELS,
|
||||
): string {
|
||||
const id = cfg.experimental?.speech_to_text_model
|
||||
return models.find((model) => model.id === id)?.id ?? models[0]?.id ?? DEFAULT_SPEECH_TO_TEXT_MODEL.id
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SPEECH_TO_TEXT_MODELS } from "../../../../src/speech-to-text/models"
|
||||
import type { SpeechToTextModelDef } from "../../../../src/speech-to-text/models"
|
||||
|
||||
export type SpeechToTextModelOption = {
|
||||
value: string
|
||||
@@ -6,8 +6,10 @@ export type SpeechToTextModelOption = {
|
||||
provider: string
|
||||
}
|
||||
|
||||
export const SPEECH_TO_TEXT_MODEL_OPTIONS: SpeechToTextModelOption[] = SPEECH_TO_TEXT_MODELS.map((model) => ({
|
||||
value: model.id,
|
||||
label: model.label,
|
||||
provider: model.provider,
|
||||
}))
|
||||
export function speechToTextModelOptions(models: readonly SpeechToTextModelDef[]): SpeechToTextModelOption[] {
|
||||
return models.map((model) => ({
|
||||
value: model.id,
|
||||
label: model.label,
|
||||
provider: model.provider,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { NotificationsProvider } from "./notifications"
|
||||
import { FeedbackProvider } from "./feedback"
|
||||
import { KiloEmbeddingModelsProvider } from "./kilo-embedding-models"
|
||||
import { ImageModelsProvider } from "./image-models"
|
||||
import { SpeechToTextModelsProvider } from "./speech-to-text-models"
|
||||
import { SpeechToTextPrewarm } from "../components/speech-to-text/SpeechToTextPrewarm"
|
||||
|
||||
type MermaidImageEvent = CustomEvent<{ dataUrl: string; filename: string }>
|
||||
@@ -77,9 +78,11 @@ const Session: ParentComponent = (props) => (
|
||||
<IndexingProvider>
|
||||
<KiloEmbeddingModelsProvider>
|
||||
<ImageModelsProvider>
|
||||
<NotificationsProvider>
|
||||
<SessionProvider>{props.children}</SessionProvider>
|
||||
</NotificationsProvider>
|
||||
<SpeechToTextModelsProvider>
|
||||
<NotificationsProvider>
|
||||
<SessionProvider>{props.children}</SessionProvider>
|
||||
</NotificationsProvider>
|
||||
</SpeechToTextModelsProvider>
|
||||
</ImageModelsProvider>
|
||||
</KiloEmbeddingModelsProvider>
|
||||
</IndexingProvider>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { createContext, createSignal, onCleanup, useContext, type Accessor, type ParentComponent } from "solid-js"
|
||||
import { SPEECH_TO_TEXT_MODELS, type SpeechToTextModelDef } from "../../../src/speech-to-text/models"
|
||||
import { useVSCode } from "./vscode"
|
||||
import type { ExtensionMessage } from "../types/messages"
|
||||
|
||||
export type SpeechToTextModelsContextValue = {
|
||||
models: Accessor<readonly SpeechToTextModelDef[]>
|
||||
}
|
||||
|
||||
export const SpeechToTextModelsContext = createContext<SpeechToTextModelsContextValue>({
|
||||
models: () => SPEECH_TO_TEXT_MODELS,
|
||||
})
|
||||
|
||||
export const SpeechToTextModelsProvider: ParentComponent = (props) => {
|
||||
const vscode = useVSCode()
|
||||
const [models, setModels] = createSignal<readonly SpeechToTextModelDef[]>([...SPEECH_TO_TEXT_MODELS])
|
||||
const request = () => vscode.postMessage({ type: "requestSpeechToTextModels" })
|
||||
const unsubscribe = vscode.onMessage((message: ExtensionMessage) => {
|
||||
if (message.type !== "speechToTextModelsLoaded") return
|
||||
setModels(message.models)
|
||||
})
|
||||
|
||||
request()
|
||||
const retry = setTimeout(request, 3000)
|
||||
onCleanup(() => clearTimeout(retry))
|
||||
onCleanup(unsubscribe)
|
||||
|
||||
return <SpeechToTextModelsContext.Provider value={{ models }}>{props.children}</SpeechToTextModelsContext.Provider>
|
||||
}
|
||||
|
||||
export function useSpeechToTextModels(): SpeechToTextModelsContextValue {
|
||||
return useContext(SpeechToTextModelsContext)
|
||||
}
|
||||
@@ -180,6 +180,15 @@ const writePatch = [
|
||||
'+import { AssistantMessage } from "../components/chat/AssistantMessage"',
|
||||
].join("\n")
|
||||
|
||||
const tailPatch = [
|
||||
"===================================================================",
|
||||
"--- packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx",
|
||||
"+++ packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx",
|
||||
"@@ -1,1 +1,1 @@",
|
||||
"-const old = true",
|
||||
"+const next = true",
|
||||
].join("\n")
|
||||
|
||||
const blockQuestions: QuestionRequest[] = [
|
||||
{
|
||||
id: "matrix-question-request",
|
||||
@@ -465,7 +474,7 @@ const blocks: SDKPart[] = [
|
||||
"*** Begin Patch\n*** Update File: packages/kilo-ui/src/components/message-part.css\n@@\n-gap: 4px;\n+gap: 8px;\n*** End Patch",
|
||||
},
|
||||
output: "",
|
||||
title: "Patch two files",
|
||||
title: "Patch three files",
|
||||
metadata: {
|
||||
files: [
|
||||
{
|
||||
@@ -486,6 +495,15 @@ const blocks: SDKPart[] = [
|
||||
additions: 1,
|
||||
deletions: 0,
|
||||
},
|
||||
{
|
||||
filePath: "/project/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx",
|
||||
relativePath: "packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx",
|
||||
type: "update",
|
||||
patch: tailPatch,
|
||||
diff: tailPatch,
|
||||
additions: 1,
|
||||
deletions: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
time: { start: stamp - 2700, end: stamp - 2400 },
|
||||
|
||||
@@ -19,6 +19,7 @@ import type { PermissionRequest } from "./permissions"
|
||||
import type { AnacondaDesktopExtensionMessage } from "../../../../src/shared/anaconda-desktop-messages"
|
||||
import type { QuestionRequest, SuggestionRequest, TodoItem } from "./questions"
|
||||
import type { ModelSelection, Provider, ProviderAuthState } from "./providers"
|
||||
import type { SpeechToTextModelDef } from "../../../../src/speech-to-text/models"
|
||||
import type { AgentInfo, AgentRequirementResult, SkillInfo, SlashCommandInfo } from "./agents"
|
||||
import type {
|
||||
BrowserSettings,
|
||||
@@ -383,6 +384,11 @@ export interface ImageModelsLoadedMessage {
|
||||
models: Array<{ id: string; name: string; description?: string }>
|
||||
}
|
||||
|
||||
export interface SpeechToTextModelsLoadedMessage {
|
||||
type: "speechToTextModelsLoaded"
|
||||
models: SpeechToTextModelDef[]
|
||||
}
|
||||
|
||||
export interface ProvidersLoadedMessage {
|
||||
type: "providersLoaded"
|
||||
providers: Record<string, Provider>
|
||||
@@ -1307,6 +1313,7 @@ export type ExtensionMessage =
|
||||
| ChatSettingsLoadedMessage
|
||||
| KiloEmbeddingModelsLoadedMessage
|
||||
| ImageModelsLoadedMessage
|
||||
| SpeechToTextModelsLoadedMessage
|
||||
| ProvidersLoadedMessage
|
||||
| AgentsLoadedMessage
|
||||
| SkillsLoadedMessage
|
||||
|
||||
@@ -518,6 +518,10 @@ export interface RequestImageModelsMessage {
|
||||
type: "requestImageModels"
|
||||
}
|
||||
|
||||
export interface RequestSpeechToTextModelsMessage {
|
||||
type: "requestSpeechToTextModels"
|
||||
}
|
||||
|
||||
export interface OpenSettingsTabRequest {
|
||||
type: "openSettingsTab"
|
||||
tab: string
|
||||
@@ -1584,6 +1588,7 @@ export type WebviewMessage =
|
||||
| AgentManagerTerminalDestinationSelectedRequest
|
||||
| AgentManagerTerminalResizeRequest
|
||||
| RequestImageModelsMessage
|
||||
| RequestSpeechToTextModelsMessage
|
||||
|
||||
// ============================================
|
||||
// VS Code API type
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const nonText = new Set(["button", "checkbox", "file", "hidden", "image", "radio", "range", "reset", "submit"])
|
||||
|
||||
/** Whether an element owns editable text focus that should not be stolen. */
|
||||
export const isTextControl = (el: Element | null): boolean => {
|
||||
if (!el) return false
|
||||
if (el.tagName === "TEXTAREA" || el.tagName === "SELECT") return true
|
||||
if (el.tagName === "INPUT") return !nonText.has((el as HTMLInputElement).type.toLowerCase())
|
||||
return ("isContentEditable" in el && (el as HTMLElement).isContentEditable) || el.getAttribute("role") === "textbox"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-web-ui",
|
||||
"version": "7.4.18",
|
||||
"version": "7.4.20",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.4.18",
|
||||
"version": "7.4.20",
|
||||
"name": "@opencode-ai/llm",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user