From 69c158f3a7fcb0595acda36f04c4fdc4873ff6c7 Mon Sep 17 00:00:00 2001 From: Truffle Date: Wed, 13 May 2026 07:10:04 +0000 Subject: [PATCH 01/78] fix(vscode): keep custom provider picker open after partial add The picker called setFetchedModels(undefined) unconditionally after "Add Selected", wiping the remaining un-picked models. Users had to re-fetch to add a subset twice in a row. Filter the picked IDs out of fetchedModels and clear the selection. Only close the picker when every fetched model has been added. Refs: #10139 --- .../components/settings/CustomProviderDialog.tsx | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx index d6ddc5254fd..388257e595f 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/CustomProviderDialog.tsx @@ -301,8 +301,18 @@ const CustomProviderDialog = (props: CustomProviderDialogProps) => { merged.map((m) => ({ variants: m.variants.map(() => ({})) })), ) setFetchStatus(language.t("provider.custom.models.fetch.added", { count: String(picked.length) })) - setFetchedModels(undefined) - setSearch("") + + // Keep the picker open with the un-picked models so the user can keep adding. + // Only close the picker when every fetched model has been added. + const pickedIds = new Set(picked.map((m) => m.id)) + const remaining = models.filter((m) => !pickedIds.has(m.id)) + if (remaining.length === 0) { + setFetchedModels(undefined) + setSearch("") + } else { + setFetchedModels(remaining) + setSelected(new Set()) + } } function cancelFetch() { From e511b230ab87c3b1a594a7e1ac12e44a096a813f Mon Sep 17 00:00:00 2001 From: Cooper Gamble Date: Sat, 30 May 2026 01:47:34 +0000 Subject: [PATCH 02/78] fix(cli): serialize Codex OAuth refresh across processes --- .changeset/steady-codex-refresh.md | 5 + .../src/kilocode/provider/codex-refresh.ts | 73 ++++--- packages/opencode/src/plugin/codex.ts | 2 +- .../test/fixture/codex-auth-refresh-worker.ts | 113 +++++++++++ .../test/kilocode/codex-auth-refresh.test.ts | 188 +++++++++++++++++- .../test/kilocode/oauth-branding.test.ts | 1 + packages/opencode/test/plugin/codex.test.ts | 51 +++++ 7 files changed, 399 insertions(+), 34 deletions(-) create mode 100644 .changeset/steady-codex-refresh.md create mode 100644 packages/opencode/test/fixture/codex-auth-refresh-worker.ts diff --git a/.changeset/steady-codex-refresh.md b/.changeset/steady-codex-refresh.md new file mode 100644 index 00000000000..2d8438541e1 --- /dev/null +++ b/.changeset/steady-codex-refresh.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Prevent concurrent Kilo processes from reusing a ChatGPT Codex refresh token. diff --git a/packages/opencode/src/kilocode/provider/codex-refresh.ts b/packages/opencode/src/kilocode/provider/codex-refresh.ts index 81a160a3411..4860bd2ed26 100644 --- a/packages/opencode/src/kilocode/provider/codex-refresh.ts +++ b/packages/opencode/src/kilocode/provider/codex-refresh.ts @@ -1,4 +1,4 @@ -import type { PluginInput } from "@kilocode/plugin" +import { Flock } from "@opencode-ai/core/util/flock" export class CodexAuthExpiredError extends Error { constructor( @@ -25,14 +25,22 @@ type Tokens = { } type Input = { - input: PluginInput + input: { + client: { + auth: { + set: (input: { path: { id: string }; body: Auth }) => Promise + } + } + } getAuth: () => Promise auth: Auth refresh: (refresh: string) => Promise account: (tokens: Tokens) => string | undefined + lock?: Flock.Options } const pending = new Map>() +const lock = "codex-auth-refresh:openai" function valid(auth: Auth) { return auth.access && auth.expires > Date.now() @@ -59,43 +67,48 @@ function recoverable(err: unknown) { } export async function refreshCodexAuth(input: Input) { - const inflight = pending.get(input.auth.refresh) + const token = input.auth.refresh + const inflight = pending.get(token) if (inflight) { const next = await inflight assign(input.auth, next) return next } - const promise = (async () => { - const fresh = await input.getAuth() - const current = oauth(fresh) - if (current && valid(current)) return current + const promise = Flock.withLock( + lock, + async () => { + const fresh = await input.getAuth() + const current = oauth(fresh) + if (current && valid(current)) return current - try { - const base = current && current.refresh !== input.auth.refresh ? current : input.auth - const tokens = await input.refresh(base.refresh) - const id = input.account(tokens) || base.accountId - const next = { - type: "oauth" as const, - refresh: tokens.refresh_token, - access: tokens.access_token, - expires: Date.now() + (tokens.expires_in ?? 3600) * 1000, - ...(id && { accountId: id }), + try { + const base = current && current.refresh !== token ? current : input.auth + const tokens = await input.refresh(base.refresh) + const id = input.account(tokens) || base.accountId + const next = { + type: "oauth" as const, + refresh: tokens.refresh_token, + access: tokens.access_token, + expires: Date.now() + (tokens.expires_in ?? 3600) * 1000, + ...(id && { accountId: id }), + } + await input.input.client.auth.set({ path: { id: "openai" }, body: next }) + return next + } catch (err) { + if (!recoverable(err)) throw err + + const latest = await input.getAuth() + const next = oauth(latest) + if (next && usable(next, token)) return next + + throw new CodexAuthExpiredError() } - await input.input.client.auth.set({ path: { id: "openai" }, body: next }) - return next - } catch (err) { - if (!recoverable(err)) throw err + }, + input.lock, + ).finally(() => pending.delete(token)) - const latest = await input.getAuth() - const next = oauth(latest) - if (next && usable(next, input.auth.refresh)) return next - - throw new CodexAuthExpiredError() - } - })().finally(() => pending.delete(input.auth.refresh)) - - pending.set(input.auth.refresh, promise) + pending.set(token, promise) const next = await promise assign(input.auth, next) return next diff --git a/packages/opencode/src/plugin/codex.ts b/packages/opencode/src/plugin/codex.ts index 65892fa7111..85945a6411c 100644 --- a/packages/opencode/src/plugin/codex.ts +++ b/packages/opencode/src/plugin/codex.ts @@ -145,7 +145,7 @@ async function exchangeCodeForTokens(code: string, redirectUri: string, pkce: Pk async function refreshAccessToken(refreshToken: string): Promise { const response = await fetch(`${ISSUER}/oauth/token`, { method: "POST", - headers: { "Content-Type": "application/x-www-form-urlencoded" }, + headers: { "Content-Type": "application/x-www-form-urlencoded", "User-Agent": `kilo/${InstallationVersion}` }, // kilocode_change body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: refreshToken, diff --git a/packages/opencode/test/fixture/codex-auth-refresh-worker.ts b/packages/opencode/test/fixture/codex-auth-refresh-worker.ts new file mode 100644 index 00000000000..e36515af91f --- /dev/null +++ b/packages/opencode/test/fixture/codex-auth-refresh-worker.ts @@ -0,0 +1,113 @@ +import fs from "fs/promises" +import path from "path" +import z from "zod" + +const Auth = z.object({ + type: z.literal("oauth"), + refresh: z.string(), + access: z.string(), + expires: z.number(), + accountId: z.string().optional(), +}) +type Auth = z.infer + +const Tokens = z.object({ + id_token: z.string(), + access_token: z.string(), + refresh_token: z.string(), + expires_in: z.number().optional(), +}) + +const Msg = z.object({ + root: z.string(), + url: z.string(), + ready: z.string(), + start: z.string(), + lock: z + .object({ + staleMs: z.number(), + timeoutMs: z.number(), + baseDelayMs: z.number(), + maxDelayMs: z.number(), + }) + .optional(), +}) + +function input() { + const raw = process.argv[2] + if (!raw) throw new Error("Missing Codex auth refresh worker input") + return Msg.parse(JSON.parse(raw)) +} + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +async function wait(file: string) { + const stop = Date.now() + 10_000 + while (Date.now() < stop) { + if ( + await fs + .stat(file) + .then(() => true) + .catch(() => false) + ) + return + await sleep(10) + } + throw new Error(`Timed out waiting for file: ${file}`) +} + +async function main() { + const msg = input() + process.env.XDG_DATA_HOME = path.join(msg.root, "share") + process.env.XDG_CACHE_HOME = path.join(msg.root, "cache") + process.env.XDG_CONFIG_HOME = path.join(msg.root, "config") + process.env.XDG_STATE_HOME = path.join(msg.root, "state") + process.env.KILO_TEST_HOME = path.join(msg.root, "home") + + const { Path } = await import("@opencode-ai/core/global") + const { refreshCodexAuth } = await import("../../src/kilocode/provider/codex-refresh") + const file = path.join(Path.data, "auth.json") + const read = async () => { + const data = z.object({ openai: Auth }).parse(JSON.parse(await fs.readFile(file, "utf8"))) + return data.openai + } + const plugin = { + client: { + auth: { + set: async (req: { body: Auth }) => { + await fs.writeFile(file, JSON.stringify({ openai: req.body })) + }, + }, + }, + } + + await fs.writeFile(msg.ready, String(process.pid)) + await wait(msg.start) + const auth = await read() + const next = await refreshCodexAuth({ + input: plugin, + getAuth: read, + auth, + refresh: async (token) => { + const response = await fetch(msg.url, { + method: "POST", + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + body: new URLSearchParams({ refresh_token: token }).toString(), + }) + if (!response.ok) throw new Error(`Token refresh failed: ${response.status}`) + return Tokens.parse(await response.json()) + }, + account: () => undefined, + lock: msg.lock, + }) + + process.stdout.write(JSON.stringify(next)) +} + +await main().catch((err) => { + const text = err instanceof Error ? (err.stack ?? err.message) : String(err) + process.stderr.write(text) + process.exit(1) +}) diff --git a/packages/opencode/test/kilocode/codex-auth-refresh.test.ts b/packages/opencode/test/kilocode/codex-auth-refresh.test.ts index 28ea37c41ae..f1cade1cba3 100644 --- a/packages/opencode/test/kilocode/codex-auth-refresh.test.ts +++ b/packages/opencode/test/kilocode/codex-auth-refresh.test.ts @@ -1,8 +1,11 @@ import { describe, expect, test } from "bun:test" import { CodexAuthExpiredError, refreshCodexAuth } from "../../src/kilocode/provider/codex-refresh" -import type { PluginInput } from "@kilocode/plugin" import { MessageV2 } from "../../src/session/message-v2" import { ProviderID } from "../../src/provider/schema" +import { spawn } from "child_process" +import fs from "fs/promises" +import os from "os" +import path from "path" type Auth = { type: "oauth" @@ -12,6 +15,13 @@ type Auth = { accountId?: string } +type Lock = { + staleMs: number + timeoutMs: number + baseDelayMs: number + maxDelayMs: number +} + const expired = (): Auth => ({ type: "oauth", access: "old-access", @@ -19,7 +29,10 @@ const expired = (): Auth => ({ expires: 0, }) -function plugin(persist: (auth: Auth) => void): PluginInput { +const root = path.join(import.meta.dir, "../..") +const worker = path.join(import.meta.dir, "../fixture/codex-auth-refresh-worker.ts") + +function plugin(persist: (auth: Auth) => void) { const set = async (req: { body: Auth }) => { persist(req.body) } @@ -27,7 +40,101 @@ function plugin(persist: (auth: Auth) => void): PluginInput { client: { auth: { set }, }, - } as unknown as PluginInput + } +} + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +async function wait(file: string) { + const stop = Date.now() + 10_000 + while (Date.now() < stop) { + if ( + await fs + .stat(file) + .then(() => true) + .catch(() => false) + ) + return + await sleep(10) + } + throw new Error(`Timed out waiting for file: ${file}`) +} + +function run(input: { root: string; url: string; ready: string; start: string; lock?: Lock }) { + const proc = spawn(process.execPath, [worker, JSON.stringify(input)], { + cwd: root, + windowsHide: true, + }) + const stdout: Buffer[] = [] + const stderr: Buffer[] = [] + proc.stdout?.on("data", (data) => stdout.push(Buffer.from(data))) + proc.stderr?.on("data", (data) => stderr.push(Buffer.from(data))) + return { + proc, + done: new Promise<{ code: number; stdout: string; stderr: string }>((resolve) => { + proc.on("close", (code) => { + resolve({ + code: code ?? 1, + stdout: Buffer.concat(stdout).toString(), + stderr: Buffer.concat(stderr).toString(), + }) + }) + }), + } +} + +async function race(input: { reuse: "early" | "late"; delay: number; lock?: Lock }) { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "codex-auth-refresh-")) + const calls: string[] = [] + const used = new Set() + const server = Bun.serve({ + hostname: "127.0.0.1", + port: 0, + async fetch(req) { + const body = new URLSearchParams(await req.text()) + const token = body.get("refresh_token") + if (!token) return new Response("missing refresh token", { status: 400 }) + calls.push(token) + if (used.has(token)) { + if (input.reuse === "late") await sleep(input.delay + 50) + return new Response("refresh token reused", { status: 401 }) + } + used.add(token) + await sleep(input.delay) + return Response.json({ + id_token: "", + access_token: "next-access", + refresh_token: "next-refresh", + expires_in: 60, + }) + }, + }) + + try { + const data = path.join(dir, "share", "kilo") + const start = path.join(dir, "start") + const first = path.join(dir, "first") + const second = path.join(dir, "second") + await fs.mkdir(data, { recursive: true }) + await fs.writeFile(path.join(data, "auth.json"), JSON.stringify({ openai: expired() })) + const url = `http://127.0.0.1:${server.port}/oauth/token` + const a = run({ root: dir, url, ready: first, start, lock: input.lock }) + const b = run({ root: dir, url, ready: second, start, lock: input.lock }) + try { + await Promise.all([wait(first), wait(second)]) + await fs.writeFile(start, "") + const out = await Promise.all([a.done, b.done]) + return { calls, out } + } finally { + if (a.proc.exitCode === null) a.proc.kill() + if (b.proc.exitCode === null) b.proc.kill() + } + } finally { + void server.stop(true) + await fs.rm(dir, { recursive: true, force: true }) + } } describe("Codex auth refresh", () => { @@ -121,4 +228,79 @@ describe("Codex auth refresh", () => { }), ).rejects.toBeInstanceOf(CodexAuthExpiredError) }) + + test("refreshes a newer stored token instead of the stale caller token", async () => { + const auth = expired() + const fresh = { ...expired(), refresh: "fresh-refresh", accountId: "account-1" } + const calls: string[] = [] + + const result = await refreshCodexAuth({ + input: plugin(() => {}), + getAuth: async () => fresh, + auth, + refresh: async (token) => { + calls.push(token) + return { id_token: "", access_token: "next-access", refresh_token: "next-refresh", expires_in: 60 } + }, + account: () => undefined, + }) + + expect(calls).toEqual(["fresh-refresh"]) + expect(result.accountId).toBe("account-1") + }) + + test("releases the lock and pending entry after a transient failure", async () => { + const auth = expired() + const calls: string[] = [] + const failed = await refreshCodexAuth({ + input: plugin(() => {}), + getAuth: async () => auth, + auth, + refresh: async (token) => { + calls.push(token) + throw new Error("offline") + }, + account: () => undefined, + }).catch((err) => err) + + expect(failed).toEqual(new Error("offline")) + + await refreshCodexAuth({ + input: plugin(() => {}), + getAuth: async () => auth, + auth, + refresh: async (token) => { + calls.push(token) + return { id_token: "", access_token: "next-access", refresh_token: "next-refresh", expires_in: 60 } + }, + account: () => undefined, + }) + + expect(calls).toEqual(["old-refresh", "old-refresh"]) + }) + + test("serializes refreshes across processes before token reuse", async () => { + for (const reuse of ["early", "late"] as const) { + for (let trial = 0; trial < 5; trial++) { + const result = await race({ reuse, delay: 100 }) + expect(result.calls).toEqual(["old-refresh"]) + expect(result.out.map((x) => x.code)).toEqual([0, 0]) + expect(result.out.map((x) => x.stderr)).toEqual(["", ""]) + } + } + }, 30_000) + + test("keeps the process lock alive during a delayed token response", async () => { + const lock = { + staleMs: 300, + timeoutMs: 10_000, + baseDelayMs: 20, + maxDelayMs: 30, + } + const result = await race({ reuse: "early", delay: 1_000, lock }) + + expect(result.calls).toEqual(["old-refresh"]) + expect(result.out.map((x) => x.code)).toEqual([0, 0]) + expect(result.out.map((x) => x.stderr)).toEqual(["", ""]) + }, 15_000) }) diff --git a/packages/opencode/test/kilocode/oauth-branding.test.ts b/packages/opencode/test/kilocode/oauth-branding.test.ts index 1a8556a5b5c..71cc234b3db 100644 --- a/packages/opencode/test/kilocode/oauth-branding.test.ts +++ b/packages/opencode/test/kilocode/oauth-branding.test.ts @@ -8,6 +8,7 @@ describe("Kilo OAuth branding", () => { const src = await Bun.file(path.join(root, "src", "plugin", "codex.ts")).text() expect(src).toContain('originator: "kilo"') + expect(src).toContain('"User-Agent": `kilo/${InstallationVersion}`') expect(src).toContain("return to Kilo") expect(src).not.toContain('originator: "opencode"') expect(src).not.toContain("return to OpenCode") diff --git a/packages/opencode/test/plugin/codex.test.ts b/packages/opencode/test/plugin/codex.test.ts index 74d28ac9dcc..97388d0c32d 100644 --- a/packages/opencode/test/plugin/codex.test.ts +++ b/packages/opencode/test/plugin/codex.test.ts @@ -3,8 +3,10 @@ import { parseJwtClaims, extractAccountIdFromClaims, extractAccountId, + CodexAuthPlugin, type IdTokenClaims, } from "../../src/plugin/codex" +import type { PluginInput } from "@kilocode/plugin" function createTestJwt(payload: object): string { const header = Buffer.from(JSON.stringify({ alg: "none" })).toString("base64url") @@ -13,6 +15,55 @@ function createTestJwt(payload: object): string { } describe("plugin.codex", () => { + test("identifies refresh requests as Kilo", async () => { + const original = globalThis.fetch + const seen: Request[] = [] + let auth = { + type: "oauth" as const, + access: "old-access", + refresh: "old-refresh", + expires: 0, + } + const input = { + client: { + auth: { + set: async (req: { body: typeof auth }) => { + auth = req.body + }, + }, + }, + } as unknown as PluginInput + globalThis.fetch = Object.assign( + async (...args: Parameters) => { + const req = new Request(...args) + seen.push(req) + if (req.url === "https://auth.openai.com/oauth/token") { + return Response.json({ + id_token: "", + access_token: "next-access", + refresh_token: "next-refresh", + expires_in: 60, + }) + } + return new Response("", { status: 200 }) + }, + { preconnect: original.preconnect }, + ) + + try { + const plugin = await CodexAuthPlugin(input) + const loaded = await plugin.auth!.loader!(async () => auth, {} as never) + await loaded.fetch("https://api.openai.com/v1/responses") + } finally { + globalThis.fetch = original + } + + const refresh = seen[0] + expect(refresh.url).toBe("https://auth.openai.com/oauth/token") + expect(refresh.headers.get("user-agent")).toMatch(/^kilo\//) + expect(await refresh.text()).toContain("refresh_token=old-refresh") + }) + describe("parseJwtClaims", () => { test("parses valid JWT with claims", () => { const payload = { email: "test@example.com", chatgpt_account_id: "acc-123" } From 14cc0e76c31685bb98e0a5d68e83dba3b2cf2aed Mon Sep 17 00:00:00 2001 From: Cooper Gamble Date: Sat, 30 May 2026 01:58:01 +0000 Subject: [PATCH 03/78] test(cli): await Codex refresh server teardown --- packages/opencode/test/kilocode/codex-auth-refresh.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/opencode/test/kilocode/codex-auth-refresh.test.ts b/packages/opencode/test/kilocode/codex-auth-refresh.test.ts index f1cade1cba3..26b179dbe75 100644 --- a/packages/opencode/test/kilocode/codex-auth-refresh.test.ts +++ b/packages/opencode/test/kilocode/codex-auth-refresh.test.ts @@ -132,7 +132,7 @@ async function race(input: { reuse: "early" | "late"; delay: number; lock?: Lock if (b.proc.exitCode === null) b.proc.kill() } } finally { - void server.stop(true) + await server.stop(true) await fs.rm(dir, { recursive: true, force: true }) } } From f202002c856a97717a125a0d59bdb27b85338f64 Mon Sep 17 00:00:00 2001 From: Cooper Gamble Date: Sat, 30 May 2026 02:04:01 +0000 Subject: [PATCH 04/78] test(cli): isolate Kilo Codex refresh fixtures --- .../test/kilocode/codex-auth-refresh.test.ts | 2 +- .../kilocode/codex-refresh-user-agent.test.ts | 52 +++++++++++++++++++ .../fixture/codex-auth-refresh-worker.ts | 2 +- packages/opencode/test/plugin/codex.test.ts | 51 ------------------ 4 files changed, 54 insertions(+), 53 deletions(-) create mode 100644 packages/opencode/test/kilocode/codex-refresh-user-agent.test.ts rename packages/opencode/test/{ => kilocode}/fixture/codex-auth-refresh-worker.ts (96%) diff --git a/packages/opencode/test/kilocode/codex-auth-refresh.test.ts b/packages/opencode/test/kilocode/codex-auth-refresh.test.ts index 26b179dbe75..3239ff32638 100644 --- a/packages/opencode/test/kilocode/codex-auth-refresh.test.ts +++ b/packages/opencode/test/kilocode/codex-auth-refresh.test.ts @@ -30,7 +30,7 @@ const expired = (): Auth => ({ }) const root = path.join(import.meta.dir, "../..") -const worker = path.join(import.meta.dir, "../fixture/codex-auth-refresh-worker.ts") +const worker = path.join(import.meta.dir, "fixture/codex-auth-refresh-worker.ts") function plugin(persist: (auth: Auth) => void) { const set = async (req: { body: Auth }) => { diff --git a/packages/opencode/test/kilocode/codex-refresh-user-agent.test.ts b/packages/opencode/test/kilocode/codex-refresh-user-agent.test.ts new file mode 100644 index 00000000000..9bf638f92bc --- /dev/null +++ b/packages/opencode/test/kilocode/codex-refresh-user-agent.test.ts @@ -0,0 +1,52 @@ +import { expect, test } from "bun:test" +import type { PluginInput } from "@kilocode/plugin" +import { CodexAuthPlugin } from "../../src/plugin/codex" + +test("identifies Codex refresh requests as Kilo", async () => { + const original = globalThis.fetch + const seen: Request[] = [] + let auth = { + type: "oauth" as const, + access: "old-access", + refresh: "old-refresh", + expires: 0, + } + const input = { + client: { + auth: { + set: async (req: { body: typeof auth }) => { + auth = req.body + }, + }, + }, + } as unknown as PluginInput + globalThis.fetch = Object.assign( + async (...args: Parameters) => { + const req = new Request(...args) + seen.push(req) + if (req.url === "https://auth.openai.com/oauth/token") { + return Response.json({ + id_token: "", + access_token: "next-access", + refresh_token: "next-refresh", + expires_in: 60, + }) + } + return new Response("", { status: 200 }) + }, + { preconnect: original.preconnect }, + ) + + try { + const plugin = await CodexAuthPlugin(input) + const loaded = await plugin.auth!.loader!(async () => auth, {} as never) + await loaded.fetch("https://api.openai.com/v1/responses") + } finally { + globalThis.fetch = original + } + + const refresh = seen[0] + expect(refresh.url).toBe("https://auth.openai.com/oauth/token") + expect(refresh.headers.get("user-agent")).toMatch(/^kilo\//) + expect(await refresh.text()).toContain("refresh_token=old-refresh") +}) diff --git a/packages/opencode/test/fixture/codex-auth-refresh-worker.ts b/packages/opencode/test/kilocode/fixture/codex-auth-refresh-worker.ts similarity index 96% rename from packages/opencode/test/fixture/codex-auth-refresh-worker.ts rename to packages/opencode/test/kilocode/fixture/codex-auth-refresh-worker.ts index e36515af91f..845b25fde88 100644 --- a/packages/opencode/test/fixture/codex-auth-refresh-worker.ts +++ b/packages/opencode/test/kilocode/fixture/codex-auth-refresh-worker.ts @@ -67,7 +67,7 @@ async function main() { process.env.KILO_TEST_HOME = path.join(msg.root, "home") const { Path } = await import("@opencode-ai/core/global") - const { refreshCodexAuth } = await import("../../src/kilocode/provider/codex-refresh") + const { refreshCodexAuth } = await import("../../../src/kilocode/provider/codex-refresh") const file = path.join(Path.data, "auth.json") const read = async () => { const data = z.object({ openai: Auth }).parse(JSON.parse(await fs.readFile(file, "utf8"))) diff --git a/packages/opencode/test/plugin/codex.test.ts b/packages/opencode/test/plugin/codex.test.ts index 97388d0c32d..74d28ac9dcc 100644 --- a/packages/opencode/test/plugin/codex.test.ts +++ b/packages/opencode/test/plugin/codex.test.ts @@ -3,10 +3,8 @@ import { parseJwtClaims, extractAccountIdFromClaims, extractAccountId, - CodexAuthPlugin, type IdTokenClaims, } from "../../src/plugin/codex" -import type { PluginInput } from "@kilocode/plugin" function createTestJwt(payload: object): string { const header = Buffer.from(JSON.stringify({ alg: "none" })).toString("base64url") @@ -15,55 +13,6 @@ function createTestJwt(payload: object): string { } describe("plugin.codex", () => { - test("identifies refresh requests as Kilo", async () => { - const original = globalThis.fetch - const seen: Request[] = [] - let auth = { - type: "oauth" as const, - access: "old-access", - refresh: "old-refresh", - expires: 0, - } - const input = { - client: { - auth: { - set: async (req: { body: typeof auth }) => { - auth = req.body - }, - }, - }, - } as unknown as PluginInput - globalThis.fetch = Object.assign( - async (...args: Parameters) => { - const req = new Request(...args) - seen.push(req) - if (req.url === "https://auth.openai.com/oauth/token") { - return Response.json({ - id_token: "", - access_token: "next-access", - refresh_token: "next-refresh", - expires_in: 60, - }) - } - return new Response("", { status: 200 }) - }, - { preconnect: original.preconnect }, - ) - - try { - const plugin = await CodexAuthPlugin(input) - const loaded = await plugin.auth!.loader!(async () => auth, {} as never) - await loaded.fetch("https://api.openai.com/v1/responses") - } finally { - globalThis.fetch = original - } - - const refresh = seen[0] - expect(refresh.url).toBe("https://auth.openai.com/oauth/token") - expect(refresh.headers.get("user-agent")).toMatch(/^kilo\//) - expect(await refresh.text()).toContain("refresh_token=old-refresh") - }) - describe("parseJwtClaims", () => { test("parses valid JWT with claims", () => { const payload = { email: "test@example.com", chatgpt_account_id: "acc-123" } From 973d02cfd15b3bf3eefefe92e7fb61059eba26f7 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 12 Jun 2026 20:08:02 +0200 Subject: [PATCH 05/78] feat(cli): share codebase indexes across worktrees --- .changeset/share-worktree-indexes.md | 6 + packages/kilo-indexing/src/file/ignore.ts | 2 + .../src/indexing/cache-manager.ts | 44 ++-- .../src/indexing/interfaces/file-processor.ts | 3 + .../src/indexing/interfaces/manager.ts | 2 +- .../src/indexing/interfaces/vector-store.ts | 6 + .../kilo-indexing/src/indexing/manager.ts | 86 +++++++- .../src/indexing/orchestrator.ts | 62 +++++- .../src/indexing/processors/file-watcher.ts | 177 +++++++++++----- .../src/indexing/processors/scanner.ts | 6 +- .../src/indexing/search-service.ts | 49 ++++- .../src/indexing/service-factory.ts | 6 +- .../vector-store/lancedb-vector-store.ts | 23 +++ .../indexing/vector-store/qdrant-client.ts | 21 ++ .../src/indexing/worktree-overlay.ts | 105 ++++++++++ .../kilocode/indexing/cache-manager.test.ts | 26 +++ .../test/kilocode/indexing/manager.test.ts | 48 +++-- .../kilocode/indexing/orchestrator.test.ts | 47 +++++ .../indexing/processors/file-watcher.test.ts | 87 +++++++- .../indexing/processors/scanner.test.ts | 34 +++ .../kilocode/indexing/search-service.test.ts | 171 +++++++++++++++ .../vector-store/lancedb-vector-store.test.ts | 16 ++ .../vector-store/qdrant-client.test.ts | 50 ++--- .../indexing/worktree-overlay.test.ts | 50 +++++ .../src/kilocode/indexing-worker-client.ts | 195 +++++++++++------- .../src/kilocode/indexing-worker-protocol.ts | 21 +- .../opencode/src/kilocode/indexing-worker.ts | 77 ++++--- packages/opencode/src/kilocode/indexing.ts | 33 ++- .../opencode/src/kilocode/tool/registry.ts | 34 ++- packages/opencode/src/tool/registry.ts | 14 +- .../test/kilocode/indexing-startup.test.ts | 8 +- .../test/kilocode/indexing-worker.test.ts | 30 +++ .../test/kilocode/indexing-worktree.test.ts | 74 +++++-- .../kilocode/tool-registry-indexing.test.ts | 18 +- ...l-registry-semantic-import-failure.test.ts | 32 +-- 35 files changed, 1353 insertions(+), 310 deletions(-) create mode 100644 .changeset/share-worktree-indexes.md create mode 100644 packages/kilo-indexing/src/indexing/worktree-overlay.ts create mode 100644 packages/kilo-indexing/test/kilocode/indexing/cache-manager.test.ts create mode 100644 packages/kilo-indexing/test/kilocode/indexing/search-service.test.ts create mode 100644 packages/kilo-indexing/test/kilocode/indexing/worktree-overlay.test.ts diff --git a/.changeset/share-worktree-indexes.md b/.changeset/share-worktree-indexes.md new file mode 100644 index 00000000000..fcb6db6bf68 --- /dev/null +++ b/.changeset/share-worktree-indexes.md @@ -0,0 +1,6 @@ +--- +"@kilocode/cli": minor +"@kilocode/kilo-indexing": minor +--- + +Share the main codebase index with Agent Manager worktrees while indexing and searching only each worktree's changed files. diff --git a/packages/kilo-indexing/src/file/ignore.ts b/packages/kilo-indexing/src/file/ignore.ts index 292405cb9f9..df5c45e889f 100644 --- a/packages/kilo-indexing/src/file/ignore.ts +++ b/packages/kilo-indexing/src/file/ignore.ts @@ -44,6 +44,8 @@ export namespace FileIgnore { "**/*.log", "**/coverage/**", "**/.nyc_output/**", + "**/.kilo/worktrees/**", + "**/.kilocode/worktrees/**", ] export const PATTERNS = [...files, ...folders] diff --git a/packages/kilo-indexing/src/indexing/cache-manager.ts b/packages/kilo-indexing/src/indexing/cache-manager.ts index 5973cf96c74..a70d591892f 100644 --- a/packages/kilo-indexing/src/indexing/cache-manager.ts +++ b/packages/kilo-indexing/src/indexing/cache-manager.ts @@ -16,6 +16,7 @@ export class CacheManager implements ICacheManager { private readonly cachePath: string private fileHashes: Record = {} private saveTimer: ReturnType | undefined + private saveTask = Promise.resolve() constructor( private readonly cacheDirectory: string, @@ -36,28 +37,36 @@ export class CacheManager implements ICacheManager { private scheduleSave(): void { if (this.saveTimer) clearTimeout(this.saveTimer) - this.saveTimer = setTimeout(() => this.performSave(), 1500) + this.saveTimer = setTimeout(() => { + void this.flush().catch((err) => log.error("failed to save cache", { err })) + }, 1500) } private async performSave(): Promise { - try { - await fs.mkdir(path.dirname(this.cachePath), { recursive: true }) - const tmp = `${this.cachePath}.tmp` - await fs.writeFile(tmp, JSON.stringify(this.fileHashes), "utf-8") - await fs.rename(tmp, this.cachePath) - } catch (err) { + await fs.mkdir(path.dirname(this.cachePath), { recursive: true }) + const tmp = `${this.cachePath}.tmp` + await fs.writeFile(tmp, JSON.stringify(this.fileHashes), "utf-8") + await fs.rename(tmp, this.cachePath) + } + + async flush(): Promise { + if (this.saveTimer) clearTimeout(this.saveTimer) + this.saveTimer = undefined + const task = this.saveTask.then(() => this.performSave()) + this.saveTask = task.catch((err) => { log.error("failed to save cache", { err }) - } + }) + await task + } + + seedHashes(hashes: Readonly>): void { + this.fileHashes = { ...hashes } + this.scheduleSave() } async clearCacheFile(): Promise { - try { - this.fileHashes = {} - await fs.mkdir(path.dirname(this.cachePath), { recursive: true }) - await fs.writeFile(this.cachePath, "{}", "utf-8") - } catch (err) { - log.error("failed to clear cache file", { err }) - } + this.fileHashes = {} + await this.flush() } getHash(filePath: string): string | undefined { @@ -77,4 +86,9 @@ export class CacheManager implements ICacheManager { getAllHashes(): Record { return { ...this.fileHashes } } + + signature(): string { + const entries = Object.entries(this.fileHashes).sort(([left], [right]) => left.localeCompare(right)) + return createHash("sha256").update(JSON.stringify(entries)).digest("hex") + } } diff --git a/packages/kilo-indexing/src/indexing/interfaces/file-processor.ts b/packages/kilo-indexing/src/indexing/interfaces/file-processor.ts index 03a80a823c2..704d5761ee7 100644 --- a/packages/kilo-indexing/src/indexing/interfaces/file-processor.ts +++ b/packages/kilo-indexing/src/indexing/interfaces/file-processor.ts @@ -1,6 +1,7 @@ import type { PointStruct } from "./vector-store" import type { Disposable, Emitter } from "../runtime" import type { IndexingTelemetryMode } from "./telemetry" +import type { WorktreeOverlay } from "../worktree-overlay" export interface ICodeParser { parseFile( @@ -36,6 +37,8 @@ export interface IFileWatcher extends Disposable { initialize(): Promise updateBatchSegmentThreshold(newThreshold: number): void setCollecting(collecting: boolean): void + setOverlay?(overlay?: WorktreeOverlay): void + shutdown?(): Promise readonly onDidStartBatchProcessing: Emitter readonly onBatchProgressUpdate: Emitter<{ diff --git a/packages/kilo-indexing/src/indexing/interfaces/manager.ts b/packages/kilo-indexing/src/indexing/interfaces/manager.ts index 5d1e0433f49..c4ffad9efc0 100644 --- a/packages/kilo-indexing/src/indexing/interfaces/manager.ts +++ b/packages/kilo-indexing/src/indexing/interfaces/manager.ts @@ -31,7 +31,7 @@ export interface ICodeIndexManager { totalItems: number currentItemUnit: string } - dispose(): void + dispose(): Promise } export type IndexingState = "Standby" | "Indexing" | "Indexed" | "Error" diff --git a/packages/kilo-indexing/src/indexing/interfaces/vector-store.ts b/packages/kilo-indexing/src/indexing/interfaces/vector-store.ts index 43253e0d8d1..8857ae25db3 100644 --- a/packages/kilo-indexing/src/indexing/interfaces/vector-store.ts +++ b/packages/kilo-indexing/src/indexing/interfaces/vector-store.ts @@ -8,6 +8,12 @@ export type PointStruct = { } export interface IVectorStore { + /** + * Opens an existing complete store without mutating it. + */ + openExisting?(): Promise + close?(): Promise + /** * Initializes the vector store * @returns Promise resolving to boolean indicating if a new collection was created diff --git a/packages/kilo-indexing/src/indexing/manager.ts b/packages/kilo-indexing/src/indexing/manager.ts index 3dbd8c3b428..132db65b8ee 100644 --- a/packages/kilo-indexing/src/indexing/manager.ts +++ b/packages/kilo-indexing/src/indexing/manager.ts @@ -1,4 +1,5 @@ -import type { VectorStoreSearchResult } from "./interfaces" +import path from "path" +import type { IVectorStore, VectorStoreSearchResult } from "./interfaces" import type { IndexingState } from "./interfaces/manager" import type { IndexingTelemetryEvent, IndexingTelemetryMeta, IndexingTelemetryTrigger } from "./interfaces/telemetry" import { CodeIndexConfigManager, type IndexingConfigInput } from "./config-manager" @@ -12,6 +13,7 @@ import { Emitter } from "./runtime" import { Log } from "../util/log" import { loadIgnore } from "./shared/load-ignore" import { sanitizeErrorMessage } from "./shared/validation-helpers" +import { WorktreeOverlay } from "./worktree-overlay" const log = Log.create({ service: "indexing-manager" }) @@ -29,6 +31,10 @@ export class CodeIndexManager { private _orchestrator: CodeIndexOrchestrator | undefined private _searchService: CodeIndexSearchService | undefined private _cacheManager: CacheManager | undefined + private _baselineStore: IVectorStore | undefined + private _baselineSignature: string | undefined + private _baselineRefresh: Promise | undefined + private _overlay: WorktreeOverlay | undefined private _isRecoveringFromError = false private _retryTimer: ReturnType | undefined private _retryResolve: (() => void) | undefined @@ -41,6 +47,7 @@ export class CodeIndexManager { constructor( public readonly workspacePath: string, private readonly cacheDirectory: string, + public readonly baselinePath?: string, ) { this._stateManager = new CodeIndexStateManager() } @@ -130,7 +137,7 @@ export class CodeIndexManager { } if (event.type !== "error") return - if (event.location !== "orchestrator:startIndexing") return + if (event.location !== "orchestrator:startIndexing" && event.location !== "orchestrator:watcher") return if (!this.isFeatureEnabled || !this.isFeatureConfigured) return if (this._retryTask || this._isRecoveringFromError) return @@ -405,15 +412,13 @@ export class CodeIndexManager { await task } - public dispose(): void { + public async dispose(): Promise { if (this._disposed) return this._disposed = true this.clearRetryTimer() this._retryTask = undefined - // RATIONALE: cancelIndexing() sets _cancelRequested and calls stopWatcher() + - // scanner.cancel(), which cooperatively aborts any in-flight scan. Using only - // stopWatcher() left the orchestrator's _runScan() unaware it should exit. - this._orchestrator?.cancelIndexing() + await this._orchestrator?.shutdown?.() + await this._baselineStore?.close?.() this._stateManager.dispose() this._telemetry.dispose() } @@ -437,13 +442,63 @@ export class CodeIndexManager { public async searchIndex(query: string, directoryPrefix?: string): Promise { if (!this.isFeatureEnabled) return [] this.assertInitialized() + await this.refreshBaseline() return this._searchService!.searchIndex(query, directoryPrefix) } + private async refreshBaseline(): Promise { + if (!this.baselinePath || this._disposed) return + if (this._baselineRefresh) return this._baselineRefresh + const baselinePath = this.baselinePath + + const task = (async () => { + const cache = new CacheManager(this.cacheDirectory, baselinePath) + await cache.initialize() + if (cache.signature() === this._baselineSignature) return + + log.info("shared indexing baseline changed; rebuilding worktree delta", { + workspacePath: this.workspacePath, + baselinePath, + }) + await this._recreateServices() + await this._orchestrator?.startIndexing("background") + })().finally(() => { + this._baselineRefresh = undefined + }) + this._baselineRefresh = task + return task + } + + private async createBaseline(factory: CodeIndexServiceFactory) { + if (!this.baselinePath) return + + const cache = new CacheManager(this.cacheDirectory, this.baselinePath) + await cache.initialize() + const hashes = new Map() + for (const [filePath, hash] of Object.entries(cache.getAllHashes())) { + const rel = path.relative(this.baselinePath, filePath) + if (!rel || rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) continue + hashes.set(rel.replaceAll("\\", "/"), hash) + } + + const store = factory.createVectorStore(this.baselinePath) + if (!store.openExisting) throw new Error("The configured vector store cannot open a shared baseline") + await store.openExisting() + await store.close?.() + + return { + store, + signature: cache.signature(), + overlay: new WorktreeOverlay(this.workspacePath, this.baselinePath, hashes), + } + } + private async _recreateServices(): Promise { log.info("starting indexing service recreation", { workspacePath: this.workspacePath }) - this._orchestrator?.stopWatcher() + await this._orchestrator?.shutdown?.() + await this._baselineStore?.close?.() this._orchestrator = undefined + this._baselineStore = undefined this._searchService = undefined this._serviceFactory = new CodeIndexServiceFactory( @@ -457,10 +512,16 @@ export class CodeIndexManager { const ignoreInstance = await loadIgnore(this.workspacePath) const config = this._configManager!.getConfig() + const baseline = await this.createBaseline(this._serviceFactory) + this._baselineStore = baseline?.store + this._baselineSignature = baseline?.signature + this._overlay = baseline?.overlay + const { embedder, vectorStore, scanner, fileWatcher } = this._serviceFactory.createServices( this._cacheManager!, ignoreInstance, ) + fileWatcher.setOverlay?.(this._overlay) log.info("created indexing services", { workspacePath: this.workspacePath, provider: embedder.embedderInfo.name, @@ -496,9 +557,16 @@ export class CodeIndexManager { scanner, fileWatcher, (event) => this.handleTelemetry(event), + this._overlay, ) - this._searchService = new CodeIndexSearchService(this._configManager!, this._stateManager, embedder, vectorStore) + this._searchService = new CodeIndexSearchService( + this._configManager!, + this._stateManager, + embedder, + vectorStore, + this._baselineStore && this._overlay ? { store: this._baselineStore, overlay: this._overlay } : undefined, + ) this._stateManager.setSystemState("Standby", "") log.info("indexing services are ready", { workspacePath: this.workspacePath }) diff --git a/packages/kilo-indexing/src/indexing/orchestrator.ts b/packages/kilo-indexing/src/indexing/orchestrator.ts index 06c1f97d41b..46c9b2b0690 100644 --- a/packages/kilo-indexing/src/indexing/orchestrator.ts +++ b/packages/kilo-indexing/src/indexing/orchestrator.ts @@ -17,6 +17,7 @@ import type { Disposable } from "./runtime" import { Log } from "../util/log" import { sanitizeErrorMessage } from "./shared/validation-helpers" import { DEFAULT_VECTOR_STORE } from "./constants" +import type { WorktreeOverlay } from "./worktree-overlay" const log = Log.create({ service: "indexing-orchestrator" }) @@ -24,6 +25,7 @@ export class CodeIndexOrchestrator { private _fileWatcherSubscriptions: Disposable[] = [] private _isProcessing = false private _cancelRequested = false + private _active?: Promise constructor( private readonly configManager: CodeIndexConfigManager, @@ -34,6 +36,7 @@ export class CodeIndexOrchestrator { private readonly scanner: DirectoryScanner, private readonly fileWatcher: IFileWatcher, private readonly onTelemetry?: IndexingTelemetryReporter, + private readonly overlay?: WorktreeOverlay, ) {} private getTelemetryMeta(): IndexingTelemetryMeta { @@ -82,6 +85,9 @@ export class CodeIndexOrchestrator { this.stateManager.setSystemState("Indexing", "Initializing file watcher...") try { + this.fileWatcher.setCollecting(false) + for (const sub of this._fileWatcherSubscriptions) sub.dispose() + this._fileWatcherSubscriptions = [] await this.fileWatcher.initialize() log.info("file watcher initialized", { workspacePath: this.workspacePath }) @@ -106,6 +112,7 @@ export class CodeIndexOrchestrator { workspacePath: this.workspacePath, totalInBatch, }) + if (this.stateManager.state === "Error") return if (totalInBatch > 0) { this.stateManager.setSystemState("Indexed", "File changes processed. Index up-to-date.") } else if (this.stateManager.state === "Indexing") { @@ -114,9 +121,11 @@ export class CodeIndexOrchestrator { } }), this.fileWatcher.onDidFinishBatchProcessing.on((summary: BatchProcessingSummary) => { - if (summary.batchError) { - log.error("batch processing failed", { err: summary.batchError }) - } + if (!summary.batchError) return + log.error("batch processing failed", { err: summary.batchError }) + this.overlay?.prepare() + this.stateManager.setSystemState("Error", `Failed to process file changes: ${summary.batchError.message}`) + this.emitError("orchestrator:watcher", summary.batchError, "watcher") }), ] this.fileWatcher.setCollecting(false) @@ -127,7 +136,16 @@ export class CodeIndexOrchestrator { } } - public async startIndexing(trigger: IndexingTelemetryTrigger = "background"): Promise { + public startIndexing(trigger: IndexingTelemetryTrigger = "background"): Promise { + if (this._active) return this._active + const task = this.runIndexing(trigger).finally(() => { + if (this._active === task) this._active = undefined + }) + this._active = task + return task + } + + private async runIndexing(trigger: IndexingTelemetryTrigger): Promise { log.info("indexing start requested", { workspacePath: this.workspacePath, state: this.stateManager.state, @@ -166,6 +184,7 @@ export class CodeIndexOrchestrator { let mode: IndexingTelemetryMode | undefined try { + this.overlay?.prepare() await this._startWatcher() if (this._cancelRequested) { @@ -183,12 +202,22 @@ export class CodeIndexOrchestrator { return } - if (collectionCreated) { + if (this.overlay) { + if (!collectionCreated) await this.vectorStore.clearCollection() + await this.cacheManager.clearCacheFile() + this.cacheManager.seedHashes(this.overlay.seed()) + await this.cacheManager.flush?.() + log.info("seeded worktree index from shared baseline", { + workspacePath: this.workspacePath, + baselinePath: this.overlay.baselinePath, + files: this.overlay.baseline.size, + }) + } else if (collectionCreated) { await this.cacheManager.clearCacheFile() log.info("cleared indexing cache after new collection creation", { workspacePath: this.workspacePath }) } - const hasExistingData = await this.vectorStore.hasIndexedData() + const hasExistingData = this.overlay ? false : await this.vectorStore.hasIndexedData() log.info("checked vector store indexed data", { workspacePath: this.workspacePath, hasExistingData, @@ -292,6 +321,10 @@ export class CodeIndexOrchestrator { return } + if (this.overlay && batchErrors.length > 0) { + throw batchErrors[0] + } + if (mode === "full") { // Validate full scan results if (cumulativeFilesIndexed === 0 && cumulativeFilesFound > 0) { @@ -312,8 +345,11 @@ export class CodeIndexOrchestrator { } } - this.fileWatcher.setCollecting(true) + this.overlay?.reconcile(this.cacheManager.getAllHashes()) + await this.cacheManager.flush?.() await this.vectorStore.markIndexingComplete() + await this.vectorStore.close?.() + this.fileWatcher.setCollecting(true) this.stateManager.setSystemState("Indexed", "File watcher started. Index up-to-date.") log.info("workspace scan finalized", { workspacePath: this.workspacePath, @@ -335,6 +371,18 @@ export class CodeIndexOrchestrator { }) } + public async shutdown(): Promise { + this._cancelRequested = true + this.scanner.cancel() + for (const sub of this._fileWatcherSubscriptions) sub.dispose() + this._fileWatcherSubscriptions = [] + if (this.fileWatcher.shutdown) await this.fileWatcher.shutdown() + else this.fileWatcher.dispose() + await this._active + await this.vectorStore.close?.() + this._isProcessing = false + } + public stopWatcher(): void { log.info("stopping file watcher", { workspacePath: this.workspacePath }) this.fileWatcher.dispose() diff --git a/packages/kilo-indexing/src/indexing/processors/file-watcher.ts b/packages/kilo-indexing/src/indexing/processors/file-watcher.ts index 22ece9fd5d3..28ae6f457d0 100644 --- a/packages/kilo-indexing/src/indexing/processors/file-watcher.ts +++ b/packages/kilo-indexing/src/indexing/processors/file-watcher.ts @@ -31,6 +31,7 @@ import { } from "../shared/get-relative-path" import { FileIgnore } from "../../file/ignore" import { Log } from "../../util/log" +import type { WorktreeOverlay } from "../worktree-overlay" import { sanitizeErrorMessage } from "../shared/validation-helpers" const log = Log.create({ service: "file-watcher" }) @@ -50,9 +51,11 @@ export class FileWatcher implements IFileWatcher { private readonly FILE_PROCESSING_CONCURRENCY_LIMIT = 10 private batchSegmentThreshold: number private maxBatchRetries: number - private collecting = true + private collecting = false private draining = false + private drainTask?: Promise private ready?: Promise + private overlay?: WorktreeOverlay public readonly onDidStartBatchProcessing = new Emitter() public readonly onBatchProgressUpdate = new Emitter<{ @@ -150,8 +153,16 @@ export class FileWatcher implements IFileWatcher { log.info("file watcher ready", { workspacePath: this.workspacePath }) } + setOverlay(overlay?: WorktreeOverlay): void { + this.overlay = overlay + } + setCollecting(collecting: boolean): void { this.collecting = collecting + if (!collecting && this.batchProcessDebounceTimer) { + clearTimeout(this.batchProcessDebounceTimer) + this.batchProcessDebounceTimer = undefined + } log.info("updated watcher collection mode", { workspacePath: this.workspacePath, collecting, @@ -170,11 +181,20 @@ export class FileWatcher implements IFileWatcher { /** * Disposes the file watcher and cleans up resources. */ + async shutdown(): Promise { + this.collecting = false + if (this.batchProcessDebounceTimer) clearTimeout(this.batchProcessDebounceTimer) + this.batchProcessDebounceTimer = undefined + await this.watcher?.close() + await this.drainTask + this.dispose() + } + dispose(): void { - this.watcher?.close() - if (this.batchProcessDebounceTimer) { - clearTimeout(this.batchProcessDebounceTimer) - } + this.collecting = false + void this.watcher?.close() + if (this.batchProcessDebounceTimer) clearTimeout(this.batchProcessDebounceTimer) + this.batchProcessDebounceTimer = undefined this.onDidStartBatchProcessing.dispose() this.onBatchProgressUpdate.dispose() this.onDidFinishBatchProcessing.dispose() @@ -187,6 +207,7 @@ export class FileWatcher implements IFileWatcher { */ private handleFileEvent(filePath: string, type: "create" | "change" | "delete"): void { if (!this.shouldIndex(filePath)) return + this.overlay?.block(filePath) this.accumulatedEvents.set(filePath, { path: filePath, type }) if (!this.collecting) return this.scheduleBatchProcessing() @@ -196,11 +217,22 @@ export class FileWatcher implements IFileWatcher { * Schedules batch processing with debounce. */ private scheduleBatchProcessing(): void { - if (!this.collecting) return + if (!this.collecting || this.drainTask) return if (this.batchProcessDebounceTimer) { clearTimeout(this.batchProcessDebounceTimer) } - this.batchProcessDebounceTimer = setTimeout(() => this.triggerBatchProcessing(), this.BATCH_DEBOUNCE_DELAY_MS) + this.batchProcessDebounceTimer = setTimeout(() => { + this.batchProcessDebounceTimer = undefined + const task = this.triggerBatchProcessing().catch((err) => { + const error = err instanceof Error ? err : new Error(String(err)) + this.collecting = false + this.onDidFinishBatchProcessing.fire({ processedFiles: [], batchError: error }) + }) + this.drainTask = task.finally(() => { + this.drainTask = undefined + if (this.collecting && this.accumulatedEvents.size > 0) this.scheduleBatchProcessing() + }) + }, this.BATCH_DEBOUNCE_DELAY_MS) } /** @@ -217,17 +249,20 @@ export class FileWatcher implements IFileWatcher { pendingEvents: this.accumulatedEvents.size, }) - while (this.collecting && this.accumulatedEvents.size > 0) { - const eventsToProcess = new Map(this.accumulatedEvents) - this.accumulatedEvents.clear() + try { + while (this.collecting && this.accumulatedEvents.size > 0) { + const eventsToProcess = new Map(this.accumulatedEvents) + this.accumulatedEvents.clear() - const filePathsInBatch = Array.from(eventsToProcess.keys()) - this.onDidStartBatchProcessing.fire(filePathsInBatch) - await this.processBatch(eventsToProcess) + const filePathsInBatch = Array.from(eventsToProcess.keys()) + this.onDidStartBatchProcessing.fire(filePathsInBatch) + await this.processBatch(eventsToProcess) + } + } finally { + this.draining = false + if (this.collecting && this.accumulatedEvents.size > 0) this.scheduleBatchProcessing() + log.info("completed watcher event drain", { workspacePath: this.workspacePath }) } - - this.draining = false - log.info("completed watcher event drain", { workspacePath: this.workspacePath }) } private shouldIndex(filePath: string) { @@ -414,45 +449,47 @@ export class FileWatcher implements IFileWatcher { batchResults: FileProcessingResult[], overallBatchError?: Error, ): Promise { - if (pointsForBatchUpsert.length > 0 && this.vectorStore && !overallBatchError) { + if (!overallBatchError) { try { - for (let i = 0; i < pointsForBatchUpsert.length; i += this.batchSegmentThreshold) { - const batch = pointsForBatchUpsert.slice(i, i + this.batchSegmentThreshold) - let retryCount = 0 - let upsertError: Error | undefined + if (pointsForBatchUpsert.length > 0 && this.vectorStore) { + for (let i = 0; i < pointsForBatchUpsert.length; i += this.batchSegmentThreshold) { + const batch = pointsForBatchUpsert.slice(i, i + this.batchSegmentThreshold) + let retryCount = 0 + let upsertError: Error | undefined - while (retryCount < this.maxBatchRetries) { - try { - await this.vectorStore.upsertPoints(batch) - break - } catch (error) { - upsertError = error as Error - retryCount++ - if (retryCount === this.maxBatchRetries) { - log.error("upsert retry exhausted", { - error: sanitizeErrorMessage(upsertError.message), - location: "upsertPoints", - errorType: "upsert_retry_exhausted", - retryCount: this.maxBatchRetries, - }) - this.emitError("file-watcher:upsert_retry_exhausted", upsertError, this.maxBatchRetries) - throw new Error(`Failed to upsert batch after ${this.maxBatchRetries} retries: ${upsertError.message}`) + while (retryCount < this.maxBatchRetries) { + try { + await this.vectorStore.upsertPoints(batch) + break + } catch (error) { + upsertError = error as Error + retryCount++ + if (retryCount === this.maxBatchRetries) { + log.error("upsert retry exhausted", { + error: sanitizeErrorMessage(upsertError.message), + location: "upsertPoints", + errorType: "upsert_retry_exhausted", + retryCount: this.maxBatchRetries, + }) + this.emitError("file-watcher:upsert_retry_exhausted", upsertError, this.maxBatchRetries) + throw new Error(`Failed to upsert batch after ${this.maxBatchRetries} retries: ${upsertError.message}`) + } + this.emitRetry(retryCount, batch.length, upsertError) + await new Promise((resolve) => + setTimeout(resolve, INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount - 1)), + ) } - this.emitRetry(retryCount, batch.length, upsertError) - await new Promise((resolve) => setTimeout(resolve, INITIAL_RETRY_DELAY_MS * Math.pow(2, retryCount - 1))) } } } - for (const { path, newHash } of successfullyProcessedForUpsert) { - if (newHash) { - this.cacheManager.updateHash(path, newHash) - } - batchResults.push({ path, status: "success" }) + for (const item of successfullyProcessedForUpsert) { + if (item.newHash) this.cacheManager.updateHash(item.path, item.newHash) + batchResults.push({ path: item.path, status: "success", newHash: item.newHash }) } } catch (error) { const err = error as Error - overallBatchError = overallBatchError || err + overallBatchError = err this.emitError("file-watcher:batch_upsert_error", err) log.error("batch upsert error", { error: sanitizeErrorMessage(err.message), @@ -460,13 +497,13 @@ export class FileWatcher implements IFileWatcher { errorType: "batch_upsert_error", affectedFiles: successfullyProcessedForUpsert.length, }) - for (const { path } of successfullyProcessedForUpsert) { - batchResults.push({ path, status: "error", error: err }) + for (const item of successfullyProcessedForUpsert) { + batchResults.push({ path: item.path, status: "error", error: err }) } } - } else if (overallBatchError && pointsForBatchUpsert.length > 0) { - for (const { path } of successfullyProcessedForUpsert) { - batchResults.push({ path, status: "error", error: overallBatchError }) + } else { + for (const item of successfullyProcessedForUpsert) { + batchResults.push({ path: item.path, status: "error", error: overallBatchError }) } } @@ -497,16 +534,33 @@ export class FileWatcher implements IFileWatcher { // Categorize events const pathsToExplicitlyDelete: string[] = [] const filesToUpsertDetails: Array<{ path: string; originalType: "create" | "change" }> = [] + const reverts = new Map() for (const event of eventsToProcess.values()) { if (event.type === "delete") { pathsToExplicitlyDelete.push(event.path) - } else { - filesToUpsertDetails.push({ - path: event.path, - originalType: event.type, - }) + continue } + + const cached = this.cacheManager.getHash(event.path) + const hash = await readFile(event.path, "utf-8") + .then((content) => createHash("sha256").update(content).digest("hex")) + .catch(() => undefined) + if (cached && hash === cached) { + batchResults.push({ path: event.path, status: "success", newHash: cached }) + processedCountInBatch++ + continue + } + if (hash && hash === this.overlay?.baselineHash(event.path)) { + pathsToExplicitlyDelete.push(event.path) + reverts.set(event.path, hash) + continue + } + + filesToUpsertDetails.push({ + path: event.path, + originalType: event.type, + }) } log.info("processing file watcher batch", { @@ -526,6 +580,9 @@ export class FileWatcher implements IFileWatcher { ) overallBatchError = deletionError processedCountInBatch = deletionCount + if (!deletionError) { + for (const [filePath, hash] of reverts) this.cacheManager.updateHash(filePath, hash) + } // Phase 2: Process files and prepare upserts const { @@ -549,6 +606,16 @@ export class FileWatcher implements IFileWatcher { overallBatchError, ) + const resultError = batchResults.find((item) => item.status === "error" || item.status === "local_error")?.error + overallBatchError ??= resultError + await this.cacheManager.flush() + + for (const event of eventsToProcess.values()) { + const result = batchResults.findLast((item) => item.path === event.path) + if (result?.status !== "success") continue + this.overlay?.settle(event.path, this.cacheManager.getHash(event.path), this.accumulatedEvents.has(event.path)) + } + // Finalize this.onDidFinishBatchProcessing.fire({ processedFiles: batchResults, diff --git a/packages/kilo-indexing/src/indexing/processors/scanner.ts b/packages/kilo-indexing/src/indexing/processors/scanner.ts index 0c137589689..6f96096fe93 100644 --- a/packages/kilo-indexing/src/indexing/processors/scanner.ts +++ b/packages/kilo-indexing/src/indexing/processors/scanner.ts @@ -295,6 +295,10 @@ export class DirectoryScanner implements IDirectoryScanner { onFileParsed?.() processedCount++ + if (!isNewFile && this.vectorStore) { + await this.vectorStore.deletePointsByMultipleFilePaths([filePath]) + } + // Process embeddings if configured if (this.embedder && this.vectorStore && blocks.length > 0) { // Add to batch accumulators @@ -303,7 +307,7 @@ export class DirectoryScanner implements IDirectoryScanner { const info = { filePath, fileHash: currentFileHash, - isNew: isNewFile, + isNew: true, } for (const block of blocks) { if (this._cancelled) break diff --git a/packages/kilo-indexing/src/indexing/search-service.ts b/packages/kilo-indexing/src/indexing/search-service.ts index 954ba7e0ea7..71da8834985 100644 --- a/packages/kilo-indexing/src/indexing/search-service.ts +++ b/packages/kilo-indexing/src/indexing/search-service.ts @@ -5,6 +5,12 @@ import type { IVectorStore } from "./interfaces/vector-store" import type { CodeIndexConfigManager } from "./config-manager" import type { CodeIndexStateManager } from "./state-manager" import { Log } from "../util/log" +import type { WorktreeOverlay } from "./worktree-overlay" + +interface BaselineSearch { + store: IVectorStore + overlay: WorktreeOverlay +} const log = Log.create({ service: "indexing-search" }) @@ -14,6 +20,7 @@ export class CodeIndexSearchService { private readonly stateManager: CodeIndexStateManager, private readonly embedder: IEmbedder, private readonly vectorStore: IVectorStore, + private readonly baseline?: BaselineSearch, ) {} public async searchIndex(query: string, directoryPrefix?: string): Promise { @@ -37,7 +44,47 @@ export class CodeIndexSearchService { } const normalizedPrefix = directoryPrefix ? path.normalize(directoryPrefix) : undefined - return await this.vectorStore.search(vector, normalizedPrefix, minScore, maxResults) + if (!this.baseline) return await this.vectorStore.search(vector, normalizedPrefix, minScore, maxResults) + if (!this.baseline.overlay.ready) throw new Error("Worktree index reconciliation is not complete.") + + const ceiling = Math.max(maxResults, Math.min(maxResults * 16, 1000)) + const delta = (async () => { + const search = async (limit: number): Promise => { + const results = await this.vectorStore.search(vector, normalizedPrefix, minScore, limit) + const filtered = results.filter((result) => this.baseline!.overlay.deltaResult(result)) + if (filtered.length >= maxResults || results.length < limit || limit >= ceiling) return filtered + return search(Math.min(limit * 2, ceiling)) + } + return search(maxResults) + })() + const base = (async () => { + const checks = new Map>() + const search = async (limit: number): Promise => { + const results = await this.baseline!.store.search(vector, normalizedPrefix, minScore, limit) + const accepted = await Promise.all( + results.map((result) => this.baseline!.overlay.baselineResult(result, checks)), + ) + const filtered = results.filter((_, index) => accepted[index]) + if (filtered.length >= maxResults || results.length < limit || limit >= ceiling) return filtered + return search(Math.min(limit * 2, ceiling)) + } + return search(maxResults) + })() + const [baseline, current] = await Promise.all([base, delta]) + const merged = new Map() + const key = (result: VectorStoreSearchResult) => + [ + result.payload?.filePath, + result.payload?.startLine, + result.payload?.endLine, + result.payload?.codeChunk, + ].join("\0") + + for (const result of baseline) merged.set(key(result), result) + for (const result of current) { + if (this.baseline.overlay.deltaResult(result)) merged.set(key(result), result) + } + return [...merged.values()].sort((left, right) => right.score - left.score).slice(0, maxResults) } catch (err) { log.error("search failed", { err }) throw err diff --git a/packages/kilo-indexing/src/indexing/service-factory.ts b/packages/kilo-indexing/src/indexing/service-factory.ts index 9dbaac9c8a1..dd69d7383d8 100644 --- a/packages/kilo-indexing/src/indexing/service-factory.ts +++ b/packages/kilo-indexing/src/indexing/service-factory.ts @@ -170,7 +170,7 @@ export class CodeIndexServiceFactory { } } - public createVectorStore(): IVectorStore { + public createVectorStore(workspacePath = this.workspacePath): IVectorStore { const config = this.configManager.getConfig() const profile = resolveEmbeddingProfile(config.embedderProvider, config.modelId, config.modelDimension) @@ -192,7 +192,7 @@ export class CodeIndexServiceFactory { vectorSize: profile.dimension, dbDir, }) - return new LanceDBVectorStore(this.workspacePath, profile.dimension, dbDir, profile) + return new LanceDBVectorStore(workspacePath, profile.dimension, dbDir, profile) } if (!config.qdrantUrl) throw new Error("Qdrant URL is required.") @@ -202,7 +202,7 @@ export class CodeIndexServiceFactory { model: profile.modelId, vectorSize: profile.dimension, }) - return new QdrantVectorStore(this.workspacePath, config.qdrantUrl, profile.dimension, config.qdrantApiKey, profile) + return new QdrantVectorStore(workspacePath, config.qdrantUrl, profile.dimension, config.qdrantApiKey, profile) } public createDirectoryScanner( diff --git a/packages/kilo-indexing/src/indexing/vector-store/lancedb-vector-store.ts b/packages/kilo-indexing/src/indexing/vector-store/lancedb-vector-store.ts index fc607371f9c..c5405d6dafc 100644 --- a/packages/kilo-indexing/src/indexing/vector-store/lancedb-vector-store.ts +++ b/packages/kilo-indexing/src/indexing/vector-store/lancedb-vector-store.ts @@ -251,6 +251,25 @@ export class LanceDBVectorStore implements IVectorStore { ) } + async openExisting(): Promise { + if (!fs.existsSync(this.dbPath)) throw new Error("Baseline LanceDB store does not exist") + + const db = await this.getDb() + const tables = await db.tableNames() + if (!tables.includes(this.vectorTableName) || !tables.includes(this.metadataTableName)) { + throw new Error("Baseline LanceDB store is incomplete") + } + + const profile = await this._getStoredEmbeddingProfile(db) + if (!profile || !this._isEmbeddingProfileMatch(profile)) { + throw new Error("Baseline LanceDB embedding profile does not match the worktree") + } + + const complete = await this._getMetadataValue(db, KEY.complete) + if (String(complete) !== "true") throw new Error("Baseline LanceDB index is not complete") + this.table = await db.openTable(this.vectorTableName) + } + async initialize(): Promise { try { await this.closeConnect() @@ -526,6 +545,10 @@ export class LanceDBVectorStore implements IVectorStore { } } + async close(): Promise { + await this.closeConnect() + } + private async closeConnect(): Promise { if (this.table) { this.table = null diff --git a/packages/kilo-indexing/src/indexing/vector-store/qdrant-client.ts b/packages/kilo-indexing/src/indexing/vector-store/qdrant-client.ts index 44b45240966..7082a0a4062 100644 --- a/packages/kilo-indexing/src/indexing/vector-store/qdrant-client.ts +++ b/packages/kilo-indexing/src/indexing/vector-store/qdrant-client.ts @@ -240,6 +240,27 @@ export class QdrantVectorStore implements IVectorStore { return true } + async openExisting(): Promise { + const info = await this.getCollectionInfo() + if (!info) throw new Error("Baseline Qdrant collection does not exist") + + const vectors = info.config?.params?.vectors + const size = + typeof vectors === "number" + ? vectors + : vectors && typeof vectors === "object" && "size" in vectors && typeof vectors.size === "number" + ? vectors.size + : 0 + if (size !== this.vectorSize) throw new Error("Baseline Qdrant vector dimension does not match the worktree") + + const payload = await this.getMetadataPayload() + const profile = this.getStoredProfile(payload) + if (!profile || !this.isProfileMatch(profile)) { + throw new Error("Baseline Qdrant embedding profile does not match the worktree") + } + if (payload?.[KEY.complete] !== true) throw new Error("Baseline Qdrant index is not complete") + } + /** * Initializes the vector store * @returns Promise resolving to boolean indicating if a new collection was created diff --git a/packages/kilo-indexing/src/indexing/worktree-overlay.ts b/packages/kilo-indexing/src/indexing/worktree-overlay.ts new file mode 100644 index 00000000000..28efc0fd298 --- /dev/null +++ b/packages/kilo-indexing/src/indexing/worktree-overlay.ts @@ -0,0 +1,105 @@ +import { createHash } from "crypto" +import { readFile } from "fs/promises" +import path from "path" +import type { VectorStoreSearchResult } from "./interfaces/vector-store" + +const normalize = (value: string) => value.replaceAll("\\", "/") + +export class WorktreeOverlay { + readonly shadows = new Set() + readonly blocked = new Set() + ready = false + + constructor( + readonly workspacePath: string, + readonly baselinePath: string, + readonly baseline: ReadonlyMap, + ) {} + + relative(filePath: string): string | undefined { + const rel = path.isAbsolute(filePath) ? path.relative(this.workspacePath, filePath) : filePath + if (!rel || rel === ".." || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) return + return normalize(path.normalize(rel)) + } + + baselineHash(filePath: string): string | undefined { + const rel = this.relative(filePath) + if (!rel) return + return this.baseline.get(rel) + } + + seed(): Record { + return Object.fromEntries( + [...this.baseline].map(([filePath, hash]) => [path.join(this.workspacePath, ...filePath.split("/")), hash]), + ) + } + + prepare(): void { + this.ready = false + this.shadows.clear() + this.blocked.clear() + } + + block(filePath: string): void { + const rel = this.relative(filePath) + if (rel) this.blocked.add(rel) + } + + settle(filePath: string, hash: string | undefined, pending = false): void { + const rel = this.relative(filePath) + if (!rel) return + + const baseline = this.baseline.get(rel) + if (baseline !== undefined && baseline !== hash) this.shadows.add(rel) + if (baseline === undefined || baseline === hash) this.shadows.delete(rel) + if (!pending) this.blocked.delete(rel) + } + + reconcile(current: Readonly>): void { + this.shadows.clear() + for (const [filePath, hash] of this.baseline) { + const absolute = path.join(this.workspacePath, ...filePath.split("/")) + if (current[absolute] !== hash) this.shadows.add(filePath) + } + this.ready = true + } + + async baselineResult( + result: VectorStoreSearchResult, + checks: Map>, + ): Promise { + const filePath = result.payload?.filePath + if (typeof filePath !== "string") return false + const rel = this.relative(filePath) + if (!rel || this.shadows.has(rel) || this.blocked.has(rel)) return false + + const expected = this.baseline.get(rel) + if (!expected) return false + const existing = checks.get(rel) + const valid = + existing ?? + Promise.all([ + readFile(path.join(this.baselinePath, ...rel.split("/")), "utf-8"), + readFile(path.join(this.workspacePath, ...rel.split("/")), "utf-8"), + ]) + .then(([baseline, current]) => { + const hash = (content: string) => createHash("sha256").update(content).digest("hex") + return { valid: hash(baseline) === expected && hash(current) === expected, content: current } + }) + .catch(() => ({ valid: false, content: "" })) + checks.set(rel, valid) + const checked = await valid + if (!checked.valid) return false + + const chunk = result.payload?.codeChunk + return typeof chunk === "string" && checked.content.includes(chunk) + } + + deltaResult(result: VectorStoreSearchResult): boolean { + const filePath = result.payload?.filePath + if (typeof filePath !== "string") return false + const rel = this.relative(filePath) + if (!rel) return false + return !this.blocked.has(rel) + } +} diff --git a/packages/kilo-indexing/test/kilocode/indexing/cache-manager.test.ts b/packages/kilo-indexing/test/kilocode/indexing/cache-manager.test.ts new file mode 100644 index 00000000000..11c6126ac59 --- /dev/null +++ b/packages/kilo-indexing/test/kilocode/indexing/cache-manager.test.ts @@ -0,0 +1,26 @@ +import { describe, expect, test } from "bun:test" +import { mkdtemp } from "fs/promises" +import { tmpdir } from "os" +import path from "path" +import { CacheManager } from "../../../src/indexing/cache-manager" + +describe("CacheManager", () => { + test("flushes a stable signature used to detect baseline changes", async () => { + const cacheDir = await mkdtemp(path.join(tmpdir(), "index-cache-")) + const workspace = path.join(cacheDir, "workspace") + const first = new CacheManager(cacheDir, workspace) + await first.initialize() + first.seedHashes({ + [path.join(workspace, "b.ts")]: "b", + [path.join(workspace, "a.ts")]: "a", + }) + await first.flush() + + const second = new CacheManager(cacheDir, workspace) + await second.initialize() + expect(second.signature()).toBe(first.signature()) + + second.updateHash(path.join(workspace, "a.ts"), "changed") + expect(second.signature()).not.toBe(first.signature()) + }) +}) diff --git a/packages/kilo-indexing/test/kilocode/indexing/manager.test.ts b/packages/kilo-indexing/test/kilocode/indexing/manager.test.ts index cbffc433eac..4999d52df4d 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/manager.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/manager.test.ts @@ -245,6 +245,30 @@ describe("CodeIndexManager", () => { expect(calls).toBe(1) }) + test("schedules auto-recovery for watcher failures", async () => { + const mgr = new CodeIndexManager("/tmp/ws", "/tmp/cache") + const data = createData(mgr) + let calls = 0 + + data._recreateServices = async () => { + data._orchestrator = { + state: "Standby", + stopWatcher() {}, + async startIndexing() { + calls += 1 + this.state = "Indexed" + data._stateManager.setSystemState("Indexed", "done") + }, + } + data._searchService = {} + } + + data.handleTelemetry(createStartError("orchestrator:watcher")) + await data._retryTask + + expect(calls).toBe(1) + }) + test("ignores non-orchestrator telemetry errors for auto-recovery", async () => { const mgr = new CodeIndexManager("/tmp/ws", "/tmp/cache") const data = createData(mgr) @@ -323,30 +347,24 @@ describe("CodeIndexManager", () => { expect(mgr.getCurrentStatus().systemStatus).toBe("Indexed") }) - test("dispose calls cancelIndexing on orchestrator", () => { + test("dispose waits for orchestrator shutdown", async () => { const mgr = new CodeIndexManager("/tmp/ws", "/tmp/cache") - let cancel = 0 - let stop = 0 + let shutdown = 0 const data = mgr as unknown as { _orchestrator?: { - stopWatcher(): void - cancelIndexing(): void + shutdown(): Promise } } data._orchestrator = { - stopWatcher() { - stop += 1 - }, - cancelIndexing() { - cancel += 1 + async shutdown() { + shutdown += 1 }, } - mgr.dispose() + await mgr.dispose() - expect(cancel).toBe(1) - expect(stop).toBe(0) + expect(shutdown).toBe(1) }) test("dispose during service recreation cancels the recreated orchestrator", async () => { @@ -386,7 +404,7 @@ describe("CodeIndexManager", () => { const init = mgr.initialize(createInput({ openAiKey: "sk-test" })) await new Promise((resolve) => setTimeout(resolve, 0)) - mgr.dispose() + await mgr.dispose() gate.resolve() await init @@ -414,7 +432,7 @@ describe("CodeIndexManager", () => { const task = data.handleTelemetry(createStartError()) await new Promise((resolve) => setTimeout(resolve, 0)) - mgr.dispose() + await mgr.dispose() gate.resolve() await data._retryTask diff --git a/packages/kilo-indexing/test/kilocode/indexing/orchestrator.test.ts b/packages/kilo-indexing/test/kilocode/indexing/orchestrator.test.ts index 7ce180ff553..d2f43697190 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/orchestrator.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/orchestrator.test.ts @@ -17,6 +17,7 @@ import { Emitter } from "../../../src/indexing/runtime" class Store { public clearCount = 0 + public closeCount = 0 public deleteCount = 0 constructor( @@ -47,6 +48,9 @@ class Store { async deleteCollection(): Promise { this.deleteCount += 1 } + async close(): Promise { + this.closeCount += 1 + } async collectionExists(): Promise { return true } @@ -117,6 +121,27 @@ class Watcher { } } +class BlockingScanner { + public isCancelled = false + public finished = false + private readonly gate = Promise.withResolvers() + readonly started = Promise.withResolvers() + + async scanDirectory(): Promise<{ stats: { processed: number; skipped: number }; totalBlockCount: number }> { + this.started.resolve() + await this.gate.promise + this.finished = true + return { stats: { processed: 0, skipped: 0 }, totalBlockCount: 0 } + } + + cancel(): void { + this.isCancelled = true + this.gate.resolve() + } + + updateBatchSegmentThreshold(_newThreshold: number): void {} +} + class FailScanner { public readonly isCancelled = false @@ -225,6 +250,28 @@ describe("CodeIndexOrchestrator telemetry", () => { expect(orchestrator.state).not.toBe("Indexing") }) + test("shutdown waits for an active scan before closing the store", async () => { + const scanner = new BlockingScanner() + const store = new Store(false) + const orchestrator = new CodeIndexOrchestrator( + createConfig(), + new CodeIndexStateManager(), + "/tmp/ws", + { async clearCacheFile() {}, async flush() {} } as unknown as CacheManager, + store as unknown as IVectorStore, + scanner as unknown as DirectoryScanner, + new Watcher() as unknown as IFileWatcher, + ) + + const active = orchestrator.startIndexing("background") + await scanner.started.promise + await orchestrator.shutdown() + await active + + expect(scanner.finished).toBe(true) + expect(store.closeCount).toBe(1) + }) + test("preserves cache and collection data on retryable start failures", async () => { const events: IndexingTelemetryEvent[] = [] const cache = { diff --git a/packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts b/packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts index 2a66a389abe..688fbb37a2d 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts @@ -2,6 +2,7 @@ import { describe, test, expect } from "bun:test" import { mkdtemp, mkdir, writeFile } from "fs/promises" import { tmpdir } from "os" import path from "path" +import { createHash } from "crypto" import { v5 as uuidv5 } from "uuid" import { CacheManager } from "../../../../src/indexing/cache-manager" import { QDRANT_CODE_BLOCK_NAMESPACE } from "../../../../src/indexing/constants" @@ -14,6 +15,7 @@ import type { } from "../../../../src/indexing/interfaces" import { FileWatcher } from "../../../../src/indexing/processors/file-watcher" import { loadIgnore } from "../../../../src/indexing/shared/load-ignore" +import { WorktreeOverlay } from "../../../../src/indexing/worktree-overlay" function createEmbedder(): IEmbedder { return { @@ -32,6 +34,8 @@ function createEmbedder(): IEmbedder { } class RetryStore implements IVectorStore { + public readonly points: PointStruct[] = [] + constructor(private readonly fail: number) {} private calls = 0 @@ -40,11 +44,12 @@ class RetryStore implements IVectorStore { return false } - async upsertPoints(_points: PointStruct[]): Promise { + async upsertPoints(points: PointStruct[]): Promise { this.calls += 1 if (this.calls <= this.fail) { throw new Error("watcher upsert failure for /tmp/watcher/path.ts") } + this.points.push(...points) } async search( @@ -214,6 +219,86 @@ describe("FileWatcher", () => { expect(error?.error).toContain("[REDACTED_PATH]") }) + test("updates worktree shadows when a baseline file changes and reverts", async () => { + const root = await mkdtemp(path.join(tmpdir(), "file-watcher-test-")) + const cacheDir = path.join(root, ".cache") + const file = path.join(root, "file.ts") + const baseline = "export const baseline = '" + "x".repeat(100) + "'\n" + const changed = "export const changed = '" + "y".repeat(100) + "'\n" + const baselineHash = createHash("sha256").update(baseline).digest("hex") + + await mkdir(cacheDir, { recursive: true }) + await writeFile(file, changed) + + const cache = new CacheManager(cacheDir, root) + await cache.initialize() + cache.seedHashes({ [file]: baselineHash }) + const overlay = new WorktreeOverlay(root, path.join(root, "baseline"), new Map([["file.ts", baselineHash]])) + const store = new RetryStore(0) + const watcher = new FileWatcher(root, cache, createEmbedder(), store) + watcher.setOverlay(overlay) + const data = watcher as unknown as { + processBatch(events: Map): Promise + } + + overlay.block(file) + await data.processBatch(new Map([[file, { path: file, type: "change" }]])) + + expect(overlay.shadows.has("file.ts")).toBe(true) + expect(overlay.blocked.has("file.ts")).toBe(false) + expect(cache.getHash(file)).toBe(createHash("sha256").update(changed).digest("hex")) + + await writeFile(file, baseline) + overlay.block(file) + await data.processBatch(new Map([[file, { path: file, type: "change" }]])) + + expect(overlay.shadows.has("file.ts")).toBe(false) + expect(overlay.blocked.has("file.ts")).toBe(false) + expect(cache.getHash(file)).toBe(baselineHash) + expect(store.points.length).toBeGreaterThan(0) + const count = store.points.length + + await writeFile(file, changed) + overlay.block(file) + await data.processBatch(new Map([[file, { path: file, type: "change" }]])) + await writeFile(file, baseline) + overlay.block(file) + await data.processBatch(new Map([[file, { path: file, type: "change" }]])) + + expect(store.points).toHaveLength(count * 2) + }) + + test("reports unexpected drain failures for recovery", async () => { + const root = await mkdtemp(path.join(tmpdir(), "file-watcher-test-")) + const cacheDir = path.join(root, ".cache") + const file = path.join(root, "file.ts") + await mkdir(cacheDir, { recursive: true }) + await writeFile(file, "export const value = '" + "x".repeat(100) + "'\n") + + const cache = new CacheManager(cacheDir, root) + await cache.initialize() + cache.flush = async () => { + throw new Error("cache flush failed") + } + const watcher = new FileWatcher(root, cache, createEmbedder(), new RetryStore(0)) + const summary = new Promise<{ batchError?: Error }>((resolve) => { + watcher.onDidFinishBatchProcessing.on(resolve) + }) + const data = watcher as unknown as { + handleFileEvent(filePath: string, type: "create" | "change" | "delete"): void + } + + watcher.setCollecting(true) + data.handleFileEvent(file, "create") + const result = await Promise.race([ + summary, + new Promise((_, reject) => setTimeout(() => reject(new Error("watcher did not report failure")), 2000)), + ]) + + expect(result.batchError?.message).toBe("cache flush failed") + await watcher.shutdown() + }) + test("processFile skips files matched by .kilocodeignore during incremental updates", async () => { const root = await mkdtemp(path.join(tmpdir(), "file-watcher-test-")) const cacheDir = path.join(root, ".cache") diff --git a/packages/kilo-indexing/test/kilocode/indexing/processors/scanner.test.ts b/packages/kilo-indexing/test/kilocode/indexing/processors/scanner.test.ts index 2b3a1a0ffe2..65293df016b 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/processors/scanner.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/processors/scanner.test.ts @@ -172,6 +172,40 @@ class RetryStore extends Store { } describe("DirectoryScanner", () => { + test("uses seeded baseline hashes to index only worktree changes", async () => { + const root = await mkdtemp(join(tmpdir(), "scanner-test-")) + const cacheDir = await mkdtemp(join(tmpdir(), "scanner-cache-")) + const same = join(root, "same.ts") + const changed = join(root, "changed.ts") + const added = join(root, "added.ts") + const deleted = join(root, "deleted.ts") + const sameContent = "export const same = 1\n" + const changedContent = "export const changed = 2\n" + + await Bun.write(same, sameContent) + await Bun.write(changed, changedContent) + await Bun.write(added, "export const added = 1\n") + + const cache = new CacheManager(cacheDir, root) + await cache.initialize() + cache.seedHashes({ + [same]: createHash("sha256").update(sameContent).digest("hex"), + [changed]: "baseline-changed", + [deleted]: "baseline-deleted", + }) + + const store = new Store() + const scan = new DirectoryScanner(new Emb(), store, new Parser(), cache, ignore(), 1, 1) + const result = await scan.scanDirectory(root) + + expect(result.stats.processed).toBe(2) + expect(store.points).toBe(2) + expect(cache.getHash(same)).toBe(createHash("sha256").update(sameContent).digest("hex")) + expect(cache.getHash(changed)).toBe(createHash("sha256").update(changedContent).digest("hex")) + expect(cache.getHash(added)).toBeDefined() + expect(cache.getHash(deleted)).toBeUndefined() + }) + test("keeps file metadata when threshold flush is triggered by that file", async () => { const root = await mkdtemp(join(tmpdir(), "scanner-test-")) const cacheDir = await mkdtemp(join(tmpdir(), "scanner-cache-")) diff --git a/packages/kilo-indexing/test/kilocode/indexing/search-service.test.ts b/packages/kilo-indexing/test/kilocode/indexing/search-service.test.ts new file mode 100644 index 00000000000..1c83734ce0a --- /dev/null +++ b/packages/kilo-indexing/test/kilocode/indexing/search-service.test.ts @@ -0,0 +1,171 @@ +import { describe, expect, test } from "bun:test" +import { createHash } from "crypto" +import { mkdir, mkdtemp, writeFile } from "fs/promises" +import { tmpdir } from "os" +import path from "path" +import { CodeIndexConfigManager } from "../../../src/indexing/config-manager" +import type { IEmbedder, IVectorStore, VectorStoreSearchResult } from "../../../src/indexing/interfaces" +import { CodeIndexSearchService } from "../../../src/indexing/search-service" +import { CodeIndexStateManager } from "../../../src/indexing/state-manager" +import { WorktreeOverlay } from "../../../src/indexing/worktree-overlay" + +const result = (filePath: string, score: number, codeChunk = filePath): VectorStoreSearchResult => ({ + id: `${filePath}:${score}`, + score, + payload: { filePath, codeChunk, startLine: 1, endLine: 1 }, +}) + +const store = (results: VectorStoreSearchResult[], limits: number[]): IVectorStore => + ({ + async search(_vector, _prefix, _score, limit) { + limits.push(limit ?? results.length) + return results.slice(0, limit) + }, + }) as unknown as IVectorStore + +const embedder = (calls: string[][]): IEmbedder => ({ + async createEmbeddings(texts) { + calls.push(texts) + return { embeddings: [[1, 2, 3]] } + }, + async validateConfiguration() { + return { valid: true } + }, + get embedderInfo() { + return { name: "openai" } + }, +}) + +const config = () => + new CodeIndexConfigManager({ + enabled: true, + embedderProvider: "openai", + openAiKey: "test", + vectorStoreProvider: "lancedb", + searchMaxResults: 2, + }) + +describe("CodeIndexSearchService worktree search", () => { + test("embeds once, hides baseline paths, and merges the current delta", async () => { + const root = await mkdtemp(path.join(tmpdir(), "search-worktree-")) + const main = await mkdtemp(path.join(tmpdir(), "search-main-")) + const content = "src/base.ts" + const hash = createHash("sha256").update(content).digest("hex") + await mkdir(path.join(root, "src"), { recursive: true }) + await mkdir(path.join(main, "src"), { recursive: true }) + await writeFile(path.join(root, "src/base.ts"), content) + await writeFile(path.join(main, "src/base.ts"), content) + const overlay = new WorktreeOverlay( + root, + main, + new Map([ + ["src/changed.ts", "base"], + ["src/base.ts", hash], + ]), + ) + overlay.reconcile({ + [path.join(root, "src/changed.ts")]: "changed", + [path.join(root, "src/base.ts")]: hash, + }) + + const baseLimits: number[] = [] + const deltaLimits: number[] = [] + const calls: string[][] = [] + const state = new CodeIndexStateManager() + state.setSystemState("Indexed") + const service = new CodeIndexSearchService( + config(), + state, + embedder(calls), + store([result("src/changed.ts", 0.9, "worktree")], deltaLimits), + { + store: store([result("src/changed.ts", 0.99), result("src/base.ts", 0.8)], baseLimits), + overlay, + }, + ) + + const results = await service.searchIndex("query") + + expect(calls).toEqual([["query"]]) + expect(results.map((item) => item.payload?.codeChunk)).toEqual(["worktree", "src/base.ts"]) + expect(baseLimits).toEqual([2, 4]) + expect(deltaLimits).toEqual([2]) + }) + + test("rejects live baseline results after the primary checkout changes", async () => { + const root = await mkdtemp(path.join(tmpdir(), "search-worktree-")) + const main = await mkdtemp(path.join(tmpdir(), "search-main-")) + const content = "export const value = 'worktree'" + const hash = createHash("sha256").update(content).digest("hex") + await writeFile(path.join(root, "file.ts"), content) + await writeFile(path.join(main, "file.ts"), "export const value = 'primary-only'") + + const overlay = new WorktreeOverlay(root, main, new Map([["file.ts", hash]])) + overlay.reconcile({ [path.join(root, "file.ts")]: hash }) + const state = new CodeIndexStateManager() + state.setSystemState("Indexed") + const service = new CodeIndexSearchService(config(), state, embedder([]), store([], []), { + store: store([result("file.ts", 0.99, "export const value = 'primary-only'")], []), + overlay, + }) + + expect(await service.searchIndex("query")).toEqual([]) + }) + + test("over-fetches when shadowed baseline results consume the first page", async () => { + const root = await mkdtemp(path.join(tmpdir(), "search-worktree-")) + const main = await mkdtemp(path.join(tmpdir(), "search-main-")) + const hash = (content: string) => createHash("sha256").update(content).digest("hex") + await mkdir(path.join(root, "src"), { recursive: true }) + await mkdir(path.join(main, "src"), { recursive: true }) + await Promise.all( + ["c", "d"].flatMap((name) => [ + writeFile(path.join(root, `src/${name}.ts`), `src/${name}.ts`), + writeFile(path.join(main, `src/${name}.ts`), `src/${name}.ts`), + ]), + ) + const overlay = new WorktreeOverlay( + root, + main, + new Map([ + ["src/a.ts", "a"], + ["src/b.ts", "b"], + ["src/c.ts", hash("src/c.ts")], + ["src/d.ts", hash("src/d.ts")], + ]), + ) + overlay.reconcile({ + [path.join(root, "src/a.ts")]: "changed-a", + [path.join(root, "src/b.ts")]: "changed-b", + [path.join(root, "src/c.ts")]: hash("src/c.ts"), + [path.join(root, "src/d.ts")]: hash("src/d.ts"), + }) + + const limits: number[] = [] + const state = new CodeIndexStateManager() + state.setSystemState("Indexed") + const service = new CodeIndexSearchService( + config(), + state, + embedder([]), + store([], []), + { + store: store( + [ + result("src/a.ts", 0.99), + result("src/b.ts", 0.98), + result("src/c.ts", 0.8), + result("src/d.ts", 0.7), + ], + limits, + ), + overlay, + }, + ) + + const results = await service.searchIndex("query") + + expect(limits).toEqual([2, 4]) + expect(results.map((item) => item.payload?.filePath)).toEqual(["src/c.ts", "src/d.ts"]) + }) +}) diff --git a/packages/kilo-indexing/test/kilocode/indexing/vector-store/lancedb-vector-store.test.ts b/packages/kilo-indexing/test/kilocode/indexing/vector-store/lancedb-vector-store.test.ts index 0b47cd6830a..7355abb8301 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/vector-store/lancedb-vector-store.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/vector-store/lancedb-vector-store.test.ts @@ -176,6 +176,22 @@ describe("LocalVectorStore", () => { }) describe("initialize", () => { + test("opens a complete compatible baseline without mutating it", async () => { + spyOn(fs, "existsSync").mockReturnValue(true as any) + store["_getStoredEmbeddingProfile"] = mock().mockResolvedValue({ + provider: "openai", + modelId: "", + dimension: vectorSize, + }) + store["_getMetadataValue"] = mock().mockResolvedValue("true") + + await store.openExisting() + + expect(mockDb.createTable).not.toHaveBeenCalled() + expect(mockDb.dropTable).not.toHaveBeenCalled() + expect(mockTable.delete).not.toHaveBeenCalled() + }) + test("should create tables if not exist", async () => { mockDb.tableNames.mockResolvedValue([]) mockDb.createTable.mockResolvedValue(mockTable) diff --git a/packages/kilo-indexing/test/kilocode/indexing/vector-store/qdrant-client.test.ts b/packages/kilo-indexing/test/kilocode/indexing/vector-store/qdrant-client.test.ts index 8a580bcde83..36739fb3556 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/vector-store/qdrant-client.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/vector-store/qdrant-client.test.ts @@ -1,4 +1,5 @@ import { describe, test, expect, beforeEach, afterEach, mock } from "bun:test" +import { createHash } from "crypto" import { DEFAULT_MAX_SEARCH_RESULTS, DEFAULT_SEARCH_MIN_SCORE } from "../../../../src/indexing/constants" @@ -29,14 +30,6 @@ mock.module("@qdrant/js-client-rest", () => ({ QdrantClient: MockQdrantClientConstructor, })) -const mockDigest = mock() -const mockUpdate = mock(() => ({ update: mockUpdate, digest: mockDigest })) -const mockCreateHash = mock(() => ({ update: mockUpdate, digest: mockDigest })) - -mock.module("crypto", () => ({ - createHash: mockCreateHash, -})) - // Now import the module under test import { QdrantVectorStore } from "../../../../src/indexing/vector-store/qdrant-client" @@ -46,8 +39,7 @@ describe("QdrantVectorStore", () => { const mockQdrantUrl = "http://mock-qdrant:6333" const mockApiKey = "test-api-key" const mockVectorSize = 1536 - const mockHashedPath = "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" - const expectedCollectionName = `ws-${mockHashedPath.substring(0, 16)}` + const expectedCollectionName = `ws-${createHash("sha256").update(mockWorkspacePath).digest("hex").substring(0, 16)}` beforeEach(() => { // Reset all mocks @@ -61,14 +53,6 @@ describe("QdrantVectorStore", () => { mockQuery.mockReset() mockDelete.mockReset() mockRetrieve.mockReset() - mockCreateHash.mockReset() - mockUpdate.mockReset() - mockDigest.mockReset() - - // Mock crypto.createHash chain - mockCreateHash.mockReturnValue({ update: mockUpdate, digest: mockDigest }) - mockUpdate.mockReturnValue({ update: mockUpdate, digest: mockDigest }) - mockDigest.mockReturnValue(mockHashedPath) vectorStore = new QdrantVectorStore(mockWorkspacePath, mockQdrantUrl, mockVectorSize, mockApiKey) }) @@ -83,9 +67,6 @@ describe("QdrantVectorStore", () => { mockQuery.mockReset() mockDelete.mockReset() mockRetrieve.mockReset() - mockCreateHash.mockReset() - mockUpdate.mockReset() - mockDigest.mockReset() }) test("should correctly initialize QdrantClient and collectionName in constructor", () => { @@ -99,9 +80,6 @@ describe("QdrantVectorStore", () => { "User-Agent": "Kilo-Code", }, }) - expect(mockCreateHash).toHaveBeenCalledWith("sha256") - expect(mockUpdate).toHaveBeenCalledWith(mockWorkspacePath) - expect(mockDigest).toHaveBeenCalledWith("hex") expect((vectorStore as any).collectionName).toBe(expectedCollectionName) expect((vectorStore as any).vectorSize).toBe(mockVectorSize) }) @@ -524,6 +502,30 @@ describe("QdrantVectorStore", () => { }) describe("initialize", () => { + test("opens a complete compatible baseline without mutating it", async () => { + mockGetCollection.mockResolvedValue({ + points_count: 3, + config: { params: { vectors: { size: mockVectorSize } } }, + }) + mockRetrieve.mockResolvedValue([ + { + payload: { + indexing_complete: true, + embedding_provider: "openai", + embedding_model_id: "", + embedding_dimension: mockVectorSize, + }, + }, + ]) + + await vectorStore.openExisting() + + expect(mockCreateCollection).not.toHaveBeenCalled() + expect(mockDeleteCollection).not.toHaveBeenCalled() + expect(mockCreatePayloadIndex).not.toHaveBeenCalled() + expect(mockUpsert).not.toHaveBeenCalled() + }) + test("should create a new collection if none exists and return true", async () => { mockGetCollection.mockRejectedValue({ response: { status: 404 }, diff --git a/packages/kilo-indexing/test/kilocode/indexing/worktree-overlay.test.ts b/packages/kilo-indexing/test/kilocode/indexing/worktree-overlay.test.ts new file mode 100644 index 00000000000..9cc5920a813 --- /dev/null +++ b/packages/kilo-indexing/test/kilocode/indexing/worktree-overlay.test.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from "bun:test" +import path from "path" +import { WorktreeOverlay } from "../../../src/indexing/worktree-overlay" + +describe("WorktreeOverlay", () => { + test("seeds baseline hashes and shadows changed or deleted files", () => { + const root = path.resolve("/tmp/worktree") + const overlay = new WorktreeOverlay( + root, + path.resolve("/tmp/main"), + new Map([ + ["src/same.ts", "same"], + ["src/changed.ts", "base"], + ["src/deleted.ts", "deleted"], + ]), + ) + + expect(overlay.seed()).toEqual({ + [path.join(root, "src/same.ts")]: "same", + [path.join(root, "src/changed.ts")]: "base", + [path.join(root, "src/deleted.ts")]: "deleted", + }) + + overlay.reconcile({ + [path.join(root, "src/same.ts")]: "same", + [path.join(root, "src/changed.ts")]: "worktree", + }) + + expect(overlay.ready).toBe(true) + expect([...overlay.shadows].sort()).toEqual(["src/changed.ts", "src/deleted.ts"]) + }) + + test("blocks updates and restores baseline visibility after a revert", () => { + const root = path.resolve("/tmp/worktree") + const file = path.join(root, "src/file.ts") + const overlay = new WorktreeOverlay(root, path.resolve("/tmp/main"), new Map([["src/file.ts", "base"]])) + + overlay.block(file) + expect(overlay.blocked.has("src/file.ts")).toBe(true) + + overlay.settle(file, "changed") + expect(overlay.blocked.has("src/file.ts")).toBe(false) + expect(overlay.shadows.has("src/file.ts")).toBe(true) + + overlay.block(file) + overlay.settle(file, "base") + expect(overlay.blocked.has("src/file.ts")).toBe(false) + expect(overlay.shadows.has("src/file.ts")).toBe(false) + }) +}) diff --git a/packages/opencode/src/kilocode/indexing-worker-client.ts b/packages/opencode/src/kilocode/indexing-worker-client.ts index f99b54d7c8e..1cc67c36f4c 100644 --- a/packages/opencode/src/kilocode/indexing-worker-client.ts +++ b/packages/opencode/src/kilocode/indexing-worker-client.ts @@ -5,7 +5,7 @@ import type { } from "@kilocode/kilo-indexing/engine" import type { IndexingStatus } from "@kilocode/kilo-indexing/status" import { withTimeout } from "@/util/timeout" -import type { Log, Message, Request, Result } from "./indexing-worker-protocol" +import type { Event, Log, Message, Request, Result } from "./indexing-worker-protocol" import type { IndexingWarning } from "./indexing-warning" declare global { @@ -22,7 +22,7 @@ export namespace IndexingWorker { } export type Driver = { - init(input: IndexingConfigInput): Promise + init(input: IndexingConfigInput, baselineDirectory?: string): Promise search(query: string, directoryPrefix?: string): Promise dispose(): Promise } @@ -31,131 +31,170 @@ export namespace IndexingWorker { type Host = Driver & { use(hooks: Hooks): void + event(message: Event): void + fail(err: unknown): void + } + + type Outgoing = + | Omit, "id"> + | Omit, "id"> + | Omit, "id"> + + type Channel = { + task: Worker + pending: Map + hosts: Map + id: number + stopped: boolean } const pool = new Map() + let shared: Channel | undefined + + const channel = () => { + if (shared && !shared.stopped) return shared - const worker = (directory: string, root: string, hooks: Hooks): Host => { const file = typeof KILO_INDEXING_WORKER_PATH !== "undefined" ? KILO_INDEXING_WORKER_PATH : new URL("./indexing-worker.ts", import.meta.url) - const key = `${directory}\0${root}` - const task = new Worker(file, { ref: false }) - const pending = new Map() - let id = 0 - let stopped = false - let active = true - let callbacks = hooks - - const reject = (err: unknown) => { - for (const item of pending.values()) item.reject(err) - pending.clear() + const state: Channel = { + task: new Worker(file, { ref: false }), + pending: new Map(), + hosts: new Map(), + id: 0, + stopped: false, } const fail = (err: unknown) => { - if (stopped) return - stopped = true - active = false - reject(err) - if (pool.get(key) === host) pool.delete(key) - callbacks.failure(err) + if (state.stopped) return + state.stopped = true + for (const item of state.pending.values()) item.reject(err) + state.pending.clear() + for (const host of state.hosts.values()) host.fail(err) + state.hosts.clear() + pool.clear() + if (shared === state) shared = undefined } - task.onmessage = (event: MessageEvent) => { + state.task.onmessage = (event: MessageEvent) => { const message = event.data if (message.type === "event") { - if (stopped || !active) return - if (message.event === "status") callbacks.status(message.data) - if (message.event === "telemetry") callbacks.telemetry(message.data) - if (message.event === "warning") callbacks.warning(message.data) - if (message.event === "log") callbacks.log(message.data) + if (message.key) { + state.hosts.get(message.key)?.event(message) + return + } + for (const host of state.hosts.values()) host.event(message) return } - const request = pending.get(message.id) + const request = state.pending.get(message.id) if (!request) return - pending.delete(message.id) + state.pending.delete(message.id) if (message.ok) { request.resolve(message) return } request.reject(new Error(message.error)) } + state.task.onerror = (event) => fail(event.error ?? new Error(event.message)) + state.task.addEventListener("close", () => fail(new Error("Indexing worker exited."))) + shared = state + return state + } - task.onerror = (event) => { - fail(event.error ?? new Error(event.message)) - } - - task.addEventListener("close", () => { - if (pool.get(key) === host) pool.delete(key) - fail(new Error("Indexing worker exited.")) - }) - - const call = (request: Request, read: (message: Result) => T) => { - if (stopped) return Promise.reject(new Error("Indexing worker is unavailable.")) - return new Promise((resolve, reject) => { - pending.set(request.id, { - resolve(message) { - try { - resolve(read(message)) - } catch (err) { - reject(err) - } - }, - reject, - }) - task.postMessage(request) + const call = (state: Channel, request: Outgoing, read: (message: Result) => T) => { + if (state.stopped) return Promise.reject(new Error("Indexing worker is unavailable.")) + const id = state.id++ + const message: Request = { ...request, id } + return new Promise((resolve, reject) => { + state.pending.set(id, { + resolve(result) { + try { + resolve(read(result)) + } catch (err) { + reject(err) + } + }, + reject, }) - } + state.task.postMessage(message) + }) + } + + const worker = (directory: string, root: string, hooks: Hooks): Host => { + const key = `${directory}\0${root}` + const state = channel() + let active = true + let callbacks = hooks const host: Host = { use(next) { callbacks = next active = true + state.hosts.set(key, host) }, - init(config) { + event(message) { + if (!active) return + if (message.event === "status") callbacks.status(message.data) + if (message.event === "telemetry") callbacks.telemetry(message.data) + if (message.event === "warning") callbacks.warning(message.data) + if (message.event === "log") callbacks.log(message.data) + }, + fail(err) { + if (!active) return + active = false + callbacks.failure(err) + }, + init(config, baselineDirectory) { active = true - const request: Request = { - type: "request", - id: id++, - method: "init", - input: { - directory, - root, - config, - lancedbPath: process.env.KILO_LANCEDB_PATH, + state.hosts.set(key, host) + return call( + state, + { + type: "request", + key, + method: "init", + input: { + directory, + root, + config, + baselineDirectory, + lancedbPath: process.env.KILO_LANCEDB_PATH, + }, }, - } - return call(request, (message) => { - if (message.ok && message.method === "init") return message.value - throw new Error("Unexpected indexing worker init response.") - }) + (message) => { + if (message.ok && message.method === "init") return message.value + throw new Error("Unexpected indexing worker init response.") + }, + ) }, search(query, directoryPrefix) { - const request: Request = { type: "request", id: id++, method: "search", input: { query, directoryPrefix } } - return call(request, (message) => { - if (message.ok && message.method === "search") return message.value - throw new Error("Unexpected indexing worker search response.") - }) + return call( + state, + { type: "request", key, method: "search", input: { query, directoryPrefix } }, + (message) => { + if (message.ok && message.method === "search") return message.value + throw new Error("Unexpected indexing worker search response.") + }, + ) }, async dispose() { - if (stopped || !active) return + if (!active || state.stopped) return active = false - const request: Request = { type: "request", id: id++, method: "dispose", input: undefined } await withTimeout( - call(request, (message) => { + call(state, { type: "request", key, method: "dispose", input: undefined }, (message) => { if (message.ok && message.method === "dispose") return message.value throw new Error("Unexpected indexing worker dispose response.") }), - 1000, + 5000, "Indexing worker reset timed out", ).catch((err) => { - stopped = true - reject(err) + callbacks.failure(err) }) }, } + state.hosts.set(key, host) return host } diff --git a/packages/opencode/src/kilocode/indexing-worker-protocol.ts b/packages/opencode/src/kilocode/indexing-worker-protocol.ts index 4b3e63ba070..7e970f9a9d6 100644 --- a/packages/opencode/src/kilocode/indexing-worker-protocol.ts +++ b/packages/opencode/src/kilocode/indexing-worker-protocol.ts @@ -10,13 +10,20 @@ export type InitInput = { directory: string root: string config: IndexingConfigInput + baselineDirectory?: string lancedbPath?: string } export type Request = - | { type: "request"; id: number; method: "init"; input: InitInput } - | { type: "request"; id: number; method: "search"; input: { query: string; directoryPrefix?: string } } - | { type: "request"; id: number; method: "dispose"; input: undefined } + | { type: "request"; id: number; key: string; method: "init"; input: InitInput } + | { + type: "request" + id: number + key: string + method: "search" + input: { query: string; directoryPrefix?: string } + } + | { type: "request"; id: number; key: string; method: "dispose"; input: undefined } export type Result = | { type: "result"; id: number; method: "init"; ok: true; value: IndexingStatus } @@ -30,9 +37,9 @@ export type Log = { } export type Event = - | { type: "event"; event: "status"; data: IndexingStatus } - | { type: "event"; event: "telemetry"; data: IndexingTelemetryEvent } - | { type: "event"; event: "warning"; data: IndexingWarning } - | { type: "event"; event: "log"; data: Log } + | { type: "event"; key?: string; event: "status"; data: IndexingStatus } + | { type: "event"; key?: string; event: "telemetry"; data: IndexingTelemetryEvent } + | { type: "event"; key?: string; event: "warning"; data: IndexingWarning } + | { type: "event"; key?: string; event: "log"; data: Log } export type Message = Result | Event diff --git a/packages/opencode/src/kilocode/indexing-worker.ts b/packages/opencode/src/kilocode/indexing-worker.ts index d057782ca57..1121c4a48ed 100644 --- a/packages/opencode/src/kilocode/indexing-worker.ts +++ b/packages/opencode/src/kilocode/indexing-worker.ts @@ -1,22 +1,31 @@ import type { CodeIndexManager } from "@kilocode/kilo-indexing/engine" +import { AsyncLocalStorage } from "node:async_hooks" import { format } from "node:util" import type { Request, Result, Event, Log } from "./indexing-worker-protocol" import { parseQdrantWarning } from "./indexing-warning" -let manager: CodeIndexManager | undefined -let progress: { dispose(): void } | undefined -let telemetry: { dispose(): void } | undefined +type Entry = { + manager: CodeIndexManager + progress: { dispose(): void } + telemetry: { dispose(): void } +} + +const managers = new Map() +const context = new AsyncLocalStorage() +const queues = new Map>() +let initQueue = Promise.resolve() function send(message: Result | Event) { postMessage(message) } function write(level: Log["level"], args: unknown[]) { + const key = context.getStore() const message = format(...args) - send({ type: "event", event: "log", data: { level, message } }) + send({ type: "event", key, event: "log", data: { level, message } }) if (level !== "warn") return const warning = parseQdrantWarning(message) - if (warning) send({ type: "event", event: "warning", data: warning }) + if (warning) send({ type: "event", key, event: "warning", data: warning }) } console.debug = (...args) => write("debug", args) @@ -25,45 +34,49 @@ console.log = (...args) => write("info", args) console.warn = (...args) => write("warn", args) console.error = (...args) => write("error", args) -function dispose() { - progress?.dispose() - telemetry?.dispose() - progress = undefined - telemetry = undefined - manager?.dispose() - manager = undefined +async function dispose(key: string) { + const entry = managers.get(key) + if (!entry) return + managers.delete(key) + entry.progress.dispose() + entry.telemetry.dispose() + await entry.manager.dispose() } async function init(request: Extract) { - dispose() + await dispose(request.key) if (request.input.lancedbPath) process.env.KILO_LANCEDB_PATH = request.input.lancedbPath const [engine, status] = await Promise.all([ import("@kilocode/kilo-indexing/engine"), import("@kilocode/kilo-indexing/status"), ]) - const next = new engine.CodeIndexManager(request.input.directory, request.input.root) - manager = next - progress = next.onProgressUpdate.on(() => { - send({ type: "event", event: "status", data: status.normalizeIndexingStatus(next) }) + const manager = new engine.CodeIndexManager( + request.input.directory, + request.input.root, + request.input.baselineDirectory, + ) + const progress = manager.onProgressUpdate.on(() => { + send({ type: "event", key: request.key, event: "status", data: status.normalizeIndexingStatus(manager) }) }) - telemetry = next.onTelemetry.on((data) => { - send({ type: "event", event: "telemetry", data }) + const telemetry = manager.onTelemetry.on((data) => { + send({ type: "event", key: request.key, event: "telemetry", data }) }) - await next.initialize(request.input.config) - send({ type: "result", id: request.id, method: "init", ok: true, value: status.normalizeIndexingStatus(next) }) + managers.set(request.key, { manager, progress, telemetry }) + await manager.initialize(request.input.config) + send({ type: "result", id: request.id, method: "init", ok: true, value: status.normalizeIndexingStatus(manager) }) } async function handle(request: Request) { try { if (request.method === "dispose") { - dispose() + await dispose(request.key) send({ type: "result", id: request.id, method: "dispose", ok: true, value: undefined }) return } if (request.method === "search") { - const value = manager ? await manager.searchIndex(request.input.query, request.input.directoryPrefix) : [] - send({ type: "result", id: request.id, method: "search", ok: true, value }) + const value = await managers.get(request.key)?.manager.searchIndex(request.input.query, request.input.directoryPrefix) + send({ type: "result", id: request.id, method: "search", ok: true, value: value ?? [] }) return } @@ -74,7 +87,19 @@ async function handle(request: Request) { } } -let queue = Promise.resolve() onmessage = (event: MessageEvent) => { - queue = queue.then(() => handle(event.data)) + const request = event.data + const prior = queues.get(request.key) ?? Promise.resolve() + const task = prior.then(() => + context.run(request.key, () => { + if (request.method !== "init") return handle(request) + const next = initQueue.then(() => handle(request)) + initQueue = next + return next + }), + ) + const queued = task.finally(() => { + if (queues.get(request.key) === queued) queues.delete(request.key) + }) + queues.set(request.key, queued) } diff --git a/packages/opencode/src/kilocode/indexing.ts b/packages/opencode/src/kilocode/indexing.ts index 5c8d73f8c43..b6f1e03799c 100644 --- a/packages/opencode/src/kilocode/indexing.ts +++ b/packages/opencode/src/kilocode/indexing.ts @@ -29,18 +29,18 @@ const missing = () => disabledIndexingStatus("Indexing plugin is not enabled for const noWorkspace = () => disabledIndexingStatus("Codebase indexing is disabled because no workspace folder is open in VS Code.") -function worktreeDisabled(): z.infer { - return { - state: "Disabled", - message: "Indexing is disabled in worktree sessions. Use the main workspace for indexing.", - processedFiles: 0, - totalFiles: 0, - percent: 0, - } -} +function baselineDirectory(dir: string): string | undefined { + if (Instance.project.vcs !== "git") return + const checkout = path.resolve(Instance.worktree) + const main = path.resolve(Instance.project.worktree) + if (checkout === main) return -function isWorktreePath(dir: string): boolean { - return /(?:\/|\\)\.kilo(?:code)?(?:\/|\\)worktrees(?:\/|\\)/.test(dir) + const scope = path.relative(checkout, path.resolve(dir)) + if (scope === ".." || scope.startsWith(`..${path.sep}`) || path.isAbsolute(scope)) return + + const baseline = path.resolve(main, scope) + if (baseline === path.resolve(dir)) return + return baseline } function failed(err: unknown): z.infer { @@ -250,11 +250,8 @@ export namespace KiloIndexing { return track(hit, await inert(() => missing())) } - if (isWorktreePath(dir)) { - return track(hit, await inert(() => worktreeDisabled())) - } - - log.info("initializing project indexing", { workspacePath: dir }) + const baseline = baselineDirectory(dir) + log.info("initializing project indexing", { workspacePath: dir, baselineDirectory: baseline }) const root = path.join(Global.Path.state, "indexing") const auth = await kiloAuth(cfg) const globalConfig = await AppRuntime.runPromise(Config.Service.use((svc) => svc.getGlobal())) @@ -382,7 +379,7 @@ export namespace KiloIndexing { if (hit.disposed) return const engine = IndexingWorker.create(dir, root, { status, telemetry, warning, log: output, failure }) base.engine = engine - box.status = await engine.init(cfgInput) + box.status = await engine.init(cfgInput, baseline) base.initialized = true }) .then( @@ -502,7 +499,7 @@ export namespace KiloIndexing { } export async function search(query: string, directoryPrefix?: string): Promise { - const entry = await hit().ready + const entry = await hit().promise entry.scope(WorkspaceContext.workspaceID) if (!entry.initialized || entry.current().state === "Disabled" || !entry.engine) return [] return entry.engine.search(query, directoryPrefix) diff --git a/packages/opencode/src/kilocode/tool/registry.ts b/packages/opencode/src/kilocode/tool/registry.ts index 230a3db587a..492074f4ed1 100644 --- a/packages/opencode/src/kilocode/tool/registry.ts +++ b/packages/opencode/src/kilocode/tool/registry.ts @@ -9,9 +9,11 @@ import { Effect } from "effect" import * as Log from "@opencode-ai/core/util/log" import { Agent } from "@/agent/agent" import * as Truncate from "@/tool/truncate" +import { Instance } from "@/project/instance" +import type { Config } from "@/config/config" const log = Log.create({ service: "kilocode-tool-registry" }) -type Deps = { agent: Agent.Interface; truncate: Truncate.Interface } +type Deps = { agent: Agent.Interface; truncate: Truncate.Interface; indexing?: boolean } type Loaders = { indexing?: () => Promise<{ KiloIndexing: { ready: () => boolean } }> semantic?: () => Promise> @@ -21,6 +23,13 @@ export namespace KiloToolRegistry { const hint = "- When you are doing an open-ended search where you do not know the exact symbol name, use the `semantic_search` tool first to narrow down the search scope, then follow up with `Grep` and/or `Read`" + export function indexing( + config: Pick, + global?: Pick, + ): boolean | undefined { + return config.indexing?.enabled ?? global?.indexing?.enabled + } + /** Resolve Kilo-specific tool Infos outside any InstanceState, so their Truncate/Agent deps are * satisfied at the outer registry scope instead of leaking into InstanceState's Effect. */ export function infos() { @@ -54,15 +63,20 @@ export namespace KiloToolRegistry { function semanticTool(deps: Deps, loaders: Loaders) { return Effect.gen(function* () { - const indexing = loaders.indexing ?? (() => import("@/kilocode/indexing")) - const ready = yield* Effect.tryPromise(() => indexing().then((mod) => mod.KiloIndexing.ready())).pipe( - Effect.catch((err) => - Effect.sync(() => { - log.warn("semantic search unavailable", { err }) - return false - }), - ), - ) + const ready = yield* (deps.indexing === undefined + ? (() => { + const indexing = loaders.indexing ?? (() => import("@/kilocode/indexing")) + const check = Instance.bind((mod: Awaited>) => mod.KiloIndexing.ready()) + return Effect.tryPromise(() => indexing().then(check)).pipe( + Effect.catch((err) => + Effect.sync(() => { + log.warn("semantic search unavailable", { err }) + return false + }), + ), + ) + })() + : Effect.succeed(deps.indexing)) if (!ready) return undefined const semantic = loaders.semantic ?? (() => import("@/kilocode/tool/semantic-search")) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 0d3a580a119..f2bdba5aad4 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -229,7 +229,11 @@ export const layer: Layer.Layer< } } - const cfg = yield* config.get() // kilocode_change: capture for KiloToolRegistry.extra + // kilocode_change start + const cfg = yield* config.get() + const global = yield* config.getGlobal() + const indexing = KiloToolRegistry.indexing(cfg, global) + // kilocode_change end const questionEnabled = ["app", "cli", "desktop", "vscode"].includes(flags.client) || flags.enableQuestionTool // kilocode_change: add vscode client const tool = yield* Effect.all({ @@ -255,7 +259,13 @@ export const layer: Layer.Layer< suggest: Tool.init(suggesttool), // kilocode_change }) - const kilo = yield* KiloToolRegistry.build(kiloToolInfos, { agent: agents, truncate }) // kilocode_change + // kilocode_change start + const kilo = yield* KiloToolRegistry.build(kiloToolInfos, { + agent: agents, + truncate, + ...(indexing === undefined ? {} : { indexing }), + }) + // kilocode_change end return { custom, diff --git a/packages/opencode/test/kilocode/indexing-startup.test.ts b/packages/opencode/test/kilocode/indexing-startup.test.ts index 02e6222f523..0969cbcd9ee 100644 --- a/packages/opencode/test/kilocode/indexing-startup.test.ts +++ b/packages/opencode/test/kilocode/indexing-startup.test.ts @@ -94,7 +94,7 @@ function inline(directory: string, root: string, hooks: IndexingWorker.Hooks): I async dispose() { progress.dispose() telemetry.dispose() - manager.dispose() + await manager.dispose() }, } } @@ -486,7 +486,11 @@ describe("indexing startup degradation", () => { expect(init).toHaveBeenCalled() expect(KiloIndexing.ready()).toBe(false) expect(await KiloIndexing.available()).toBe(false) - expect(await KiloIndexing.search("boot failure")).toEqual([]) + const search = KiloIndexing.search("boot failure") + const pending = await Promise.race([search.then(() => false), Promise.resolve(true)]) + expect(pending).toBe(true) + gate.resolve({ requiresRestart: false }) + expect(await search).toEqual([]) }, }) } finally { diff --git a/packages/opencode/test/kilocode/indexing-worker.test.ts b/packages/opencode/test/kilocode/indexing-worker.test.ts index 91b5ddd3c95..5107495c910 100644 --- a/packages/opencode/test/kilocode/indexing-worker.test.ts +++ b/packages/opencode/test/kilocode/indexing-worker.test.ts @@ -25,6 +25,36 @@ test("runs indexing engine requests in its worker", async () => { expect(failures).toEqual([]) }) +test("routes multiple directories through the shared indexing worker", async () => { + await using first = await tmpdir() + await using second = await tmpdir() + const failures: unknown[] = [] + const create = (directory: string) => + IndexingWorker.create(directory, directory, { + status() {}, + telemetry() {}, + warning() {}, + log() {}, + failure(err) { + failures.push(err) + }, + }) + const left = create(first.path) + const right = create(second.path) + + try { + const statuses = await Promise.all([ + left.init({ enabled: false, embedderProvider: "openai" }), + right.init({ enabled: false, embedderProvider: "openai" }), + ]) + expect(statuses.map((status) => status.state)).toEqual(["Disabled", "Disabled"]) + } finally { + await Promise.all([left.dispose(), right.dispose()]) + } + + expect(failures).toEqual([]) +}) + test("reuses enabled workers across provider initialization errors", async () => { await using tmp = await tmpdir() const drivers = new Set() diff --git a/packages/opencode/test/kilocode/indexing-worktree.test.ts b/packages/opencode/test/kilocode/indexing-worktree.test.ts index fa42d92e1be..8c7daa2f9a5 100644 --- a/packages/opencode/test/kilocode/indexing-worktree.test.ts +++ b/packages/opencode/test/kilocode/indexing-worktree.test.ts @@ -1,7 +1,9 @@ import { afterEach, describe, expect, test } from "bun:test" import { mkdir } from "node:fs/promises" +import path from "node:path" import type { Config } from "../../src/config/config" import { KiloIndexing } from "../../src/kilocode/indexing" +import { IndexingWorker } from "../../src/kilocode/indexing-worker-client" import { WithInstance } from "../../src/project/with-instance" import { disposeAllInstances, tmpdir } from "../fixture/fixture" @@ -18,50 +20,78 @@ const cfg: Partial = { } const configDir = process.env["KILO_CONFIG_DIR"] +const indexed = { + state: "Complete" as const, + message: "Index up-to-date.", + processedFiles: 0, + totalFiles: 0, + percent: 100, +} afterEach(async () => { + IndexingWorker.override() if (configDir === undefined) delete process.env["KILO_CONFIG_DIR"] else process.env["KILO_CONFIG_DIR"] = configDir await disposeAllInstances() }) -describe("indexing worktree disable", () => { - test("returns disabled status in .kilo/worktrees paths", async () => { +describe("indexing worktrees", () => { + test("shares the primary checkout index with a linked worktree", async () => { await using tmp = await tmpdir({ git: true, config: cfg }) process.env["KILO_CONFIG_DIR"] = tmp.path - const dir = `${tmp.path}/.kilo/worktrees/feature` - await mkdir(dir, { recursive: true }) + const worktree = path.join(tmp.path, ".kilo", "worktrees", "feature") + await Bun.$`git -C ${tmp.path} worktree add -b feature ${worktree}`.quiet() + + const calls: Array<{ directory: string; baseline?: string }> = [] + IndexingWorker.override((directory) => ({ + async init(_input, baseline) { + calls.push({ directory, baseline }) + return indexed + }, + async search() { + return [] + }, + async dispose() {}, + })) await WithInstance.provide({ - directory: dir, + directory: worktree, fn: async () => { - const status = await KiloIndexing.current() - - expect(status.state).toBe("Disabled") - expect(status.message).toBe("Indexing is disabled in worktree sessions. Use the main workspace for indexing.") - expect(await KiloIndexing.available()).toBe(false) - expect(KiloIndexing.ready()).toBe(false) - expect(await KiloIndexing.search("worktree")).toEqual([]) + expect((await KiloIndexing.current()).state).toBe("Complete") + expect(await KiloIndexing.available()).toBe(true) }, }) + + expect(calls).toEqual([{ directory: worktree, baseline: tmp.path }]) }) - test("returns disabled status in legacy .kilocode/worktrees paths", async () => { + test("does not classify an ordinary directory from its pathname", async () => { await using tmp = await tmpdir({ git: true, config: cfg }) process.env["KILO_CONFIG_DIR"] = tmp.path - const dir = `${tmp.path}/.kilocode/worktrees/feature` - await mkdir(dir, { recursive: true }) + const directory = path.join(tmp.path, ".kilocode", "worktrees", "feature") + await mkdir(directory, { recursive: true }) + await Bun.write(path.join(directory, "file.ts"), "export const value = 1\n") + + const calls: Array = [] + IndexingWorker.override(() => ({ + async init(_input, baseline) { + calls.push(baseline) + return indexed + }, + async search() { + return [] + }, + async dispose() {}, + })) await WithInstance.provide({ - directory: dir, + directory, fn: async () => { - const status = await KiloIndexing.current() - - expect(status.state).toBe("Disabled") - expect(status.message).toBe("Indexing is disabled in worktree sessions. Use the main workspace for indexing.") - expect(await KiloIndexing.available()).toBe(false) - expect(KiloIndexing.ready()).toBe(false) + expect((await KiloIndexing.current()).state).toBe("Complete") + expect(await KiloIndexing.available()).toBe(true) }, }) + + expect(calls).toEqual([undefined]) }) }) diff --git a/packages/opencode/test/kilocode/tool-registry-indexing.test.ts b/packages/opencode/test/kilocode/tool-registry-indexing.test.ts index 781e4452e44..d0bffabb77b 100644 --- a/packages/opencode/test/kilocode/tool-registry-indexing.test.ts +++ b/packages/opencode/test/kilocode/tool-registry-indexing.test.ts @@ -86,7 +86,9 @@ describe("kilocode tool registry indexing", () => { () => Effect.gen(function* () { const err = new Error("ready rejected") - const ready = spyOn(KiloIndexing, "ready").mockImplementation(() => Promise.reject(err) as unknown as boolean) + const ready = spyOn(KiloIndexing, "ready").mockImplementation( + () => Promise.reject(err) as unknown as boolean, + ) const warn = spyOn(logger, "warn").mockImplementation(() => {}) try { @@ -177,6 +179,20 @@ describe("kilocode tool registry indexing", () => { ), ) + test("enables semantic search from indexing configuration before the index is ready", () => { + expect( + KiloToolRegistry.indexing({ + indexing: { enabled: true }, + }), + ).toBe(true) + expect( + KiloToolRegistry.indexing({ + indexing: { enabled: false }, + }), + ).toBe(false) + expect(KiloToolRegistry.indexing({}, { indexing: { enabled: true } })).toBe(true) + }) + test("conditionally includes Kilo registry extras", () => { const prev = process.env["KILO_CLIENT"] const def = (id: string): Tool.Def => ({ diff --git a/packages/opencode/test/kilocode/tool-registry-semantic-import-failure.test.ts b/packages/opencode/test/kilocode/tool-registry-semantic-import-failure.test.ts index 682680ccbdf..a9556603806 100644 --- a/packages/opencode/test/kilocode/tool-registry-semantic-import-failure.test.ts +++ b/packages/opencode/test/kilocode/tool-registry-semantic-import-failure.test.ts @@ -5,6 +5,8 @@ import { KiloToolRegistry } from "../../src/kilocode/tool/registry" import { Agent } from "../../src/agent/agent" import * as Truncate from "../../src/tool/truncate" import type * as Tool from "../../src/tool/tool" +import { WithInstance } from "../../src/project/with-instance" +import { tmpdir } from "../fixture/fixture" const logger = Log.create({ service: "kilocode-tool-registry" }) const deps = { agent: {} as Agent.Interface, truncate: {} as Truncate.Interface } @@ -14,19 +16,25 @@ describe("kilocode tool registry semantic tool import failure", () => { const err = new Error("semantic tool import failed") const warn = spyOn(logger, "warn").mockImplementation(() => {}) + await using tmp = await tmpdir({ git: true }) + try { - const result = await Effect.runPromise( - KiloToolRegistry.build(infos(), deps, { - indexing: async () => ({ - KiloIndexing: { - ready: () => true, - }, - }), - semantic: async () => { - throw err - }, - }), - ) + const result = await WithInstance.provide({ + directory: tmp.path, + fn: () => + Effect.runPromise( + KiloToolRegistry.build(infos(), deps, { + indexing: async () => ({ + KiloIndexing: { + ready: () => true, + }, + }), + semantic: async () => { + throw err + }, + }), + ), + }) expect(result.semantic).toBeUndefined() expect(result.recall.id).toBe("recall") From 6e915601b26c320f400c9a96a1d86c52e509c193 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 12 Jun 2026 20:28:01 +0200 Subject: [PATCH 06/78] fix(cli): stabilize shared worktree indexing --- packages/kilo-indexing/src/indexing/manager.ts | 2 ++ packages/opencode/src/kilocode/indexing-worker.ts | 5 ++++- packages/opencode/src/kilocode/tool/registry.ts | 4 +--- packages/opencode/src/tool/registry.ts | 2 +- packages/opencode/test/kilocode/indexing-worktree.test.ts | 3 ++- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/kilo-indexing/src/indexing/manager.ts b/packages/kilo-indexing/src/indexing/manager.ts index 132db65b8ee..77b5797787d 100644 --- a/packages/kilo-indexing/src/indexing/manager.ts +++ b/packages/kilo-indexing/src/indexing/manager.ts @@ -461,6 +461,7 @@ export class CodeIndexManager { baselinePath, }) await this._recreateServices() + if (this._disposed) return await this._orchestrator?.startIndexing("background") })().finally(() => { this._baselineRefresh = undefined @@ -483,6 +484,7 @@ export class CodeIndexManager { const store = factory.createVectorStore(this.baselinePath) if (!store.openExisting) throw new Error("The configured vector store cannot open a shared baseline") + // Validate compatibility without keeping every worktree baseline connection open. await store.openExisting() await store.close?.() diff --git a/packages/opencode/src/kilocode/indexing-worker.ts b/packages/opencode/src/kilocode/indexing-worker.ts index 1121c4a48ed..bfbca31e99f 100644 --- a/packages/opencode/src/kilocode/indexing-worker.ts +++ b/packages/opencode/src/kilocode/indexing-worker.ts @@ -94,7 +94,10 @@ onmessage = (event: MessageEvent) => { context.run(request.key, () => { if (request.method !== "init") return handle(request) const next = initQueue.then(() => handle(request)) - initQueue = next + initQueue = next.then( + () => undefined, + () => undefined, + ) return next }), ) diff --git a/packages/opencode/src/kilocode/tool/registry.ts b/packages/opencode/src/kilocode/tool/registry.ts index 492074f4ed1..a404c8ba60a 100644 --- a/packages/opencode/src/kilocode/tool/registry.ts +++ b/packages/opencode/src/kilocode/tool/registry.ts @@ -9,7 +9,6 @@ import { Effect } from "effect" import * as Log from "@opencode-ai/core/util/log" import { Agent } from "@/agent/agent" import * as Truncate from "@/tool/truncate" -import { Instance } from "@/project/instance" import type { Config } from "@/config/config" const log = Log.create({ service: "kilocode-tool-registry" }) @@ -66,8 +65,7 @@ export namespace KiloToolRegistry { const ready = yield* (deps.indexing === undefined ? (() => { const indexing = loaders.indexing ?? (() => import("@/kilocode/indexing")) - const check = Instance.bind((mod: Awaited>) => mod.KiloIndexing.ready()) - return Effect.tryPromise(() => indexing().then(check)).pipe( + return Effect.tryPromise(() => indexing().then((mod) => mod.KiloIndexing.ready())).pipe( Effect.catch((err) => Effect.sync(() => { log.warn("semantic search unavailable", { err }) diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index f2bdba5aad4..c16b3f9839e 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -263,7 +263,7 @@ export const layer: Layer.Layer< const kilo = yield* KiloToolRegistry.build(kiloToolInfos, { agent: agents, truncate, - ...(indexing === undefined ? {} : { indexing }), + indexing: indexing ?? false, }) // kilocode_change end diff --git a/packages/opencode/test/kilocode/indexing-worktree.test.ts b/packages/opencode/test/kilocode/indexing-worktree.test.ts index 8c7daa2f9a5..234ef1724ce 100644 --- a/packages/opencode/test/kilocode/indexing-worktree.test.ts +++ b/packages/opencode/test/kilocode/indexing-worktree.test.ts @@ -57,13 +57,14 @@ describe("indexing worktrees", () => { await WithInstance.provide({ directory: worktree, fn: async () => { + await KiloIndexing.search("worktree") expect((await KiloIndexing.current()).state).toBe("Complete") expect(await KiloIndexing.available()).toBe(true) }, }) expect(calls).toEqual([{ directory: worktree, baseline: tmp.path }]) - }) + }, 15_000) test("does not classify an ordinary directory from its pathname", async () => { await using tmp = await tmpdir({ git: true, config: cfg }) From 0e735ba7b60b083f309f46936ec781a0097e0334 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Fri, 12 Jun 2026 20:35:23 +0200 Subject: [PATCH 07/78] test(cli): align semantic search registry coverage --- .../kilocode/tool-registry-indexing.test.ts | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/packages/opencode/test/kilocode/tool-registry-indexing.test.ts b/packages/opencode/test/kilocode/tool-registry-indexing.test.ts index d0bffabb77b..5a45a767407 100644 --- a/packages/opencode/test/kilocode/tool-registry-indexing.test.ts +++ b/packages/opencode/test/kilocode/tool-registry-indexing.test.ts @@ -52,7 +52,7 @@ describe("kilocode tool registry indexing", () => { ), ) - it.live("keeps non-indexing tools when indexing readiness throws", () => + it.live("registers semantic search from config even when readiness throws", () => provideTmpdirInstance( () => Effect.gen(function* () { @@ -66,22 +66,21 @@ describe("kilocode tool registry indexing", () => { const registry = yield* ToolRegistry.Service const ids = yield* registry.ids() - expect(ids).not.toContain("semantic_search") + expect(ids).toContain("semantic_search") expect(ids).toContain("question") expect(ids).toContain("read") expect(ids).toContain("suggest") - expect(warn.mock.calls[0]?.[0]).toBe("semantic search unavailable") - expect(warn.mock.calls[0]?.[1]?.err).toBeDefined() + expect(warn).not.toHaveBeenCalled() } finally { ready.mockRestore() warn.mockRestore() } }), - { git: true }, + { git: true, config: { indexing: { enabled: true } } }, ), ) - it.live("keeps non-indexing tools when indexing readiness rejects", () => + it.live("registers semantic search from config even when readiness rejects", () => provideTmpdirInstance( () => Effect.gen(function* () { @@ -95,22 +94,21 @@ describe("kilocode tool registry indexing", () => { const registry = yield* ToolRegistry.Service const ids = yield* registry.ids() - expect(ids).not.toContain("semantic_search") + expect(ids).toContain("semantic_search") expect(ids).toContain("question") expect(ids).toContain("read") expect(ids).toContain("suggest") - expect(warn.mock.calls[0]?.[0]).toBe("semantic search unavailable") - expect(warn.mock.calls[0]?.[1]?.err).toBeDefined() + expect(warn).not.toHaveBeenCalled() } finally { ready.mockRestore() warn.mockRestore() } }), - { git: true }, + { git: true, config: { indexing: { enabled: true } } }, ), ) - it.live("registers semantic_search when indexing is ready", () => + it.live("registers semantic_search when indexing is enabled", () => provideTmpdirInstance( () => Effect.gen(function* () { @@ -125,7 +123,7 @@ describe("kilocode tool registry indexing", () => { ready.mockRestore() } }), - { git: true }, + { git: true, config: { indexing: { enabled: true } } }, ), ) @@ -153,7 +151,7 @@ describe("kilocode tool registry indexing", () => { ), ) - it.live("includes semantic_search hint in glob and grep descriptions when indexing is ready", () => + it.live("includes semantic_search hint in glob and grep descriptions when indexing is enabled", () => provideTmpdirInstance( () => Effect.gen(function* () { @@ -175,7 +173,7 @@ describe("kilocode tool registry indexing", () => { ready.mockRestore() } }), - { git: true }, + { git: true, config: { indexing: { enabled: true } } }, ), ) From 7ada6206d6771a0a118caed66d1d71c596ccb971 Mon Sep 17 00:00:00 2001 From: Vishal Kumar Singh Date: Thu, 14 May 2026 19:19:47 +0530 Subject: [PATCH 08/78] fix(webview-ui): ignore Enter during IME composition Treat keyCode 229 and isComposing like other stacks so Enter does not send, pick models, rename, or submit docks while an IME is confirming text. Fixes #7216 --- .../webview-ui/kiloclaw/components/ConversationList.tsx | 2 +- .../kilo-vscode/webview-ui/kiloclaw/components/MessageArea.tsx | 2 +- .../webview-ui/kiloclaw/components/MessageBubble.tsx | 2 +- .../webview-ui/kiloclaw/components/StatusSidebar.tsx | 2 +- .../webview-ui/src/components/chat/PermissionDock.tsx | 2 +- .../kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx | 2 +- .../kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx | 2 +- .../webview-ui/src/components/shared/ModeSwitcher.tsx | 2 +- .../webview-ui/src/components/shared/ModelSelector.tsx | 2 +- .../webview-ui/src/components/shared/ThinkingSelector.tsx | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/kiloclaw/components/ConversationList.tsx b/packages/kilo-vscode/webview-ui/kiloclaw/components/ConversationList.tsx index 8ff31637edb..c86fa1fca27 100644 --- a/packages/kilo-vscode/webview-ui/kiloclaw/components/ConversationList.tsx +++ b/packages/kilo-vscode/webview-ui/kiloclaw/components/ConversationList.tsx @@ -132,7 +132,7 @@ function ConversationItem(props: { conversation: ConversationListItem }) { } const onKey = (e: KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === "Enter" && !e.isComposing && e.keyCode !== 229) { e.preventDefault() commitRename() } else if (e.key === "Escape") { diff --git a/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageArea.tsx b/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageArea.tsx index ce9217804b3..abd8d882041 100644 --- a/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageArea.tsx +++ b/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageArea.tsx @@ -150,7 +150,7 @@ export function MessageArea() { } const onKeyDown = (e: KeyboardEvent) => { - if (e.key === "Enter" && !e.shiftKey) { + if (e.key === "Enter" && !e.shiftKey && !e.isComposing && e.keyCode !== 229) { e.preventDefault() submit() } diff --git a/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageBubble.tsx b/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageBubble.tsx index a1b8e37c03a..5d77093f6a7 100644 --- a/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageBubble.tsx +++ b/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageBubble.tsx @@ -254,7 +254,7 @@ export function MessageBubble(props: MessageBubbleProps) { value={editText()} onInput={(e) => setEditText(e.currentTarget.value)} onKeyDown={(e) => { - if (e.key === "Enter" && !e.shiftKey) { + if (e.key === "Enter" && !e.shiftKey && !e.isComposing && e.keyCode !== 229) { e.preventDefault() saveEdit() } else if (e.key === "Escape") { diff --git a/packages/kilo-vscode/webview-ui/kiloclaw/components/StatusSidebar.tsx b/packages/kilo-vscode/webview-ui/kiloclaw/components/StatusSidebar.tsx index 1d2246a9d16..62d2db87464 100644 --- a/packages/kilo-vscode/webview-ui/kiloclaw/components/StatusSidebar.tsx +++ b/packages/kilo-vscode/webview-ui/kiloclaw/components/StatusSidebar.tsx @@ -77,7 +77,7 @@ export function StatusSidebar() { } const onTitleKeyDown = (e: KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === "Enter" && !e.isComposing && e.keyCode !== 229) { e.preventDefault() commitTitleRename() } else if (e.key === "Escape") { diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx index 62cbcb56f2e..e91869c23f2 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx @@ -133,7 +133,7 @@ export const PermissionDock: Component<{ ) const plain = (e: KeyboardEvent) => - e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey && !e.isComposing + e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey && !e.isComposing && e.keyCode !== 229 const skip = (e: KeyboardEvent, target: Element | undefined) => { const local = !!target?.closest("[data-component='permission-shortcuts']") diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx index d30f37f1b77..5d9b1ed5e82 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx @@ -615,7 +615,7 @@ export const PromptInput: Component = (props) => { session.abort() return } - if (e.key === "Enter" && !e.shiftKey && !e.isComposing) { + if (e.key === "Enter" && !e.shiftKey && !e.isComposing && e.keyCode !== 229) { e.preventDefault() handleSend() } diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx index 0898aebbfe0..6e6edee3bcb 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx @@ -288,7 +288,7 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) => reject() return } - if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { + if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && !e.isComposing && e.keyCode !== 229) { e.preventDefault() e.stopPropagation() if (store.sending) return diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx index 4e4524ff5df..42a06bd094e 100644 --- a/packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx @@ -87,7 +87,7 @@ export const ModeSwitcherBase: Component = (props) => { } else if (e.key === "End") { e.preventDefault() focusItem(len - 1) - } else if (e.key === "Enter" || e.key === " ") { + } else if (e.key === " " || (e.key === "Enter" && !e.isComposing && e.keyCode !== 229)) { e.preventDefault() if (cur >= 0 && cur < len) pick(props.agents[cur].name) } diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx index 00eb157021b..58110df6941 100644 --- a/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx @@ -549,7 +549,7 @@ export const ModelSelectorBase: Component = (props) => { return } - if (e.key === "Enter") { + if (e.key === "Enter" && !e.isComposing && e.keyCode !== 229) { e.preventDefault() const row = rowMap().get(selectedKey()) if (row) selectRow(row) diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/ThinkingSelector.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/ThinkingSelector.tsx index 28f1ba4c89c..e10ce1124fe 100644 --- a/packages/kilo-vscode/webview-ui/src/components/shared/ThinkingSelector.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/shared/ThinkingSelector.tsx @@ -117,7 +117,7 @@ export const ThinkingSelectorBase: Component = (props focusItem(len - 1) return } - if (e.key === "Enter" || e.key === " ") { + if (e.key === " " || (e.key === "Enter" && !e.isComposing && e.keyCode !== 229)) { e.preventDefault() if (cur >= 0 && cur < len) pick(items[cur]) return From 38e27d4789c8004f0fcb2bc8f0702541c267361d Mon Sep 17 00:00:00 2001 From: Vishal Kumar Singh Date: Fri, 15 May 2026 23:11:41 +0530 Subject: [PATCH 09/78] test(webview-ui): centralize IME Enter guard and add unit tests Extract isEnterKeyCommitNotIme so PromptInput, docks, selectors, and KiloClaw inputs share one definition. Bun tests cover Enter, composing, and keyCode 229. --- .../tests/unit/ime-enter-key.test.ts | 20 +++++++++++++++++++ .../kiloclaw/components/ConversationList.tsx | 3 ++- .../kiloclaw/components/MessageArea.tsx | 3 ++- .../kiloclaw/components/MessageBubble.tsx | 3 ++- .../kiloclaw/components/StatusSidebar.tsx | 3 ++- .../src/components/chat/PermissionDock.tsx | 3 ++- .../src/components/chat/PromptInput.tsx | 3 ++- .../src/components/chat/QuestionDock.tsx | 3 ++- .../src/components/shared/ModeSwitcher.tsx | 3 ++- .../src/components/shared/ModelSelector.tsx | 3 ++- .../components/shared/ThinkingSelector.tsx | 3 ++- .../webview-ui/src/utils/ime-enter.ts | 10 ++++++++++ 12 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 packages/kilo-vscode/tests/unit/ime-enter-key.test.ts create mode 100644 packages/kilo-vscode/webview-ui/src/utils/ime-enter.ts diff --git a/packages/kilo-vscode/tests/unit/ime-enter-key.test.ts b/packages/kilo-vscode/tests/unit/ime-enter-key.test.ts new file mode 100644 index 00000000000..219f84ed011 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/ime-enter-key.test.ts @@ -0,0 +1,20 @@ +import { describe, it, expect } from "bun:test" +import { isEnterKeyCommitNotIme } from "../../webview-ui/src/utils/ime-enter" + +describe("isEnterKeyCommitNotIme", () => { + it("is true for a normal Enter keydown", () => { + expect(isEnterKeyCommitNotIme({ key: "Enter", isComposing: false, keyCode: 13 })).toBe(true) + }) + + it("is false while isComposing is true", () => { + expect(isEnterKeyCommitNotIme({ key: "Enter", isComposing: true, keyCode: 13 })).toBe(false) + }) + + it("is false when keyCode is 229 (IME-processed key on Windows)", () => { + expect(isEnterKeyCommitNotIme({ key: "Enter", isComposing: false, keyCode: 229 })).toBe(false) + }) + + it("is false for non-Enter keys", () => { + expect(isEnterKeyCommitNotIme({ key: "a", isComposing: false, keyCode: 65 })).toBe(false) + }) +}) diff --git a/packages/kilo-vscode/webview-ui/kiloclaw/components/ConversationList.tsx b/packages/kilo-vscode/webview-ui/kiloclaw/components/ConversationList.tsx index c86fa1fca27..476d857bdd5 100644 --- a/packages/kilo-vscode/webview-ui/kiloclaw/components/ConversationList.tsx +++ b/packages/kilo-vscode/webview-ui/kiloclaw/components/ConversationList.tsx @@ -5,6 +5,7 @@ import { For, Show, createMemo, createSignal, onCleanup, onMount } from "solid-j import { useClaw } from "../context/claw" import { useKiloClawLanguage } from "../context/language" import type { ConversationListItem } from "../lib/types" +import { isEnterKeyCommitNotIme } from "../../src/utils/ime-enter" type Group = { label: string; items: ConversationListItem[] } @@ -132,7 +133,7 @@ function ConversationItem(props: { conversation: ConversationListItem }) { } const onKey = (e: KeyboardEvent) => { - if (e.key === "Enter" && !e.isComposing && e.keyCode !== 229) { + if (isEnterKeyCommitNotIme(e)) { e.preventDefault() commitRename() } else if (e.key === "Escape") { diff --git a/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageArea.tsx b/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageArea.tsx index abd8d882041..d6014362a72 100644 --- a/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageArea.tsx +++ b/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageArea.tsx @@ -10,6 +10,7 @@ import { useKiloClawLanguage } from "../context/language" import { MessageBubble } from "./MessageBubble" import { computeBotDisplay, useNowTicker } from "./botStatus" import type { Message } from "../lib/types" +import { isEnterKeyCommitNotIme } from "../../src/utils/ime-enter" export function MessageArea() { const claw = useClaw() @@ -150,7 +151,7 @@ export function MessageArea() { } const onKeyDown = (e: KeyboardEvent) => { - if (e.key === "Enter" && !e.shiftKey && !e.isComposing && e.keyCode !== 229) { + if (isEnterKeyCommitNotIme(e) && !e.shiftKey) { e.preventDefault() submit() } diff --git a/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageBubble.tsx b/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageBubble.tsx index 5d77093f6a7..476ec5a9aea 100644 --- a/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageBubble.tsx +++ b/packages/kilo-vscode/webview-ui/kiloclaw/components/MessageBubble.tsx @@ -13,6 +13,7 @@ import { Markdown } from "@kilocode/kilo-ui/markdown" import { showToast } from "@kilocode/kilo-ui/toast" import type { ContentBlock, ExecApprovalDecision, Message } from "../lib/types" import { useKiloClawLanguage } from "../context/language" +import { isEnterKeyCommitNotIme } from "../../src/utils/ime-enter" const ULID_TIME_LEN = 10 const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" @@ -254,7 +255,7 @@ export function MessageBubble(props: MessageBubbleProps) { value={editText()} onInput={(e) => setEditText(e.currentTarget.value)} onKeyDown={(e) => { - if (e.key === "Enter" && !e.shiftKey && !e.isComposing && e.keyCode !== 229) { + if (isEnterKeyCommitNotIme(e) && !e.shiftKey) { e.preventDefault() saveEdit() } else if (e.key === "Escape") { diff --git a/packages/kilo-vscode/webview-ui/kiloclaw/components/StatusSidebar.tsx b/packages/kilo-vscode/webview-ui/kiloclaw/components/StatusSidebar.tsx index 62d2db87464..b2353b8cd81 100644 --- a/packages/kilo-vscode/webview-ui/kiloclaw/components/StatusSidebar.tsx +++ b/packages/kilo-vscode/webview-ui/kiloclaw/components/StatusSidebar.tsx @@ -6,6 +6,7 @@ import { Show, createMemo, createSignal } from "solid-js" import { useClaw } from "../context/claw" import { useKiloClawLanguage } from "../context/language" +import { isEnterKeyCommitNotIme } from "../../src/utils/ime-enter" function dot(status: string | null | undefined): string { if (!status) return "kiloclaw-dot-offline" @@ -77,7 +78,7 @@ export function StatusSidebar() { } const onTitleKeyDown = (e: KeyboardEvent) => { - if (e.key === "Enter" && !e.isComposing && e.keyCode !== 229) { + if (isEnterKeyCommitNotIme(e)) { e.preventDefault() commitTitleRename() } else if (e.key === "Escape") { diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx index e91869c23f2..ec696fb3dc5 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx @@ -23,6 +23,7 @@ import { PermissionDiff } from "./PermissionDiff" import { permissionDiffs } from "./permission-diff-utils" import { normalizeUrls } from "../../../../../opencode/src/kilocode/util/url" import type { PermissionRequest } from "../../types/messages" +import { isEnterKeyCommitNotIme } from "../../utils/ime-enter" let rulesExpandedPreference = false @@ -133,7 +134,7 @@ export const PermissionDock: Component<{ ) const plain = (e: KeyboardEvent) => - e.key === "Enter" && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey && !e.isComposing && e.keyCode !== 229 + isEnterKeyCommitNotIme(e) && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey const skip = (e: KeyboardEvent, target: Element | undefined) => { const local = !!target?.closest("[data-component='permission-shortcuts']") diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx index 5d9b1ed5e82..253f526815a 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx @@ -48,6 +48,7 @@ import { formatReviewCommentsMarkdown } from "../../utils/review-comment-markdow import { pendingDraftKey, scopeDraftKey, sessionDraftKey } from "../../utils/prompt-drafts" import { ReviewComments } from "./ReviewComments" import { partReview, reviewBody } from "../../../../src/shared/review-comments" +import { isEnterKeyCommitNotIme } from "../../utils/ime-enter" // Per-session input text storage (module-level so it survives remounts) const drafts = new Map() @@ -615,7 +616,7 @@ export const PromptInput: Component = (props) => { session.abort() return } - if (e.key === "Enter" && !e.shiftKey && !e.isComposing && e.keyCode !== 229) { + if (isEnterKeyCommitNotIme(e) && !e.shiftKey) { e.preventDefault() handleSend() } diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx index 6e6edee3bcb..06380094d82 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx @@ -19,6 +19,7 @@ import { toggleAnswer, tr, } from "./question-dock-utils" +import { isEnterKeyCommitNotIme } from "../../utils/ime-enter" export const QuestionDock: Component<{ request: QuestionRequest }> = (props) => { const session = useSession() @@ -288,7 +289,7 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) => reject() return } - if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && !e.isComposing && e.keyCode !== 229) { + if (isEnterKeyCommitNotIme(e) && (e.metaKey || e.ctrlKey)) { e.preventDefault() e.stopPropagation() if (store.sending) return diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx index 42a06bd094e..6e237dbdda0 100644 --- a/packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx @@ -13,6 +13,7 @@ import { Button } from "@kilocode/kilo-ui/button" import { useSession } from "../../context/session" import { useLanguage } from "../../context/language" import type { AgentInfo } from "../../types/messages" +import { isEnterKeyCommitNotIme } from "../../utils/ime-enter" /** Format an agent for display. Uses displayName if available, otherwise title-cases the slug. */ function formatAgentLabel(agent: AgentInfo): string { @@ -87,7 +88,7 @@ export const ModeSwitcherBase: Component = (props) => { } else if (e.key === "End") { e.preventDefault() focusItem(len - 1) - } else if (e.key === " " || (e.key === "Enter" && !e.isComposing && e.keyCode !== 229)) { + } else if (e.key === " " || isEnterKeyCommitNotIme(e)) { e.preventDefault() if (cur >= 0 && cur < len) pick(props.agents[cur].name) } diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx index 58110df6941..2c53c04948f 100644 --- a/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx @@ -31,6 +31,7 @@ import type { EnrichedModel } from "../../context/provider" import { useSession, SessionContext } from "../../context/session" import { useLanguage } from "../../context/language" import type { ModelSelection } from "../../types/messages" +import { isEnterKeyCommitNotIme } from "../../utils/ime-enter" import { KILO_GATEWAY_ID, isSmall, @@ -549,7 +550,7 @@ export const ModelSelectorBase: Component = (props) => { return } - if (e.key === "Enter" && !e.isComposing && e.keyCode !== 229) { + if (isEnterKeyCommitNotIme(e)) { e.preventDefault() const row = rowMap().get(selectedKey()) if (row) selectRow(row) diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/ThinkingSelector.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/ThinkingSelector.tsx index e10ce1124fe..a6f05f06915 100644 --- a/packages/kilo-vscode/webview-ui/src/components/shared/ThinkingSelector.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/shared/ThinkingSelector.tsx @@ -11,6 +11,7 @@ import { type Accessor, Component, createSignal, For, onCleanup, Show } from "so import { PopupSelector } from "./PopupSelector" import { Button } from "@kilocode/kilo-ui/button" import { useSession } from "../../context/session" +import { isEnterKeyCommitNotIme } from "../../utils/ime-enter" // --------------------------------------------------------------------------- // Reusable base component @@ -117,7 +118,7 @@ export const ThinkingSelectorBase: Component = (props focusItem(len - 1) return } - if (e.key === " " || (e.key === "Enter" && !e.isComposing && e.keyCode !== 229)) { + if (e.key === " " || isEnterKeyCommitNotIme(e)) { e.preventDefault() if (cur >= 0 && cur < len) pick(items[cur]) return diff --git a/packages/kilo-vscode/webview-ui/src/utils/ime-enter.ts b/packages/kilo-vscode/webview-ui/src/utils/ime-enter.ts new file mode 100644 index 00000000000..f15bcb2da14 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/utils/ime-enter.ts @@ -0,0 +1,10 @@ +/** + * IME composition: Enter should confirm glyphs, not trigger chat send / shortcuts. + * Browsers set `isComposing` while composing; on Windows, `keyCode === 229` often + * marks IME-handled keys even when `isComposing` is false for a given keydown. + */ +export function isEnterKeyCommitNotIme( + e: Pick, +): boolean { + return e.key === "Enter" && !e.isComposing && e.keyCode !== 229 +} From 8efc296ae54292183579e004eae52813a96abe0d Mon Sep 17 00:00:00 2001 From: Vishal Kumar Singh Date: Mon, 25 May 2026 22:07:16 +0530 Subject: [PATCH 10/78] chore(vscode): add changeset for IME Enter composition guard Release notes entry for the webview-ui fix that ignores Enter during IME composition (incl. Windows keyCode 229), per AGENTS.md / kilo-review. --- .changeset/ime-enter-composition-guard.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/ime-enter-composition-guard.md diff --git a/.changeset/ime-enter-composition-guard.md b/.changeset/ime-enter-composition-guard.md new file mode 100644 index 00000000000..f4e0e871716 --- /dev/null +++ b/.changeset/ime-enter-composition-guard.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Ignore Enter while IME composition is active (including Windows virtual-key 229) so composed text is not accidentally submitted from chat and sidebar inputs. From 258b8bae13823a68dab496af69b0aad80965bf5d Mon Sep 17 00:00:00 2001 From: Vishal Kumar Singh Date: Sat, 13 Jun 2026 01:24:51 +0530 Subject: [PATCH 11/78] style(vscode): format IME Enter guard files --- .../webview-ui/src/components/chat/PermissionDock.tsx | 3 +-- packages/kilo-vscode/webview-ui/src/utils/ime-enter.ts | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx index ec696fb3dc5..39243f904ac 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx @@ -133,8 +133,7 @@ export const PermissionDock: Component<{ "button, input, select, textarea, a[href], [contenteditable='true'], [role='button'], [role='menu'], [role='menuitem'], [role='listbox'], [role='option'], [role='combobox'], [role='textbox']", ) - const plain = (e: KeyboardEvent) => - isEnterKeyCommitNotIme(e) && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey + const plain = (e: KeyboardEvent) => isEnterKeyCommitNotIme(e) && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey const skip = (e: KeyboardEvent, target: Element | undefined) => { const local = !!target?.closest("[data-component='permission-shortcuts']") diff --git a/packages/kilo-vscode/webview-ui/src/utils/ime-enter.ts b/packages/kilo-vscode/webview-ui/src/utils/ime-enter.ts index f15bcb2da14..d26affd4714 100644 --- a/packages/kilo-vscode/webview-ui/src/utils/ime-enter.ts +++ b/packages/kilo-vscode/webview-ui/src/utils/ime-enter.ts @@ -3,8 +3,6 @@ * Browsers set `isComposing` while composing; on Windows, `keyCode === 229` often * marks IME-handled keys even when `isComposing` is false for a given keydown. */ -export function isEnterKeyCommitNotIme( - e: Pick, -): boolean { +export function isEnterKeyCommitNotIme(e: Pick): boolean { return e.key === "Enter" && !e.isComposing && e.keyCode !== 229 } From 66af6907005b99bb39a0869b35dfe1ec180cc0b5 Mon Sep 17 00:00:00 2001 From: Josh Holmer Date: Mon, 1 Jun 2026 03:52:49 -0400 Subject: [PATCH 12/78] feat(cli): show terminal title status indicators --- .changeset/terminal-title-status.md | 5 + packages/opencode/src/cli/cmd/tui/app.tsx | 40 ++--- .../opencode/src/kilocode/cli/cmd/tui/app.tsx | 56 ++++++- .../kilocode/cli/cmd/tui/terminal-title.ts | 115 +++++++++++++ .../test/kilocode/terminal-title.test.ts | 153 ++++++++++++++++++ .../tui-terminal-title-reactivity.test.ts | 20 +++ 6 files changed, 357 insertions(+), 32 deletions(-) create mode 100644 .changeset/terminal-title-status.md create mode 100644 packages/opencode/src/kilocode/cli/cmd/tui/terminal-title.ts create mode 100644 packages/opencode/test/kilocode/terminal-title.test.ts create mode 100644 packages/opencode/test/kilocode/tui-terminal-title-reactivity.test.ts diff --git a/.changeset/terminal-title-status.md b/.changeset/terminal-title-status.md new file mode 100644 index 00000000000..942b3de15f8 --- /dev/null +++ b/.changeset/terminal-title-status.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Show terminal title status indicators when sessions are working, need attention, or have finished. diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index fefc896413c..2d37bc1d668 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -17,6 +17,7 @@ import { batch, Show, on, + untrack, // kilocode_change } from "solid-js" import { win32DisableProcessedInput, win32FlushInputBuffer, win32InstallCtrlCGuard } from "./win32" // kilocode_change import { Flag } from "@opencode-ai/core/flag/flag" @@ -52,7 +53,6 @@ import { DialogAlert } from "./ui/dialog-alert" import { DialogConfirm } from "./ui/dialog-confirm" import { ToastProvider, useToast } from "./ui/toast" import { ExitProvider, useExit } from "./context/exit" -import { Session as SessionApi } from "@/session/session" // kilocode_change start import { DialogSelect } from "./ui/dialog-select" import { Link } from "./ui/link" @@ -351,6 +351,7 @@ function App(props: { onSnapshot?: () => Promise }) { renderer.clearSelection() } const [terminalTitleEnabled, setTerminalTitleEnabled] = createSignal(kv.get("terminal_title_enabled", true)) + const [done, setDone] = createSignal>({}) // kilocode_change const [pasteSummaryEnabled, setPasteSummaryEnabled] = createSignal( kv.get("paste_summary_enabled", !sync.data.config.experimental?.disable_paste_summary), ) @@ -366,30 +367,21 @@ function App(props: { onSnapshot?: () => Promise }) { const titleDefault = KiloApp.APP_TITLE // kilocode_change - if (route.data.type === "home") { - renderer.setTerminalTitle(titleDefault) // kilocode_change - return - } - - if (route.data.type === "session") { - const session = sync.session.get(route.data.sessionID) - if (!session || SessionApi.isDefaultTitle(session.title)) { - renderer.setTerminalTitle(titleDefault) // kilocode_change - return - } - - const title = session.title.length > 40 ? session.title.slice(0, 37) + "..." : session.title - renderer.setTerminalTitle(`${titleDefault} | ${title}`) // kilocode_change - return - } - - if (route.data.type === "plugin") { - renderer.setTerminalTitle(`${titleDefault} | ${route.data.id}`) // kilocode_change - } - // kilocode_change start - const kiloTitle = KiloApp.getTerminalTitle(route, titleDefault) - if (kiloTitle) renderer.setTerminalTitle(kiloTitle) + const kiloTitle = KiloApp.getTerminalTitle({ + route, + base: titleDefault, + sync, + done: untrack(done), + }) + if (kiloTitle) { + const id = kiloTitle.id + if (id && kiloTitle.active && untrack(() => done()[id]) !== true) { + setDone((prev) => ({ ...prev, [id]: true })) + } + renderer.setTerminalTitle(kiloTitle.title) + return + } // kilocode_change end }) diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/app.tsx b/packages/opencode/src/kilocode/cli/cmd/tui/app.tsx index 73d55db3a04..1dd6f89a873 100644 --- a/packages/opencode/src/kilocode/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/kilocode/cli/cmd/tui/app.tsx @@ -24,9 +24,12 @@ import { registerKiloCommands } from "@/kilocode/kilo-commands" import { initializeTUIDependencies } from "@kilocode/kilo-gateway/tui" import { DialogProcessList } from "@/kilocode/cli/cmd/tui/component/dialog-process-list" import { useIndexingWarnings } from "@/kilocode/cli/cmd/tui/indexing-warning" +import { KiloTerminalTitle } from "./terminal-title" +import { Session as SessionApi } from "@/session/session" // Re-export so upstream can render the route without importing directly export { KiloClawView } from "@/kilocode/claw/view" +export { KiloTerminalTitle } from "./terminal-title" // Hot reload TUI-local settings (keybinds/theme/ui) when changed from the Kilo Console. // Called from the App body (below SDKProvider and the TuiConfig provider). @@ -112,15 +115,52 @@ export function useSessionEffects(deps: { // --------------------------------------------------------------------------- /** - * Returns the terminal title for kiloclaw routes. - * Returns undefined for other routes (caller should handle them). + * Returns the terminal title for supported TUI routes. */ -export function getTerminalTitle( - route: ReturnType, - base: string, -): string | undefined { - if (route.data.type === "kiloclaw") return `${base} | KiloClaw` - return undefined +export function getTerminalTitle(input: { + route: ReturnType + base: string + sync: ReturnType + done: Record +}): KiloTerminalTitle.Result | undefined { + if (input.route.data.type === "home") { + return { + title: KiloTerminalTitle.format({ base: input.base, indicator: "none" }), + active: false, + indicator: "none", + } + } + + if (input.route.data.type === "session") { + const state = KiloTerminalTitle.session({ + base: input.base, + id: input.route.data.sessionID, + data: input.sync.data, + done: input.done, + }) + const session = input.sync.session.get(input.route.data.sessionID) + const title = !session || SessionApi.isDefaultTitle(session.title) ? undefined : session.title + return { + ...state, + title: KiloTerminalTitle.format({ base: input.base, title, indicator: state.indicator }), + } + } + + if (input.route.data.type === "plugin") { + return { + title: KiloTerminalTitle.format({ base: input.base, title: input.route.data.id, indicator: "none" }), + active: false, + indicator: "none", + } + } + + if (input.route.data.type === "kiloclaw") { + return { + title: KiloTerminalTitle.format({ base: input.base, title: "KiloClaw", indicator: "none" }), + active: false, + indicator: "none", + } + } } // --------------------------------------------------------------------------- diff --git a/packages/opencode/src/kilocode/cli/cmd/tui/terminal-title.ts b/packages/opencode/src/kilocode/cli/cmd/tui/terminal-title.ts new file mode 100644 index 00000000000..6c634f8bf6e --- /dev/null +++ b/packages/opencode/src/kilocode/cli/cmd/tui/terminal-title.ts @@ -0,0 +1,115 @@ +type Session = { + id: string + title: string + parentID?: string | null +} + +type Status = { + type: "idle" | "retry" | "busy" | "offline" +} + +type Message = { + id: string + role: string +} + +type Part = { + type: string + tool?: string + state?: { + status: string + } +} + +type Queue = Record + +export namespace KiloTerminalTitle { + export type Indicator = "none" | "working" | "attention" | "finished" + + export type Data = { + session: readonly Session[] + session_status: Record + permission: Queue + question: Queue + suggestion: Queue + network: Queue + message: Record + part: Record + } + + export type Result = { + title: string + id?: string + active: boolean + indicator: Indicator + } + + const icon = { + none: "", + working: "💭", + attention: "🔶", + finished: "✅", + } satisfies Record + + export function format(input: { base: string; title?: string; indicator: Indicator }) { + const text = input.title ? `${input.base} | ${truncate(input.title)}` : input.base + const prefix = icon[input.indicator] + if (!prefix) return text + return `${prefix} ${text}` + } + + export function session(input: { base: string; id: string; data: Data; done: Record }): Result { + const info = input.data.session.find((item) => item.id === input.id) + const id = root(input.data.session, input.id) + const ids = family(input.data.session, id) + const indicator = state({ data: input.data, ids, done: input.done[id] === true }) + + return { + title: format({ base: input.base, title: info?.title, indicator }), + id, + active: indicator === "working" || indicator === "attention", + indicator, + } + } + + function truncate(title: string) { + if (title.length <= 40) return title + return title.slice(0, 37) + "..." + } + + function root(list: readonly Session[], id: string) { + return list.find((item) => item.id === id)?.parentID ?? id + } + + function family(list: readonly Session[], id: string) { + return list.filter((item) => item.id === id || item.parentID === id).map((item) => item.id) + } + + function state(input: { data: Data; ids: readonly string[]; done: boolean }): Indicator { + if (input.ids.some((id) => attention(input.data, id))) return "attention" + if (input.ids.some((id) => working(input.data.session_status[id]))) return "working" + if (input.done) return "finished" + return "none" + } + + function working(status: Status | undefined) { + return status?.type === "busy" || status?.type === "retry" + } + + function attention(data: Data, id: string) { + if (data.session_status[id]?.type === "offline") return true + if ((data.permission[id]?.length ?? 0) > 0) return true + if ((data.question[id]?.length ?? 0) > 0) return true + if ((data.suggestion[id]?.length ?? 0) > 0) return true + if ((data.network[id]?.length ?? 0) > 0) return true + return plan(data, id) + } + + function plan(data: Data, id: string) { + const msg = data.message[id]?.at(-1) + if (msg?.role !== "assistant") return false + return (data.part[msg.id] ?? []).some( + (part) => part.type === "tool" && part.tool === "plan_exit" && part.state?.status === "completed", + ) + } +} diff --git a/packages/opencode/test/kilocode/terminal-title.test.ts b/packages/opencode/test/kilocode/terminal-title.test.ts new file mode 100644 index 00000000000..0de824033f7 --- /dev/null +++ b/packages/opencode/test/kilocode/terminal-title.test.ts @@ -0,0 +1,153 @@ +import { describe, expect, test } from "bun:test" +import { KiloTerminalTitle } from "../../src/kilocode/cli/cmd/tui/terminal-title" + +const base = "Kilo CLI" + +function data(input: Partial = {}): KiloTerminalTitle.Data { + return { + session: [{ id: "parent", title: "Build status" }], + session_status: {}, + permission: {}, + question: {}, + suggestion: {}, + network: {}, + message: {}, + part: {}, + ...input, + } +} + +describe("KiloTerminalTitle", () => { + test("format_noIndicator_returnsBaseTitle", () => { + expect(KiloTerminalTitle.format({ base, indicator: "none" })).toBe("Kilo CLI") + }) + + test("format_workingPrefix_prependsThinkingIcon", () => { + expect(KiloTerminalTitle.format({ base, title: "Build status", indicator: "working" })).toBe( + "💭 Kilo CLI | Build status", + ) + }) + + test("format_attentionPrefix_prependsWarningIcon", () => { + expect(KiloTerminalTitle.format({ base, title: "Build status", indicator: "attention" })).toBe( + "🔶 Kilo CLI | Build status", + ) + }) + + test("format_finishedPrefix_prependsCheckIcon", () => { + expect(KiloTerminalTitle.format({ base, title: "Build status", indicator: "finished" })).toBe( + "✅ Kilo CLI | Build status", + ) + }) + + test("format_longSessionTitle_truncatesToExistingLimit", () => { + expect( + KiloTerminalTitle.format({ + base, + title: "12345678901234567890123456789012345678901234567890", + indicator: "working", + }), + ).toBe("💭 Kilo CLI | 1234567890123456789012345678901234567...") + }) + + test("session_newIdleSession_hasNoIndicator", () => { + expect( + KiloTerminalTitle.session({ + base, + id: "parent", + data: data(), + done: {}, + }), + ).toEqual({ title: "Kilo CLI | Build status", id: "parent", active: false, indicator: "none" }) + }) + + test("session_busySession_isWorking", () => { + expect( + KiloTerminalTitle.session({ + base, + id: "parent", + data: data({ session_status: { parent: { type: "busy" } } }), + done: {}, + }), + ).toEqual({ title: "💭 Kilo CLI | Build status", id: "parent", active: true, indicator: "working" }) + }) + + test("session_pendingPermission_overridesBusy", () => { + expect( + KiloTerminalTitle.session({ + base, + id: "parent", + data: data({ + session_status: { parent: { type: "busy" } }, + permission: { parent: [{}] }, + }), + done: {}, + }).indicator, + ).toBe("attention") + }) + + test("session_childQuestion_marksParentAttention", () => { + expect( + KiloTerminalTitle.session({ + base, + id: "parent", + data: data({ + session: [ + { id: "parent", title: "Build status" }, + { id: "child", title: "Child", parentID: "parent" }, + ], + question: { child: [{ blocking: true }] }, + }), + done: {}, + }).indicator, + ).toBe("attention") + }) + + test("session_latestAssistantPlanExit_marksAttention", () => { + expect( + KiloTerminalTitle.session({ + base, + id: "parent", + data: data({ + message: { parent: [{ id: "m1", role: "assistant" }] }, + part: { + m1: [{ type: "tool", tool: "plan_exit", state: { status: "completed" } }], + }, + }), + done: {}, + }).indicator, + ).toBe("attention") + }) + + test("session_latestUserAfterPlanExit_clearsPlanExitAttention", () => { + expect( + KiloTerminalTitle.session({ + base, + id: "parent", + data: data({ + message: { + parent: [ + { id: "m1", role: "assistant" }, + { id: "m2", role: "user" }, + ], + }, + part: { + m1: [{ type: "tool", tool: "plan_exit", state: { status: "completed" } }], + }, + }), + done: {}, + }).indicator, + ).toBe("none") + }) + + test("session_doneIdleSession_isFinished", () => { + expect( + KiloTerminalTitle.session({ + base, + id: "parent", + data: data(), + done: { parent: true }, + }).indicator, + ).toBe("finished") + }) +}) diff --git a/packages/opencode/test/kilocode/tui-terminal-title-reactivity.test.ts b/packages/opencode/test/kilocode/tui-terminal-title-reactivity.test.ts new file mode 100644 index 00000000000..7ebfe8dd67d --- /dev/null +++ b/packages/opencode/test/kilocode/tui-terminal-title-reactivity.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, test } from "bun:test" +import fs from "node:fs" +import path from "node:path" + +const APP_FILE = path.resolve(import.meta.dir, "../../src/cli/cmd/tui/app.tsx") + +describe("terminal title done tracking", () => { + test("app.tsx untracks the done snapshot passed to getTerminalTitle", () => { + const content = fs.readFileSync(APP_FILE, "utf-8") + + expect(content).toContain("done: untrack(done)") + }) + + test("app.tsx untracks the done guard before setDone", () => { + const content = fs.readFileSync(APP_FILE, "utf-8") + + expect(content).toContain("const id = kiloTitle.id") + expect(content).toContain("untrack(() => done()[id]) !== true") + }) +}) From 019270e407234f809457b8b7e0c2c989f61caa21 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Mon, 15 Jun 2026 10:28:16 +0200 Subject: [PATCH 13/78] test(cli): await ordinary workspace indexing --- packages/opencode/test/kilocode/indexing-worktree.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/opencode/test/kilocode/indexing-worktree.test.ts b/packages/opencode/test/kilocode/indexing-worktree.test.ts index 234ef1724ce..2fb30773d6a 100644 --- a/packages/opencode/test/kilocode/indexing-worktree.test.ts +++ b/packages/opencode/test/kilocode/indexing-worktree.test.ts @@ -88,6 +88,7 @@ describe("indexing worktrees", () => { await WithInstance.provide({ directory, fn: async () => { + await KiloIndexing.search("ordinary directory") expect((await KiloIndexing.current()).state).toBe("Complete") expect(await KiloIndexing.available()).toBe(true) }, From 77536599114721d5392ba7dfde492659c30b3964 Mon Sep 17 00:00:00 2001 From: Z User Date: Mon, 8 Jun 2026 08:58:06 +0000 Subject: [PATCH 14/78] chore: remove 39 unused dependencies across monorepo Removed unused packages identified via depcheck static analysis and verified with full codebase grep to confirm zero usage. Root (5): - @aws-sdk/client-s3, heap-snapshot-toolkit, @morphllm/morphsdk - @actions/artifact, sst packages/core (1): - @types/semver packages/ui (6): - @shikijs/transformers, @solid-primitives/bounds, luxon - marked-katex-extension, marked-shiki, @types/luxon packages/kilo-gateway (1): - @kilocode/sdk packages/kilo-ui (3): - @storybook/addon-a11y, @storybook/addon-docs, @storybook/addon-themes packages/kilo-vscode (3): - dotenv, @storybook/addon-a11y, @storybook/addon-docs packages/kilo-console (1): - @opencode-ai/ui packages/opencode (20): - @hono/node-server, @hono/node-ws, @hono/standard-validator - @openauthjs/openauth, partial-json, zod-to-json-schema - @standard-schema/spec, @babel/core, @types/babel__core - @npmcli/config, @opentelemetry/context-async-hooks - @opentelemetry/exporter-trace-otlp-http, @pierre/diffs - chokidar, glob, hono, hono-openapi, minimatch - rotating-file-stream, xdg-basedir, @types/npmcli__arborist Result: 264 packages pruned from node_modules (3962 -> 3698) --- bun.lock | 835 ++++++++--------------------- package.json | 7 +- packages/core/package.json | 3 +- packages/kilo-console/package.json | 1 - packages/kilo-gateway/package.json | 1 - packages/kilo-ui/package.json | 3 - packages/kilo-vscode/package.json | 3 - packages/opencode/package.json | 25 +- packages/ui/package.json | 6 - 9 files changed, 226 insertions(+), 658 deletions(-) diff --git a/bun.lock b/bun.lock index 962a6b0c75c..a2d87c4c54a 100644 --- a/bun.lock +++ b/bun.lock @@ -5,16 +5,12 @@ "": { "name": "@kilocode/kilo", "dependencies": { - "@aws-sdk/client-s3": "3.1025.0", "@kilocode/plugin": "workspace:*", "@kilocode/sdk": "workspace:*", - "@morphllm/morphsdk": "0.2.166", "@opencode-ai/script": "workspace:*", - "heap-snapshot-toolkit": "1.1.3", "typescript": "catalog:", }, "devDependencies": { - "@actions/artifact": "5.0.1", "@changesets/changelog-github": "^0.5.1", "@changesets/cli": "^2.27.10", "@tsconfig/bun": "catalog:", @@ -27,61 +23,32 @@ "oxlint-tsgolint": "0.21.0", "prettier": "3.6.2", "semver": "^7.6.0", - "sst": "3.18.10", "turbo": "2.9.14", }, }, "packages/core": { "name": "@opencode-ai/core", - "version": "7.3.46", + "version": "7.3.40", "bin": { "opencode": "./bin/opencode", }, "dependencies": { - "@ai-sdk/alibaba": "1.0.17", - "@ai-sdk/amazon-bedrock": "4.0.96", - "@ai-sdk/anthropic": "3.0.71", - "@ai-sdk/azure": "3.0.49", - "@ai-sdk/cerebras": "2.0.54", - "@ai-sdk/cohere": "3.0.27", - "@ai-sdk/deepinfra": "2.0.41", - "@ai-sdk/gateway": "3.0.104", - "@ai-sdk/google": "3.0.63", - "@ai-sdk/google-vertex": "4.0.112", - "@ai-sdk/groq": "3.0.31", - "@ai-sdk/mistral": "3.0.27", - "@ai-sdk/openai": "3.0.53", - "@ai-sdk/openai-compatible": "2.0.48", - "@ai-sdk/perplexity": "3.0.26", - "@ai-sdk/provider": "3.0.8", - "@ai-sdk/provider-utils": "4.0.23", - "@ai-sdk/togetherai": "2.0.41", - "@ai-sdk/vercel": "2.0.39", - "@ai-sdk/xai": "3.0.92", - "@aws-sdk/credential-providers": "3.993.0", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", - "@kilocode/kilo-gateway": "workspace:*", "@npmcli/arborist": "9.4.0", "@npmcli/config": "10.8.1", - "@openrouter/ai-sdk-provider": "2.9.0", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", - "ai-gateway-provider": "3.1.2", "cross-spawn": "catalog:", "effect": "catalog:", - "gitlab-ai-provider": "6.6.0", "glob": "13.0.5", - "google-auth-library": "10.5.0", - "immer": "11.1.4", "mime-types": "3.0.2", "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "rotating-file-stream": "3.2.9", "semver": "^7.6.3", - "venice-ai-sdk-provider": "2.0.1", "xdg-basedir": "5.1.0", "zod": "catalog:", }, @@ -91,12 +58,11 @@ "@types/cross-spawn": "catalog:", "@types/npm-package-arg": "6.1.4", "@types/npmcli__arborist": "6.3.3", - "@types/semver": "catalog:", }, }, "packages/http-recorder": { "name": "@opencode-ai/http-recorder", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "@effect/platform-node": "catalog:", "effect": "catalog:", @@ -109,12 +75,11 @@ }, "packages/kilo-console": { "name": "@kilocode/kilo-console", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "@kilocode/kilo-web-ui": "workspace:*", "@kilocode/sdk": "workspace:*", "@lottiefiles/dotlottie-web": "0.74.0", - "@opencode-ai/ui": "workspace:*", "@solidjs/router": "catalog:", "ghostty-web": "0.4.0", "solid-js": "catalog:", @@ -131,7 +96,7 @@ }, "packages/kilo-docs": { "name": "@kilocode/kilo-docs", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "@docsearch/css": "^4", "@docsearch/js": "^4", @@ -161,17 +126,16 @@ }, "packages/kilo-gateway": { "name": "@kilocode/kilo-gateway", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/anthropic": "3.0.71", "@ai-sdk/mistral": "3.0.27", "@ai-sdk/openai": "3.0.53", - "@ai-sdk/openai-compatible": "2.0.48", + "@ai-sdk/openai-compatible": "2.0.41", "@clack/prompts": "1.0.0-alpha.1", "@kilocode/plugin": "workspace:*", - "@kilocode/sdk": "workspace:*", - "@openrouter/ai-sdk-provider": "2.9.0", + "@openrouter/ai-sdk-provider": "2.8.1", "ai": "catalog:", "open": "10.1.2", "zod": "catalog:", @@ -198,7 +162,7 @@ }, "packages/kilo-i18n": { "name": "@kilocode/kilo-i18n", - "version": "7.3.46", + "version": "7.3.40", "devDependencies": { "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", @@ -208,7 +172,7 @@ }, "packages/kilo-indexing": { "name": "@kilocode/kilo-indexing", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "@aws-sdk/client-bedrock-runtime": "3.1005.0", "@aws-sdk/credential-provider-ini": "3.972.31", @@ -240,11 +204,11 @@ }, "packages/kilo-jetbrains": { "name": "@kilocode/kilo-jetbrains", - "version": "7.3.46", + "version": "7.3.40", }, "packages/kilo-telemetry": { "name": "@kilocode/kilo-telemetry", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "@kilocode/kilo-gateway": "workspace:*", "posthog-node": "4.4.0", @@ -258,7 +222,7 @@ }, "packages/kilo-ui": { "name": "@kilocode/kilo-ui", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "0.13.11", @@ -277,9 +241,6 @@ }, "devDependencies": { "@playwright/test": "1.57.0", - "@storybook/addon-a11y": "10.2.10", - "@storybook/addon-docs": "10.2.10", - "@storybook/addon-themes": "10.2.10", "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", "storybook": "10.2.10", @@ -295,7 +256,7 @@ }, "packages/kilo-vscode": { "name": "kilo-code", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "@anthropic-ai/sdk": "^0.39.0", "@kilocode/kilo-gateway": "workspace:*", @@ -312,7 +273,6 @@ "@xterm/addon-web-links": "0.12.0", "@xterm/xterm": "6.0.0", "diff": "8.0.4", - "dotenv": "^16.4.7", "fastest-levenshtein": "^1.0.16", "friendly-words": "1.3.1", "fuzzysort": "3.1.0", @@ -333,8 +293,6 @@ "devDependencies": { "@axe-core/playwright": "4.11.3", "@playwright/test": "1.57.0", - "@storybook/addon-a11y": "10.2.10", - "@storybook/addon-docs": "10.2.10", "@types/diff": "^6.0.0", "@types/mocha": "^10.0.10", "@types/node": "22.x", @@ -361,7 +319,7 @@ }, "packages/kilo-web-ui": { "name": "@kilocode/kilo-web-ui", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "@kilocode/kilo-ui": "workspace:*", "@kobalte/core": "catalog:", @@ -376,7 +334,7 @@ }, "packages/llm": { "name": "@opencode-ai/llm", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", @@ -394,7 +352,7 @@ }, "packages/opencode": { "name": "@kilocode/cli", - "version": "7.3.46", + "version": "7.3.40", "bin": { "kilo": "./bin/kilo", "kilocode": "./bin/kilo", @@ -407,7 +365,7 @@ "@ai-sdk/amazon-bedrock": "4.0.96", "@ai-sdk/anthropic": "3.0.71", "@ai-sdk/azure": "3.0.49", - "@ai-sdk/cerebras": "2.0.54", + "@ai-sdk/cerebras": "2.0.41", "@ai-sdk/cohere": "3.0.27", "@ai-sdk/deepinfra": "2.0.41", "@ai-sdk/gateway": "3.0.104", @@ -416,22 +374,19 @@ "@ai-sdk/groq": "3.0.31", "@ai-sdk/mistral": "3.0.27", "@ai-sdk/openai": "3.0.53", - "@ai-sdk/openai-compatible": "2.0.48", + "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", - "@ai-sdk/xai": "3.0.92", + "@ai-sdk/xai": "3.0.82", "@aws-sdk/credential-providers": "3.1025.0", "@clack/prompts": "1.0.0-alpha.1", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", "@gitlab/gitlab-ai-provider": "3.6.0", "@gitlab/opencode-gitlab-auth": "1.3.3", - "@hono/node-server": "1.19.13", - "@hono/node-ws": "1.3.0", - "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", "@kilocode/kilo-gateway": "workspace:*", "@kilocode/kilo-indexing": "workspace:*", @@ -443,36 +398,27 @@ "@modelcontextprotocol/sdk": "1.29.0", "@morphllm/morphsdk": "0.2.166", "@npmcli/arborist": "9.4.0", - "@npmcli/config": "10.8.1", "@octokit/graphql": "9.0.2", "@octokit/rest": "catalog:", - "@openauthjs/openauth": "catalog:", "@opencode-ai/script": "workspace:*", - "@opencode-ai/ui": "workspace:*", - "@openrouter/ai-sdk-provider": "2.9.0", + "@openrouter/ai-sdk-provider": "2.8.1", "@opentelemetry/api": "1.9.0", - "@opentelemetry/context-async-hooks": "2.6.1", - "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", "@opentui/core": "catalog:", "@opentui/keymap": "catalog:", "@opentui/solid": "catalog:", "@parcel/watcher": "2.5.1", - "@pierre/diffs": "catalog:", "@secretlint/core": "10.2.2", "@secretlint/secretlint-rule-preset-recommend": "10.2.2", - "@silvia-odwyer/photon-node": "0.3.4", "@solid-primitives/event-bus": "1.1.2", "@solid-primitives/scheduled": "1.5.2", - "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", "ai-gateway-provider": "3.1.2", "bonjour-service": "1.3.0", "bun-pty": "0.4.8", "chardet": "2.1.1", - "chokidar": "4.0.3", "cli-sound": "1.1.3", "clipboardy": "4.0.0", "cross-spawn": "catalog:", @@ -482,28 +428,21 @@ "effect": "catalog:", "fuzzysort": "3.1.0", "gitlab-ai-provider": "6.6.0", - "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", - "hono": "catalog:", - "hono-openapi": "catalog:", - "htmlparser2": "8.0.2", "iconv-lite": "0.7.2", "ignore": "7.0.5", "immer": "11.1.4", "jsonc-parser": "3.3.1", "mammoth": "1.12.0", "mime-types": "3.0.2", - "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "open": "10.1.2", "opencode-gitlab-auth": "2.0.1", "opencode-poe-auth": "0.0.1", "opentui-spinner": "catalog:", - "partial-json": "0.1.7", "remeda": "catalog:", "ripgrep": "0.3.1", - "rotating-file-stream": "3.2.9", "semver": "^7.6.3", "simple-git": "3.36.0", "solid-js": "catalog:", @@ -517,14 +456,11 @@ "vscode-jsonrpc": "8.2.1", "web-tree-sitter": "0.25.10", "which": "6.0.1", - "xdg-basedir": "5.1.0", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "yargs": "18.0.0", "zod": "catalog:", - "zod-to-json-schema": "3.24.5", }, "devDependencies": { - "@babel/core": "7.28.4", "@effect/language-service": "0.84.2", "@octokit/webhooks-types": "7.6.1", "@opencode-ai/core": "workspace:*", @@ -537,9 +473,7 @@ "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1", - "@standard-schema/spec": "1.0.0", "@tsconfig/bun": "catalog:", - "@types/babel__core": "7.20.5", "@types/bun": "catalog:", "@types/cross-spawn": "catalog:", "@types/mime-types": "3.0.1", @@ -557,12 +491,11 @@ "typescript": "catalog:", "vscode-languageserver-types": "3.17.5", "why-is-node-running": "3.2.2", - "zod-to-json-schema": "3.24.5", }, }, "packages/plugin": { "name": "@kilocode/plugin", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "@kilocode/sdk": "workspace:*", "effect": "catalog:", @@ -578,9 +511,9 @@ "typescript": "catalog:", }, "peerDependencies": { - "@opentui/core": ">=0.2.11", - "@opentui/keymap": ">=0.2.11", - "@opentui/solid": ">=0.2.11", + "@opentui/core": ">=0.2.6", + "@opentui/keymap": ">=0.2.6", + "@opentui/solid": ">=0.2.6", }, "optionalPeers": [ "@opentui/core", @@ -590,7 +523,7 @@ }, "packages/plugin-atomic-chat": { "name": "@kilocode/plugin-atomic-chat", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "@kilocode/plugin": "workspace:*", }, @@ -604,7 +537,7 @@ }, "packages/script": { "name": "@opencode-ai/script", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "semver": "^7.6.3", }, @@ -615,7 +548,7 @@ }, "packages/sdk/js": { "name": "@kilocode/sdk", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "cross-spawn": "catalog:", }, @@ -630,7 +563,7 @@ }, "packages/storybook": { "name": "@opencode-ai/storybook", - "version": "7.3.46", + "version": "7.3.40", "devDependencies": { "@opencode-ai/ui": "workspace:*", "@solidjs/meta": "catalog:", @@ -653,14 +586,12 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "7.3.46", + "version": "7.3.40", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "catalog:", "@opencode-ai/core": "workspace:*", "@pierre/diffs": "catalog:", - "@shikijs/transformers": "3.9.2", - "@solid-primitives/bounds": "0.1.3", "@solid-primitives/event-listener": "2.4.5", "@solid-primitives/media": "2.3.3", "@solid-primitives/resize-observer": "2.1.3", @@ -671,10 +602,7 @@ "dompurify": "3.4.2", "fuzzysort": "catalog:", "katex": "0.16.27", - "luxon": "catalog:", "marked": "catalog:", - "marked-katex-extension": "5.1.6", - "marked-shiki": "catalog:", "mermaid": "11.15.0", "morphdom": "2.7.8", "motion": "12.34.5", @@ -693,7 +621,6 @@ "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", "@types/katex": "0.16.7", - "@types/luxon": "catalog:", "@typescript/native-preview": "catalog:", "tailwindcss": "catalog:", "typescript": "catalog:", @@ -711,15 +638,13 @@ "tree-sitter-bash", ], "patchedDependencies": { - "mammoth@1.12.0": "patches/mammoth@1.12.0.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "@npmcli/agent@4.0.0": "patches/@npmcli%2Fagent@4.0.0.patch", - "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", + "mammoth@1.12.0": "patches/mammoth@1.12.0.patch", }, "overrides": { "@effect/platform-node-shared": "4.0.0-beta.46", "@opentui/core": "catalog:", - "@opentui/keymap": "catalog:", "@opentui/solid": "catalog:", "@types/bun": "catalog:", "@types/node": "catalog:", @@ -738,17 +663,17 @@ }, "catalog": { "@cloudflare/workers-types": "4.20251008.0", - "@effect/opentelemetry": "4.0.0-beta.65", - "@effect/platform-node": "4.0.0-beta.65", + "@effect/opentelemetry": "4.0.0-beta.57", + "@effect/platform-node": "4.0.0-beta.57", "@hono/zod-validator": "0.4.2", "@kobalte/core": "0.13.11", "@lydell/node-pty": "1.2.0-beta.10", "@npmcli/arborist": "9.4.0", "@octokit/rest": "22.0.0", "@openauthjs/openauth": "0.0.0-20250322224806", - "@opentui/core": "0.2.11", - "@opentui/keymap": "0.2.11", - "@opentui/solid": "0.2.11", + "@opentui/core": "0.2.6", + "@opentui/keymap": "0.2.6", + "@opentui/solid": "0.2.6", "@pierre/diffs": "1.1.22", "@playwright/test": "1.59.1", "@solid-primitives/storage": "4.3.3", @@ -770,7 +695,7 @@ "dompurify": "3.4.2", "drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-orm": "1.0.0-beta.19-d95b7a4", - "effect": "4.0.0-beta.65", + "effect": "4.0.0-beta.59", "fuzzysort": "3.1.0", "hono": "4.12.12", "hono-openapi": "1.1.2", @@ -793,15 +718,13 @@ "zod": "4.1.8", }, "packages": { - "@actions/artifact": ["@actions/artifact@5.0.1", "", { "dependencies": { "@actions/core": "^2.0.0", "@actions/github": "^6.0.1", "@actions/http-client": "^3.0.0", "@azure/storage-blob": "^12.29.1", "@octokit/core": "^5.2.1", "@octokit/plugin-request-log": "^1.0.4", "@octokit/plugin-retry": "^3.0.9", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1", "archiver": "^7.0.1", "jwt-decode": "^3.1.2", "unzip-stream": "^0.3.1" } }, "sha512-dHJ5rHduhCKUikKTT9eXeWoUvfKia3IjR1sO/VTAV3DVAL4yMTRnl2iO5mcfiBjySHLwPNezwENAVskKYU5ymw=="], - "@actions/core": ["@actions/core@1.11.1", "", { "dependencies": { "@actions/exec": "^1.1.1", "@actions/http-client": "^2.0.1" } }, "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A=="], "@actions/exec": ["@actions/exec@1.1.1", "", { "dependencies": { "@actions/io": "^1.0.1" } }, "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w=="], "@actions/github": ["@actions/github@6.0.1", "", { "dependencies": { "@actions/http-client": "^2.2.0", "@octokit/core": "^5.0.1", "@octokit/plugin-paginate-rest": "^9.2.2", "@octokit/plugin-rest-endpoint-methods": "^10.4.0", "@octokit/request": "^8.4.1", "@octokit/request-error": "^5.1.1", "undici": "^5.28.5" } }, "sha512-xbZVcaqD4XnQAe35qSQqskb3SqIAfRyLBrHMd/8TuL7hJSz2QtbDwnNM8zWx4zO5l2fnGtseNE3MbEvD7BxVMw=="], - "@actions/http-client": ["@actions/http-client@3.0.2", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^6.23.0" } }, "sha512-JP38FYYpyqvUsz+Igqlc/JG6YO9PaKuvqjM3iGvaLqFnJ7TFmcLyy2IDrY0bI0qCQug8E9K+elv5ZNfw62ZJzA=="], + "@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], "@actions/io": ["@actions/io@1.1.3", "", {}, "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="], @@ -817,7 +740,7 @@ "@ai-sdk/azure": ["@ai-sdk/azure@3.0.49", "", { "dependencies": { "@ai-sdk/openai": "3.0.48", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wskgAL+OmrHG7by/iWIxEBQCEdc1mDudha/UZav46i0auzdFfsDB/k2rXZaC4/3nWSgMZkxr0W3ncyouEGX/eg=="], - "@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.54", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-m+oLYlZUA0j5aeE8BrjDd2DSCx6HujqPB2C0YjQF7iGQ4sTCCmhHfH2o0uY7vlSPIJpA2ak6YyF7R14a5dq8fw=="], + "@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.41", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kDMEpjaRdRXIUi1EH8WHwLRahyDTYv9SAJnP6VCCeq8X+tVqZbMLCqqxSG5dRknrI65ucjvzQt+FiDKTAa7AHg=="], "@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OqcCq2PiFY1dbK/0Ck45KuvE8jfdxRuuAE9Y5w46dAk6U+9vPOeg1CDcmR+ncqmrYrhRl3nmyDttyDahyjCzAw=="], @@ -843,7 +766,7 @@ "@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], - "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q=="], + "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], "@ai-sdk/perplexity": ["@ai-sdk/perplexity@3.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-dXzrVsLR5f6tr+U04jq4AXoRroGFBTvODnLgss0SWbzNjGGQg3XqtQ9j7rCLo6o8qbYGuAHvqUrIpUCuiscuFg=="], @@ -855,7 +778,7 @@ "@ai-sdk/vercel": ["@ai-sdk/vercel@2.0.39", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8eu3ljJpkCTP4ppcyYB+NcBrkcBoSOFthCSgk5VnjaxnDaOJFaxnPwfddM7wx3RwMk2CiK1O61Px/LlqNc7QkQ=="], - "@ai-sdk/xai": ["@ai-sdk/xai@3.0.92", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yqRMdEVfSkZCXs5mqeKfYOuQElo93igwz/4zWbYkhZWAW7tDh+5uqRef6MtQVYF/bTC1EsdbeO9DtsOtAZMOLA=="], + "@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -869,10 +792,6 @@ "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], - "@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="], - - "@aws-crypto/sha1-browser": ["@aws-crypto/sha1-browser@5.2.0", "", { "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg=="], - "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], @@ -885,12 +804,8 @@ "@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.1025.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.26", "@aws-sdk/credential-provider-node": "^3.972.29", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.9", "@aws-sdk/middleware-user-agent": "^3.972.28", "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.14", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.13", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-retry": "^4.4.46", "@smithy/middleware-serde": "^4.2.16", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.1", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.44", "@smithy/util-defaults-mode-node": "^4.2.48", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.13", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ke7vyS7Dmo3St1a354AlpAjocZpG25Ql52XQ1AXRD3VQ791FBT7vX+EqGCfAEIWLPxpcldaW2Nny6P6klEOkkw=="], - "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.1025.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.26", "@aws-sdk/credential-provider-node": "^3.972.29", "@aws-sdk/middleware-bucket-endpoint": "^3.972.8", "@aws-sdk/middleware-expect-continue": "^3.972.8", "@aws-sdk/middleware-flexible-checksums": "^3.974.6", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-location-constraint": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.9", "@aws-sdk/middleware-sdk-s3": "^3.972.27", "@aws-sdk/middleware-ssec": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.28", "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/signature-v4-multi-region": "^3.996.15", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.14", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.13", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/eventstream-serde-config-resolver": "^4.3.12", "@smithy/eventstream-serde-node": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-blob-browser": "^4.2.13", "@smithy/hash-node": "^4.2.12", "@smithy/hash-stream-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/md5-js": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-retry": "^4.4.46", "@smithy/middleware-serde": "^4.2.16", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.1", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.44", "@smithy/util-defaults-mode-node": "^4.2.48", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.13", "@smithy/util-stream": "^4.5.21", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-9Byz2fPnuGRRL8DTTD5bYPl1Iwm+ysLiCMgptffa3lNkVLCiUZc5e5TAaOjk0MvyeXieq+jn35AmQL6cgN2KHQ=="], - "@aws-sdk/core": ["@aws-sdk/core@3.974.1", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/xml-builder": "^3.972.18", "@smithy/core": "^3.23.15", "@smithy/node-config-provider": "^4.3.14", "@smithy/property-provider": "^4.2.14", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-gy/gffKz0zaHDaqRiLCdIvgHmaAL/HXuAtMcBP7euYSFx4BsbsdlfmUBJag+Gqe62z6/XuloKyQyaiH+kS3Vrg=="], - "@aws-sdk/crc64-nvme": ["@aws-sdk/crc64-nvme@3.972.7", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-QUagVVBbC8gODCF6e1aV0mE2TXWB9Opz4k8EJFdNrujUVQm5R4AjJa1mpOqzwOuROBzqJU9zawzig7M96L8Ejg=="], - "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.24", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-i6eMWlKfgQkNY3S/kg1ZnBZm2lhd6r8B3yobCalvrfCCGjvthREwsyDViuWl7gOWSvuUjQEFFGcJhGbCstcqJg=="], "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.27", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-xfUt2CUZDC+Tf16A6roD1b4pk/nrXdkoLY3TEhv198AXDtBo5xUJP1zd0e8SmuKLN4PpIBX96OizZbmMlcI6oQ=="], @@ -913,26 +828,14 @@ "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.14", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/eventstream-codec": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-m4X56gxG76/CKfxNVbOFuYwnAZcHgS6HOH8lgp15HoGHIAVTcZfZrXvcYzJFOMLEJgVn+JHBu6EiNV+xSNXXFg=="], - "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-Vbc2frZH7wXlMNd+ZZSXUEs/l1Sv8Jj4zUnIfwrYF5lwaLdXHZ9xx4U3rjUcaye3HRhFVc+E5DbBxpRAbB16BA=="], - "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-QUqLs7Af1II9X4fCRAu+EGHG3KHyOp4RkuLhRKoA3NuFlh6TL8i+zXBl8w2LUxqm44B/Kom45hgSlwA1SpTsXQ=="], - "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-2Yn0f1Qiq/DjxYR3wfI3LokXnjOhFM7Ssn4LTdFDIxRMCE6I32MAsVnhPX1cUZsuVA9tiZtwwhlSLAtFGxAZlQ=="], - - "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.974.9", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "^3.974.1", "@aws-sdk/crc64-nvme": "^3.972.7", "@aws-sdk/types": "^3.973.8", "@smithy/is-array-buffer": "^4.2.2", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.23", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-ye6xVuMEQ5NCT+yQOryGYsuCXnOwu7iGFGzV+qpXZOWtqXIAAaFostapxj6RCubw36rekVwmdB2lcspFuyNfYQ=="], - "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-IJSsIMeVQ8MMCPbuh1AbltkFhLBLXn7aejzfX5YKT/VLDHn++Dcz8886tXckE+wQssyPUhaXrJhdakO2VilRhg=="], - "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-rI3NZvJcEvjoD0+0PI0iUAwlPw2IlSlhyvgBK/3WkKJQE/YiKFedd9dMN2lVacdNxPNhxL/jzQaKQdrGtQagjQ=="], - "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-OOuGvvz1Dm20SjZo5oEBePFqxt5nf8AwkNDSyUHvD9/bfNASmstcYxFAHUowy4n6Io7mWUZ04JURZwSBvyQanQ=="], "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.11", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-+zz6f79Kj9V5qFK2P+D8Ehjnw4AhphAlCAsPjUqEcInA9umtSSKMrHbSagEeOIsDNuvVrH98bjRHcyQukTrhaQ=="], - "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.972.30", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-arn-parser": "^3.972.3", "@smithy/core": "^3.23.15", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/smithy-client": "^4.12.11", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.23", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-hoQRxjJu4tt3gEOQin21rJKotClJC+x7AmCh9ylRct1DJeaNI/BRlFxMbuhJe54bG6xANPagSs0my8K30QyV9g=="], - - "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Gli9A0u8EVVb+5bFDGS/QbSVg28w/wpEidg1ggVcSj65BDTdGR6punsOcVjqdiu1i42WHWo51MCvARPIIz9juw=="], - "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.31", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-endpoints": "^3.996.7", "@smithy/core": "^3.23.15", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-retry": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-L+hXN2HDomlIsWSHW5DVD7ppccCeRnlHXZ5uHG34ePTjF5bm0I1fmrJLbUGiW97xRXWryit5cjdP4Sx2FwiGog=="], "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.16", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@aws-sdk/util-format-url": "^3.972.10", "@smithy/eventstream-codec": "^4.2.14", "@smithy/eventstream-serde-browser": "^4.2.14", "@smithy/fetch-http-handler": "^5.3.17", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-86+S9oCyRVGzoMRpQhxkArp7kD2K75GPmaNevd9B6EyNhWoNvnCZZ3WbgN4j7ZT+jvtvBCGZvI2XHsWZJ+BRIg=="], @@ -941,14 +844,10 @@ "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.12", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/config-resolver": "^4.4.16", "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-QQI43Mxd53nBij0pm8HXC+t4IOC6gnhhZfzxE0OATQyO6QfPV4e+aTIRRuAJKA6Nig/cR8eLwPryqYTX9ZrjAQ=="], - "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.18", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "^3.972.30", "@aws-sdk/types": "^3.973.8", "@smithy/protocol-http": "^5.3.14", "@smithy/signature-v4": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-4KT8UXRmvNAP5zKq9UI1MIwbnmSChZncBt89RKu/skMqZSSWGkBZTAJsZ+no+txfmF3kVaUFv31CTBZkQ5BJpQ=="], - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1005.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.19", "@aws-sdk/nested-clients": "^3.996.8", "@aws-sdk/types": "^3.973.5", "@smithy/property-provider": "^4.2.11", "@smithy/shared-ini-file-loader": "^4.4.6", "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-vMxd+ivKqSxU9bHx5vmAlFKDAkjGotFU56IOkDa5DaTu1WWwbcse0yFHEm9I537oVvodaiwMl3VBwgHfzQ2rvw=="], "@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], - "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-HzSD8PMFrvgi2Kserxuff5VitNq2sgf3w9qxmskKDiDTThWfVteJxuCS9JXiPIPtmCrp+7N9asfIaVhBFORllA=="], - "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.7", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-endpoints": "^3.4.1", "tslib": "^2.6.2" } }, "sha512-ty4LQxN1QC+YhUP28NfEgZDEGXkyqOQy+BDriBozqHsrYO4JMgiPhfizqOGF7P+euBTZ5Ez6SKlLAMCLo8tzmw=="], "@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.10", "", { "dependencies": { "@aws-sdk/types": "^3.973.8", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-DEKiHNJVtNxdyTeQspzY+15Po/kHm6sF0Cs4HV9Q2+lplB63+DrvdeiSoOSdWEWAoO2RcY1veoXVDz2tWxWCgQ=="], @@ -989,8 +888,6 @@ "@azure/core-util": ["@azure/core-util@1.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@typespec/ts-http-runtime": "^0.3.0", "tslib": "^2.6.2" } }, "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A=="], - "@azure/core-xml": ["@azure/core-xml@1.5.1", "", { "dependencies": { "fast-xml-parser": "^5.5.9", "tslib": "^2.8.1" } }, "sha512-xcNRHqCoSp4AunOALEae6A8f3qATb83gSrm31Iqb01OzblvC3/W/bfXozcq78EzIdzZzuH1bZ2NvRR0TdX709w=="], - "@azure/identity": ["@azure/identity@4.13.1", "", { "dependencies": { "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.2", "@azure/core-rest-pipeline": "^1.17.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.0.0", "@azure/msal-browser": "^5.5.0", "@azure/msal-node": "^5.1.0", "open": "^10.1.0", "tslib": "^2.2.0" } }, "sha512-5C/2WD5Vb1lHnZS16dNQRPMjN6oV/Upba+C9nBIs15PmOi6A3ZGs4Lr2u60zw4S04gi+u3cEXiqTVP7M4Pz3kw=="], "@azure/keyvault-common": ["@azure/keyvault-common@2.1.0", "", { "dependencies": { "@azure-rest/core-client": "^2.3.3", "@azure/abort-controller": "^2.0.0", "@azure/core-auth": "^1.3.0", "@azure/core-rest-pipeline": "^1.8.0", "@azure/core-tracing": "^1.0.0", "@azure/core-util": "^1.10.0", "@azure/logger": "^1.1.4", "tslib": "^2.2.0" } }, "sha512-aCDidWuKY06LWQ4x7/8TIXK6iRqTaRWRL3t7T+LC+j1b07HtoIsOxP/tU90G4jCSBn5TAyUTCtA4MS/y5Hudaw=="], @@ -1005,15 +902,11 @@ "@azure/msal-node": ["@azure/msal-node@5.1.3", "", { "dependencies": { "@azure/msal-common": "16.5.0", "jsonwebtoken": "^9.0.0", "uuid": "^8.3.0" } }, "sha512-LqT8mRZpEils9zGR9eW+Ljqifh2aMA99UF/X0jxIKDYZeHr6onlHwhVP4xHCeLhh55BI63JCbdf1iWJbMh1mPw=="], - "@azure/storage-blob": ["@azure/storage-blob@12.31.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-client": "^1.9.3", "@azure/core-http-compat": "^2.2.0", "@azure/core-lro": "^2.2.0", "@azure/core-paging": "^1.6.2", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/core-xml": "^1.4.5", "@azure/logger": "^1.1.4", "@azure/storage-common": "^12.3.0", "events": "^3.0.0", "tslib": "^2.8.1" } }, "sha512-DBgNv10aCSxopt92DkTDD0o9xScXeBqPKGmR50FPZQaEcH4JLQ+GEOGEDv19V5BMkB7kxr+m4h6il/cCDPvmHg=="], - - "@azure/storage-common": ["@azure/storage-common@12.3.0", "", { "dependencies": { "@azure/abort-controller": "^2.1.2", "@azure/core-auth": "^1.9.0", "@azure/core-http-compat": "^2.2.0", "@azure/core-rest-pipeline": "^1.19.1", "@azure/core-tracing": "^1.2.0", "@azure/core-util": "^1.11.0", "@azure/logger": "^1.1.4", "events": "^3.3.0", "tslib": "^2.8.1" } }, "sha512-/OFHhy86aG5Pe8dP5tsp+BuJ25JOAl9yaMU3WZbkeoiFMHFtJ7tu5ili7qEdBXNW9G5lDB19trwyI6V49F/8iQ=="], - "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], - "@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="], + "@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], @@ -1071,10 +964,6 @@ "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="], - "@bufbuild/protobuf": ["@bufbuild/protobuf@2.11.0", "", {}, "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ=="], - - "@bufbuild/protoplugin": ["@bufbuild/protoplugin@2.11.0", "", { "dependencies": { "@bufbuild/protobuf": "2.11.0", "@typescript/vfs": "^1.6.2", "typescript": "5.4.5" } }, "sha512-lyZVNFUHArIOt4W0+dwYBe5GBwbKzbOy8ObaloEqsw9Mmiwv2O48TwddDoHN4itylC+BaEGqFdI1W8WQt2vWJQ=="], - "@changesets/apply-release-plan": ["@changesets/apply-release-plan@7.1.1", "", { "dependencies": { "@changesets/config": "^3.1.4", "@changesets/get-version-range-type": "^0.4.0", "@changesets/git": "^3.0.4", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "detect-indent": "^6.0.0", "fs-extra": "^7.0.1", "lodash.startcase": "^4.4.0", "outdent": "^0.5.0", "prettier": "^2.7.1", "resolve-from": "^5.0.0", "semver": "^7.5.3" } }, "sha512-9qPCm/rLx/xoOFXIHGB229+4GOL76S4MC+7tyOuTsR6+1jYlfFDQORdvwR5hDA6y4FL2BPt3qpbcQIS+dW85LA=="], "@changesets/assemble-release-plan": ["@changesets/assemble-release-plan@6.0.10", "", { "dependencies": { "@changesets/errors": "^0.2.0", "@changesets/get-dependents-graph": "^2.1.4", "@changesets/should-skip-package": "^0.1.2", "@changesets/types": "^6.1.0", "@manypkg/get-packages": "^1.1.3", "semver": "^7.5.3" } }, "sha512-rSDcqdJ9KbVyjpBIuCidhvZNIiVt1XaIYp73ycVQRIA5n/j6wQaEk0ChRLMUQ1vkxZe51PTQ9OIhbg6HQMW45A=="], @@ -1129,9 +1018,9 @@ "@effect/language-service": ["@effect/language-service@0.84.2", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-l04qNxpiA8rY5yXWckRPJ7Mk5MNerXuNymSFf+IdflfI5i8jgL1bpBNLuP6ijg7wgjdHc/KmTnCj2kT0SCntuA=="], - "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.65", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.65" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-0CD2fSsXrDM7FP2WFkbGJO1DwMqWR3UKHh6oBDXPHAPA+RsJSKoh3pLQsbQfldLuKnhOy87Bv0v9r9IdrIHCQw=="], + "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.57", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.57" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-gdjZPEP0QQg4qmI1vd+443kheeQZKytrjJIzCJncy6ZEpyk/SfrqeStLqLXdTRcms3IB0ls0vOV7KNq7YmBRVA=="], - "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.65", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.65", "mime": "^4.1.0", "undici": "^8.0.2" }, "peerDependencies": { "effect": "^4.0.0-beta.65", "ioredis": "^5.7.0" } }, "sha512-QQy3KRcMwP0TngQdfQGl2u1zp03B7k7DuF5SNS8aZhD0dDBpKZpCwFad1ODY5qdY3ycPgMwBwKRRK7y/aw0C9w=="], + "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.57", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.57", "mime": "^4.1.0", "undici": "^8.0.2" }, "peerDependencies": { "effect": "^4.0.0-beta.57", "ioredis": "^5.7.0" } }, "sha512-la0xxPSAYOsY0d+uVxEBxok3jYB31iPQmIaZZRUj2SNWqcGGHJc6KorKtI8guqSLuv9FGZ255kBWXRbG6hMeeg=="], "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.46", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.46" } }, "sha512-Yzci82XbZ1W3tuiownsJawrJZTGeTrTZKLD0uxdBWCBzlVyqDwoSwRwO5qh33DurJj9B7iS8MDf14fpGRBPNGQ=="], @@ -1249,9 +1138,7 @@ "@hey-api/types": ["@hey-api/types@0.1.2", "", {}, "sha512-uNNtiVAWL7XNrV/tFXx7GLY9lwaaDazx1173cGW3+UEaw4RUPsHEmiB4DSpcjNxMIcrctfz2sGKLnVx5PBG2RA=="], - "@hono/node-server": ["@hono/node-server@1.19.13", "", { "peerDependencies": { "hono": "^4" } }, "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ=="], - - "@hono/node-ws": ["@hono/node-ws@1.3.0", "", { "dependencies": { "ws": "^8.17.0" }, "peerDependencies": { "@hono/node-server": "^1.19.2", "hono": "^4.6.0" } }, "sha512-ju25YbbvLuXdqBCmLZLqnNYu1nbHIQjoyUqA8ApZOeL1k4skuiTcw5SW77/5SUYo2Xi2NVBJoVlfQurnKEp03Q=="], + "@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], "@hono/standard-validator": ["@hono/standard-validator@0.1.5", "", { "peerDependencies": { "@standard-schema/spec": "1.0.0", "hono": ">=3.9.0" } }, "sha512-EIyZPPwkyLn6XKwFj5NBEWHXhXbgmnVh2ceIFo5GO7gKI9WmzTjPDKnppQB0KrqKeAkq3kpoW4SIbu5X1dgx3w=="], @@ -1475,8 +1362,6 @@ "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw=="], - "@nodable/entities": ["@nodable/entities@2.1.0", "", {}, "sha512-nyT7T3nbMyBI/lvr6L5TyWbFJAI9FTgVRakNoBqCD+PmID8DzFrrNdLLtHMwMszOtqZa8PAOV24ZqDnQrhQINA=="], - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], @@ -1527,12 +1412,10 @@ "@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@9.2.2", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ=="], - "@octokit/plugin-request-log": ["@octokit/plugin-request-log@1.0.4", "", { "peerDependencies": { "@octokit/core": ">=3" } }, "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA=="], + "@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="], "@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@10.4.1", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg=="], - "@octokit/plugin-retry": ["@octokit/plugin-retry@3.0.9", "", { "dependencies": { "@octokit/types": "^6.0.3", "bottleneck": "^2.15.3" } }, "sha512-r+fArdP5+TG6l1Rv/C9hVoty6tldw6cE2pRHNGmFPdyfrc696R6JjrQ3d7HdVqGwuzfyrcaLAKD7K8TX8aehUQ=="], - "@octokit/request": ["@octokit/request@8.4.1", "", { "dependencies": { "@octokit/endpoint": "^9.0.6", "@octokit/request-error": "^5.1.1", "@octokit/types": "^13.1.0", "universal-user-agent": "^6.0.0" } }, "sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw=="], "@octokit/request-error": ["@octokit/request-error@5.1.1", "", { "dependencies": { "@octokit/types": "^13.1.0", "deprecation": "^2.0.0", "once": "^1.4.0" } }, "sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g=="], @@ -1543,8 +1426,6 @@ "@octokit/webhooks-types": ["@octokit/webhooks-types@7.6.1", "", {}, "sha512-S8u2cJzklBC0FgTwWVLaM8tMrDuDMVE4xiTK4EYXM9GntyvrdbSoxqDQa+Fh57CCNApyIpyeqPhhFEmHPfrXgw=="], - "@openauthjs/openauth": ["@openauthjs/openauth@0.0.0-20250322224806", "", { "dependencies": { "@standard-schema/spec": "1.0.0-beta.3", "aws4fetch": "1.0.20", "jose": "5.9.6" }, "peerDependencies": { "arctic": "^2.2.2", "hono": "^4.0.0" } }, "sha512-p5IWSRXvABcwocH2dNI0w8c1QJelIOFulwhKk+aLLFfUbs8u1pr7kQbYe8yCSM2+bcLHiwbogpUQc2ovrGwCuw=="], - "@opencode-ai/core": ["@opencode-ai/core@workspace:packages/core"], "@opencode-ai/http-recorder": ["@opencode-ai/http-recorder@workspace:packages/http-recorder"], @@ -1561,7 +1442,7 @@ "@opencode-ai/ui": ["@opencode-ai/ui@workspace:packages/ui"], - "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.9.0", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Seva+NCa0WUQnJIUE5GzHsUv1WTIeyqwz0ELl2VtS6NP+eF+77yCXGFVOMbvoCM7QMjlnhv7931e89R+8pJdcQ=="], + "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.8.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Y6j3yivgoEUf/kutD/k5GX/mzZfioRFoSx0gbQ+mIOzMaH/vJv1rCkztiuvlLw5xRYQil7oxHUZvmSfXqOx1NQ=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], @@ -1591,33 +1472,23 @@ "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], - "@opentui/core": ["@opentui/core@0.2.11", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.11", "@opentui/core-darwin-x64": "0.2.11", "@opentui/core-linux-arm64": "0.2.11", "@opentui/core-linux-x64": "0.2.11", "@opentui/core-win32-arm64": "0.2.11", "@opentui/core-win32-x64": "0.2.11" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-X0zLmcDEvMrPzWYp769I7VEVb+og38vaete9tGZXu9HnJgu/paPUUplUT+6denBQccr2qx1rBYV6EtgbBpLEyw=="], + "@opentui/core": ["@opentui/core@0.2.6", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.6", "@opentui/core-darwin-x64": "0.2.6", "@opentui/core-linux-arm64": "0.2.6", "@opentui/core-linux-x64": "0.2.6", "@opentui/core-win32-arm64": "0.2.6", "@opentui/core-win32-x64": "0.2.6" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-dBpMaWVM7wtW2/2TlGPrkPjg6gOL3MVU/5XXk+U1LDJB8L4q4NeYWVdzfAVNcEvgmuuCy/cVqdY2D4ei+e7MMg=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-h2MXtE2Cu3XlKVoQMXthnbhleO68zGXkoh/r1Q5pCoZh6RuXqns5/94D/aZThXBWwzPuEoyarMlxxR9OqrpvHw=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hR5nsxNj+059utzenTCF0kealUlibON6fLuebFUCGM/5kJnqa+shIh0XbUDFm0+F47vqVUgZufBdUuieQZIbvQ=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-Y0jbPClnOBTPSIy+2THG86MTqIG/jGFlOOKuw4JfCDqEjPBM3pLWIHnJb3WxHRi2LlvfyBxvrUTXWlW6JpI0QQ=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-pJ/bH4WC/mbBaakM1YdH6TVo67jhy0KPd61bCz97w0I/PJGr8fmNKvhmMt/AwyFgOQi3FYZiEKLMpGdvUcSsrQ=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-blQyyuTaW4q/OQ3whs7Kt7GCXhBUR5EQHHDdjOqQAr0HYpohUa6sbHMbiBcX2Ehc9ZWwtiaOoWiyZ5YXy2SAvg=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Pnd3kOxig8ii+/IqYheOPEgferylsQA0L6tKBnHQ9jRlCJOcu0Rv65Jepueh212vevdV9DzPURJnhejG06J6g=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.11", "", { "os": "linux", "cpu": "x64" }, "sha512-0nEB5+MgzQRYiVcQd1vHXPWNPWGh4JEmQTJKyG3OHnTzPaJ1FVSQ/V71ECyRSl3ymY3F+U0eW9cFgw1hCieK2w=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-458Mx9tBzEPzfft8cSt5ZaIpEepoxBXBOL6AUVmDTKWaZ3uouraPcEKraGAyvOTDQp2XDI3R8c/2GdaR77FaUQ=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-+KKH77fzm0qF8py9G2pU32DzB1bAgDMfBajrs7gKL5NtSEnknrwfh7hIs/tq41aF6j9zvIzgtykByh26tcjFog=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-BDUrdrT1RCcVnQoHJmUut4y811jDBAEtc6GJFB4Gs265Be8SrTjVCus6p2fSQ7j9sZQ1OcjO+5+4NkheSZICDQ=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.11", "", { "os": "win32", "cpu": "x64" }, "sha512-dMmb9DX0W0HWadLdgciMbonqIc1xdcKiVmaQSYxw5eGCzFRPZIOrKHByesP+2ipkMuLx85W/MJUFal/lW8XSNg=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-SUYAzRJ9TSoD2Qt8kn6FJz6dbTrFEPVig5mScB4zFGgGQO/Bbod2/Q31vLS/IQrX+FDb67WaErD+kuMCnMPPLA=="], - "@opentui/keymap": ["@opentui/keymap@0.2.11", "", { "dependencies": { "@opentui/core": "0.2.11" }, "peerDependencies": { "@opentui/react": "0.2.11", "@opentui/solid": "0.2.11", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-pCrJrY3mTuXdDaaRneId1JsJCtGE+7prTtWihzOLZzVJTJYyYtT38gMI7MpyAoloVDfEL5cTe8C+v7wv+IYREw=="], + "@opentui/keymap": ["@opentui/keymap@0.2.6", "", { "dependencies": { "@opentui/core": "0.2.6" }, "peerDependencies": { "@opentui/react": "0.2.6", "@opentui/solid": "0.2.6", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-+6OYuedrFCKVo4ryGFNwws++2VOmPcXU3PwpY0mP47gYQY2nvQ+etWIs2Y7r5eMIqUfxVCldkKsrzcEcA4tb/A=="], - "@opentui/solid": ["@opentui/solid@0.2.11", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.11", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-M3WHxBFORHVE0yqMJYpi9PfjXWlnRTw/LYuBhZaJv0HTo+zTs60P/ukGcwnHDWnMpTGf3BH9x0Yi2dIqjHRY6Q=="], - - "@oslojs/asn1": ["@oslojs/asn1@1.0.0", "", { "dependencies": { "@oslojs/binary": "1.0.0" } }, "sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA=="], - - "@oslojs/binary": ["@oslojs/binary@1.0.0", "", {}, "sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ=="], - - "@oslojs/crypto": ["@oslojs/crypto@1.0.1", "", { "dependencies": { "@oslojs/asn1": "1.0.0", "@oslojs/binary": "1.0.0" } }, "sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ=="], - - "@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="], - - "@oslojs/jwt": ["@oslojs/jwt@0.2.0", "", { "dependencies": { "@oslojs/encoding": "0.4.1" } }, "sha512-bLE7BtHrURedCn4Mco3ma9L4Y1GR2SMBuIvjWr7rmQ4/W/4Jy70TIAgZ+0nIlk0xHz1vNP8x8DCns45Sb2XRbg=="], + "@opentui/solid": ["@opentui/solid@0.2.6", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.6", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-2y225WlOGi/fCaajkxBmLyVW8Cr+OmhowHdvrYcz5w2kBD15sKbJLIYu1G9DxceirT1uIyasGy2TGzRRcVkTDg=="], "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.19.1", "", { "os": "android", "cpu": "arm" }, "sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg=="], @@ -1751,14 +1622,6 @@ "@posthog/types": ["@posthog/types@1.369.3", "", {}, "sha512-Ywqvs4513PixR2TIA5O3GMEyK4F65uefwxPfsIUeHr9ruGylyXp00YJ4CEbp8U0DMzCkeF+LsMKVnHgN3pAXcA=="], - "@protobuf-ts/plugin": ["@protobuf-ts/plugin@2.11.1", "", { "dependencies": { "@bufbuild/protobuf": "^2.4.0", "@bufbuild/protoplugin": "^2.4.0", "@protobuf-ts/protoc": "^2.11.1", "@protobuf-ts/runtime": "^2.11.1", "@protobuf-ts/runtime-rpc": "^2.11.1", "typescript": "^3.9" }, "bin": { "protoc-gen-ts": "bin/protoc-gen-ts", "protoc-gen-dump": "bin/protoc-gen-dump" } }, "sha512-HyuprDcw0bEEJqkOWe1rnXUP0gwYLij8YhPuZyZk6cJbIgc/Q0IFgoHQxOXNIXAcXM4Sbehh6kjVnCzasElw1A=="], - - "@protobuf-ts/protoc": ["@protobuf-ts/protoc@2.11.1", "", { "bin": { "protoc": "protoc.js" } }, "sha512-mUZJaV0daGO6HUX90o/atzQ6A7bbN2RSuHtdwo8SSF2Qoe3zHwa4IHyCN1evftTeHfLmdz+45qo47sL+5P8nyg=="], - - "@protobuf-ts/runtime": ["@protobuf-ts/runtime@2.11.1", "", {}, "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ=="], - - "@protobuf-ts/runtime-rpc": ["@protobuf-ts/runtime-rpc@2.11.1", "", { "dependencies": { "@protobuf-ts/runtime": "^2.11.1" } }, "sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ=="], - "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], @@ -1857,7 +1720,7 @@ "@secretlint/types": ["@secretlint/types@10.2.2", "", {}, "sha512-Nqc90v4lWCXyakD6xNyNACBJNJ0tNCwj2WNk/7ivyacYHxiITVgmLUFXTBOeCdy79iz6HtN9Y31uw/jbLrdOAg=="], - "@shikijs/core": ["@shikijs/core@3.9.2", "", { "dependencies": { "@shikijs/types": "3.9.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3q/mzmw09B2B6PgFNeiaN8pkNOixWS726IHmJEpjDAcneDPMQmUg2cweT9cWXY4XcyQS3i6mOOUgQz9RRUP6HA=="], + "@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg=="], @@ -1869,7 +1732,7 @@ "@shikijs/transformers": ["@shikijs/transformers@3.9.2", "", { "dependencies": { "@shikijs/core": "3.9.2", "@shikijs/types": "3.9.2" } }, "sha512-MW5hT4TyUp6bNAgTExRYLk1NNasVQMTCw1kgbxHcEC0O5cbepPWaB+1k+JzW9r3SP2/R8kiens8/3E6hGKfgsA=="], - "@shikijs/types": ["@shikijs/types@3.9.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-/M5L0Uc2ljyn2jKvj4Yiah7ow/W+DJSglVafvWAJ/b8AZDeeRAdMu3c2riDzB7N42VD+jSnWxeP9AKtd4TfYVw=="], + "@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], @@ -1885,18 +1748,12 @@ "@sigstore/verify": ["@sigstore/verify@3.1.0", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag=="], - "@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="], - "@simple-git/args-pathspec": ["@simple-git/args-pathspec@1.0.3", "", {}, "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA=="], "@simple-git/argv-parser": ["@simple-git/argv-parser@1.1.1", "", { "dependencies": { "@simple-git/args-pathspec": "^1.0.3" } }, "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw=="], "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="], - "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw=="], - - "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.3", "", { "dependencies": { "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw=="], - "@smithy/config-resolver": ["@smithy/config-resolver@4.4.16", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.14", "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.4.1", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-GFlGPNLZKrGfqWpqVb31z7hvYCA9ZscfX1buYnvvMGcRYsQQnhH+4uN6mWWflcD5jB4OXP/LBrdpukEdjl41tg=="], "@smithy/core": ["@smithy/core@3.23.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.14", "@smithy/util-stream": "^4.5.23", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-E7GVCgsQttzfujEZb6Qep005wWf4xiL4x06apFEtzQMWYBPggZh/0cnOxPficw5cuK/YjjkehKoIN4YUaSh0UQ=="], @@ -1915,18 +1772,12 @@ "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.17", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/querystring-builder": "^4.2.14", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw=="], - "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.15", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-0PJ4Al3fg2nM4qKrAIxyNcApgqHAXcBkN8FeizOz69z0rb26uZ6lMESYtxegaTlXB5Hj84JfwMPavMrwDMjucA=="], - "@smithy/hash-node": ["@smithy/hash-node@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g=="], - "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-tw4GANWkZPb6+BdD4Fgucqzey2+r73Z/GRo9zklsCdwrnxxumUV83ZIaBDdudV4Ylazw3EPTiJZhpX42105ruQ=="], - "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw=="], "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], - "@smithy/md5-js": ["@smithy/md5-js@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-V2v0vx+h0iUSNG1Alt+GNBMSLGCrl9iVsdd+Ap67HPM9PN479x12V8LkuMoKImNZxn3MXeuyUjls+/7ZACZghA=="], - "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.14", "", { "dependencies": { "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw=="], "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.30", "", { "dependencies": { "@smithy/core": "^3.23.15", "@smithy/middleware-serde": "^4.2.18", "@smithy/node-config-provider": "^4.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-qS2XqhKeXmdZ4nEQ4cOxIczSP/Y91wPAHYuRwmWDCh975B7/57uxsm5d6sisnUThn2u2FwzMdJNM7AbO1YPsPg=="], @@ -1989,14 +1840,10 @@ "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@smithy/util-waiter": ["@smithy/util-waiter@4.2.16", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-GtclrKoZ3Lt7jPQ7aTIYKfjY92OgceScftVnkTsG8e1KV8rkvZgN+ny6YSRhd9hxB8rZtwVbmln7NTvE5O3GmQ=="], - "@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="], "@socket.io/component-emitter": ["@socket.io/component-emitter@3.1.2", "", {}, "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="], - "@solid-primitives/bounds": ["@solid-primitives/bounds@0.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/resize-observer": "^2.1.3", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-UbiyKMdSPmtijcEDnYLQL3zzaejpwWDAJJ4Gt5P0hgVs6A72piov0GyNw7V2SroH7NZFwxlYS22YmOr8A5xc1Q=="], - "@solid-primitives/event-bus": ["@solid-primitives/event-bus@1.1.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-l+n10/51neGcMaP3ypYt21bXfoeWh8IaC8k7fYuY3ww2a8S1Zv2N2a7FF5Qn+waTu86l0V8/nRHjkyqVIZBYwA=="], "@solid-primitives/event-listener": ["@solid-primitives/event-listener@2.4.5", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-nwRV558mIabl4yVAhZKY8cb6G+O1F0M6Z75ttTu5hk+SxdOnKSGj+eetDIu7Oax1P138ZdUU01qnBPR8rnxaEA=="], @@ -2033,29 +1880,27 @@ "@standard-community/standard-openapi": ["@standard-community/standard-openapi@0.2.9", "", { "peerDependencies": { "@standard-community/standard-json": "^0.3.5", "@standard-schema/spec": "^1.0.0", "arktype": "^2.1.20", "effect": "^3.17.14", "openapi-types": "^12.1.3", "sury": "^10.0.0", "typebox": "^1.0.0", "valibot": "^1.1.0", "zod": "^3.25.0 || ^4.0.0", "zod-openapi": "^4" }, "optionalPeers": ["arktype", "effect", "sury", "typebox", "valibot", "zod", "zod-openapi"] }, "sha512-htj+yldvN1XncyZi4rehbf9kLbu8os2Ke/rfqoZHCMHuw34kiF3LP/yQPdA0tQ940y8nDq3Iou8R3wG+AGGyvg=="], - "@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@storybook/addon-a11y": ["@storybook/addon-a11y@10.2.10", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-1S9pDXgvbHhBStGarCvfJ3/rfcaiAcQHRhuM3Nk4WGSIYtC1LCSRuzYdDYU0aNRpdCbCrUA7kUCbqvIE3tH+3Q=="], + "@storybook/addon-a11y": ["@storybook/addon-a11y@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.3.5" } }, "sha512-5k6lpgfIeLxvNhE8v3wEzdiu73ONKjF4gmH1AHvfqYd8kIVzQJai0KCDxgvqNncXHQhIWkaf1fg6+9hKaYJyaw=="], - "@storybook/addon-docs": ["@storybook/addon-docs@10.2.10", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.2.10", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.2.10", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-2wIYtdvZIzPbQ5194M5Igpy8faNbQ135nuO5ZaZ2VuttqGr+IJcGnDP42zYwbAsGs28G8ohpkbSgIzVyJWUhPQ=="], + "@storybook/addon-docs": ["@storybook/addon-docs@10.3.5", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.3.5", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.3.5", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.3.5" } }, "sha512-WuHbxia/o5TX4Rg/IFD0641K5qId/Nk0dxhmAUNoFs5L0+yfZUwh65XOBbzXqrkYmYmcVID4v7cgDRmzstQNkA=="], "@storybook/addon-links": ["@storybook/addon-links@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.3.5" }, "optionalPeers": ["react"] }, "sha512-Xe2wCGZ+hpZ0cDqAIBHk+kPc8nODNbu585ghd5bLrlYJMDVXoNM/fIlkrLgjIDVbfpgeJLUEg7vldJrn+FyOLw=="], "@storybook/addon-onboarding": ["@storybook/addon-onboarding@10.3.5", "", { "peerDependencies": { "storybook": "^10.3.5" } }, "sha512-s3/gIy9Tqxji27iclLY+KSk8kGeow1JxXMl1lPLyu8n6XVvv+tFrUPhAvUTs+fVenG6JQEWc0uzpYBdFRWbMtw=="], - "@storybook/addon-themes": ["@storybook/addon-themes@10.2.10", "", { "dependencies": { "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.2.10" } }, "sha512-j7ixCgzpWeTU7K4BkNHtEg3NdmRg9YW7ynvv0OjD3vaz4+FUVWOq7PPwb3SktLS1tOl4UA13IpApD8nSpBiY6A=="], - "@storybook/addon-vitest": ["@storybook/addon-vitest@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1" }, "peerDependencies": { "@vitest/browser": "^3.0.0 || ^4.0.0", "@vitest/browser-playwright": "^4.0.0", "@vitest/runner": "^3.0.0 || ^4.0.0", "storybook": "^10.3.5", "vitest": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["@vitest/browser", "@vitest/browser-playwright", "@vitest/runner", "vitest"] }, "sha512-PQDeeMwoF55kvzlhFqVKOryBJskkVk71AbDh7F0y8PdRRxlGbTvIUkKXktHZWBdESo0dV6BkeVxGQ4ZpiFxirg=="], "@storybook/builder-vite": ["@storybook/builder-vite@10.3.5", "", { "dependencies": { "@storybook/csf-plugin": "10.3.5", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.3.5", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-i4KwCOKbhtlbQIbhm53+Kk7bMnxa0cwTn1pxmtA/x5wm1Qu7FrrBQV0V0DNjkUqzcSKo1CjspASJV/HlY0zYlw=="], - "@storybook/csf-plugin": ["@storybook/csf-plugin@10.2.10", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.2.10", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-aFvgaNDAnKMjuyhPK5ialT22pPqMN0XfPBNPeeNVPYztngkdKBa8WFqF/umDd47HxAjebq+vn6uId1xHyOHH3g=="], + "@storybook/csf-plugin": ["@storybook/csf-plugin@10.3.5", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.3.5", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-qlEzNKxOjq86pvrbuMwiGD/bylnsXk1dg7ve0j77YFjEEchqtl7qTlrXvFdNaLA89GhW6D/EV6eOCu/eobPDgw=="], "@storybook/global": ["@storybook/global@5.0.0", "", {}, "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ=="], "@storybook/icons": ["@storybook/icons@2.0.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-/smVjw88yK3CKsiuR71vNgWQ9+NuY2L+e8X7IMrFjexjm6ZR8ULrV2DRkTA61aV6ryefslzHEGDInGpnNeIocg=="], - "@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.2.10", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.2.10" } }, "sha512-TmBrhyLHn8B8rvDHKk5uW5BqzO1M1T+fqFNWg88NIAJOoyX4Uc90FIJjDuN1OJmWKGwB5vLmPwaKBYsTe1yS+w=="], + "@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.3.5", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.3.5" } }, "sha512-Gw8R7XZm0zSUH0XAuxlQJhmizsLzyD6x00KOlP6l7oW9eQHXGfxg3seNDG3WrSAcW07iP1/P422kuiriQlOv7g=="], "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], @@ -2237,8 +2082,6 @@ "@types/linkify-it": ["@types/linkify-it@3.0.5", "", {}, "sha512-yg6E+u0/+Zjva+buc3EIb+29XEg4wltq7cSmd4Uc2EE/1nUVmxyzpX6gUXD0V8jIrG0r7YeOGVIbYRkxeooCtw=="], - "@types/luxon": ["@types/luxon@3.7.1", "", {}, "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg=="], - "@types/markdown-it": ["@types/markdown-it@12.2.3", "", { "dependencies": { "@types/linkify-it": "*", "@types/mdurl": "*" } }, "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ=="], "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], @@ -2341,8 +2184,6 @@ "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260316.1", "", { "os": "win32", "cpu": "x64" }, "sha512-LvpV1hyQS0U9yMLHgWexhC7oSeBpcNbIJtYC6Iyvu63Mb6J/cP0k2fQmnAVB2yesMMQFtuY6v2YIx17vE0Ymfw=="], - "@typescript/vfs": ["@typescript/vfs@1.6.4", "", { "dependencies": { "debug": "^4.4.3" }, "peerDependencies": { "typescript": "*" } }, "sha512-PJFXFS4ZJKiJ9Qiuix6Dz/OwEIqHD7Dme1UwZhTK11vR+5dqW2ACbdndWQexBzCx+CPuMe5WBYQWCsFyGlQLlQ=="], - "@typespec/ts-http-runtime": ["@typespec/ts-http-runtime@0.3.5", "", { "dependencies": { "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "tslib": "^2.6.2" } }, "sha512-yURCknZhvywvQItHMMmFSo+fq5arCUIyz/CVk7jD89MSai7dkaX8ufjCWp3NttLojoTVbcE72ri+be/TnEbMHw=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], @@ -2457,12 +2298,6 @@ "apache-arrow": ["apache-arrow@18.1.0", "", { "dependencies": { "@swc/helpers": "^0.5.11", "@types/command-line-args": "^5.2.3", "@types/command-line-usage": "^5.0.4", "@types/node": "^20.13.0", "command-line-args": "^5.2.1", "command-line-usage": "^7.0.1", "flatbuffers": "^24.3.25", "json-bignum": "^0.0.3", "tslib": "^2.6.2" }, "bin": { "arrow2csv": "bin/arrow2csv.js" } }, "sha512-v/ShMp57iBnBp4lDgV8Jx3d3Q5/Hac25FWmQ98eMahUiHPXcvwIMKJD0hBIgclm/FCG+LwPkAKtkRO1O/W0YGg=="], - "archiver": ["archiver@7.0.1", "", { "dependencies": { "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", "zip-stream": "^6.0.1" } }, "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ=="], - - "archiver-utils": ["archiver-utils@5.0.2", "", { "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA=="], - - "arctic": ["arctic@2.3.4", "", { "dependencies": { "@oslojs/crypto": "1.0.1", "@oslojs/encoding": "1.1.0", "@oslojs/jwt": "0.2.0" } }, "sha512-+p30BOWsctZp+CVYCt7oAean/hWGW42sH5LAcRQX56ttEkFJWbzXBhmSpibbzwSJkRrotmsA+oAoJoVsU0f5xA=="], - "argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], @@ -2485,8 +2320,6 @@ "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], - "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="], - "async-lock": ["async-lock@1.4.1", "", {}, "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ=="], "async-mutex": ["async-mutex@0.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA=="], @@ -2503,8 +2336,6 @@ "avvio": ["avvio@9.2.0", "", { "dependencies": { "@fastify/error": "^4.0.0", "fastq": "^1.17.1" } }, "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ=="], - "aws-sdk": ["aws-sdk@2.1692.0", "", { "dependencies": { "buffer": "4.9.2", "events": "1.1.1", "ieee754": "1.1.13", "jmespath": "0.16.0", "querystring": "0.2.0", "sax": "1.2.1", "url": "0.10.3", "util": "^0.12.4", "uuid": "8.0.0", "xml2js": "0.6.2" } }, "sha512-x511uiJ/57FIsbgUe5csJ13k3uzu25uWQE+XqfBis/sB0SFoiElJWXRkgEAUh0U6n40eT3ay5Ue4oPkRMu1LYw=="], - "aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], "axe-core": ["axe-core@4.11.3", "", {}, "sha512-zBQouZixDTbo3jMGqHKyePxYxr1e5W8UdTmBQ7sNtaA9M2bE32daxxPLS/jojhKOHxQ7LWwPjfiwf/fhaJWzlg=="], @@ -2513,8 +2344,6 @@ "azure-devops-node-api": ["azure-devops-node-api@12.5.0", "", { "dependencies": { "tunnel": "0.0.6", "typed-rest-client": "^1.8.4" } }, "sha512-R5eFskGvOm3U/GzeAuxRkUsAl0hrAwGgWn6zAd2KrZmrEhWZVqLew4OOupbQlXUuojUzpGtq62SmdhJ06N88og=="], - "b4a": ["b4a@1.8.0", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg=="], - "babel-plugin-jsx-dom-expressions": ["babel-plugin-jsx-dom-expressions@0.40.6", "", { "dependencies": { "@babel/helper-module-imports": "7.18.6", "@babel/plugin-syntax-jsx": "^7.18.6", "@babel/types": "^7.20.7", "html-entities": "2.3.3", "parse5": "^7.1.2" }, "peerDependencies": { "@babel/core": "^7.20.12" } }, "sha512-v3P1MW46Lm7VMpAkq0QfyzLWWkC8fh+0aE5Km4msIgDx5kjenHU0pF2s+4/NH8CQn/kla6+Hvws+2AF7bfV5qQ=="], "babel-plugin-module-resolver": ["babel-plugin-module-resolver@5.0.2", "", { "dependencies": { "find-babel-config": "^2.1.1", "glob": "^9.3.3", "pkg-up": "^3.1.0", "reselect": "^4.1.7", "resolve": "^1.22.8" } }, "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg=="], @@ -2523,18 +2352,6 @@ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], - "bare-events": ["bare-events@2.8.2", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ=="], - - "bare-fs": ["bare-fs@4.7.1", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw=="], - - "bare-os": ["bare-os@3.8.7", "", {}, "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w=="], - - "bare-path": ["bare-path@3.0.0", "", { "dependencies": { "bare-os": "^3.0.1" } }, "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw=="], - - "bare-stream": ["bare-stream@2.13.0", "", { "dependencies": { "streamx": "^2.25.0", "teex": "^1.0.1" }, "peerDependencies": { "bare-abort-controller": "*", "bare-buffer": "*", "bare-events": "*" }, "optionalPeers": ["bare-abort-controller", "bare-buffer", "bare-events"] }, "sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA=="], - - "bare-url": ["bare-url@2.4.1", "", { "dependencies": { "bare-path": "^3.0.0" } }, "sha512-fZapLWNB25gS+etK27NV9KgBNXgo2yeYHuj+OyPblQd6GYAE3JVy6aKxszMV5jhGGFwraXQKA5fldvf3lMyEqw=="], - "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.10.20", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ=="], @@ -2547,8 +2364,6 @@ "bin-links": ["bin-links@6.0.0", "", { "dependencies": { "cmd-shim": "^8.0.0", "npm-normalize-package-bin": "^5.0.0", "proc-log": "^6.0.0", "read-cmd-shim": "^6.0.0", "write-file-atomic": "^7.0.0" } }, "sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w=="], - "binary": ["binary@0.3.0", "", { "dependencies": { "buffers": "~0.1.1", "chainsaw": "~0.1.0" } }, "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg=="], - "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="], "binaryextensions": ["binaryextensions@6.11.0", "", { "dependencies": { "editions": "^6.21.0" } }, "sha512-sXnYK/Ij80TO3lcqZVV2YgfKN5QjUWIRk/XSm2J/4bd/lPko3lvk0O4ZppH6m+6hB2/GTu+ptNwVFe1xh+QLQw=="], @@ -2565,8 +2380,6 @@ "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], - "bottleneck": ["bottleneck@2.19.5", "", {}, "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="], - "boundary": ["boundary@2.0.0", "", {}, "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA=="], "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], @@ -2579,14 +2392,12 @@ "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], - "buffer": ["buffer@4.9.2", "", { "dependencies": { "base64-js": "^1.0.2", "ieee754": "^1.1.4", "isarray": "^1.0.0" } }, "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg=="], + "buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - "buffer-crc32": ["buffer-crc32@1.0.0", "", {}, "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w=="], + "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="], - "buffers": ["buffers@0.1.1", "", {}, "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ=="], - "bun-ffi-structs": ["bun-ffi-structs@0.2.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-N/ZWtyN0piZlrXQT7TO0V+q952orYqkfhXRXM1Hcbb+R3QSiBH4vLnib187Mrs1H7pWIYECAmPeapGYDOMCl+w=="], "bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="], @@ -2621,8 +2432,6 @@ "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], - "chainsaw": ["chainsaw@0.1.0", "", { "dependencies": { "traverse": ">=0.3.0 <0.4" } }, "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ=="], - "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "chalk-template": ["chalk-template@0.4.0", "", { "dependencies": { "chalk": "^4.1.2" } }, "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg=="], @@ -2703,8 +2512,6 @@ "common-path-prefix": ["common-path-prefix@3.0.0", "", {}, "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w=="], - "compress-commons": ["compress-commons@6.0.2", "", { "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", "is-stream": "^2.0.1", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" } }, "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg=="], - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], "concordance": ["concordance@5.0.4", "", { "dependencies": { "date-time": "^3.1.0", "esutils": "^2.0.3", "fast-diff": "^1.2.0", "js-string-escape": "^1.0.1", "lodash": "^4.17.15", "md5-hex": "^3.0.1", "semver": "^7.3.2", "well-known-symbols": "^2.0.0" } }, "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw=="], @@ -2735,8 +2542,6 @@ "crc-32": ["crc-32@1.2.2", "", { "bin": { "crc32": "bin/crc32.njs" } }, "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ=="], - "crc32-stream": ["crc32-stream@6.0.0", "", { "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^4.0.0" } }, "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g=="], - "cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -2903,7 +2708,7 @@ "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], - "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + "dotenv": ["dotenv@8.6.0", "", {}, "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g=="], "drizzle-kit": ["drizzle-kit@1.0.0-beta.19-d95b7a4", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "get-tsconfig": "^4.13.6", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-M0sqc+42TYBod6kEZ3AsW6+JWe3+76gR1aDFbHH5DmuLKEwewmbzlhBG6qnvV6YA1cIIbkuam3dC7r6PREOCXw=="], @@ -2921,7 +2726,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "effect": ["effect@4.0.0-beta.65", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-QYKvQPAj3CmtsvWkHQww15wX4KG2gNsszDWEcOO5sZCMknp66u6Si/Opmt3wwWCwsyvRmDAdIg+JIz5qzbbFIw=="], + "effect": ["effect@4.0.0-beta.59", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-xyUDLeHSe8d6lWGOvR6Fgn2HL6gYeTZ/S4Jzk9uc4ZUxMPPsNZlNXrvk0C7/utQFzeX7uAWcVnG2BjbA0SRoAA=="], "electron-to-chromium": ["electron-to-chromium@1.5.340", "", {}, "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA=="], @@ -3001,8 +2806,6 @@ "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - "events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="], - "eventsource": ["eventsource@3.0.7", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], "eventsource-parser": ["eventsource-parser@3.0.7", "", {}, "sha512-zwxwiQqexizSXFZV13zMiEtW1E3lv7RlUv+1f5FBiR4x7wFhEjm3aFTyYkZQWzyN08WnPdox015GoRH5D/E5YA=="], @@ -3037,8 +2840,6 @@ "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], - "fast-fifo": ["fast-fifo@1.3.2", "", {}, "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="], - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], @@ -3053,7 +2854,7 @@ "fast-xml-builder": ["fast-xml-builder@1.1.5", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA=="], - "fast-xml-parser": ["fast-xml-parser@5.7.1", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.5", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-8Cc3f8GUGUULg34pBch/KGyPLglS+OFs05deyOlY7fL2MTagYPKrVQNmR1fLF/yJ9PH5ZSTd3YDF6pnmeZU+zA=="], + "fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="], "fastest-levenshtein": ["fastest-levenshtein@1.0.16", "", {}, "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="], @@ -3145,8 +2946,6 @@ "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="], - "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], - "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], @@ -3211,8 +3010,6 @@ "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], - "heap-snapshot-toolkit": ["heap-snapshot-toolkit@1.1.3", "", {}, "sha512-joThu2rEsDu8/l4arupRDI1qP4CZXNG+J6Wr348vnbLGSiBkwRdqZ6aOHl5BzEiC+Dc8OTbMlmWjD0lbXD5K2Q=="], - "hono": ["hono@4.12.12", "", {}, "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q=="], "hono-openapi": ["hono-openapi@1.1.2", "", { "peerDependencies": { "@hono/standard-validator": "^0.2.0", "@standard-community/standard-json": "^0.3.5", "@standard-community/standard-openapi": "^0.2.9", "@types/json-schema": "^7.0.15", "hono": "^4.8.3", "openapi-types": "^12.1.3" }, "optionalPeers": ["@hono/standard-validator", "hono"] }, "sha512-toUcO60MftRBxqcVyxsHNYs2m4vf4xkQaiARAucQx3TiBPDtMNNkoh+C4I1vAretQZiGyaLOZNWn1YxfSyUA5g=="], @@ -3225,7 +3022,7 @@ "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], - "htmlparser2": ["htmlparser2@8.0.2", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "entities": "^4.4.0" } }, "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA=="], + "htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="], "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], @@ -3245,7 +3042,7 @@ "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], - "ieee754": ["ieee754@1.1.13", "", {}, "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="], + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], @@ -3279,8 +3076,6 @@ "irregular-plurals": ["irregular-plurals@3.5.0", "", {}, "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ=="], - "is-arguments": ["is-arguments@1.2.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA=="], - "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], @@ -3297,8 +3092,6 @@ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], - "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], - "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], "is-in-ssh": ["is-in-ssh@1.0.0", "", {}, "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw=="], @@ -3317,8 +3110,6 @@ "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], - "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], - "is-stream": ["is-stream@3.0.0", "", {}, "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA=="], "is-subdir": ["is-subdir@1.2.0", "", { "dependencies": { "better-path-resolve": "1.0.0" } }, "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw=="], @@ -3355,9 +3146,7 @@ "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - "jmespath": ["jmespath@0.16.0", "", {}, "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw=="], - - "jose": ["jose@5.2.3", "", {}, "sha512-KUXdbctm1uHVL8BYhnyHkgp3zDX5KW8ZhAKVFEfUbU2P8Alpzjb+48hHvjOdQIyPshoblhzsuqOwEEAbtHVirA=="], + "jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], "js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="], @@ -3419,8 +3208,6 @@ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], - "jwt-decode": ["jwt-decode@3.1.2", "", {}, "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="], - "katex": ["katex@0.16.27", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw=="], "keytar": ["keytar@7.9.0", "", { "dependencies": { "node-addon-api": "^4.3.0", "prebuild-install": "^7.0.1" } }, "sha512-VPD8mtVtm5JNtA2AErl6Chp06JBfy7diFQ7TQQhdpWOl6MrCRB+eRbvAZUsbGQS9kiMq0coJsy0W0vHpDCkWsQ=="], @@ -3439,8 +3226,6 @@ "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], - "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], - "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="], "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], @@ -3527,8 +3312,6 @@ "lucide-solid": ["lucide-solid@0.576.0", "", { "peerDependencies": { "solid-js": "^1.4.7" } }, "sha512-WX1JQJXCE8k4YWfj6pLx57i6AyzmH1LRyGP6LUDzIPgPIki9Pk4sIhSfTfF+jCBj8qRcp5aIz+tUURLJrzGAFw=="], - "luxon": ["luxon@3.6.1", "", {}, "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ=="], - "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -3545,10 +3328,6 @@ "marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="], - "marked-katex-extension": ["marked-katex-extension@5.1.6", "", { "peerDependencies": { "katex": ">=0.16 <0.17", "marked": ">=4 <18" } }, "sha512-vYpLXwmlIDKILIhJtiRTgdyZRn5sEYdFBuTmbpjD7lbCIzg0/DWyK3HXIntN3Tp8zV6hvOUgpZNLWRCgWVc24A=="], - - "marked-shiki": ["marked-shiki@1.2.1", "", { "peerDependencies": { "marked": ">=7.0.0", "shiki": ">=1.0.0" } }, "sha512-yHxYQhPY5oYaIRnROn98foKhuClark7M373/VpLxiy5TrDu9Jd/LsMwo8w+U91Up4oDb9IXFrP0N1MFRz8W/DQ=="], - "matcher": ["matcher@5.0.0", "", { "dependencies": { "escape-string-regexp": "^5.0.0" } }, "sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -3621,8 +3400,6 @@ "minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="], - "mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], - "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], @@ -3719,16 +3496,12 @@ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - "object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="], - "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], - "oidc-token-hash": ["oidc-token-hash@5.2.0", "", {}, "sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw=="], - "on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], @@ -3751,10 +3524,6 @@ "opencode-poe-auth": ["opencode-poe-auth@0.0.1", "", { "dependencies": { "open": "^10.0.0", "poe-oauth": "*" }, "peerDependencies": { "@opencode-ai/plugin": "*" } }, "sha512-cXqTlS6AXHzo1oBdosnxbT47ZJEZ9WXn050X8Re6wZ1vaNnTpB/l2fMQt90evT7RBK0fB8UjXQUDMKyd7bbiqg=="], - "opencontrol": ["opencontrol@0.0.6", "", { "dependencies": { "@modelcontextprotocol/sdk": "1.6.1", "@tsconfig/bun": "1.0.7", "hono": "4.7.4", "zod": "3.24.2", "zod-to-json-schema": "3.24.3" }, "bin": { "opencontrol": "bin/index.mjs" } }, "sha512-QeCrpOK5D15QV8kjnGVeD/BHFLwcVr+sn4T6KKmP0WAMs2pww56e4h+eOGHb5iPOufUQXbdbBKi6WV2kk7tefQ=="], - - "openid-client": ["openid-client@5.6.4", "", { "dependencies": { "jose": "^4.15.4", "lru-cache": "^6.0.0", "object-hash": "^2.2.0", "oidc-token-hash": "^5.0.3" } }, "sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA=="], - "opentui-spinner": ["opentui-spinner@0.0.6", "", { "dependencies": { "cli-spinners": "^3.3.0" }, "peerDependencies": { "@opentui/core": "^0.1.49", "@opentui/react": "^0.1.49", "@opentui/solid": "^0.1.49", "typescript": "^5" }, "optionalPeers": ["@opentui/react", "@opentui/solid"] }, "sha512-xupLOeVQEAXEvVJCvHkfX6fChDWmJIPHe5jyUrVb8+n4XVTX8mBNhitFfB9v2ZbkC1H2UwPab/ElePHoW37NcA=="], "option": ["option@0.2.4", "", {}, "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A=="], @@ -3813,8 +3582,6 @@ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], - "partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="], - "path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="], "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], @@ -3945,8 +3712,6 @@ "query-selector-shadow-dom": ["query-selector-shadow-dom@1.0.1", "", {}, "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw=="], - "querystring": ["querystring@0.2.0", "", {}, "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g=="], - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], "quick-format-unescaped": ["quick-format-unescaped@4.0.4", "", {}, "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="], @@ -3983,8 +3748,6 @@ "readable-stream": ["readable-stream@4.7.0", "", { "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", "events": "^3.3.0", "process": "^0.11.10", "string_decoder": "^1.3.0" } }, "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg=="], - "readdir-glob": ["readdir-glob@1.1.3", "", { "dependencies": { "minimatch": "^5.1.0" } }, "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA=="], - "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], @@ -4055,15 +3818,13 @@ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], - "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], - "safe-regex2": ["safe-regex2@5.1.0", "", { "dependencies": { "ret": "~0.5.0" }, "bin": { "safe-regex2": "bin/safe-regex2.js" } }, "sha512-pNHAuBW7TrcleFHsxBr5QMi/Iyp0ENjUKz7GCcX1UO7cMh+NmVK6HxQckNL1tJp1XAJVjG6B8OKIPqodqj9rtw=="], "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], - "sax": ["sax@1.2.1", "", {}, "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="], + "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], @@ -4181,24 +3942,6 @@ "ssri": ["ssri@13.0.1", "", { "dependencies": { "minipass": "^7.0.3" } }, "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ=="], - "sst": ["sst@3.18.10", "", { "dependencies": { "aws-sdk": "2.1692.0", "aws4fetch": "1.0.18", "jose": "5.2.3", "opencontrol": "0.0.6", "openid-client": "5.6.4" }, "optionalDependencies": { "sst-darwin-arm64": "3.18.10", "sst-darwin-x64": "3.18.10", "sst-linux-arm64": "3.18.10", "sst-linux-x64": "3.18.10", "sst-linux-x86": "3.18.10", "sst-win32-arm64": "3.18.10", "sst-win32-x64": "3.18.10", "sst-win32-x86": "3.18.10" }, "bin": { "sst": "bin/sst.mjs" } }, "sha512-SY+ldeJ9K5E9q+DhjXA3e2W3BEOzBwkE3IyLSD71uA3/5nRhUAST31iOWEpW36LbIvSQ9uOVDFcebztoLJ8s7w=="], - - "sst-darwin-arm64": ["sst-darwin-arm64@3.18.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3MwIpMZhhdZKDqLp9ZQNlwkWix5+q+N0PWstuTomYwgZOxCCe6u9IIsoIszSk+GAJJN/jvGZyLiXKeV4iiQvw=="], - - "sst-darwin-x64": ["sst-darwin-x64@3.18.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-nQ0jMKkPOa+kj6Ygz8+kYhBua/vgNTLkd+4r8NSmk7v+Zs78lKnx3T//kEzS0yik6Q6QwGfokwrTcA1Jii2xSw=="], - - "sst-linux-arm64": ["sst-linux-arm64@3.18.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-mj9VNj3SvLS+HaXx2PhCX0aTA7CwJNoM6JhRc0s/zCilqchcvqDjbhpYBJO4brEPv6aOaaa7T3WvIQqtYauK4Q=="], - - "sst-linux-x64": ["sst-linux-x64@3.18.10", "", { "os": "linux", "cpu": "x64" }, "sha512-7iy1Eq2eqnT9Ag/8OVgC04vRjV7AAQyf/BvzLc+6Sz+GvRiKA8VEuPnbXNYQF+NIvEqsawfcd7MknSTtImpsvQ=="], - - "sst-linux-x86": ["sst-linux-x86@3.18.10", "", { "os": "linux", "cpu": "none" }, "sha512-77qZSuPZeQ5bdRCiq1pQEdY8EcGNHboKrx4P2yFid2FBDKJsXxOXtIxJdloyx+ljBn0+nxl/g040QBmXxdc9tA=="], - - "sst-win32-arm64": ["sst-win32-arm64@3.18.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-aY+FhMxvYs8crlrKALpLn/kKmud8YQj6LkMHsrOAAIJhfNyxhCja2vrYQaY+bcqdsS5W2LMVcS2hyaMqKXZKcg=="], - - "sst-win32-x64": ["sst-win32-x64@3.18.10", "", { "os": "win32", "cpu": "x64" }, "sha512-rY+yJXOpG+P5xXnaQRpCvBK2zwwLhjzpYidGkp6F+cGgiVdh2Wre/CIQNRaVHr20ncj8lLe/RsHWa9QCNM48jg=="], - - "sst-win32-x86": ["sst-win32-x86@3.18.10", "", { "os": "win32", "cpu": "none" }, "sha512-pq8SmV0pIjBFMY6DraUZ4akyTxHnfjIKCRbBLdMxFUZK8TzA1NK2YdjRt1AwrgXRYGRyctrz/mt4WyO0SMOVQQ=="], - "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="], "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], @@ -4215,8 +3958,6 @@ "storybook-solidjs-vite": ["storybook-solidjs-vite@10.0.9", "", { "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.1", "@storybook/builder-vite": "^10.0.0", "@storybook/global": "^5.0.0", "vite-plugin-solid": "^2.11.8" }, "peerDependencies": { "solid-js": "^1.9.0", "storybook": "^0.0.0-0 || ^10.0.0", "typescript": ">= 4.9.x", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["typescript"] }, "sha512-n6MwWCL9mK/qIaUutE9vhGB0X1I1hVnKin2NL+iVC5oXfAiuaABVZlr/1oEeEypsgCdyDOcbEbhJmDWmaqGpPw=="], - "streamx": ["streamx@2.25.0", "", { "dependencies": { "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" } }, "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg=="], - "string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -4271,14 +4012,12 @@ "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], - "tar-stream": ["tar-stream@3.1.8", "", { "dependencies": { "b4a": "^1.6.4", "bare-fs": "^4.5.5", "fast-fifo": "^1.2.0", "streamx": "^2.15.0" } }, "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ=="], + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], "tarn": ["tarn@3.0.2", "", {}, "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="], "tedious": ["tedious@18.6.2", "", { "dependencies": { "@azure/core-auth": "^1.7.2", "@azure/identity": "^4.2.1", "@azure/keyvault-keys": "^4.4.0", "@js-joda/core": "^5.6.1", "@types/node": ">=18", "bl": "^6.0.11", "iconv-lite": "^0.6.3", "js-md4": "^0.3.2", "native-duplexpair": "^1.0.0", "sprintf-js": "^1.1.3" } }, "sha512-g7jC56o3MzLkE3lHkaFe2ZdOVFBahq5bsB60/M4NYUbocw/MCrS89IOEQUFr+ba6pb8ZHczZ/VqCyYeYq0xBAg=="], - "teex": ["teex@1.0.1", "", { "dependencies": { "streamx": "^2.12.5" } }, "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg=="], - "temp-dir": ["temp-dir@3.0.0", "", {}, "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw=="], "term-size": ["term-size@2.2.1", "", {}, "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg=="], @@ -4287,8 +4026,6 @@ "test-exclude": ["test-exclude@7.0.2", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", "minimatch": "^10.2.2" } }, "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw=="], - "text-decoder": ["text-decoder@1.2.7", "", { "dependencies": { "b4a": "^1.6.4" } }, "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ=="], - "text-table": ["text-table@0.2.0", "", {}, "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="], "textextensions": ["textextensions@6.11.0", "", { "dependencies": { "editions": "^6.21.0" } }, "sha512-tXJwSr9355kFJI3lbCkPpUH5cP8/M0GGy2xLO34aZCjMXBaK3SoPnZwr/oWmo1FdCnELcs4npdCIOFtq9W3ruQ=="], @@ -4325,8 +4062,6 @@ "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], - "traverse": ["traverse@0.3.9", "", {}, "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ=="], - "tree-sitter-bash": ["tree-sitter-bash@0.25.0", "", { "dependencies": { "node-addon-api": "^8.2.1", "node-gyp-build": "^4.8.2" }, "peerDependencies": { "tree-sitter": "^0.25.0" }, "optionalPeers": ["tree-sitter"] }, "sha512-gZtlj9+qFS81qKxpLfD6H0UssQ3QBc/F0nKkPsiFDyfQF2YBqYvglFJUzchrPpVhZe9kLZTrJ9n2J6lmka69Vg=="], "tree-sitter-powershell": ["tree-sitter-powershell@0.25.10", "", { "dependencies": { "node-addon-api": "^7.1.0", "node-gyp-build": "^4.8.0" }, "peerDependencies": { "tree-sitter": "^0.25.0" }, "optionalPeers": ["tree-sitter"] }, "sha512-bEt8QoySpGFnU3aa8WedQyNMaN6aTwy/WUbvIVt0JSKF+BbJoSHNHu+wCbhj7xLMsfB0AuffmiJm+B8gzva8Lg=="], @@ -4405,20 +4140,14 @@ "unplugin": ["unplugin@2.3.11", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "acorn": "^8.15.0", "picomatch": "^4.0.3", "webpack-virtual-modules": "^0.6.2" } }, "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww=="], - "unzip-stream": ["unzip-stream@0.3.4", "", { "dependencies": { "binary": "^0.3.0", "mkdirp": "^0.5.1" } }, "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw=="], - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], - "url": ["url@0.10.3", "", { "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" } }, "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ=="], - "url-join": ["url-join@4.0.1", "", {}, "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="], "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], - "util": ["util@0.12.5", "", { "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", "is-generator-function": "^1.0.7", "is-typed-array": "^1.1.3", "which-typed-array": "^1.1.2" } }, "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA=="], - "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], @@ -4517,7 +4246,7 @@ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], - "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], "yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], @@ -4535,35 +4264,23 @@ "yoga-layout": ["yoga-layout@3.2.1", "", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="], - "zip-stream": ["zip-stream@6.0.1", "", { "dependencies": { "archiver-utils": "^5.0.0", "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" } }, "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA=="], - "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], "zod-openapi": ["zod-openapi@5.4.6", "", { "peerDependencies": { "zod": "^3.25.74 || ^4.0.0" } }, "sha512-P2jsOOBAq/6hCwUsMCjUATZ8szkMsV5VAwZENfyxp2Hc/XPJQpVwAgevWZc65xZauCwWB9LAn7zYeiCJFAEL+A=="], - "zod-to-json-schema": ["zod-to-json-schema@3.24.5", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g=="], + "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], "zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], - "@actions/artifact/@actions/core": ["@actions/core@2.0.3", "", { "dependencies": { "@actions/exec": "^2.0.0", "@actions/http-client": "^3.0.2" } }, "sha512-Od9Thc3T1mQJYddvVPM4QGiLUewdh+3txmDYHHxoNdkqysR1MbCT+rFOtNUxYAz+7+6RIsqipVahY2GJqGPyxA=="], - - "@actions/core/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], - - "@actions/github/@actions/http-client": ["@actions/http-client@2.2.3", "", { "dependencies": { "tunnel": "^0.0.6", "undici": "^5.25.4" } }, "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA=="], - - "@actions/http-client/undici": ["undici@6.25.0", "", {}, "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg=="], - - "@ai-sdk/alibaba/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], - "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@3.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ALmj/53EXpcRqMbGpPJPP4UOSWw0q4VGpnDo7YctvsynjkrKDmoneDG/1a7VQnSPYHnJp6tTRMf5ZdxZ5whulg=="], "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "@ai-sdk/cerebras/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], + "@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="], - "@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], + "@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], "@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], @@ -4571,24 +4288,14 @@ "@ai-sdk/deepinfra/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], - "@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CbR82EgGPNrj/6q0HtclwuCqe0/pDShyv3nWDP/A9DroujzWXnLMlUJVrgPOsg4b40zQCwwVs2XSKCxvt/4QaA=="], - "@ai-sdk/google-vertex/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], - "@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], "@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], - - "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], - "@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="], "@ai-sdk/togetherai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], @@ -4597,13 +4304,9 @@ "@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "@ai-sdk/xai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], - - "@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], - "@antfu/install-pkg/package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], - "@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@anthropic-ai/sdk/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], @@ -4611,8 +4314,6 @@ "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1032.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.1", "@aws-sdk/nested-clients": "^3.996.21", "@aws-sdk/types": "^3.973.8", "@smithy/property-provider": "^4.2.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-n+PU8Z+gll7p3wDrH+Wo6fkt8sPrVnq30YYM6Ryga95oJlEneNMEbDHj0iqjMX3V7gaGdJo/hJWyPo4lscP+mA=="], - "@aws-sdk/xml-builder/fast-xml-parser": ["fast-xml-parser@5.5.8", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="], - "@axe-core/playwright/axe-core": ["axe-core@4.11.4", "", {}, "sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA=="], "@azure/identity/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], @@ -4627,12 +4328,8 @@ "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@bufbuild/protoplugin/typescript": ["typescript@5.4.5", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ=="], - "@changesets/apply-release-plan/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="], - "@changesets/changelog-github/dotenv": ["dotenv@8.6.0", "", {}, "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g=="], - "@changesets/write/prettier": ["prettier@2.8.8", "", { "bin": { "prettier": "bin-prettier.js" } }, "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q=="], "@effect/platform-node/undici": ["undici@8.1.0", "", {}, "sha512-E9MkTS4xXLnRPYqxH2e6Hr2/49e7WFDKczKcCaFH4VaZs2iNvHMqeIkyUAD9vM8kujy9TjVrRlQ5KkdEJxB2pw=="], @@ -4659,12 +4356,16 @@ "@hey-api/openapi-ts/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], + "@hono/standard-validator/@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="], + "@hono/zod-validator/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@kilocode/kilo-docs/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "@kilocode/kilo-indexing/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + "@manypkg/find-root/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + "@manypkg/find-root/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], "@manypkg/find-root/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], @@ -4673,12 +4374,6 @@ "@manypkg/get-packages/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], - "@modelcontextprotocol/sdk/@hono/node-server": ["@hono/node-server@1.19.11", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g=="], - - "@modelcontextprotocol/sdk/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], - - "@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], - "@morphllm/morphsdk/ai": ["ai@6.0.158", "", { "dependencies": { "@ai-sdk/gateway": "3.0.95", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-gLTp1UXFtMqKUi3XHs33K7UFglbvojkxF/aq337TxnLGOhHIW9+GyP2jwW4hYX87f1es+wId3VQoPRRu9zEStQ=="], "@morphllm/morphsdk/openai": ["openai@4.104.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA=="], @@ -4697,9 +4392,9 @@ "@octokit/plugin-paginate-rest/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], - "@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], + "@octokit/plugin-request-log/@octokit/core": ["@octokit/core@7.0.6", "", { "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", "@octokit/request": "^10.0.6", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "before-after-hook": "^4.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q=="], - "@octokit/plugin-retry/@octokit/types": ["@octokit/types@6.41.0", "", { "dependencies": { "@octokit/openapi-types": "^12.11.0" } }, "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg=="], + "@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@12.6.0", "", { "dependencies": { "@octokit/openapi-types": "^20.0.0" } }, "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw=="], "@octokit/request/@octokit/types": ["@octokit/types@13.10.0", "", { "dependencies": { "@octokit/openapi-types": "^24.2.0" } }, "sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA=="], @@ -4711,20 +4406,8 @@ "@octokit/rest/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@13.2.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-Tj4PkZyIL6eBMYcG/76QGsedF0+dWVeLhYprTmuFVVxzDW7PQh23tM0TP0z+1MvSkxB29YFZwnUX+cXfTiSdyw=="], - "@octokit/rest/@octokit/plugin-request-log": ["@octokit/plugin-request-log@6.0.0", "", { "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q=="], - "@octokit/rest/@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.1.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-VztDkhM0ketQYSh5Im3IcKWFZl7VIrrsCaHbDINkdYeiiAsJzjhS2xRFCSJgfN6VOcsoW4laMtsmf3HcNqIimg=="], - "@openauthjs/openauth/@standard-schema/spec": ["@standard-schema/spec@1.0.0-beta.3", "", {}, "sha512-0ifF3BjA1E8SY9C+nUew8RefNOIq0cDlYALPty4rhUm8Rrl6tCM8hBT4bhGhx7I7iXD0uAgt50lgo8dD73ACMw=="], - - "@openauthjs/openauth/jose": ["jose@5.9.6", "", {}, "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ=="], - - "@opencode-ai/core/@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.993.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.993.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-cognito-identity": "^3.972.3", "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-ini": "^3.972.9", "@aws-sdk/credential-provider-login": "^3.972.9", "@aws-sdk/credential-provider-node": "^3.972.10", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-1M/nukgPSLqe9krzOKHnE8OylUaKAiokAV3xRLdeExVHcRE7WG5uzCTKWTj1imKvPjDqXq/FWhlbbdWIn7xIwA=="], - - "@opencode-ai/storybook/@storybook/addon-a11y": ["@storybook/addon-a11y@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "axe-core": "^4.2.0" }, "peerDependencies": { "storybook": "^10.3.5" } }, "sha512-5k6lpgfIeLxvNhE8v3wEzdiu73ONKjF4gmH1AHvfqYd8kIVzQJai0KCDxgvqNncXHQhIWkaf1fg6+9hKaYJyaw=="], - - "@opencode-ai/storybook/@storybook/addon-docs": ["@storybook/addon-docs@10.3.5", "", { "dependencies": { "@mdx-js/react": "^3.0.0", "@storybook/csf-plugin": "10.3.5", "@storybook/icons": "^2.0.1", "@storybook/react-dom-shim": "10.3.5", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" }, "peerDependencies": { "storybook": "^10.3.5" } }, "sha512-WuHbxia/o5TX4Rg/IFD0641K5qId/Nk0dxhmAUNoFs5L0+yfZUwh65XOBbzXqrkYmYmcVID4v7cgDRmzstQNkA=="], - "@opencode-ai/storybook/@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="], "@opencode-ai/storybook/react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], @@ -4749,34 +4432,30 @@ "@opentelemetry/sdk-logs/@opentelemetry/resources": ["@opentelemetry/resources@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A=="], - "@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], - - "@oslojs/jwt/@oslojs/encoding": ["@oslojs/encoding@0.4.1", "", {}, "sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q=="], - - "@protobuf-ts/plugin/typescript": ["typescript@3.9.10", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q=="], - "@qdrant/js-client-rest/undici": ["undici@6.25.0", "", {}, "sha512-ZgpWDC5gmNiuY9CnLVXEH8rl50xhRCuLNA97fAUnKi8RRuV4E6KG31pDTsLVUKnohJE0I3XDrTeEydAXRw47xg=="], - "@shikijs/engine-javascript/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + "@shikijs/transformers/@shikijs/core": ["@shikijs/core@3.9.2", "", { "dependencies": { "@shikijs/types": "3.9.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-3q/mzmw09B2B6PgFNeiaN8pkNOixWS726IHmJEpjDAcneDPMQmUg2cweT9cWXY4XcyQS3i6mOOUgQz9RRUP6HA=="], - "@shikijs/engine-oniguruma/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], - - "@shikijs/langs/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], - - "@shikijs/themes/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + "@shikijs/transformers/@shikijs/types": ["@shikijs/types@3.9.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-/M5L0Uc2ljyn2jKvj4Yiah7ow/W+DJSglVafvWAJ/b8AZDeeRAdMu3c2riDzB7N42VD+jSnWxeP9AKtd4TfYVw=="], "@solid-primitives/resize-observer/@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA=="], + "@storybook/addon-a11y/storybook": ["storybook@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw=="], + + "@storybook/addon-docs/storybook": ["storybook@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw=="], + "@storybook/addon-links/storybook": ["storybook@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw=="], "@storybook/addon-onboarding/storybook": ["storybook@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw=="], "@storybook/addon-vitest/storybook": ["storybook@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw=="], - "@storybook/builder-vite/@storybook/csf-plugin": ["@storybook/csf-plugin@10.3.5", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.3.5", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-qlEzNKxOjq86pvrbuMwiGD/bylnsXk1dg7ve0j77YFjEEchqtl7qTlrXvFdNaLA89GhW6D/EV6eOCu/eobPDgw=="], - "@storybook/builder-vite/storybook": ["storybook@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw=="], + "@storybook/csf-plugin/storybook": ["storybook@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw=="], + + "@storybook/react-dom-shim/storybook": ["storybook@10.3.5", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-uBSZu/GZa9aEIW3QMGvdQPMZWhGxSe4dyRWU8B3/Vd47Gy/XLC7tsBxRr13txmmPOEDHZR94uLuq0H50fvuqBw=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], @@ -4807,9 +4486,31 @@ "@textlint/linter-formatter/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], + "@types/cacache/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], + "@types/cross-spawn/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "@types/mssql/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "@types/node-fetch/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "@types/npm-registry-fetch/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "@types/npmcli__arborist/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "@types/npmlog/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "@types/pacote/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "@types/qrcode/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "@types/readable-stream/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "@types/ssri/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "@types/ws/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + + "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], "@vscode/ripgrep/yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], @@ -4835,8 +4536,6 @@ "ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.93", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.69", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hcXDU8QDwpAzLVTuY932TQVlIij9+iaVTxc5mPGY6yb//JMAAC5hMVhg93IrxlrxWLvMgjezNgoZGwquR+SGnw=="], - "ai-gateway-provider/@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.41", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kDMEpjaRdRXIUi1EH8WHwLRahyDTYv9SAJnP6VCCeq8X+tVqZbMLCqqxSG5dRknrI65ucjvzQt+FiDKTAa7AHg=="], - "ai-gateway-provider/@ai-sdk/google": ["@ai-sdk/google@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-uz8tIlkDgQJG9Js2Wh9JHzd4kI9+hYJqf9XXJLx60vyN5mRIqhr49iwR5zGP5Gl8odp2PeR3Gh2k+5bh3Z1HHw=="], "ai-gateway-provider/@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.95", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/google": "3.0.53", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-xL44fHlTtDM7RLkMTgyqMfkfthA38JS91bbMaHItObIhte1PAIY936ZV1PLl/Z9A/oBAXjHWbXo5xDoHzB7LEg=="], @@ -4847,9 +4546,7 @@ "ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.5.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-r1fJL1Cb3gQDa2MpWH/sfx1BsEW0uzlRriJM6eihaKqbtKDmZoBisF32VcVaQYassighX7NGCkF68EsrZA43uQ=="], - "archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], - - "archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], + "apache-arrow/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], "ava/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], @@ -4865,21 +4562,13 @@ "ava/yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], - "aws-sdk/events": ["events@1.1.1", "", {}, "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw=="], - - "aws-sdk/uuid": ["uuid@8.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw=="], - - "aws-sdk/xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="], - "axios/proxy-from-env": ["proxy-from-env@2.1.0", "", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="], "babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], - "bl/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - - "buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + "bun-types/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], "c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], @@ -4893,8 +4582,6 @@ "chalk-template/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "cheerio/htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="], - "cheerio/undici": ["undici@7.25.0", "", {}, "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ=="], "clean-stack/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], @@ -4905,8 +4592,6 @@ "command-line-usage/typical": ["typical@7.3.0", "", {}, "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw=="], - "compress-commons/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], - "cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], @@ -4925,8 +4610,6 @@ "drizzle-kit/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], - "effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "effect/uuid": ["uuid@13.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w=="], "encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], @@ -4937,6 +4620,8 @@ "enquirer/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "esbuild-plugin-solid/@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="], + "esbuild-plugin-solid/babel-preset-solid": ["babel-preset-solid@1.9.10", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.3" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.10" }, "optionalPeers": ["solid-js"] }, "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ=="], "eslint/ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="], @@ -4975,8 +4660,6 @@ "gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], - "htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], - "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "isomorphic-git/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], @@ -5001,8 +4684,6 @@ "knip/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - "light-my-request/cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], "light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="], @@ -5051,23 +4732,9 @@ "nypm/citty": ["citty@0.2.2", "", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="], - "opencontrol/@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.6.1", "", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^4.1.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-oxzMzYCkZHMntzuyerehK3fV6A2Kwh5BD6CGEJSVDU2QNEhfLOptf2X7esQgaHZXHZY0oHmMsOtIDLP71UJXgA=="], + "opentui-spinner/@opentui/core": ["@opentui/core@0.2.2", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.2", "@opentui/core-darwin-x64": "0.2.2", "@opentui/core-linux-arm64": "0.2.2", "@opentui/core-linux-x64": "0.2.2", "@opentui/core-win32-arm64": "0.2.2", "@opentui/core-win32-x64": "0.2.2" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-wxg1CD58SVrowu+WgbhZNi3UP/wWxPio2Kj2IeTjomoIE+6EXLxR8eCCxHYVuQUd9E4fknrKkY5HmiSsp6oPow=="], - "opencontrol/@tsconfig/bun": ["@tsconfig/bun@1.0.7", "", {}, "sha512-udGrGJBNQdXGVulehc1aWT73wkR9wdaGBtB6yL70RJsqwW/yJhIg6ZbRlPOfIUiFNrnBuYLBi9CSmMKfDC7dvA=="], - - "opencontrol/hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="], - - "opencontrol/zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], - - "opencontrol/zod-to-json-schema": ["zod-to-json-schema@3.24.3", "", { "peerDependencies": { "zod": "^3.24.1" } }, "sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A=="], - - "openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="], - - "openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - - "opentui-spinner/@opentui/core": ["@opentui/core@0.2.10", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.10", "@opentui/core-darwin-x64": "0.2.10", "@opentui/core-linux-arm64": "0.2.10", "@opentui/core-linux-x64": "0.2.10", "@opentui/core-win32-arm64": "0.2.10", "@opentui/core-win32-x64": "0.2.10" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-oviCtx0jYjc7F8X2b8+0IkQLg6WH47Nwl6CFeZo5dU0k6OpSbTbi07ZleObaiECAp+S1YLhAtVdgzHU7hBZlaw=="], - - "opentui-spinner/@opentui/solid": ["@opentui/solid@0.2.10", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.10", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-+4/MB90yIQiPwg8Y4wY092yva9BvRTsJeeeEO3e2H7P8k8zxYk4G9bzuhqYLxA9mTVQ+zVDlrmFoPQhT7vpIRw=="], + "opentui-spinner/@opentui/solid": ["@opentui/solid@0.2.2", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.2", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-ZBVfCoVAhcUGQWPAWOTdzuVldMaRkuPpCu4U1VZCqmIw9DtbCuiVr0WnDocDxKhJLbTu8bl3qEWtVCf6lTSi3w=="], "ora/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], @@ -5099,6 +4766,8 @@ "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], + "protobufjs/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + "proxy-addr/ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], "qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], @@ -5111,10 +4780,6 @@ "read-yaml-file/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], - "readable-stream/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="], - - "readdir-glob/minimatch": ["minimatch@5.1.9", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw=="], - "redent/indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], "restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], @@ -5125,18 +4790,12 @@ "sharp/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "shiki/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], - - "shiki/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], - "slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], "spdx-correct/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], - "sst/aws4fetch": ["aws4fetch@1.0.18", "", {}, "sha512-3Cf+YaUl07p24MoQ46rFwulAmiyCwH2+1zw1ZyPAX5OtJ34Hh185DwB8y/qRLb6cYYYtSFJ9pthyLc0MD4e8sQ=="], - "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], "storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], @@ -5163,11 +4822,13 @@ "table-layout/array-back": ["array-back@6.2.3", "", {}, "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw=="], - "tar/yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], - "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], - "tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + "tar-stream/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + + "tedious/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], "tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], @@ -5179,16 +4840,14 @@ "tree-sitter-bash/node-addon-api": ["node-addon-api@8.7.0", "", {}, "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA=="], - "url/punycode": ["punycode@1.3.2", "", {}, "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="], - "validate-npm-package-license/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], - "venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], - "vite-plugin-icons-spritesheet/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], "vite-plugin-icons-spritesheet/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], + "vite-plugin-solid/@babel/core": ["@babel/core@7.28.4", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.4", "@babel/types": "^7.28.4", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA=="], + "vite-plugin-solid/babel-preset-solid": ["babel-preset-solid@1.9.10", "", { "dependencies": { "babel-plugin-jsx-dom-expressions": "^0.40.3" }, "peerDependencies": { "@babel/core": "^7.0.0", "solid-js": "^1.9.10" }, "optionalPeers": ["solid-js"] }, "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ=="], "vitest/why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], @@ -5201,50 +4860,10 @@ "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "xml2js/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], - "xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], "yargs-unparser/decamelize": ["decamelize@4.0.0", "", {}, "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ=="], - "yauzl/buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], - - "yazl/buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], - - "zod-to-json-schema/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - - "@actions/artifact/@actions/core/@actions/exec": ["@actions/exec@2.0.0", "", { "dependencies": { "@actions/io": "^2.0.0" } }, "sha512-k8ngrX2voJ/RIN6r9xB82NVqKpnMRtxDoiO+g3olkIUpQNqjArXrCQceduQZCQj3P3xm32pChRLqRrtXTlqhIw=="], - - "@ai-sdk/azure/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@ai-sdk/cerebras/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@ai-sdk/cerebras/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], - - "@ai-sdk/cohere/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@ai-sdk/deepinfra/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@ai-sdk/groq/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@ai-sdk/mistral/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@ai-sdk/openai-compatible/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@ai-sdk/openai-compatible/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], - - "@ai-sdk/perplexity/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@ai-sdk/togetherai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@ai-sdk/vercel/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@ai-sdk/xai/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], - - "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], @@ -5263,6 +4882,8 @@ "@morphllm/morphsdk/ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.95", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ=="], + "@morphllm/morphsdk/openai/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + "@morphllm/morphsdk/openai/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], @@ -5277,9 +4898,19 @@ "@octokit/plugin-paginate-rest/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], - "@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], + "@octokit/plugin-request-log/@octokit/core/@octokit/auth-token": ["@octokit/auth-token@6.0.0", "", {}, "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w=="], - "@octokit/plugin-retry/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@12.11.0", "", {}, "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="], + "@octokit/plugin-request-log/@octokit/core/@octokit/graphql": ["@octokit/graphql@9.0.3", "", { "dependencies": { "@octokit/request": "^10.0.6", "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.0" } }, "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA=="], + + "@octokit/plugin-request-log/@octokit/core/@octokit/request": ["@octokit/request@10.0.8", "", { "dependencies": { "@octokit/endpoint": "^11.0.3", "@octokit/request-error": "^7.0.2", "@octokit/types": "^16.0.0", "fast-content-type-parse": "^3.0.0", "json-with-bigint": "^3.5.3", "universal-user-agent": "^7.0.2" } }, "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw=="], + + "@octokit/plugin-request-log/@octokit/core/@octokit/request-error": ["@octokit/request-error@7.1.0", "", { "dependencies": { "@octokit/types": "^16.0.0" } }, "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw=="], + + "@octokit/plugin-request-log/@octokit/core/@octokit/types": ["@octokit/types@16.0.0", "", { "dependencies": { "@octokit/openapi-types": "^27.0.0" } }, "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg=="], + + "@octokit/plugin-request-log/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], + + "@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], "@octokit/request-error/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], @@ -5297,16 +4928,6 @@ "@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], - "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-node": "^3.972.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7Ne3Yk/bgQPVebAkv7W+RfhiwTRSbfER9BtbhOa2w/+dIr902LrJf6vrZlxiqaJbGj2ALx8M+ZK1YIHVxSwu9A=="], - - "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iOq86f2H67924kQUIPOAvlmMaOAvOLoDOIb66I2YqSUpMYB6ufiuJW3RlREgskxv86S5qKzMnfy/X6CqMjK6XQ=="], - - "@opencode-ai/storybook/@storybook/addon-docs/@storybook/csf-plugin": ["@storybook/csf-plugin@10.3.5", "", { "dependencies": { "unplugin": "^2.3.5" }, "peerDependencies": { "esbuild": "*", "rollup": "*", "storybook": "^10.3.5", "vite": "*", "webpack": "*" }, "optionalPeers": ["esbuild", "rollup", "vite", "webpack"] }, "sha512-qlEzNKxOjq86pvrbuMwiGD/bylnsXk1dg7ve0j77YFjEEchqtl7qTlrXvFdNaLA89GhW6D/EV6eOCu/eobPDgw=="], - - "@opencode-ai/storybook/@storybook/addon-docs/@storybook/react-dom-shim": ["@storybook/react-dom-shim@10.3.5", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "storybook": "^10.3.5" } }, "sha512-Gw8R7XZm0zSUH0XAuxlQJhmizsLzyD6x00KOlP6l7oW9eQHXGfxg3seNDG3WrSAcW07iP1/P422kuiriQlOv7g=="], - - "@opencode-ai/storybook/@storybook/addon-docs/react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="], - "@opencode-ai/storybook/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], "@opencode-ai/storybook/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], @@ -5321,7 +4942,17 @@ "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="], - "@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@storybook/addon-a11y/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@storybook/addon-a11y/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + + "@storybook/addon-a11y/storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + + "@storybook/addon-docs/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@storybook/addon-docs/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + + "@storybook/addon-docs/storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], "@storybook/addon-links/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], @@ -5347,6 +4978,18 @@ "@storybook/builder-vite/storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + "@storybook/csf-plugin/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@storybook/csf-plugin/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + + "@storybook/csf-plugin/storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + + "@storybook/react-dom-shim/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], + + "@storybook/react-dom-shim/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], + + "@storybook/react-dom-shim/storybook/open": ["open@10.2.0", "", { "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" } }, "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA=="], + "@tailwindcss/vite/@tailwindcss/node/lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], "@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.11", "", { "os": "android", "cpu": "arm64" }, "sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg=="], @@ -5381,8 +5024,6 @@ "@textlint/linter-formatter/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@vscode/ripgrep/yauzl/buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], - "@vscode/test-cli/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "@vscode/test-cli/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -5409,10 +5050,6 @@ "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="], - "ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="], - - "ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rwLi/Rsuj2pYniQXIrvClHvXDzgM4UQHHnvHTWEF14efnlKclG/1ghpNC+adsRujAbCTr6gRsSbDE2vEqriV7g=="], @@ -5425,12 +5062,6 @@ "ai-gateway-provider/@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "archiver-utils/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], - - "archiver-utils/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], - - "archiver-utils/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "ava/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "ava/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -5445,18 +5076,12 @@ "ava/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], - "aws-sdk/xml2js/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], - - "aws-sdk/xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], - "babel-plugin-module-resolver/glob/minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="], "babel-plugin-module-resolver/glob/minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="], "babel-plugin-module-resolver/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], - "bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - "c12/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], "c8/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], @@ -5527,6 +5152,8 @@ "enquirer/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + "esbuild-plugin-solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "eslint/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -5563,12 +5190,14 @@ "jszip/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], - "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - - "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "kilo-code/openai/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], "log-symbols/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + + "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "mocha/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], "mocha/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], @@ -5585,27 +5214,17 @@ "normalize-package-data/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "opencontrol/@modelcontextprotocol/sdk/express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="], + "opentui-spinner/@opentui/core/@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tY5n3ZRQx+b0kyhQJJLsyJMeZ+0w4FV37YZc/Qqv3qvOqE9kZPw/7adR77FYwWDm/7fax94mLMrR8Y5bKUkDmw=="], - "opencontrol/@modelcontextprotocol/sdk/pkce-challenge": ["pkce-challenge@4.1.0", "", {}, "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ=="], + "opentui-spinner/@opentui/core/@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-W/R7OnqY30FXcTG0tiP2JkQFmgtYbIte5afQ5PC12TliRoee1RqG3iCG6kY1jxW+3Vg6jge88uiSjUEDpeV2gA=="], - "opencontrol/@modelcontextprotocol/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "opentui-spinner/@opentui/core/@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-1pzTYFEZauYuw6AGycw2TYGtAlZVGjuUtSdxH1fP51kBPS3oVWduUY2j7GKREz3SU5NulvO2Wc6HWsm3feMqwQ=="], - "opencontrol/@modelcontextprotocol/sdk/zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + "opentui-spinner/@opentui/core/@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ucVwUtUYeOYGVFPBLbPoxzbrPdhD0PDyKNQ2X4n1AJ9jlQX4gqBZRcXMEF8hiXDjFxsikZwef7De0ciCcWvAMg=="], - "opentui-spinner/@opentui/core/@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+lbDDj42Og+UtTZEwlHhGXichmOlkxSqn0J+Jqjat5/Tt5oZykj1NZjFIQ7ZSz4Miz7EmZwgYKE2CyOmmm9MoQ=="], + "opentui-spinner/@opentui/core/@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-MPhYdJNdxmC5Bqsq6sis/+VkjRgkEjm+bQ1Tl++NSKLuiTU32Re0ImcZlgHbe+LZtZoGMZHVSgZlkGd3oYXO2g=="], - "opentui-spinner/@opentui/core/@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-5iAoA0aqMWWAQ93nh8Bb0ipwt9h+tvEFc88+YO9St43uUJ+XrXcmMj3T8wtl6dSu/SN0UoDWNaUMHUmtykiPtg=="], - - "opentui-spinner/@opentui/core/@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-EnrkxgH5K76Oi/Br1UHPZblXG5P60snmtySfnxuVaeECNZrbTkV6BV/A0WoBeWshJweGbx1D+eTF+sEEjQCi8w=="], - - "opentui-spinner/@opentui/core/@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.10", "", { "os": "linux", "cpu": "x64" }, "sha512-fI+r3kCPqIxsWwPVGpKUQy4zHK8y+jkDRCwa3UbaUy48RQ44jMuf2RhVhmi4xmCvSc8UPJBbYsw1tLuh9kmXjg=="], - - "opentui-spinner/@opentui/core/@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-8F4z2hIRgkVWcr6CMVeJ9N4+1rmURPt2Pq2GBPko8ch6rxHR+a//KD1MfphyuLTHBS1tJ4vfZSWSoiaESImtrA=="], - - "opentui-spinner/@opentui/core/@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.10", "", { "os": "win32", "cpu": "x64" }, "sha512-Ki+qNBlIFW5K2wcG/RHrlPp7yEQKXeiNX3mlje25iwX62Ac5w391HBpOmUjbPoq20McPyDRnhbLfbXQSPtickg=="], - - "opentui-spinner/@opentui/solid/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="], + "opentui-spinner/@opentui/core/@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-19BroLfn2h0RDYfJS5o96Fc8kYCDhRBcseIXtHIkoKIsKMxx62KiDLo/byVye6rp+yQRRB7Xkd2uWqsbdiWo9w=="], "ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], @@ -5629,10 +5248,6 @@ "qrcode/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], - "readable-stream/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - - "readdir-glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], - "secretlint/globby/slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="], "storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], @@ -5647,9 +5262,7 @@ "table/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "tar-fs/tar-stream/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], - - "tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "tar-stream/bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], "test-exclude/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], @@ -5657,14 +5270,12 @@ "test-exclude/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + "vite-plugin-solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "@actions/artifact/@actions/core/@actions/exec/@actions/io": ["@actions/io@2.0.0", "", {}, "sha512-Jv33IN09XLO+0HS79aaODsvIRyduiF7NY/F6LYeK5oeUmrsz7aFdRphQjFoESF4jS7lMauDOttKALcpapVDIAg=="], - - "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], @@ -5679,20 +5290,32 @@ "@octokit/graphql/@octokit/request/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + "@octokit/plugin-request-log/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], + + "@octokit/plugin-request-log/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + "@octokit/rest/@octokit/core/@octokit/request/@octokit/endpoint": ["@octokit/endpoint@11.0.3", "", { "dependencies": { "@octokit/types": "^16.0.0", "universal-user-agent": "^7.0.2" } }, "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag=="], "@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], - "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/client-cognito-identity/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], - - "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], - "@opencode-ai/storybook/storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], "@opencode-ai/storybook/storybook/@vitest/expect/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], "@opencode-ai/storybook/storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + "@storybook/addon-a11y/storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "@storybook/addon-a11y/storybook/@vitest/expect/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "@storybook/addon-a11y/storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "@storybook/addon-docs/storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "@storybook/addon-docs/storybook/@vitest/expect/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "@storybook/addon-docs/storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + "@storybook/addon-links/storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], "@storybook/addon-links/storybook/@vitest/expect/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], @@ -5717,6 +5340,18 @@ "@storybook/builder-vite/storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + "@storybook/csf-plugin/storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "@storybook/csf-plugin/storybook/@vitest/expect/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "@storybook/csf-plugin/storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + + "@storybook/react-dom-shim/storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], + + "@storybook/react-dom-shim/storybook/@vitest/expect/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], + + "@storybook/react-dom-shim/storybook/@vitest/expect/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], + "@tailwindcss/vite/@tailwindcss/node/lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "@tailwindcss/vite/@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], @@ -5765,24 +5400,10 @@ "@vscode/test-cli/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + "@vscode/vsce/hosted-git-info/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "@vscode/vsce/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "ai-gateway-provider/@ai-sdk/openai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "ai-gateway-provider/@ai-sdk/xai/@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "archiver-utils/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], - - "archiver-utils/glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], - - "archiver-utils/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "ava/yargs/cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "ava/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -5837,8 +5458,6 @@ "mocha/yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "opentui-spinner/@opentui/solid/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "pkg-conf/find-up/locate-path/p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], "pkg-up/find-up/locate-path/p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="], @@ -5857,12 +5476,8 @@ "qrcode/yargs/yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], - "readdir-glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], - "tar-fs/tar-stream/bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], - "test-exclude/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], "test-exclude/glob/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="], @@ -5873,6 +5488,10 @@ "@opencode-ai/storybook/storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + "@storybook/addon-a11y/storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "@storybook/addon-docs/storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + "@storybook/addon-links/storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], "@storybook/addon-onboarding/storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], @@ -5881,6 +5500,10 @@ "@storybook/builder-vite/storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + "@storybook/csf-plugin/storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + + "@storybook/react-dom-shim/storybook/@vitest/expect/@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.2.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA=="], + "@tailwindcss/vite/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@vscode/test-cli/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], @@ -5891,12 +5514,6 @@ "@vscode/test-cli/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "archiver-utils/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - - "archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], - - "archiver-utils/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "ava/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "ava/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], @@ -5931,8 +5548,6 @@ "qrcode/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], - "tar-fs/tar-stream/bl/buffer/ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], - "test-exclude/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "test-exclude/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], @@ -5943,10 +5558,6 @@ "@vscode/test-cli/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "archiver-utils/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - - "archiver-utils/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "mocha/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "mocha/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], diff --git a/package.json b/package.json index 9317d05d31d..c21c9d67090 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,6 @@ } }, "devDependencies": { - "@actions/artifact": "5.0.1", "@tsconfig/bun": "catalog:", "@types/mime-types": "3.0.1", "@typescript/native-preview": "catalog:", @@ -92,20 +91,16 @@ "oxlint-tsgolint": "0.21.0", "prettier": "3.6.2", "semver": "^7.6.0", - "sst": "3.18.10", "turbo": "2.9.14", "@types/bun": "catalog:", "@changesets/changelog-github": "^0.5.1", "@changesets/cli": "^2.27.10" }, "dependencies": { - "@aws-sdk/client-s3": "3.1025.0", "@kilocode/plugin": "workspace:*", "@opencode-ai/script": "workspace:*", "@kilocode/sdk": "workspace:*", - "heap-snapshot-toolkit": "1.1.3", - "typescript": "catalog:", - "@morphllm/morphsdk": "0.2.166" + "typescript": "catalog:" }, "repository": { "type": "git", diff --git a/packages/core/package.json b/packages/core/package.json index 9b2c68a6f6c..8b327350796 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -22,8 +22,7 @@ "@types/bun": "catalog:", "@types/cross-spawn": "catalog:", "@types/npm-package-arg": "6.1.4", - "@types/npmcli__arborist": "6.3.3", - "@types/semver": "catalog:" + "@types/npmcli__arborist": "6.3.3" }, "dependencies": { "@kilocode/kilo-gateway": "workspace:*", diff --git a/packages/kilo-console/package.json b/packages/kilo-console/package.json index 4a9cd9366c7..999c2cfa2f7 100644 --- a/packages/kilo-console/package.json +++ b/packages/kilo-console/package.json @@ -14,7 +14,6 @@ "@kilocode/kilo-web-ui": "workspace:*", "@kilocode/sdk": "workspace:*", "@lottiefiles/dotlottie-web": "0.74.0", - "@opencode-ai/ui": "workspace:*", "@solidjs/router": "catalog:", "ghostty-web": "0.4.0", "solid-js": "catalog:", diff --git a/packages/kilo-gateway/package.json b/packages/kilo-gateway/package.json index 5e37cf4e1f8..bcb24a6267a 100644 --- a/packages/kilo-gateway/package.json +++ b/packages/kilo-gateway/package.json @@ -33,7 +33,6 @@ }, "dependencies": { "@kilocode/plugin": "workspace:*", - "@kilocode/sdk": "workspace:*", "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/anthropic": "3.0.71", "@ai-sdk/openai": "3.0.53", diff --git a/packages/kilo-ui/package.json b/packages/kilo-ui/package.json index b68c0fcf20b..226f55f3fdb 100644 --- a/packages/kilo-ui/package.json +++ b/packages/kilo-ui/package.json @@ -96,9 +96,6 @@ }, "devDependencies": { "@playwright/test": "1.57.0", - "@storybook/addon-a11y": "10.2.10", - "@storybook/addon-docs": "10.2.10", - "@storybook/addon-themes": "10.2.10", "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", "storybook": "10.2.10", diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index db81d6edab2..780afdebbb3 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -1051,8 +1051,6 @@ "devDependencies": { "@axe-core/playwright": "4.11.3", "@playwright/test": "1.57.0", - "@storybook/addon-a11y": "10.2.10", - "@storybook/addon-docs": "10.2.10", "@types/diff": "^6.0.0", "@types/mocha": "^10.0.10", "@types/node": "22.x", @@ -1093,7 +1091,6 @@ "@xterm/addon-web-links": "0.12.0", "@xterm/xterm": "6.0.0", "diff": "8.0.4", - "dotenv": "^16.4.7", "fastest-levenshtein": "^1.0.16", "friendly-words": "1.3.1", "ignore": "^7.0.3", diff --git a/packages/opencode/package.json b/packages/opencode/package.json index 86e440fb400..cacdb391d79 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -36,7 +36,6 @@ } }, "devDependencies": { - "@babel/core": "7.28.4", "@effect/language-service": "0.84.2", "@octokit/webhooks-types": "7.6.1", "@opencode-ai/script": "workspace:*", @@ -49,9 +48,7 @@ "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1", - "@standard-schema/spec": "1.0.0", "@tsconfig/bun": "catalog:", - "@types/babel__core": "7.20.5", "@types/bun": "catalog:", "@types/cross-spawn": "catalog:", "@types/mime-types": "3.0.1", @@ -68,7 +65,6 @@ "typescript": "catalog:", "vscode-languageserver-types": "3.17.5", "why-is-node-running": "3.2.2", - "zod-to-json-schema": "3.24.5", "@types/npmcli__arborist": "6.3.3" }, "dependencies": { @@ -101,9 +97,6 @@ "@effect/platform-node": "catalog:", "@gitlab/gitlab-ai-provider": "3.6.0", "@gitlab/opencode-gitlab-auth": "1.3.3", - "@hono/node-server": "1.19.13", - "@hono/node-ws": "1.3.0", - "@hono/standard-validator": "0.1.5", "@hono/zod-validator": "catalog:", "@kilocode/kilo-gateway": "workspace:*", "@kilocode/kilo-indexing": "workspace:*", @@ -115,36 +108,29 @@ "@modelcontextprotocol/sdk": "1.29.0", "@morphllm/morphsdk": "0.2.166", "@npmcli/arborist": "9.4.0", - "@npmcli/config": "10.8.1", "@octokit/graphql": "9.0.2", "@octokit/rest": "catalog:", - "@openauthjs/openauth": "catalog:", "@opencode-ai/script": "workspace:*", "@opencode-ai/ui": "workspace:*", "@openrouter/ai-sdk-provider": "2.9.0", "@opentelemetry/api": "1.9.0", - "@opentelemetry/context-async-hooks": "2.6.1", - "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", "@opentui/core": "catalog:", "@opentui/keymap": "catalog:", "@opentui/solid": "catalog:", "@parcel/watcher": "2.5.1", - "@pierre/diffs": "catalog:", "@secretlint/core": "10.2.2", "@secretlint/secretlint-rule-preset-recommend": "10.2.2", "@silvia-odwyer/photon-node": "0.3.4", "@solid-primitives/event-bus": "1.1.2", "@solid-primitives/scheduled": "1.5.2", - "@standard-schema/spec": "1.0.0", "@zip.js/zip.js": "2.7.62", "ai": "catalog:", "ai-gateway-provider": "3.1.2", "bonjour-service": "1.3.0", "bun-pty": "0.4.8", "chardet": "2.1.1", - "chokidar": "4.0.3", "cli-sound": "1.1.3", "clipboardy": "4.0.0", "cross-spawn": "catalog:", @@ -154,28 +140,21 @@ "effect": "catalog:", "fuzzysort": "3.1.0", "gitlab-ai-provider": "6.6.0", - "glob": "13.0.5", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", - "hono": "catalog:", - "hono-openapi": "catalog:", - "htmlparser2": "8.0.2", "iconv-lite": "0.7.2", "ignore": "7.0.5", "immer": "11.1.4", "jsonc-parser": "3.3.1", "mammoth": "1.12.0", "mime-types": "3.0.2", - "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "open": "10.1.2", "opencode-gitlab-auth": "2.0.1", "opencode-poe-auth": "0.0.1", "opentui-spinner": "catalog:", - "partial-json": "0.1.7", "remeda": "catalog:", "ripgrep": "0.3.1", - "rotating-file-stream": "3.2.9", "semver": "^7.6.3", "simple-git": "3.36.0", "solid-js": "catalog:", @@ -189,11 +168,9 @@ "vscode-jsonrpc": "8.2.1", "web-tree-sitter": "0.25.10", "which": "6.0.1", - "xdg-basedir": "5.1.0", "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", "yargs": "18.0.0", - "zod": "catalog:", - "zod-to-json-schema": "3.24.5" + "zod": "catalog:" }, "overrides": { "drizzle-orm": "catalog:" diff --git a/packages/ui/package.json b/packages/ui/package.json index 34d75786576..d9cb5d8b391 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -35,7 +35,6 @@ "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", "@types/katex": "0.16.7", - "@types/luxon": "catalog:", "@typescript/native-preview": "catalog:", "tailwindcss": "catalog:", "typescript": "catalog:", @@ -47,8 +46,6 @@ "@kobalte/core": "catalog:", "@kilocode/sdk": "workspace:*", "@pierre/diffs": "catalog:", - "@shikijs/transformers": "3.9.2", - "@solid-primitives/bounds": "0.1.3", "@solid-primitives/event-listener": "2.4.5", "@solid-primitives/media": "2.3.3", "@solid-primitives/resize-observer": "2.1.3", @@ -58,10 +55,7 @@ "dompurify": "3.4.2", "fuzzysort": "catalog:", "katex": "0.16.27", - "luxon": "catalog:", "marked": "catalog:", - "marked-katex-extension": "5.1.6", - "marked-shiki": "catalog:", "mermaid": "11.15.0", "morphdom": "2.7.8", "motion": "12.34.5", From 19e5d17821c11d37dc4db192cfa46d5eac55a288 Mon Sep 17 00:00:00 2001 From: Z User Date: Mon, 8 Jun 2026 09:15:32 +0000 Subject: [PATCH 15/78] fix: remove orphaned storybook addon references from .storybook/main.ts packages/kilo-ui and packages/kilo-vscode still referenced @storybook/addon-docs, @storybook/addon-a11y, @storybook/addon-themes in their addons array after the deps were removed. --- packages/kilo-ui/.storybook/main.ts | 2 +- packages/kilo-vscode/.storybook/main.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kilo-ui/.storybook/main.ts b/packages/kilo-ui/.storybook/main.ts index d47e3caac84..565ac1f1c0e 100644 --- a/packages/kilo-ui/.storybook/main.ts +++ b/packages/kilo-ui/.storybook/main.ts @@ -5,7 +5,7 @@ import solidPlugin from "vite-plugin-solid" const config: StorybookConfig = { framework: "storybook-solidjs-vite", stories: ["../src/**/*.stories.@(ts|tsx)"], - addons: ["@storybook/addon-docs", "@storybook/addon-themes", "@storybook/addon-a11y"], + addons: [], refs: {}, viteFinal: async (config) => { return mergeConfig(config, { diff --git a/packages/kilo-vscode/.storybook/main.ts b/packages/kilo-vscode/.storybook/main.ts index 300333356fb..2d368c49bec 100644 --- a/packages/kilo-vscode/.storybook/main.ts +++ b/packages/kilo-vscode/.storybook/main.ts @@ -5,7 +5,7 @@ import solidPlugin from "vite-plugin-solid" const config: StorybookConfig = { framework: "storybook-solidjs-vite", stories: ["../webview-ui/src/stories/**/*.stories.@(ts|tsx)"], - addons: ["@storybook/addon-docs", "@storybook/addon-a11y"], + addons: [], staticDirs: [{ from: "../assets/icons", to: "/icons" }], refs: {}, viteFinal: async (config) => { From 5135ab95cf4953ff42b535e3d2b935c45d508598 Mon Sep 17 00:00:00 2001 From: Z User Date: Tue, 9 Jun 2026 07:34:00 +0000 Subject: [PATCH 16/78] fix: add storybook story files to knip entry points Story files in webview-ui/src/stories/ were flagged as unused by knip after storybook addon references were removed from .storybook/main.ts. Adding them as explicit knip entry points resolves the CI failure. --- packages/kilo-vscode/knip.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/knip.json b/packages/kilo-vscode/knip.json index 78b5a311058..67c0c14e45c 100644 --- a/packages/kilo-vscode/knip.json +++ b/packages/kilo-vscode/knip.json @@ -12,7 +12,9 @@ "src/**/__tests__/**/*.{ts,spec.ts}", "src/**/*.test.ts", "tests/**/*.{ts,mts}", - "script/*.ts" + "script/*.ts", + "webview-ui/src/stories/**/*.stories.@(ts|tsx)", + "webview-ui/src/stories/*.tsx" ], "project": ["src/**/*.ts", "webview-ui/**/*.{ts,tsx}"], "ignoreExportsUsedInFile": true, From b63d43cf8107615b6d4032d9516ea4156046c2a6 Mon Sep 17 00:00:00 2001 From: Z User Date: Mon, 15 Jun 2026 13:51:06 +0000 Subject: [PATCH 17/78] chore: update bun.lock after rebase --- bun.lock | 236 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 126 insertions(+), 110 deletions(-) diff --git a/bun.lock b/bun.lock index a2d87c4c54a..bac65e0f3de 100644 --- a/bun.lock +++ b/bun.lock @@ -28,27 +28,55 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "7.3.40", + "version": "7.3.46", "bin": { "opencode": "./bin/opencode", }, "dependencies": { + "@ai-sdk/alibaba": "1.0.17", + "@ai-sdk/amazon-bedrock": "4.0.96", + "@ai-sdk/anthropic": "3.0.71", + "@ai-sdk/azure": "3.0.49", + "@ai-sdk/cerebras": "2.0.54", + "@ai-sdk/cohere": "3.0.27", + "@ai-sdk/deepinfra": "2.0.41", + "@ai-sdk/gateway": "3.0.104", + "@ai-sdk/google": "3.0.63", + "@ai-sdk/google-vertex": "4.0.112", + "@ai-sdk/groq": "3.0.31", + "@ai-sdk/mistral": "3.0.27", + "@ai-sdk/openai": "3.0.53", + "@ai-sdk/openai-compatible": "2.0.48", + "@ai-sdk/perplexity": "3.0.26", + "@ai-sdk/provider": "3.0.8", + "@ai-sdk/provider-utils": "4.0.23", + "@ai-sdk/togetherai": "2.0.41", + "@ai-sdk/vercel": "2.0.39", + "@ai-sdk/xai": "3.0.92", + "@aws-sdk/credential-providers": "3.993.0", "@effect/opentelemetry": "catalog:", "@effect/platform-node": "catalog:", + "@kilocode/kilo-gateway": "workspace:*", "@npmcli/arborist": "9.4.0", "@npmcli/config": "10.8.1", + "@openrouter/ai-sdk-provider": "2.9.0", "@opentelemetry/api": "1.9.0", "@opentelemetry/context-async-hooks": "2.6.1", "@opentelemetry/exporter-trace-otlp-http": "0.214.0", "@opentelemetry/sdk-trace-base": "2.6.1", + "ai-gateway-provider": "3.1.2", "cross-spawn": "catalog:", "effect": "catalog:", + "gitlab-ai-provider": "6.6.0", "glob": "13.0.5", + "google-auth-library": "10.5.0", + "immer": "11.1.4", "mime-types": "3.0.2", "minimatch": "10.2.5", "npm-package-arg": "13.0.2", "rotating-file-stream": "3.2.9", "semver": "^7.6.3", + "venice-ai-sdk-provider": "2.0.1", "xdg-basedir": "5.1.0", "zod": "catalog:", }, @@ -62,7 +90,7 @@ }, "packages/http-recorder": { "name": "@opencode-ai/http-recorder", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "@effect/platform-node": "catalog:", "effect": "catalog:", @@ -75,7 +103,7 @@ }, "packages/kilo-console": { "name": "@kilocode/kilo-console", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "@kilocode/kilo-web-ui": "workspace:*", "@kilocode/sdk": "workspace:*", @@ -96,7 +124,7 @@ }, "packages/kilo-docs": { "name": "@kilocode/kilo-docs", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "@docsearch/css": "^4", "@docsearch/js": "^4", @@ -126,16 +154,16 @@ }, "packages/kilo-gateway": { "name": "@kilocode/kilo-gateway", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/anthropic": "3.0.71", "@ai-sdk/mistral": "3.0.27", "@ai-sdk/openai": "3.0.53", - "@ai-sdk/openai-compatible": "2.0.41", + "@ai-sdk/openai-compatible": "2.0.48", "@clack/prompts": "1.0.0-alpha.1", "@kilocode/plugin": "workspace:*", - "@openrouter/ai-sdk-provider": "2.8.1", + "@openrouter/ai-sdk-provider": "2.9.0", "ai": "catalog:", "open": "10.1.2", "zod": "catalog:", @@ -162,7 +190,7 @@ }, "packages/kilo-i18n": { "name": "@kilocode/kilo-i18n", - "version": "7.3.40", + "version": "7.3.46", "devDependencies": { "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", @@ -172,7 +200,7 @@ }, "packages/kilo-indexing": { "name": "@kilocode/kilo-indexing", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "@aws-sdk/client-bedrock-runtime": "3.1005.0", "@aws-sdk/credential-provider-ini": "3.972.31", @@ -204,11 +232,11 @@ }, "packages/kilo-jetbrains": { "name": "@kilocode/kilo-jetbrains", - "version": "7.3.40", + "version": "7.3.46", }, "packages/kilo-telemetry": { "name": "@kilocode/kilo-telemetry", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "@kilocode/kilo-gateway": "workspace:*", "posthog-node": "4.4.0", @@ -222,7 +250,7 @@ }, "packages/kilo-ui": { "name": "@kilocode/kilo-ui", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "0.13.11", @@ -256,7 +284,7 @@ }, "packages/kilo-vscode": { "name": "kilo-code", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "@anthropic-ai/sdk": "^0.39.0", "@kilocode/kilo-gateway": "workspace:*", @@ -319,7 +347,7 @@ }, "packages/kilo-web-ui": { "name": "@kilocode/kilo-web-ui", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "@kilocode/kilo-ui": "workspace:*", "@kobalte/core": "catalog:", @@ -334,7 +362,7 @@ }, "packages/llm": { "name": "@opencode-ai/llm", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", @@ -352,7 +380,7 @@ }, "packages/opencode": { "name": "@kilocode/cli", - "version": "7.3.40", + "version": "7.3.46", "bin": { "kilo": "./bin/kilo", "kilocode": "./bin/kilo", @@ -365,7 +393,7 @@ "@ai-sdk/amazon-bedrock": "4.0.96", "@ai-sdk/anthropic": "3.0.71", "@ai-sdk/azure": "3.0.49", - "@ai-sdk/cerebras": "2.0.41", + "@ai-sdk/cerebras": "2.0.54", "@ai-sdk/cohere": "3.0.27", "@ai-sdk/deepinfra": "2.0.41", "@ai-sdk/gateway": "3.0.104", @@ -374,13 +402,13 @@ "@ai-sdk/groq": "3.0.31", "@ai-sdk/mistral": "3.0.27", "@ai-sdk/openai": "3.0.53", - "@ai-sdk/openai-compatible": "2.0.41", + "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/perplexity": "3.0.26", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@ai-sdk/togetherai": "2.0.41", "@ai-sdk/vercel": "2.0.39", - "@ai-sdk/xai": "3.0.82", + "@ai-sdk/xai": "3.0.92", "@aws-sdk/credential-providers": "3.1025.0", "@clack/prompts": "1.0.0-alpha.1", "@effect/opentelemetry": "catalog:", @@ -401,7 +429,8 @@ "@octokit/graphql": "9.0.2", "@octokit/rest": "catalog:", "@opencode-ai/script": "workspace:*", - "@openrouter/ai-sdk-provider": "2.8.1", + "@opencode-ai/ui": "workspace:*", + "@openrouter/ai-sdk-provider": "2.9.0", "@opentelemetry/api": "1.9.0", "@opentelemetry/sdk-trace-base": "2.6.1", "@opentelemetry/sdk-trace-node": "2.6.1", @@ -411,6 +440,7 @@ "@parcel/watcher": "2.5.1", "@secretlint/core": "10.2.2", "@secretlint/secretlint-rule-preset-recommend": "10.2.2", + "@silvia-odwyer/photon-node": "0.3.4", "@solid-primitives/event-bus": "1.1.2", "@solid-primitives/scheduled": "1.5.2", "@zip.js/zip.js": "2.7.62", @@ -495,7 +525,7 @@ }, "packages/plugin": { "name": "@kilocode/plugin", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "@kilocode/sdk": "workspace:*", "effect": "catalog:", @@ -511,9 +541,9 @@ "typescript": "catalog:", }, "peerDependencies": { - "@opentui/core": ">=0.2.6", - "@opentui/keymap": ">=0.2.6", - "@opentui/solid": ">=0.2.6", + "@opentui/core": ">=0.2.11", + "@opentui/keymap": ">=0.2.11", + "@opentui/solid": ">=0.2.11", }, "optionalPeers": [ "@opentui/core", @@ -523,7 +553,7 @@ }, "packages/plugin-atomic-chat": { "name": "@kilocode/plugin-atomic-chat", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "@kilocode/plugin": "workspace:*", }, @@ -537,7 +567,7 @@ }, "packages/script": { "name": "@opencode-ai/script", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "semver": "^7.6.3", }, @@ -548,7 +578,7 @@ }, "packages/sdk/js": { "name": "@kilocode/sdk", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "cross-spawn": "catalog:", }, @@ -563,7 +593,7 @@ }, "packages/storybook": { "name": "@opencode-ai/storybook", - "version": "7.3.40", + "version": "7.3.46", "devDependencies": { "@opencode-ai/ui": "workspace:*", "@solidjs/meta": "catalog:", @@ -586,7 +616,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "7.3.40", + "version": "7.3.46", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "catalog:", @@ -638,13 +668,15 @@ "tree-sitter-bash", ], "patchedDependencies": { + "mammoth@1.12.0": "patches/mammoth@1.12.0.patch", "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", "@npmcli/agent@4.0.0": "patches/@npmcli%2Fagent@4.0.0.patch", - "mammoth@1.12.0": "patches/mammoth@1.12.0.patch", + "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", }, "overrides": { "@effect/platform-node-shared": "4.0.0-beta.46", "@opentui/core": "catalog:", + "@opentui/keymap": "catalog:", "@opentui/solid": "catalog:", "@types/bun": "catalog:", "@types/node": "catalog:", @@ -663,17 +695,17 @@ }, "catalog": { "@cloudflare/workers-types": "4.20251008.0", - "@effect/opentelemetry": "4.0.0-beta.57", - "@effect/platform-node": "4.0.0-beta.57", + "@effect/opentelemetry": "4.0.0-beta.65", + "@effect/platform-node": "4.0.0-beta.65", "@hono/zod-validator": "0.4.2", "@kobalte/core": "0.13.11", "@lydell/node-pty": "1.2.0-beta.10", "@npmcli/arborist": "9.4.0", "@octokit/rest": "22.0.0", "@openauthjs/openauth": "0.0.0-20250322224806", - "@opentui/core": "0.2.6", - "@opentui/keymap": "0.2.6", - "@opentui/solid": "0.2.6", + "@opentui/core": "0.2.11", + "@opentui/keymap": "0.2.11", + "@opentui/solid": "0.2.11", "@pierre/diffs": "1.1.22", "@playwright/test": "1.59.1", "@solid-primitives/storage": "4.3.3", @@ -695,7 +727,7 @@ "dompurify": "3.4.2", "drizzle-kit": "1.0.0-beta.19-d95b7a4", "drizzle-orm": "1.0.0-beta.19-d95b7a4", - "effect": "4.0.0-beta.59", + "effect": "4.0.0-beta.65", "fuzzysort": "3.1.0", "hono": "4.12.12", "hono-openapi": "1.1.2", @@ -740,7 +772,7 @@ "@ai-sdk/azure": ["@ai-sdk/azure@3.0.49", "", { "dependencies": { "@ai-sdk/openai": "3.0.48", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-wskgAL+OmrHG7by/iWIxEBQCEdc1mDudha/UZav46i0auzdFfsDB/k2rXZaC4/3nWSgMZkxr0W3ncyouEGX/eg=="], - "@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.41", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kDMEpjaRdRXIUi1EH8WHwLRahyDTYv9SAJnP6VCCeq8X+tVqZbMLCqqxSG5dRknrI65ucjvzQt+FiDKTAa7AHg=="], + "@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.54", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-m+oLYlZUA0j5aeE8BrjDd2DSCx6HujqPB2C0YjQF7iGQ4sTCCmhHfH2o0uY7vlSPIJpA2ak6YyF7R14a5dq8fw=="], "@ai-sdk/cohere": ["@ai-sdk/cohere@3.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OqcCq2PiFY1dbK/0Ck45KuvE8jfdxRuuAE9Y5w46dAk6U+9vPOeg1CDcmR+ncqmrYrhRl3nmyDttyDahyjCzAw=="], @@ -766,7 +798,7 @@ "@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], - "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z9MC6M4Oh/yUY/F/eszOtO8wc2nMz99XmZQKd2gWTtyIfe716xTfrKe3aYZKg20NZDtyjqPPKPSR+wqz7q1T7Q=="], "@ai-sdk/perplexity": ["@ai-sdk/perplexity@3.0.26", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-dXzrVsLR5f6tr+U04jq4AXoRroGFBTvODnLgss0SWbzNjGGQg3XqtQ9j7rCLo6o8qbYGuAHvqUrIpUCuiscuFg=="], @@ -778,7 +810,7 @@ "@ai-sdk/vercel": ["@ai-sdk/vercel@2.0.39", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-8eu3ljJpkCTP4ppcyYB+NcBrkcBoSOFthCSgk5VnjaxnDaOJFaxnPwfddM7wx3RwMk2CiK1O61Px/LlqNc7QkQ=="], - "@ai-sdk/xai": ["@ai-sdk/xai@3.0.82", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.41", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-A0VFMufnVf4wODcT3SPQUUzvYXiIO1VhFuXj9r6z/vP4rlo+QRDPw3WSTchcz93ROQWSfBE3I6Szqz342OHi5w=="], + "@ai-sdk/xai": ["@ai-sdk/xai@3.0.92", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.48", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-yqRMdEVfSkZCXs5mqeKfYOuQElo93igwz/4zWbYkhZWAW7tDh+5uqRef6MtQVYF/bTC1EsdbeO9DtsOtAZMOLA=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -1018,9 +1050,9 @@ "@effect/language-service": ["@effect/language-service@0.84.2", "", { "bin": { "effect-language-service": "cli.js" } }, "sha512-l04qNxpiA8rY5yXWckRPJ7Mk5MNerXuNymSFf+IdflfI5i8jgL1bpBNLuP6ijg7wgjdHc/KmTnCj2kT0SCntuA=="], - "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.57", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.57" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-gdjZPEP0QQg4qmI1vd+443kheeQZKytrjJIzCJncy6ZEpyk/SfrqeStLqLXdTRcms3IB0ls0vOV7KNq7YmBRVA=="], + "@effect/opentelemetry": ["@effect/opentelemetry@4.0.0-beta.65", "", { "peerDependencies": { "@opentelemetry/api": "^1.9", "@opentelemetry/api-logs": ">=0.203.0 <0.300.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/sdk-logs": ">=0.203.0 <0.300.0", "@opentelemetry/sdk-metrics": "^2.0.0", "@opentelemetry/sdk-trace-base": "^2.0.0", "@opentelemetry/sdk-trace-node": "^2.0.0", "@opentelemetry/sdk-trace-web": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.33.0", "effect": "^4.0.0-beta.65" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/api-logs", "@opentelemetry/resources", "@opentelemetry/sdk-logs", "@opentelemetry/sdk-metrics", "@opentelemetry/sdk-trace-base", "@opentelemetry/sdk-trace-node", "@opentelemetry/sdk-trace-web"] }, "sha512-0CD2fSsXrDM7FP2WFkbGJO1DwMqWR3UKHh6oBDXPHAPA+RsJSKoh3pLQsbQfldLuKnhOy87Bv0v9r9IdrIHCQw=="], - "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.57", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.57", "mime": "^4.1.0", "undici": "^8.0.2" }, "peerDependencies": { "effect": "^4.0.0-beta.57", "ioredis": "^5.7.0" } }, "sha512-la0xxPSAYOsY0d+uVxEBxok3jYB31iPQmIaZZRUj2SNWqcGGHJc6KorKtI8guqSLuv9FGZ255kBWXRbG6hMeeg=="], + "@effect/platform-node": ["@effect/platform-node@4.0.0-beta.65", "", { "dependencies": { "@effect/platform-node-shared": "^4.0.0-beta.65", "mime": "^4.1.0", "undici": "^8.0.2" }, "peerDependencies": { "effect": "^4.0.0-beta.65", "ioredis": "^5.7.0" } }, "sha512-QQy3KRcMwP0TngQdfQGl2u1zp03B7k7DuF5SNS8aZhD0dDBpKZpCwFad1ODY5qdY3ycPgMwBwKRRK7y/aw0C9w=="], "@effect/platform-node-shared": ["@effect/platform-node-shared@4.0.0-beta.46", "", { "dependencies": { "@types/ws": "^8.18.1", "ws": "^8.19.0" }, "peerDependencies": { "effect": "^4.0.0-beta.46" } }, "sha512-Yzci82XbZ1W3tuiownsJawrJZTGeTrTZKLD0uxdBWCBzlVyqDwoSwRwO5qh33DurJj9B7iS8MDf14fpGRBPNGQ=="], @@ -1442,7 +1474,7 @@ "@opencode-ai/ui": ["@opencode-ai/ui@workspace:packages/ui"], - "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.8.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Y6j3yivgoEUf/kutD/k5GX/mzZfioRFoSx0gbQ+mIOzMaH/vJv1rCkztiuvlLw5xRYQil7oxHUZvmSfXqOx1NQ=="], + "@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.9.0", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Seva+NCa0WUQnJIUE5GzHsUv1WTIeyqwz0ELl2VtS6NP+eF+77yCXGFVOMbvoCM7QMjlnhv7931e89R+8pJdcQ=="], "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], @@ -1472,23 +1504,23 @@ "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="], - "@opentui/core": ["@opentui/core@0.2.6", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.6", "@opentui/core-darwin-x64": "0.2.6", "@opentui/core-linux-arm64": "0.2.6", "@opentui/core-linux-x64": "0.2.6", "@opentui/core-win32-arm64": "0.2.6", "@opentui/core-win32-x64": "0.2.6" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-dBpMaWVM7wtW2/2TlGPrkPjg6gOL3MVU/5XXk+U1LDJB8L4q4NeYWVdzfAVNcEvgmuuCy/cVqdY2D4ei+e7MMg=="], + "@opentui/core": ["@opentui/core@0.2.11", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.11", "@opentui/core-darwin-x64": "0.2.11", "@opentui/core-linux-arm64": "0.2.11", "@opentui/core-linux-x64": "0.2.11", "@opentui/core-win32-arm64": "0.2.11", "@opentui/core-win32-x64": "0.2.11" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-X0zLmcDEvMrPzWYp769I7VEVb+og38vaete9tGZXu9HnJgu/paPUUplUT+6denBQccr2qx1rBYV6EtgbBpLEyw=="], - "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-hR5nsxNj+059utzenTCF0kealUlibON6fLuebFUCGM/5kJnqa+shIh0XbUDFm0+F47vqVUgZufBdUuieQZIbvQ=="], + "@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-h2MXtE2Cu3XlKVoQMXthnbhleO68zGXkoh/r1Q5pCoZh6RuXqns5/94D/aZThXBWwzPuEoyarMlxxR9OqrpvHw=="], - "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-pJ/bH4WC/mbBaakM1YdH6TVo67jhy0KPd61bCz97w0I/PJGr8fmNKvhmMt/AwyFgOQi3FYZiEKLMpGdvUcSsrQ=="], + "@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-Y0jbPClnOBTPSIy+2THG86MTqIG/jGFlOOKuw4JfCDqEjPBM3pLWIHnJb3WxHRi2LlvfyBxvrUTXWlW6JpI0QQ=="], - "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Pnd3kOxig8ii+/IqYheOPEgferylsQA0L6tKBnHQ9jRlCJOcu0Rv65Jepueh212vevdV9DzPURJnhejG06J6g=="], + "@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-blQyyuTaW4q/OQ3whs7Kt7GCXhBUR5EQHHDdjOqQAr0HYpohUa6sbHMbiBcX2Ehc9ZWwtiaOoWiyZ5YXy2SAvg=="], - "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.6", "", { "os": "linux", "cpu": "x64" }, "sha512-458Mx9tBzEPzfft8cSt5ZaIpEepoxBXBOL6AUVmDTKWaZ3uouraPcEKraGAyvOTDQp2XDI3R8c/2GdaR77FaUQ=="], + "@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.11", "", { "os": "linux", "cpu": "x64" }, "sha512-0nEB5+MgzQRYiVcQd1vHXPWNPWGh4JEmQTJKyG3OHnTzPaJ1FVSQ/V71ECyRSl3ymY3F+U0eW9cFgw1hCieK2w=="], - "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-BDUrdrT1RCcVnQoHJmUut4y811jDBAEtc6GJFB4Gs265Be8SrTjVCus6p2fSQ7j9sZQ1OcjO+5+4NkheSZICDQ=="], + "@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-+KKH77fzm0qF8py9G2pU32DzB1bAgDMfBajrs7gKL5NtSEnknrwfh7hIs/tq41aF6j9zvIzgtykByh26tcjFog=="], - "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.6", "", { "os": "win32", "cpu": "x64" }, "sha512-SUYAzRJ9TSoD2Qt8kn6FJz6dbTrFEPVig5mScB4zFGgGQO/Bbod2/Q31vLS/IQrX+FDb67WaErD+kuMCnMPPLA=="], + "@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.11", "", { "os": "win32", "cpu": "x64" }, "sha512-dMmb9DX0W0HWadLdgciMbonqIc1xdcKiVmaQSYxw5eGCzFRPZIOrKHByesP+2ipkMuLx85W/MJUFal/lW8XSNg=="], - "@opentui/keymap": ["@opentui/keymap@0.2.6", "", { "dependencies": { "@opentui/core": "0.2.6" }, "peerDependencies": { "@opentui/react": "0.2.6", "@opentui/solid": "0.2.6", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-+6OYuedrFCKVo4ryGFNwws++2VOmPcXU3PwpY0mP47gYQY2nvQ+etWIs2Y7r5eMIqUfxVCldkKsrzcEcA4tb/A=="], + "@opentui/keymap": ["@opentui/keymap@0.2.11", "", { "dependencies": { "@opentui/core": "0.2.11" }, "peerDependencies": { "@opentui/react": "0.2.11", "@opentui/solid": "0.2.11", "react": ">=19.2.0", "solid-js": "1.9.12" }, "optionalPeers": ["@opentui/react", "@opentui/solid", "react", "solid-js"] }, "sha512-pCrJrY3mTuXdDaaRneId1JsJCtGE+7prTtWihzOLZzVJTJYyYtT38gMI7MpyAoloVDfEL5cTe8C+v7wv+IYREw=="], - "@opentui/solid": ["@opentui/solid@0.2.6", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.6", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-2y225WlOGi/fCaajkxBmLyVW8Cr+OmhowHdvrYcz5w2kBD15sKbJLIYu1G9DxceirT1uIyasGy2TGzRRcVkTDg=="], + "@opentui/solid": ["@opentui/solid@0.2.11", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.11", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-M3WHxBFORHVE0yqMJYpi9PfjXWlnRTw/LYuBhZaJv0HTo+zTs60P/ukGcwnHDWnMpTGf3BH9x0Yi2dIqjHRY6Q=="], "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.19.1", "", { "os": "android", "cpu": "arm" }, "sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg=="], @@ -1748,6 +1780,8 @@ "@sigstore/verify": ["@sigstore/verify@3.1.0", "", { "dependencies": { "@sigstore/bundle": "^4.0.0", "@sigstore/core": "^3.1.0", "@sigstore/protobuf-specs": "^0.5.0" } }, "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag=="], + "@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="], + "@simple-git/args-pathspec": ["@simple-git/args-pathspec@1.0.3", "", {}, "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA=="], "@simple-git/argv-parser": ["@simple-git/argv-parser@1.1.1", "", { "dependencies": { "@simple-git/args-pathspec": "^1.0.3" } }, "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw=="], @@ -2726,7 +2760,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "effect": ["effect@4.0.0-beta.59", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-xyUDLeHSe8d6lWGOvR6Fgn2HL6gYeTZ/S4Jzk9uc4ZUxMPPsNZlNXrvk0C7/utQFzeX7uAWcVnG2BjbA0SRoAA=="], + "effect": ["effect@4.0.0-beta.65", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-QYKvQPAj3CmtsvWkHQww15wX4KG2gNsszDWEcOO5sZCMknp66u6Si/Opmt3wwWCwsyvRmDAdIg+JIz5qzbbFIw=="], "electron-to-chromium": ["electron-to-chromium@1.5.340", "", {}, "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA=="], @@ -4274,13 +4308,15 @@ "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + "@ai-sdk/alibaba/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "@ai-sdk/azure/@ai-sdk/openai": ["@ai-sdk/openai@3.0.48", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ALmj/53EXpcRqMbGpPJPP4UOSWw0q4VGpnDo7YctvsynjkrKDmoneDG/1a7VQnSPYHnJp6tTRMf5ZdxZ5whulg=="], "@ai-sdk/azure/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="], + "@ai-sdk/cerebras/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], - "@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + "@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], "@ai-sdk/cohere/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], @@ -4288,12 +4324,20 @@ "@ai-sdk/deepinfra/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + "@ai-sdk/fireworks/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "@ai-sdk/google-vertex/@ai-sdk/google": ["@ai-sdk/google@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CbR82EgGPNrj/6q0HtclwuCqe0/pDShyv3nWDP/A9DroujzWXnLMlUJVrgPOsg4b40zQCwwVs2XSKCxvt/4QaA=="], + "@ai-sdk/google-vertex/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "@ai-sdk/groq/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], "@ai-sdk/mistral/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + "@ai-sdk/openai-compatible/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], + + "@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], + "@ai-sdk/perplexity/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], "@ai-sdk/togetherai/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="], @@ -4304,9 +4348,11 @@ "@ai-sdk/vercel/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], - "@antfu/install-pkg/package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], + "@ai-sdk/xai/@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], - "@anthropic-ai/sdk/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], + "@ai-sdk/xai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], + + "@antfu/install-pkg/package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], @@ -4364,8 +4410,6 @@ "@kilocode/kilo-indexing/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], - "@manypkg/find-root/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "@manypkg/find-root/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], "@manypkg/find-root/fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], @@ -4408,6 +4452,8 @@ "@octokit/rest/@octokit/plugin-rest-endpoint-methods": ["@octokit/plugin-rest-endpoint-methods@16.1.1", "", { "dependencies": { "@octokit/types": "^15.0.1" }, "peerDependencies": { "@octokit/core": ">=6" } }, "sha512-VztDkhM0ketQYSh5Im3IcKWFZl7VIrrsCaHbDINkdYeiiAsJzjhS2xRFCSJgfN6VOcsoW4laMtsmf3HcNqIimg=="], + "@opencode-ai/core/@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.993.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.993.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-cognito-identity": "^3.972.3", "@aws-sdk/credential-provider-env": "^3.972.9", "@aws-sdk/credential-provider-http": "^3.972.11", "@aws-sdk/credential-provider-ini": "^3.972.9", "@aws-sdk/credential-provider-login": "^3.972.9", "@aws-sdk/credential-provider-node": "^3.972.10", "@aws-sdk/credential-provider-process": "^3.972.9", "@aws-sdk/credential-provider-sso": "^3.972.9", "@aws-sdk/credential-provider-web-identity": "^3.972.9", "@aws-sdk/nested-clients": "3.993.0", "@aws-sdk/types": "^3.973.1", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-1M/nukgPSLqe9krzOKHnE8OylUaKAiokAV3xRLdeExVHcRE7WG5uzCTKWTj1imKvPjDqXq/FWhlbbdWIn7xIwA=="], + "@opencode-ai/storybook/@types/react": ["@types/react@18.0.25", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g=="], "@opencode-ai/storybook/react": ["react@18.2.0", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ=="], @@ -4486,30 +4532,6 @@ "@textlint/linter-formatter/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], - "@types/cacache/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - - "@types/cross-spawn/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - - "@types/mssql/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - - "@types/node-fetch/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - - "@types/npm-registry-fetch/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - - "@types/npmcli__arborist/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - - "@types/npmlog/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - - "@types/pacote/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - - "@types/qrcode/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - - "@types/readable-stream/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - - "@types/ssri/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - - "@types/ws/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], "@vscode/ripgrep/yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="], @@ -4536,6 +4558,8 @@ "ai-gateway-provider/@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@4.0.93", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.69", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@smithy/eventstream-codec": "^4.0.1", "@smithy/util-utf8": "^4.0.0", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-hcXDU8QDwpAzLVTuY932TQVlIij9+iaVTxc5mPGY6yb//JMAAC5hMVhg93IrxlrxWLvMgjezNgoZGwquR+SGnw=="], + "ai-gateway-provider/@ai-sdk/cerebras": ["@ai-sdk/cerebras@2.0.41", "", { "dependencies": { "@ai-sdk/openai-compatible": "2.0.37", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kDMEpjaRdRXIUi1EH8WHwLRahyDTYv9SAJnP6VCCeq8X+tVqZbMLCqqxSG5dRknrI65ucjvzQt+FiDKTAa7AHg=="], + "ai-gateway-provider/@ai-sdk/google": ["@ai-sdk/google@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-uz8tIlkDgQJG9Js2Wh9JHzd4kI9+hYJqf9XXJLx60vyN5mRIqhr49iwR5zGP5Gl8odp2PeR3Gh2k+5bh3Z1HHw=="], "ai-gateway-provider/@ai-sdk/google-vertex": ["@ai-sdk/google-vertex@4.0.95", "", { "dependencies": { "@ai-sdk/anthropic": "3.0.64", "@ai-sdk/google": "3.0.53", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21", "google-auth-library": "^10.5.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-xL44fHlTtDM7RLkMTgyqMfkfthA38JS91bbMaHItObIhte1PAIY936ZV1PLl/Z9A/oBAXjHWbXo5xDoHzB7LEg=="], @@ -4546,8 +4570,6 @@ "ai-gateway-provider/@openrouter/ai-sdk-provider": ["@openrouter/ai-sdk-provider@2.5.1", "", { "peerDependencies": { "ai": "^6.0.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-r1fJL1Cb3gQDa2MpWH/sfx1BsEW0uzlRriJM6eihaKqbtKDmZoBisF32VcVaQYassighX7NGCkF68EsrZA43uQ=="], - "apache-arrow/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "ava/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "ava/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], @@ -4568,8 +4590,6 @@ "babel-plugin-module-resolver/glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="], - "bun-types/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "c12/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], "c12/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], @@ -4732,10 +4752,6 @@ "nypm/citty": ["citty@0.2.2", "", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="], - "opentui-spinner/@opentui/core": ["@opentui/core@0.2.2", "", { "dependencies": { "bun-ffi-structs": "0.2.2", "diff": "9.0.0", "marked": "17.0.1", "string-width": "7.2.0", "strip-ansi": "7.1.2", "yoga-layout": "3.2.1" }, "optionalDependencies": { "@opentui/core-darwin-arm64": "0.2.2", "@opentui/core-darwin-x64": "0.2.2", "@opentui/core-linux-arm64": "0.2.2", "@opentui/core-linux-x64": "0.2.2", "@opentui/core-win32-arm64": "0.2.2", "@opentui/core-win32-x64": "0.2.2" }, "peerDependencies": { "web-tree-sitter": "0.25.10" } }, "sha512-wxg1CD58SVrowu+WgbhZNi3UP/wWxPio2Kj2IeTjomoIE+6EXLxR8eCCxHYVuQUd9E4fknrKkY5HmiSsp6oPow=="], - - "opentui-spinner/@opentui/solid": ["@opentui/solid@0.2.2", "", { "dependencies": { "@babel/core": "7.28.0", "@babel/preset-typescript": "7.27.1", "@opentui/core": "0.2.2", "babel-plugin-module-resolver": "5.0.2", "babel-preset-solid": "1.9.12", "entities": "7.0.1", "s-js": "^0.4.9" }, "peerDependencies": { "solid-js": "1.9.12" } }, "sha512-ZBVfCoVAhcUGQWPAWOTdzuVldMaRkuPpCu4U1VZCqmIw9DtbCuiVr0WnDocDxKhJLbTu8bl3qEWtVCf6lTSi3w=="], - "ora/cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="], "ora/log-symbols": ["log-symbols@6.0.0", "", { "dependencies": { "chalk": "^5.3.0", "is-unicode-supported": "^1.3.0" } }, "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw=="], @@ -4766,8 +4782,6 @@ "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], - "protobufjs/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "proxy-addr/ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], "qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], @@ -4828,8 +4842,6 @@ "tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "tedious/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "tedious/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "tedious/sprintf-js": ["sprintf-js@1.1.3", "", {}, "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="], @@ -4842,6 +4854,8 @@ "validate-npm-package-license/spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + "venice-ai-sdk-provider/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.41", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-kNAGINk71AlOXx10Dq/PXw4t/9XjdK8uxfpVElRwtSFMdeSiLVt58p9TPx4/FJD+hxZuVhvxYj9r42osxWq79g=="], + "vite-plugin-icons-spritesheet/glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], "vite-plugin-icons-spritesheet/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], @@ -4864,6 +4878,12 @@ "yargs-unparser/decamelize": ["decamelize@4.0.0", "", {}, "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ=="], + "@ai-sdk/cerebras/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], + + "@ai-sdk/openai-compatible/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], + + "@ai-sdk/xai/@ai-sdk/provider-utils/eventsource-parser": ["eventsource-parser@3.1.0", "", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], @@ -4882,8 +4902,6 @@ "@morphllm/morphsdk/ai/@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.95", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZmUNNbZl3V42xwQzPaNUi+s8eqR2lnrxf0bvB6YbLXpLjHYv0k2Y78t12cNOfY0bxGeuVVTLyk856uLuQIuXEQ=="], - "@morphllm/morphsdk/openai/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "@morphllm/morphsdk/openai/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="], @@ -4928,6 +4946,10 @@ "@octokit/rest/@octokit/core/before-after-hook": ["before-after-hook@4.0.0", "", {}, "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ=="], + "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/credential-provider-node": "^3.972.10", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7Ne3Yk/bgQPVebAkv7W+RfhiwTRSbfER9BtbhOa2w/+dIr902LrJf6vrZlxiqaJbGj2ALx8M+ZK1YIHVxSwu9A=="], + + "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.993.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.11", "@aws-sdk/middleware-host-header": "^3.972.3", "@aws-sdk/middleware-logger": "^3.972.3", "@aws-sdk/middleware-recursion-detection": "^3.972.3", "@aws-sdk/middleware-user-agent": "^3.972.11", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", "@aws-sdk/util-endpoints": "3.993.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.9", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.23.2", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.16", "@smithy/middleware-retry": "^4.4.33", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.10", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.11.5", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.32", "@smithy/util-defaults-mode-node": "^4.2.35", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-iOq86f2H67924kQUIPOAvlmMaOAvOLoDOIb66I2YqSUpMYB6ufiuJW3RlREgskxv86S5qKzMnfy/X6CqMjK6XQ=="], + "@opencode-ai/storybook/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], "@opencode-ai/storybook/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="], @@ -5050,6 +5072,10 @@ "ai-gateway-provider/@ai-sdk/amazon-bedrock/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.69", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LshR7X3pFugY0o41G2VKTmg1XoGpSl7uoYWfzk6zjVZLhCfeFiwgpOga+eTV4XY1VVpZwKVqRnkDbIL7K2eH5g=="], + "ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@2.0.37", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-+POSFVcgiu47BK64dhsI6OpcDC0/VAE2ZSaXdXGNNhpC/ava++uSRJYks0k2bpfY0wwCTgpAWZsXn/dG2Yppiw=="], + + "ai-gateway-provider/@ai-sdk/cerebras/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], + "ai-gateway-provider/@ai-sdk/google/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.21", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-MtFUYI1/8mgDvRmaBDjbLJPFFrMG777AvSgyIFQtZHIMzm88R/12vYBBpnk7pfiWLFE1DSZzY4WDYzGbKAcmiw=="], "ai-gateway-provider/@ai-sdk/google-vertex/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.21" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-rwLi/Rsuj2pYniQXIrvClHvXDzgM4UQHHnvHTWEF14efnlKclG/1ghpNC+adsRujAbCTr6gRsSbDE2vEqriV7g=="], @@ -5190,8 +5216,6 @@ "jszip/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], - "kilo-code/openai/@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="], - "log-symbols/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], @@ -5214,18 +5238,6 @@ "normalize-package-data/hosted-git-info/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "opentui-spinner/@opentui/core/@opentui/core-darwin-arm64": ["@opentui/core-darwin-arm64@0.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tY5n3ZRQx+b0kyhQJJLsyJMeZ+0w4FV37YZc/Qqv3qvOqE9kZPw/7adR77FYwWDm/7fax94mLMrR8Y5bKUkDmw=="], - - "opentui-spinner/@opentui/core/@opentui/core-darwin-x64": ["@opentui/core-darwin-x64@0.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-W/R7OnqY30FXcTG0tiP2JkQFmgtYbIte5afQ5PC12TliRoee1RqG3iCG6kY1jxW+3Vg6jge88uiSjUEDpeV2gA=="], - - "opentui-spinner/@opentui/core/@opentui/core-linux-arm64": ["@opentui/core-linux-arm64@0.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-1pzTYFEZauYuw6AGycw2TYGtAlZVGjuUtSdxH1fP51kBPS3oVWduUY2j7GKREz3SU5NulvO2Wc6HWsm3feMqwQ=="], - - "opentui-spinner/@opentui/core/@opentui/core-linux-x64": ["@opentui/core-linux-x64@0.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ucVwUtUYeOYGVFPBLbPoxzbrPdhD0PDyKNQ2X4n1AJ9jlQX4gqBZRcXMEF8hiXDjFxsikZwef7De0ciCcWvAMg=="], - - "opentui-spinner/@opentui/core/@opentui/core-win32-arm64": ["@opentui/core-win32-arm64@0.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-MPhYdJNdxmC5Bqsq6sis/+VkjRgkEjm+bQ1Tl++NSKLuiTU32Re0ImcZlgHbe+LZtZoGMZHVSgZlkGd3oYXO2g=="], - - "opentui-spinner/@opentui/core/@opentui/core-win32-x64": ["@opentui/core-win32-x64@0.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-19BroLfn2h0RDYfJS5o96Fc8kYCDhRBcseIXtHIkoKIsKMxx62KiDLo/byVye6rp+yQRRB7Xkd2uWqsbdiWo9w=="], - "ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], "p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], @@ -5298,6 +5310,10 @@ "@octokit/rest/@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], + "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/client-cognito-identity/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], + + "@opencode-ai/core/@aws-sdk/credential-providers/@aws-sdk/nested-clients/@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.993.0", "", { "dependencies": { "@aws-sdk/types": "^3.973.1", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-j6vioBeRZ4eHX4SWGvGPpwGg/xSOcK7f1GL0VM+rdf3ZFTIsUEhCFmD78B+5r2PgztcECSzEfvHQX01k8dPQPw=="], + "@opencode-ai/storybook/storybook/@vitest/expect/@vitest/utils": ["@vitest/utils@3.2.4", "", { "dependencies": { "@vitest/pretty-format": "3.2.4", "loupe": "^3.1.4", "tinyrainbow": "^2.0.0" } }, "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA=="], "@opencode-ai/storybook/storybook/@vitest/expect/chai": ["chai@5.3.3", "", { "dependencies": { "assertion-error": "^2.0.1", "check-error": "^2.1.1", "deep-eql": "^5.0.1", "loupe": "^3.1.0", "pathval": "^2.0.0" } }, "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw=="], From 056e06911b0bf4232021e8a56676ae9b3f888bb0 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Mon, 15 Jun 2026 16:00:12 +0200 Subject: [PATCH 18/78] feat(vscode): render images in diff viewer --- .changeset/render-image-diffs.md | 5 + .../kilo-vscode/src/agent-manager/GitOps.ts | 27 ++++-- .../src/agent-manager/local-diff.ts | 83 ++++++++++++---- .../kilo-vscode/src/agent-manager/types.ts | 3 + packages/kilo-vscode/src/diff/shared/image.ts | 69 ++++++++++++++ packages/kilo-vscode/src/diff/shared/path.ts | 9 ++ .../src/diff/sources/git-status.ts | 57 ++++++++--- .../kilo-vscode/src/diff/sources/session.ts | 48 ++++++++-- .../kilo-vscode/src/diff/sources/staged.ts | 71 +++++++++++--- .../kilo-vscode/src/diff/sources/unstaged.ts | 95 +++++++++++++++---- .../kilo-vscode/src/diff/sources/worktree.ts | 2 + packages/kilo-vscode/src/diff/types.ts | 16 ++++ packages/kilo-vscode/src/review-utils.ts | 18 ++-- .../tests/unit/agent-manager-arch.test.ts | 1 + .../unit/agent-manager-diff-state.test.ts | 24 ++++- .../kilo-vscode/tests/unit/diff-image.test.ts | 78 +++++++++++++++ .../tests/unit/diff-session-source.test.ts | 49 ++++++++++ .../kilo-vscode/tests/unit/local-diff.test.ts | 36 +++++++ .../tests/unit/review-utils.test.ts | 29 ++++++ .../webview-ui/agent-manager/DiffPanel.tsx | 87 ++++++++++------- .../agent-manager/agent-manager.css | 72 ++++++++++++++ .../webview-ui/agent-manager/i18n/en.ts | 6 ++ .../diff-viewer/FullScreenDiffView.tsx | 87 ++++++++++------- .../webview-ui/diff-viewer/ImageDiffView.tsx | 80 ++++++++++++++++ .../diff-viewer/diff-open-policy.ts | 4 +- .../webview-ui/diff-viewer/diff-state.ts | 6 +- .../src/types/messages/agent-manager.ts | 16 ++++ 27 files changed, 919 insertions(+), 159 deletions(-) create mode 100644 .changeset/render-image-diffs.md create mode 100644 packages/kilo-vscode/src/diff/shared/image.ts create mode 100644 packages/kilo-vscode/src/diff/shared/path.ts create mode 100644 packages/kilo-vscode/tests/unit/diff-image.test.ts create mode 100644 packages/kilo-vscode/tests/unit/review-utils.test.ts create mode 100644 packages/kilo-vscode/webview-ui/diff-viewer/ImageDiffView.tsx diff --git a/.changeset/render-image-diffs.md b/.changeset/render-image-diffs.md new file mode 100644 index 00000000000..7445a209c7d --- /dev/null +++ b/.changeset/render-image-diffs.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Render image changes in diff viewers and open images with VS Code's image preview. diff --git a/packages/kilo-vscode/src/agent-manager/GitOps.ts b/packages/kilo-vscode/src/agent-manager/GitOps.ts index c490a576411..92a2984b5a9 100644 --- a/packages/kilo-vscode/src/agent-manager/GitOps.ts +++ b/packages/kilo-vscode/src/agent-manager/GitOps.ts @@ -48,6 +48,12 @@ export interface ExecResult { stderr: string } +export interface ExecBufferResult { + code: number + stdout: Buffer + stderr: string +} + /** * Fixed SSH command injected by {@link nonInteractiveEnv} when the user has * not already configured their own. Exported so callers can check whether a @@ -532,12 +538,21 @@ export class GitOps { return this.exec(args, cwd, options) } - private exec(args: string[], cwd: string, options?: ExecOptions): Promise { + execGitBuffer(args: string[], cwd: string): Promise { + return this.execBuffer(args, cwd) + } + + private async exec(args: string[], cwd: string, options?: ExecOptions): Promise { + const result = await this.execBuffer(args, cwd, options) + return { code: result.code, stdout: result.stdout.toString("utf8"), stderr: result.stderr } + } + + private execBuffer(args: string[], cwd: string, options?: ExecOptions): Promise { if (this.controller.signal.aborted) { - return Promise.resolve({ code: 1, stdout: "", stderr: "GitOps disposed" }) + return Promise.resolve({ code: 1, stdout: Buffer.alloc(0), stderr: "GitOps disposed" }) } const invoke = () => - new Promise((resolve) => { + new Promise((resolve) => { const child = spawn("git", args, { cwd, env: options?.env, @@ -547,7 +562,7 @@ export class GitOps { if (options?.stdin !== undefined) { if (!child.stdin) { - resolve({ code: 1, stdout: "", stderr: "stdin not available for git process" }) + resolve({ code: 1, stdout: Buffer.alloc(0), stderr: "stdin not available for git process" }) return } child.stdin.end(options.stdin) @@ -559,12 +574,12 @@ export class GitOps { child.stderr?.on("data", (chunk: Buffer) => err.push(chunk)) child.on("error", (error) => { - resolve({ code: 1, stdout: "", stderr: error.message }) + resolve({ code: 1, stdout: Buffer.alloc(0), stderr: error.message }) }) child.on("close", (code) => { resolve({ code: code ?? 1, - stdout: Buffer.concat(out).toString("utf8"), + stdout: Buffer.concat(out), stderr: Buffer.concat(err).toString("utf8"), }) }) diff --git a/packages/kilo-vscode/src/agent-manager/local-diff.ts b/packages/kilo-vscode/src/agent-manager/local-diff.ts index 82ac3e2521b..e643e866270 100644 --- a/packages/kilo-vscode/src/agent-manager/local-diff.ts +++ b/packages/kilo-vscode/src/agent-manager/local-diff.ts @@ -1,6 +1,7 @@ import * as fs from "fs/promises" -import * as path from "path" import { binaryFile } from "../diff/shared/binary" +import { imageMime, loadImage, readImageFile } from "../diff/shared/image" +import { resolveInside } from "../diff/shared/path" import type { GitOps } from "./GitOps" import type { WorktreeDiffEntry } from "./types" @@ -139,16 +140,20 @@ async function numstat(git: GitOps, dir: string, base: string, file?: string) { } async function statStamp(dir: string, file: string): Promise { - const stat = await fs.stat(path.join(dir, file)).catch(() => undefined) + const full = resolveInside(dir, file) + if (!full) return `missing:${file}` + const stat = await fs.lstat(full).catch(() => undefined) if (!stat) return `missing:${file}` return `${stat.size}:${stat.mtimeMs}` } async function lineCount(file: string): Promise { - const stat = await fs.stat(file).catch(() => undefined) + const stat = await fs.lstat(file).catch(() => undefined) if (!stat || stat.size === 0) return 0 if (stat.size > MAX_UNTRACKED_BYTES) return 0 - const content = await fs.readFile(file, "utf-8").catch(() => "") + const content = stat.isSymbolicLink() + ? await fs.readlink(file).catch(() => "") + : await fs.readFile(file, "utf-8").catch(() => "") if (!content) return 0 if (content.endsWith("\n")) return content.split("\n").length - 1 return content.split("\n").length @@ -191,7 +196,8 @@ async function list(git: GitOps, dir: string, anc: string, log?: Log): Promise undefined) + const full = resolveInside(dir, file) + if (!full) continue + const exists = await fs.lstat(full).catch(() => undefined) if (!exists) continue const binary = await binaryFile(full) result.push({ @@ -226,6 +233,7 @@ async function list(git: GitOps, dir: string, anc: string, log?: Log): Promise => { const state = states.get(`${dir}\0${base}`) - const meta = state?.metas.get(file) - if (!state || !meta) return diffFile(git, dir, base, file, log) + if (!state) return diffFile(git, dir, base, file, log) + const meta = state.metas.get(file) + if (!meta) return null return materialize(git, dir, state.anc, meta, log) }, } } async function detailMeta(git: GitOps, dir: string, anc: string, file: string): Promise { + const full = resolveInside(dir, file) + if (!full) return undefined const tracked = await git.execGit(["ls-files", "--error-unmatch", "--", file], dir) if (tracked.code !== 0) { - const full = path.join(dir, file) - const exists = await fs.stat(full).catch(() => undefined) + const untracked = await git.execGit(["ls-files", "--others", "--exclude-standard", "--", file], dir) + if (untracked.code !== 0 || !untracked.stdout.split("\n").includes(file)) return undefined + const exists = await fs.lstat(full).catch(() => undefined) if (!exists) return undefined const binary = await binaryFile(full) return { @@ -323,7 +336,10 @@ async function detailMeta(git: GitOps, dir: string, anc: string, file: string): tracked: true, generatedLike: generatedLike(pathPart), binary: stat.binary, - stamp: status === "deleted" ? `deleted:${anc}` : await statStamp(dir, pathPart), + stamp: + status === "deleted" + ? `deleted:${anc}` + : `${imageMime(pathPart) ? `${anc}:` : ""}${await statStamp(dir, pathPart)}`, } } @@ -334,10 +350,25 @@ async function blobSize(git: GitOps, dir: string, anc: string, file: string): Pr } async function fileSize(dir: string, file: string): Promise { - const stat = await fs.stat(path.join(dir, file)).catch(() => undefined) + const full = resolveInside(dir, file) + if (!full) return 0 + const stat = await fs.lstat(full).catch(() => undefined) return stat?.size ?? 0 } +async function readBlob(git: GitOps, dir: string, ref: string, file: string): Promise { + const result = await git.execGitBuffer(["show", `${ref}:${file}`], dir) + return result.code === 0 ? result.stdout : undefined +} + +async function readFile(dir: string, file: string): Promise { + const full = resolveInside(dir, file) + if (!full) return undefined + const stat = await fs.lstat(full).catch(() => undefined) + if (!stat?.isFile()) return undefined + return readImageFile(full) +} + async function readBefore(git: GitOps, dir: string, anc: string, file: string, status: Status): Promise { if (status === "added") return "" const result = await git.execGit(["show", `${anc}:${file}`], dir) @@ -346,9 +377,12 @@ async function readBefore(git: GitOps, dir: string, anc: string, file: string, s async function readAfter(dir: string, file: string, status: Status): Promise { if (status === "deleted") return "" - const full = path.join(dir, file) - const exists = await fs.stat(full).catch(() => undefined) - if (!exists) return "" + const full = resolveInside(dir, file) + if (!full) return "" + const stat = await fs.lstat(full).catch(() => undefined) + if (!stat) return "" + if (stat.isSymbolicLink()) return fs.readlink(full).catch(() => "") + if (!stat.isFile()) return "" return fs.readFile(full, "utf-8").catch(() => "") } @@ -385,12 +419,21 @@ export async function diffFile( } async function materialize(git: GitOps, dir: string, anc: string, meta: Meta, log?: Log): Promise { - if (meta.binary) return summarize(meta) + const mime = imageMime(meta.file) + if (meta.binary && !mime) return summarize(meta) + const beforeBytes = meta.status === "added" ? 0 : await blobSize(git, dir, anc, meta.file) + const afterBytes = meta.status === "deleted" ? 0 : await fileSize(dir, meta.file) + if (mime) { + const image = await loadImage( + meta.file, + meta.status === "added" ? undefined : { bytes: beforeBytes, read: () => readBlob(git, dir, anc, meta.file) }, + meta.status === "deleted" ? undefined : { bytes: afterBytes, read: () => readFile(dir, meta.file) }, + ) + return { ...summarize(meta), summarized: false, image } + } // Cheap size probe before materializing content — protects the extension // host from OOM on huge tracked files. `git cat-file -s` returns the blob // size without streaming its contents, and `fs.stat` is a plain syscall. - const beforeBytes = meta.status === "added" ? 0 : await blobSize(git, dir, anc, meta.file) - const afterBytes = meta.status === "deleted" ? 0 : await fileSize(dir, meta.file) if (beforeBytes > MAX_DETAIL_BYTES || afterBytes > MAX_DETAIL_BYTES) { log?.("diffFile: file too large for detail view, returning summarized entry", { file: meta.file, diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index 360b32c7217..ed766104333 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -8,6 +8,7 @@ */ import type { SnapshotFileDiff } from "@kilocode/sdk/v2/client" +import type { DiffImage } from "../diff/types" import type { Worktree, ManagedSession, Section } from "./WorktreeStateManager" import type { WorktreeStats, LocalStats } from "./GitStatsPoller" import type { ApplyConflict } from "./GitOps" @@ -33,6 +34,8 @@ export type WorktreeDiffEntry = SnapshotFileDiff & { generatedLike?: boolean summarized?: boolean stamp?: string + kind?: "image" + image?: DiffImage } // --------------------------------------------------------------------------- diff --git a/packages/kilo-vscode/src/diff/shared/image.ts b/packages/kilo-vscode/src/diff/shared/image.ts new file mode 100644 index 00000000000..6b3a887f152 --- /dev/null +++ b/packages/kilo-vscode/src/diff/shared/image.ts @@ -0,0 +1,69 @@ +import { createReadStream } from "fs" +import * as path from "path" +import type { DiffImage, DiffImageSide } from "../types" + +const MIMES: Record = { + ".avif": "image/avif", + ".bmp": "image/bmp", + ".gif": "image/gif", + ".ico": "image/x-icon", + ".jpe": "image/jpeg", + ".jpeg": "image/jpeg", + ".jpg": "image/jpeg", + ".png": "image/png", + ".svg": "image/svg+xml", + ".webp": "image/webp", +} + +export const MAX_IMAGE_BYTES = 5_000_000 + +export interface DiffImageSource { + bytes: number + read: () => Promise +} + +export function imageMime(file: string): string | undefined { + return MIMES[path.extname(file).toLowerCase()] +} + +export function encodeImageSide(mime: string, data: Buffer | undefined, bytes = data?.byteLength ?? 0): DiffImageSide { + if (bytes > MAX_IMAGE_BYTES) return { mime, bytes, error: "too-large" } + if (!data || data.byteLength === 0) return { mime, bytes, error: "unreadable" } + if (data.byteLength > MAX_IMAGE_BYTES) return { mime, bytes: data.byteLength, error: "too-large" } + return { mime, bytes: data.byteLength, data: data.toString("base64") } +} + +export function readImageFile(file: string): Promise { + return new Promise((resolve) => { + const chunks: Buffer[] = [] + let bytes = 0 + const stream = createReadStream(file, { end: MAX_IMAGE_BYTES, highWaterMark: 64 * 1024 }) + stream.on("data", (chunk) => { + const data = typeof chunk === "string" ? Buffer.from(chunk) : chunk + chunks.push(data) + bytes += data.byteLength + }) + stream.on("error", () => resolve(undefined)) + stream.on("end", () => resolve(Buffer.concat(chunks, bytes))) + }) +} + +async function load(mime: string, source: DiffImageSource): Promise { + if (source.bytes > MAX_IMAGE_BYTES) return { mime, bytes: source.bytes, error: "too-large" } + const data = await source.read().catch(() => undefined) + return encodeImageSide(mime, data, source.bytes) +} + +export async function loadImage( + file: string, + before?: DiffImageSource, + after?: DiffImageSource, +): Promise { + const mime = imageMime(file) + if (!mime) return undefined + const [left, right] = await Promise.all([ + before ? load(mime, before) : undefined, + after ? load(mime, after) : undefined, + ]) + return { before: left, after: right } +} diff --git a/packages/kilo-vscode/src/diff/shared/path.ts b/packages/kilo-vscode/src/diff/shared/path.ts new file mode 100644 index 00000000000..3d0f9d7e0df --- /dev/null +++ b/packages/kilo-vscode/src/diff/shared/path.ts @@ -0,0 +1,9 @@ +import * as path from "path" + +export function resolveInside(dir: string, file: string): string | undefined { + if (path.isAbsolute(file)) return undefined + const full = path.resolve(dir, file) + const base = path.resolve(dir) + if (full !== base && !full.startsWith(base + path.sep)) return undefined + return full +} diff --git a/packages/kilo-vscode/src/diff/sources/git-status.ts b/packages/kilo-vscode/src/diff/sources/git-status.ts index 44cf3001ac7..b6844af9aa1 100644 --- a/packages/kilo-vscode/src/diff/sources/git-status.ts +++ b/packages/kilo-vscode/src/diff/sources/git-status.ts @@ -2,9 +2,10 @@ // off `git diff` variants and need the same name-status + numstat stitching. import * as fs from "fs/promises" -import * as path from "path" import type { GitOps } from "../../agent-manager/GitOps" import { generatedLike } from "../../agent-manager/local-diff" +import { imageMime, readImageFile } from "../shared/image" +import { resolveInside } from "../shared/path" import type { DiffFile } from "../types" export { MAX_DETAIL_BYTES } from "../../agent-manager/local-diff" @@ -51,6 +52,19 @@ export function parseNumstat(stdout: string): Map { + const map = new Map() + for (const line of stdout.split("\n")) { + const tab = line.indexOf("\t") + if (!line.startsWith(":") || tab < 0) continue + const meta = line.slice(1, tab).split(" ") + const file = line.slice(tab + 1) + if (!file || !meta[2] || !meta[3]) continue + map.set(file, { before: meta[2], after: meta[3] }) + } + return map +} + function statusFromCode(code: string): Status { if (code.startsWith("A")) return "added" if (code.startsWith("D")) return "deleted" @@ -62,6 +76,7 @@ function statusFromCode(code: string): Status { * are left empty — the controller fetches detail lazily through `fetchFile`. */ export function summarize(entry: FileEntry): DiffFile { + const image = imageMime(entry.file) !== undefined return { file: entry.file, before: "", @@ -72,13 +87,15 @@ export function summarize(entry: FileEntry): DiffFile { tracked: entry.tracked, generatedLike: generatedLike(entry.file), // Binary metadata is complete because no deferred text body exists. - summarized: !entry.binary, + // Images are the exception: their encoded sides load lazily on expansion. + summarized: image || !entry.binary, // Synthetic stamp keyed on the stats we actually polled: any change to // the file's diff produces new additions/deletions, which invalidates // the webview-side cached detail via mergeWorktreeDiffs. Callers can // supply a custom stamp (see `FileEntry.stamp`) when additions/deletions // aren't a reliable change signal — notably untracked files. stamp: entry.stamp ?? `${entry.status}:${entry.additions}:${entry.deletions}`, + kind: image ? "image" : undefined, } } @@ -95,6 +112,11 @@ export async function showBlob(git: GitOps, dir: string, ref: string, file: stri return result.code === 0 ? result.stdout : "" } +export async function showBlobBytes(git: GitOps, dir: string, ref: string, file: string): Promise { + const result = await git.execGitBuffer(["show", `${ref}:${file}`], dir) + return result.code === 0 ? result.stdout : undefined +} + /** * Read a working-tree file off disk, matching git's blob semantics for * symlinks: when the entry is a symlink, return its target string (what git @@ -116,6 +138,14 @@ export async function readDisk(dir: string, file: string): Promise { return fs.readFile(full, "utf-8").catch(() => "") } +export async function readDiskBytes(dir: string, file: string): Promise { + const full = resolveInside(dir, file) + if (!full) return undefined + const stat = await fs.lstat(full).catch(() => undefined) + if (!stat?.isFile()) return undefined + return readImageFile(full) +} + // Used to size-cap detail reads without materializing the blob. Mirrors // local-diff.ts's `blobSize` helper. export async function blobSize(git: GitOps, dir: string, ref: string, file: string): Promise { @@ -124,6 +154,19 @@ export async function blobSize(git: GitOps, dir: string, ref: string, file: stri return parseInt(result.stdout.trim(), 10) || 0 } +export async function blobOid(git: GitOps, dir: string, ref: string, file: string): Promise { + const result = await git.execGit(["rev-parse", "--verify", `${ref}:${file}`], dir) + return result.code === 0 ? result.stdout.trim() : "missing" +} + +export async function diskStamp(dir: string, file: string): Promise { + const full = resolveInside(dir, file) + if (!full) return "missing" + const stat = await fs.lstat(full).catch(() => undefined) + if (!stat) return "missing" + return `${stat.size}:${stat.mtimeMs}` +} + /** * Size of the working-tree entry at `file`. Uses `lstat` so symlinks report * the link's own size (length of the target string) instead of resolving to @@ -135,13 +178,3 @@ export async function fileSize(dir: string, file: string): Promise { const stat = await fs.lstat(full).catch(() => undefined) return stat?.size ?? 0 } - -// Rejects absolute paths and any `..` traversal that would escape `dir`. -// Returns the resolved path when safe, `undefined` otherwise. -export function resolveInside(dir: string, file: string): string | undefined { - if (path.isAbsolute(file)) return undefined - const full = path.resolve(dir, file) - const base = path.resolve(dir) - if (full !== base && !full.startsWith(base + path.sep)) return undefined - return full -} diff --git a/packages/kilo-vscode/src/diff/sources/session.ts b/packages/kilo-vscode/src/diff/sources/session.ts index c5c16242cd2..58fc6d8bdd4 100644 --- a/packages/kilo-vscode/src/diff/sources/session.ts +++ b/packages/kilo-vscode/src/diff/sources/session.ts @@ -1,5 +1,7 @@ +import { createHash } from "crypto" import type { SnapshotFileDiff } from "@kilocode/sdk/v2/client" import { normalize, text } from "@kilocode/kilo-ui/session-diff" +import { encodeImageSide, imageMime } from "../shared/image" import type { DiffFile } from "../types" import type { DiffSource, DiffSourceDescriptor, DiffSourceFetch } from "./types" @@ -36,6 +38,7 @@ export function createSessionDiffSource( ): DiffSource { // Cached across fetches so subsequent polling ticks skip the config lookup. let snapshotsDisabled = false + let cache: { key: string; diffs: DiffFile[] } | undefined return { descriptor: sessionDescriptor(sessionId), @@ -54,24 +57,50 @@ export function createSessionDiffSource( } const raw = await fetch({ sessionID: sessionId, directory: workspaceRoot }) - return { diffs: raw.map(toSessionDiffFile) } + const key = raw.map(fingerprint).join("|") + if (cache?.key === key) return { diffs: cache.diffs } + const diffs = raw.map(toSessionDiffFile) + cache = { key, diffs } + return { diffs } }, } } +function fingerprint(raw: SnapshotFileDiff): string { + const patch = createHash("sha1") + .update(raw.patch ?? "") + .digest("hex") + return [raw.file, raw.status, raw.additions, raw.deletions, patch].join(":") +} + /** * Project a backend `SnapshotFileDiff` onto the `DiffFile` shape the viewer * expects. Shared with `createTurnDiffSource` since both hit the same endpoint. */ export function toSessionDiffFile(raw: SnapshotFileDiff): DiffFile { + const file = raw.file ?? "" + const mime = imageMime(file) // Empty patch means binary or summarized (>256 KB) — normalize() can't - // parse it, so short-circuit to empty strings. - const view = raw.patch === "" ? null : normalize(raw) + // parse it, so short-circuit to empty strings. Binary snapshot images do + // not retain their sides, while text-backed SVG patches can be rebuilt. + const view = raw.patch === "" || (mime && mime !== "image/svg+xml") ? null : normalize(raw) + const before = view ? text(view, "deletions") : "" + const after = view ? text(view, "additions") : "" + const image = (() => { + if (mime === "image/svg+xml" && view) { + return { + before: raw.status === "added" ? undefined : encodeImageSide(mime, Buffer.from(before)), + after: raw.status === "deleted" ? undefined : encodeImageSide(mime, Buffer.from(after)), + } + } + if (mime) return {} + return undefined + })() return { - file: raw.file ?? "", - before: view ? text(view, "deletions") : "", - after: view ? text(view, "additions") : "", - patch: raw.patch, + file, + before: mime ? "" : before, + after: mime ? "" : after, + patch: mime ? "" : raw.patch, additions: raw.additions, deletions: raw.deletions, status: raw.status, @@ -79,6 +108,9 @@ export function toSessionDiffFile(raw: SnapshotFileDiff): DiffFile { generatedLike: false, // A zero-stat empty patch has no text body to fetch; nonzero stats // indicate a deferred large-file summary. - summarized: raw.patch === "" && (raw.additions !== 0 || raw.deletions !== 0), + summarized: !mime && raw.patch === "" && (raw.additions !== 0 || raw.deletions !== 0), + kind: mime ? "image" : undefined, + image, + stamp: mime ? fingerprint(raw) : undefined, } } diff --git a/packages/kilo-vscode/src/diff/sources/staged.ts b/packages/kilo-vscode/src/diff/sources/staged.ts index c47cc552fb3..d21a508902b 100644 --- a/packages/kilo-vscode/src/diff/sources/staged.ts +++ b/packages/kilo-vscode/src/diff/sources/staged.ts @@ -2,15 +2,20 @@ import * as vscode from "vscode" import { GitOps } from "../../agent-manager/GitOps" import { generatedLike } from "../../agent-manager/local-diff" import { appendOutput, getWorkspaceRoot } from "../../review-utils" +import { imageMime, loadImage } from "../shared/image" +import { resolveInside } from "../shared/path" import type { DiffFile } from "../types" import type { DiffSource, DiffSourceDescriptor, DiffSourceFetch } from "./types" import { + blobOid, blobSize, INDEX_REF, MAX_DETAIL_BYTES, parseNameStatus, parseNumstat, + parseRawOids, showBlob, + showBlobBytes, summarize, type FileEntry, } from "./git-status" @@ -24,6 +29,11 @@ export const STAGED_DESCRIPTOR: DiffSourceDescriptor = { capabilities: { revert: false, comments: true }, } +function stamp(entry: FileEntry, before: string, after: string): FileEntry { + if (!imageMime(entry.file)) return entry + return { ...entry, stamp: `${entry.status}:${before}:${after}` } +} + /** * Diff between the git index and HEAD — what `git diff --cached` would show. * Polls on the standard interval; revert isn't supported (use `git reset` from @@ -37,24 +47,36 @@ export function createStagedDiffSource(): DiffSource { const root = (): string | undefined => getWorkspaceRoot() const listEntries = async (dir: string): Promise => { - const [nameStatus, numstat] = await Promise.all([ + const [nameStatus, numstat, raw] = await Promise.all([ git.execGit(["-c", "core.quotepath=false", "diff", "--cached", "--name-status", "--no-renames", "HEAD"], dir), git.execGit(["-c", "core.quotepath=false", "diff", "--cached", "--numstat", "--no-renames", "HEAD"], dir), + git.execGit( + ["-c", "core.quotepath=false", "diff", "--cached", "--raw", "--abbrev=64", "--no-renames", "HEAD"], + dir, + ), ]) if (nameStatus.code !== 0) { log("git diff --cached --name-status failed", { code: nameStatus.code, stderr: nameStatus.stderr.trim() }) return [] } const counts = parseNumstat(numstat.code === 0 ? numstat.stdout : "") - const items = parseNameStatus(nameStatus.stdout) - return items.map((item) => ({ - file: item.file, - status: item.status, - additions: counts.get(item.file)?.additions ?? 0, - deletions: counts.get(item.file)?.deletions ?? 0, - tracked: true, - binary: counts.get(item.file)?.binary ?? false, - })) + const refs = parseRawOids(raw.code === 0 ? raw.stdout : "") + return parseNameStatus(nameStatus.stdout).map((item) => { + const ref = refs.get(item.file) + const entry = { + file: item.file, + status: item.status, + additions: counts.get(item.file)?.additions ?? 0, + deletions: counts.get(item.file)?.deletions ?? 0, + tracked: true, + binary: counts.get(item.file)?.binary ?? false, + } + return stamp( + entry, + item.status === "added" ? "missing" : (ref?.before ?? "missing"), + item.status === "deleted" ? "missing" : (ref?.after ?? "missing"), + ) + }) } return { @@ -73,7 +95,7 @@ export function createStagedDiffSource(): DiffSource { async fetchFile(file: string): Promise { const dir = root() - if (!dir || !file) return null + if (!dir || !file || !resolveInside(dir, file)) return null // Resolve the entry for this single file so we know its status. Reading // both refs blindly would still work, but knowing the status lets us @@ -81,10 +103,23 @@ export function createStagedDiffSource(): DiffSource { const entry = await fileEntry(git, dir, file, log) if (!entry) return null - if (entry.binary) return summarize(entry) - + const mime = imageMime(file) + if (entry.binary && !mime) return summarize(entry) const beforeBytes = entry.status === "added" ? 0 : await blobSize(git, dir, "HEAD", file) const afterBytes = entry.status === "deleted" ? 0 : await blobSize(git, dir, INDEX_REF, file) + if (mime) { + const image = await loadImage( + file, + entry.status === "added" + ? undefined + : { bytes: beforeBytes, read: () => showBlobBytes(git, dir, "HEAD", file) }, + entry.status === "deleted" + ? undefined + : { bytes: afterBytes, read: () => showBlobBytes(git, dir, INDEX_REF, file) }, + ) + return { ...summarize(entry), summarized: false, image } + } + if (beforeBytes > MAX_DETAIL_BYTES || afterBytes > MAX_DETAIL_BYTES) { log("Staged detail skipped: file too large", { file, beforeBytes, afterBytes, cap: MAX_DETAIL_BYTES }) return summarize(entry) @@ -110,7 +145,7 @@ export function createStagedDiffSource(): DiffSource { tracked: true, generatedLike: generatedLike(file), summarized, - stamp: `${entry.status}:${entry.additions}:${entry.deletions}`, + stamp: entry.stamp ?? `${entry.status}:${entry.additions}:${entry.deletions}`, } }, @@ -144,7 +179,7 @@ async function fileEntry( dir, ) const stats = parseNumstat(counts.code === 0 ? counts.stdout : "") - return { + const entry = { file: item.file, status: item.status, additions: stats.get(item.file)?.additions ?? 0, @@ -152,4 +187,10 @@ async function fileEntry( tracked: true, binary: stats.get(item.file)?.binary ?? false, } + if (!imageMime(item.file)) return entry + const [before, after] = await Promise.all([ + item.status === "added" ? "missing" : blobOid(git, dir, "HEAD", item.file), + item.status === "deleted" ? "missing" : blobOid(git, dir, INDEX_REF, item.file), + ]) + return stamp(entry, before, after) } diff --git a/packages/kilo-vscode/src/diff/sources/unstaged.ts b/packages/kilo-vscode/src/diff/sources/unstaged.ts index 3aeb5db1704..b4d0c996161 100644 --- a/packages/kilo-vscode/src/diff/sources/unstaged.ts +++ b/packages/kilo-vscode/src/diff/sources/unstaged.ts @@ -4,18 +4,24 @@ import { GitOps } from "../../agent-manager/GitOps" import { generatedLike } from "../../agent-manager/local-diff" import { appendOutput, getWorkspaceRoot } from "../../review-utils" import { binaryFile } from "../shared/binary" +import { imageMime, loadImage } from "../shared/image" +import { resolveInside } from "../shared/path" import type { DiffFile } from "../types" import type { DiffSource, DiffSourceDescriptor, DiffSourceFetch } from "./types" import { + blobOid, blobSize, + diskStamp, fileSize, INDEX_REF, MAX_DETAIL_BYTES, parseNameStatus, parseNumstat, + parseRawOids, readDisk, - resolveInside, + readDiskBytes, showBlob, + showBlobBytes, summarize, type FileEntry, } from "./git-status" @@ -29,6 +35,11 @@ export const UNSTAGED_DESCRIPTOR: DiffSourceDescriptor = { capabilities: { revert: false, comments: true }, } +function stamp(entry: FileEntry, before: string, after: string): FileEntry { + if (!imageMime(entry.file)) return entry + return { ...entry, stamp: `${entry.status}:${before}:${after}` } +} + /** * Diff between the working tree and the index — what `git diff` shows for * tracked files, plus untracked files (treated as fully-added). Read-only; @@ -42,23 +53,33 @@ export function createUnstagedDiffSource(): DiffSource { const root = (): string | undefined => getWorkspaceRoot() const listTracked = async (dir: string): Promise => { - const [nameStatus, numstat] = await Promise.all([ + const [nameStatus, numstat, raw] = await Promise.all([ git.execGit(["-c", "core.quotepath=false", "diff", "--name-status", "--no-renames"], dir), git.execGit(["-c", "core.quotepath=false", "diff", "--numstat", "--no-renames"], dir), + git.execGit(["-c", "core.quotepath=false", "diff", "--raw", "--abbrev=64", "--no-renames"], dir), ]) if (nameStatus.code !== 0) { log("git diff --name-status failed", { code: nameStatus.code, stderr: nameStatus.stderr.trim() }) return [] } const counts = parseNumstat(numstat.code === 0 ? numstat.stdout : "") - return parseNameStatus(nameStatus.stdout).map((item) => ({ - file: item.file, - status: item.status, - additions: counts.get(item.file)?.additions ?? 0, - deletions: counts.get(item.file)?.deletions ?? 0, - tracked: true, - binary: counts.get(item.file)?.binary ?? false, - })) + const refs = parseRawOids(raw.code === 0 ? raw.stdout : "") + return Promise.all( + parseNameStatus(nameStatus.stdout).map(async (item) => { + const entry = { + file: item.file, + status: item.status, + additions: counts.get(item.file)?.additions ?? 0, + deletions: counts.get(item.file)?.deletions ?? 0, + tracked: true, + binary: counts.get(item.file)?.binary ?? false, + } + if (!imageMime(item.file)) return entry + const before = item.status === "added" ? "missing" : (refs.get(item.file)?.before ?? "missing") + const after = item.status === "deleted" ? "missing" : await diskStamp(dir, item.file) + return stamp(entry, before, after) + }), + ) } const listUntracked = async (dir: string): Promise => { @@ -116,17 +137,24 @@ export function createUnstagedDiffSource(): DiffSource { async fetchFile(file: string): Promise { const dir = root() - if (!dir || !file) return null + if (!dir || !file || !resolveInside(dir, file)) return null const entry = await fileEntry(git, dir, file, log) if (!entry) return null + const mime = imageMime(file) + if (entry.binary && !mime) return summarize(entry) - if (entry.binary) return summarize(entry) + const bytes = await detailBytes(git, dir, file, entry) + const image = await imageDetail(git, dir, file, entry, bytes.before, bytes.after) + if (image) return image - const beforeBytes = !entry.tracked || entry.status === "added" ? 0 : await blobSize(git, dir, INDEX_REF, file) - const afterBytes = entry.status === "deleted" ? 0 : await fileSize(dir, file) - if (beforeBytes > MAX_DETAIL_BYTES || afterBytes > MAX_DETAIL_BYTES) { - log("Unstaged detail skipped: file too large", { file, beforeBytes, afterBytes, cap: MAX_DETAIL_BYTES }) + if (bytes.before > MAX_DETAIL_BYTES || bytes.after > MAX_DETAIL_BYTES) { + log("Unstaged detail skipped: file too large", { + file, + beforeBytes: bytes.before, + afterBytes: bytes.after, + cap: MAX_DETAIL_BYTES, + }) return summarize(entry) } @@ -170,6 +198,31 @@ export function createUnstagedDiffSource(): DiffSource { } } +async function detailBytes(git: GitOps, dir: string, file: string, entry: FileEntry) { + const before = !entry.tracked || entry.status === "added" ? 0 : await blobSize(git, dir, INDEX_REF, file) + const after = entry.status === "deleted" ? 0 : await fileSize(dir, file) + return { before, after } +} + +async function imageDetail( + git: GitOps, + dir: string, + file: string, + entry: FileEntry, + beforeBytes: number, + afterBytes: number, +): Promise { + if (!imageMime(file)) return undefined + const image = await loadImage( + file, + !entry.tracked || entry.status === "added" + ? undefined + : { bytes: beforeBytes, read: () => showBlobBytes(git, dir, INDEX_REF, file) }, + entry.status === "deleted" ? undefined : { bytes: afterBytes, read: () => readDiskBytes(dir, file) }, + ) + return { ...summarize(entry), summarized: false, image } +} + async function fileEntry( git: GitOps, dir: string, @@ -190,7 +243,7 @@ async function fileEntry( dir, ) const stats = parseNumstat(counts.code === 0 ? counts.stdout : "") - return { + const entry = { file: item.file, status: item.status, additions: stats.get(item.file)?.additions ?? 0, @@ -198,6 +251,12 @@ async function fileEntry( tracked: true, binary: stats.get(item.file)?.binary ?? false, } + if (!imageMime(item.file)) return entry + const [before, after] = await Promise.all([ + item.status === "added" ? "missing" : blobOid(git, dir, INDEX_REF, item.file), + item.status === "deleted" ? "missing" : diskStamp(dir, item.file), + ]) + return stamp(entry, before, after) } } @@ -210,6 +269,8 @@ async function fileEntry( log("Unstaged file rejected: outside workspace", { file }) return undefined } + const untracked = await git.execGit(["ls-files", "--others", "--exclude-standard", "--", file], dir) + if (untracked.code !== 0 || !untracked.stdout.split("\n").includes(file)) return undefined const stat = await fs.lstat(full).catch(() => undefined) if (!stat) { log("Unstaged file not found", { file }) diff --git a/packages/kilo-vscode/src/diff/sources/worktree.ts b/packages/kilo-vscode/src/diff/sources/worktree.ts index 36a436fce3a..1f3d8ee25a4 100644 --- a/packages/kilo-vscode/src/diff/sources/worktree.ts +++ b/packages/kilo-vscode/src/diff/sources/worktree.ts @@ -156,5 +156,7 @@ function toDiffFile(entry: WorktreeDiffEntry): DiffFile { generatedLike: entry.generatedLike, summarized: entry.summarized, stamp: entry.stamp, + kind: entry.kind, + image: entry.image, } } diff --git a/packages/kilo-vscode/src/diff/types.ts b/packages/kilo-vscode/src/diff/types.ts index a6d9def400f..913d4560a31 100644 --- a/packages/kilo-vscode/src/diff/types.ts +++ b/packages/kilo-vscode/src/diff/types.ts @@ -12,6 +12,20 @@ export interface PanelContext { baseBranchOverride?: string } +export type DiffImageError = "too-large" | "unreadable" + +export interface DiffImageSide { + mime: string + bytes: number + data?: string + error?: DiffImageError +} + +export interface DiffImage { + before?: DiffImageSide + after?: DiffImageSide +} + /** Mirrors `WorktreeFileDiff` in webview-ui/src/types/messages/agent-manager.ts. */ export interface DiffFile { file: string @@ -26,4 +40,6 @@ export interface DiffFile { generatedLike?: boolean summarized?: boolean stamp?: string + kind?: "image" + image?: DiffImage } diff --git a/packages/kilo-vscode/src/review-utils.ts b/packages/kilo-vscode/src/review-utils.ts index 42dac76b284..c0d7eccbeda 100644 --- a/packages/kilo-vscode/src/review-utils.ts +++ b/packages/kilo-vscode/src/review-utils.ts @@ -23,15 +23,17 @@ export function openFileInEditor( prefix = "Kilo", ): void { const uri = vscode.Uri.file(filePath) - const target = Math.max(1, Math.floor(line ?? 1)) - const col = column !== undefined && column > 0 ? column - 1 : 0 - const pos = new vscode.Position(target - 1, col) - const selection = new vscode.Range(pos, pos) + const options: vscode.TextDocumentShowOptions = { viewColumn, preview: true } + if (line !== undefined && line > 0) { + const target = Math.max(1, Math.floor(line)) + const col = column !== undefined && column > 0 ? column - 1 : 0 + const pos = new vscode.Position(target - 1, col) + options.selection = new vscode.Range(pos, pos) + } - vscode.workspace.openTextDocument(uri).then( - (doc) => vscode.window.showTextDocument(doc, { viewColumn, preview: true, selection }), - (err) => console.error(`[Kilo New] ${prefix}: Failed to open file:`, uri.fsPath, err), - ) + void vscode.commands + .executeCommand("vscode.open", uri, options) + .then(undefined, (err) => console.error(`[Kilo New] ${prefix}: Failed to open file:`, uri.fsPath, err)) } export function openWorkspaceRelativeFile(relativePath: string, line?: number, column?: number): void { diff --git a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts index d45c3873e2e..78599763053 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts @@ -24,6 +24,7 @@ const TSX_FILES = [ path.join(ROOT, "webview-ui/agent-manager/sortable-tab.tsx"), path.join(ROOT, "webview-ui/agent-manager/DiffPanel.tsx"), path.join(ROOT, "webview-ui/diff-viewer/FullScreenDiffView.tsx"), + path.join(ROOT, "webview-ui/diff-viewer/ImageDiffView.tsx"), path.join(ROOT, "webview-ui/diff-viewer/MarkdownDiffView.tsx"), path.join(ROOT, "webview-ui/diff-viewer/MarkdownAnnotationLayer.tsx"), path.join(ROOT, "webview-ui/diff-viewer/markdown-comment-ranges.ts"), diff --git a/packages/kilo-vscode/tests/unit/agent-manager-diff-state.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-diff-state.test.ts index 009b459df7e..381ac518a38 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-diff-state.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-diff-state.test.ts @@ -41,6 +41,20 @@ describe("agent manager diff state", () => { expect(result.stale.size).toBe(0) }) + it("preserves loaded image data when summary metadata is unchanged", () => { + const image = { + before: { mime: "image/png", bytes: 3, data: "b2xk" }, + after: { mime: "image/png", bytes: 3, data: "bmV3" }, + } + const prev = [diff({ file: "asset.png", kind: "image", summarized: false, image })] + const next = [diff({ file: "asset.png", kind: "image", summarized: true })] + const result = mergeWorktreeDiffs(prev, next) + + expect(result.diffs[0]).toBe(prev[0]) + expect(result.diffs[0]?.image).toBe(image) + expect(result.stale.size).toBe(0) + }) + it("replaces detailed content when patch anchors change", () => { const prev = [diff({ summarized: false, before: "old\n", after: "new\n", patch: "@@ -1 +1 @@\n-old\n+new\n" })] const next = [diff({ summarized: false, before: "old\n", after: "new\n", patch: "@@ -100 +100 @@\n-old\n+new\n" })] @@ -74,6 +88,7 @@ describe("agent manager diff state", () => { diff({ file: "src/app.ts", generatedLike: false, additions: 3 }), diff({ file: "node_modules/pkg/index.js", generatedLike: true, additions: 3 }), diff({ file: "audio/notification.wav", summarized: false, additions: 0 }), + diff({ file: "assets/banner.png", kind: "image", summarized: true, additions: 0 }), diff({ file: "src/huge.ts", additions: EXTREME_DIFF_CHANGED_LINES + 1 }), ]), ).toEqual(["src/app.ts", "node_modules/pkg/index.js", "src/huge.ts"]) @@ -121,13 +136,18 @@ describe("agent manager diff state", () => { expect(toggleOpenFiles(diffs, files)).toEqual([]) }) - it("prevents non-text diffs from entering open state", () => { + it("opens images while preventing other non-text diffs from entering open state", () => { const audio = diff({ file: "audio/alert.wav", summarized: false, additions: 0 }) + const image = diff({ file: "assets/banner.png", kind: "image", summarized: true, additions: 0 }) const text = diff({ file: "src/app.ts" }) expect(isDiffExpandable(audio)).toBe(false) + expect(isDiffExpandable(image)).toBe(true) expect(isDiffExpandable(text)).toBe(true) - expect(sanitizeOpenFiles([audio, text], [audio.file, text.file])).toEqual([text.file]) + expect(sanitizeOpenFiles([audio, image, text], [audio.file, image.file, text.file])).toEqual([ + image.file, + text.file, + ]) }) }) diff --git a/packages/kilo-vscode/tests/unit/diff-image.test.ts b/packages/kilo-vscode/tests/unit/diff-image.test.ts new file mode 100644 index 00000000000..6bb7e4df969 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/diff-image.test.ts @@ -0,0 +1,78 @@ +import { describe, expect, it } from "bun:test" +import * as fs from "fs/promises" +import * as os from "os" +import * as path from "path" +import { imageMime, loadImage, MAX_IMAGE_BYTES, readImageFile } from "../../src/diff/shared/image" +import { parseRawOids } from "../../src/diff/sources/git-status" + +describe("diff images", () => { + it("recognizes every image format handled by the VS Code image preview", () => { + const files = [ + "photo.jpg", + "photo.jpe", + "photo.jpeg", + "image.png", + "image.bmp", + "animation.gif", + "favicon.ico", + "image.webp", + "image.avif", + "vector.svg", + ] + + for (const file of files) expect(imageMime(file)).toStartWith("image/") + expect(imageMime("ASSETS/LOGO.PNG")).toBe("image/png") + expect(imageMime("archive.zip")).toBeUndefined() + expect(imageMime("photo.tiff")).toBeUndefined() + }) + + it("parses image blob identities from a single raw diff", () => { + const before = "1".repeat(40) + const after = "2".repeat(40) + const refs = parseRawOids(`:100644 100644 ${before} ${after} M\tassets/banner.png\n`) + + expect(refs.get("assets/banner.png")).toEqual({ before, after }) + }) + + it("encodes image sides without converting bytes to text", async () => { + const before = Buffer.from([0x00, 0xff, 0x10, 0x80]) + const after = Buffer.from([0x89, 0x50, 0x4e, 0x47]) + const image = await loadImage( + "asset.png", + { bytes: before.byteLength, read: async () => before }, + { bytes: after.byteLength, read: async () => after }, + ) + + expect(image?.before).toEqual({ mime: "image/png", bytes: 4, data: before.toString("base64") }) + expect(image?.after).toEqual({ mime: "image/png", bytes: 4, data: after.toString("base64") }) + }) + + it("bounds mutable image reads to the payload cap", async () => { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "diff-image-test-")) + const file = path.join(dir, "large.png") + try { + await fs.writeFile(file, Buffer.alloc(MAX_IMAGE_BYTES + 100)) + expect((await readImageFile(file))?.byteLength).toBe(MAX_IMAGE_BYTES + 1) + } finally { + await fs.rm(dir, { recursive: true, force: true }) + } + }) + + it("does not read image sides over the webview payload cap", async () => { + let reads = 0 + const image = await loadImage("asset.webp", { + bytes: MAX_IMAGE_BYTES + 1, + read: async () => { + reads++ + return Buffer.from("unused") + }, + }) + + expect(reads).toBe(0) + expect(image?.before).toEqual({ + mime: "image/webp", + bytes: MAX_IMAGE_BYTES + 1, + error: "too-large", + }) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/diff-session-source.test.ts b/packages/kilo-vscode/tests/unit/diff-session-source.test.ts index 4db1b322db7..ce870f0abbe 100644 --- a/packages/kilo-vscode/tests/unit/diff-session-source.test.ts +++ b/packages/kilo-vscode/tests/unit/diff-session-source.test.ts @@ -91,6 +91,55 @@ describe("createSessionDiffSource.fetch", () => { expect(result.diffs[2]?.summarized).toBe(true) }) + it("rebuilds text-backed SVG snapshot sides without sending them to Pierre", async () => { + const before = '' + const after = '' + const patch = [ + "diff --git a/assets/banner.svg b/assets/banner.svg", + "--- a/assets/banner.svg", + "+++ b/assets/banner.svg", + "@@ -1 +1 @@", + `-${before}`, + `+${after}`, + "", + ].join("\n") + const { fetch } = recording([{ file: "assets/banner.svg", patch, additions: 1, deletions: 1, status: "modified" }]) + const result = await createSessionDiffSource("s-image", fetch, "/repo").fetch() + + expect(result.diffs[0]).toMatchObject({ + file: "assets/banner.svg", + before: "", + after: "", + patch: "", + kind: "image", + summarized: false, + image: { + before: { mime: "image/svg+xml", data: Buffer.from(`${before}\n`).toString("base64") }, + after: { mime: "image/svg+xml", data: Buffer.from(`${after}\n`).toString("base64") }, + }, + }) + }) + + it("reuses converted image details while the snapshot is unchanged", async () => { + const { fetch } = recording([ + { file: "assets/banner.svg", patch: modifiedPatch, additions: 1, deletions: 1, status: "modified" }, + ]) + const source = createSessionDiffSource("s-image-cache", fetch, "/repo") + const first = await source.fetch() + const second = await source.fetch() + + expect(second.diffs).toBe(first.diffs) + }) + + it("marks binary snapshot images unavailable when their sides were not retained", async () => { + const { fetch } = recording([ + { file: "assets/banner.png", patch: "", additions: 0, deletions: 0, status: "modified" }, + ]) + const result = await createSessionDiffSource("s-image", fetch, "/repo").fetch() + + expect(result.diffs[0]?.image).toEqual({}) + }) + it("propagates errors from the underlying fetch", async () => { const { fetch } = recording(new Error("network down")) const source = createSessionDiffSource("s3", fetch) diff --git a/packages/kilo-vscode/tests/unit/local-diff.test.ts b/packages/kilo-vscode/tests/unit/local-diff.test.ts index 67467db1e60..1e00c06470d 100644 --- a/packages/kilo-vscode/tests/unit/local-diff.test.ts +++ b/packages/kilo-vscode/tests/unit/local-diff.test.ts @@ -237,6 +237,28 @@ describe("diffSummary", () => { }) }) + it("loads binary-safe before and after data for image diffs", async () => { + await withRepo(async (dir, base) => { + const before = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x00, 0x01]) + const after = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x00, 0xff]) + await fs.writeFile(path.join(dir, "banner.png"), before) + runSync(dir, ["add", "banner.png"]) + runSync(dir, ["commit", "-m", "add banner"]) + runSync(dir, ["branch", "-f", base]) + await fs.writeFile(path.join(dir, "banner.png"), after) + + const local = createLocalDiff(git()) + const summary = (await local.summary(dir, base)).find((entry) => entry.file === "banner.png") + const detail = await local.file(dir, base, "banner.png") + + expect(summary?.kind).toBe("image") + expect(summary?.summarized).toBe(true) + expect(detail?.summarized).toBe(false) + expect(detail?.image?.before?.data).toBe(before.toString("base64")) + expect(detail?.image?.after?.data).toBe(after.toString("base64")) + }) + }) + it("marks generated-like files via generatedLike flag", async () => { await withRepo(async (dir, base) => { await fs.mkdir(path.join(dir, "dist"), { recursive: true }) @@ -266,6 +288,20 @@ describe("diffFile", () => { }) }) + it("rejects image detail paths outside the repository", async () => { + await withRepo(async (dir, base) => { + const name = `${path.basename(dir)}-secret.png` + const secret = path.join(path.dirname(dir), name) + await fs.writeFile(secret, Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x00])) + try { + expect(await diffFile(git(), dir, base, `../${name}`)).toBeNull() + expect(await diffFile(git(), dir, base, secret)).toBeNull() + } finally { + await fs.rm(secret, { force: true }) + } + }) + }) + it("returns before/after/patch for a modified tracked file", async () => { await withRepo(async (dir, base) => { await fs.writeFile(path.join(dir, "seed.txt"), "seed\nmore\n") diff --git a/packages/kilo-vscode/tests/unit/review-utils.test.ts b/packages/kilo-vscode/tests/unit/review-utils.test.ts new file mode 100644 index 00000000000..67793473010 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/review-utils.test.ts @@ -0,0 +1,29 @@ +import { afterEach, describe, expect, it, spyOn } from "bun:test" +import * as vscode from "vscode" +import { openFileInEditor } from "../../src/review-utils" + +const execute = spyOn(vscode.commands, "executeCommand") + +afterEach(() => { + execute.mockClear() +}) + +describe("openFileInEditor", () => { + it("delegates to vscode.open so custom image editors can handle the file", () => { + openFileInEditor("/repo/assets/banner.png", undefined, undefined, vscode.ViewColumn.One) + + expect(execute).toHaveBeenCalledTimes(1) + const [command, uri, options] = execute.mock.calls[0]! + expect(command).toBe("vscode.open") + expect((uri as vscode.Uri).fsPath).toBe("/repo/assets/banner.png") + expect(options).toEqual({ viewColumn: vscode.ViewColumn.One, preview: true }) + }) + + it("preserves line and column navigation for text editors", () => { + openFileInEditor("/repo/src/app.ts", 7, 3, vscode.ViewColumn.One) + + const options = execute.mock.calls[0]?.[2] as vscode.TextDocumentShowOptions + expect(options.selection?.start.line).toBe(6) + expect(options.selection?.start.character).toBe(2) + }) +}) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx index ea9b09a4e3b..279265b660e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx @@ -57,6 +57,7 @@ import { DiffEndMarker } from "../diff-viewer/DiffEndMarker" import { VirtualDiffList } from "../diff-viewer/VirtualDiffList" import { treeOrder } from "../diff-viewer/file-tree-utils" import { isMarkdownFile, MarkdownDiffView } from "../diff-viewer/MarkdownDiffView" +import { ImageDiffView } from "../diff-viewer/ImageDiffView" import { createDiffRows, diffToken } from "../diff-viewer/diff-state" // --- Data model --- @@ -247,6 +248,15 @@ export const DiffPanel: Component = (props) => { ), ) + const request = (diff: WorktreeFileDiff) => { + if (!props.onRequestDiff || props.loadingFiles?.has(diff.file)) return + if (!isDiffExpandable(diff) || diff.summarized !== true) return + const value = diffToken(diff) + if (requested.get(diff.file) === value) return + requested.set(diff.file, value) + props.onRequestDiff(diff.file) + } + createEffect( on( () => [open(), props.diffs] as const, @@ -255,15 +265,10 @@ export const DiffPanel: Component = (props) => { for (const file of requested.keys()) { if (!files.has(file)) requested.delete(file) } - if (!props.onRequestDiff) return for (const file of next) { - if (props.loadingFiles?.has(file)) continue const diff = props.diffs.find((item) => item.file === file) - if (!diff || !isDiffExpandable(diff) || diff.summarized !== true) continue - const value = diffToken(diff) - if (requested.get(file) === value) continue - requested.set(file, value) - props.onRequestDiff(file) + if (!diff || diff.kind === "image") continue + request(diff) } }, { defer: true }, @@ -559,6 +564,10 @@ export const DiffPanel: Component = (props) => { const isLoadingDetail = () => props.loadingFiles?.has(diff.file) ?? false const fileCommentCount = () => (commentsByFile().get(diff.file) ?? []).length + createEffect(() => { + if (diff.kind === "image" && open().includes(diff.file)) request(diff) + }) + return ( = (props) => { + + {t("agentManager.review.image")} + {t("agentManager.review.largeFileCollapsed")} @@ -681,36 +693,43 @@ export const DiffPanel: Component = (props) => { } > - before={{ name: diff.file, contents: diff.before }} - after={{ name: diff.file, contents: diff.after }} - patch={diff.patch} - diffStyle={props.diffStyle ?? "unified"} - virtualized={shouldVirtualizeDiff(diff)} - annotations={annotationsForFile(diff.file)} - renderAnnotation={buildAnnotation} - enableGutterUtility={true} - onGutterUtilityClick={(result) => handleGutterClick(diff.file, result)} - onLineNumberClick={(event) => { - if (event.annotationSide === "deletions") return - props.onOpenFile?.(diff.file, event.lineNumber) - }} - /> + + before={{ name: diff.file, contents: diff.before }} + after={{ name: diff.file, contents: diff.after }} + patch={diff.patch} + diffStyle={props.diffStyle ?? "unified"} + virtualized={shouldVirtualizeDiff(diff)} + annotations={annotationsForFile(diff.file)} + renderAnnotation={buildAnnotation} + enableGutterUtility={true} + onGutterUtilityClick={(result) => handleGutterClick(diff.file, result)} + onLineNumberClick={(event) => { + if (event.annotationSide === "deletions") return + props.onOpenFile?.(diff.file, event.lineNumber) + }} + /> + } + > + handleGutterClick(diff.file, result)} + onLineNumberClick={(event) => { + if (event.annotationSide === "deletions") return + props.onOpenFile?.(diff.file, event.lineNumber) + }} + /> + } > - handleGutterClick(diff.file, result)} - onLineNumberClick={(event) => { - if (event.annotationSide === "deletions") return - props.onOpenFile?.(diff.file, event.lineNumber) - }} - /> + diff --git a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css index 0ef4932198c..fc9d3af1df9 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -1954,6 +1954,78 @@ body.am-wt-dragging-active * { gap: 8px; } +.am-image-diff { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr)); + gap: 1px; + border-top: 1px solid var(--border-weak-base); + background: var(--border-weak-base); + cursor: auto; +} + +.am-image-diff-pane { + min-width: 0; + background: var(--vscode-editor-background, var(--surface-base)); +} + +.am-image-diff-label { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 32px; + padding: 0 12px; + border-bottom: 1px solid var(--border-weak-base); + color: var(--text-weak); + font-size: var(--font-size-small); + font-weight: 600; +} + +.am-image-diff-canvas { + display: flex; + align-items: center; + justify-content: center; + min-height: 180px; + padding: 20px; + background-color: var(--vscode-editor-background, var(--surface-base)); + background-image: conic-gradient( + from 90deg at 1px 1px, + color-mix(in oklab, var(--text-base) 5%, transparent) 25%, + transparent 0 + ); + background-size: 16px 16px; + overflow: auto; +} + +.am-image-diff-canvas img { + display: block; + max-width: 100%; + max-height: 440px; + object-fit: contain; + box-shadow: 0 8px 28px color-mix(in oklab, black 20%, transparent); +} + +.am-diff-panel .am-image-diff-canvas img { + max-height: 320px; +} + +.am-image-diff-message, +.am-image-diff-unavailable { + color: var(--text-weak); + font-size: var(--font-size-small); + text-align: center; +} + +.am-image-diff-unavailable { + display: flex; + align-items: center; + justify-content: center; + min-height: 120px; + padding: 20px; + border-top: 1px solid var(--border-weak-base); + background: var(--vscode-editor-background, var(--surface-base)); +} + .am-markdown-diff { display: grid; grid-template-columns: minmax(0, 1fr); diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts index cc94798638a..f2522ece3d8 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts @@ -187,6 +187,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} collapsed", "agentManager.review.collapsedWithLarge": "{{collapsed}} collapsed, {{large}} large", "agentManager.review.largeFileCollapsed": "Large file (collapsed)", + "agentManager.review.image": "image", + "agentManager.review.imageBefore": "Before", + "agentManager.review.imageAfter": "After", + "agentManager.review.imageTooLarge": "Image is too large to preview ({{size}}).", + "agentManager.review.imageUnreadable": "This image could not be rendered.", + "agentManager.review.imageUnavailable": "Image preview is unavailable for this session snapshot.", "agentManager.review.endOfLongDiff": "You made it to the end!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx b/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx index 5922c9080c3..34a3f7a31c1 100644 --- a/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx +++ b/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx @@ -58,6 +58,7 @@ import { import { DiffEndMarker } from "./DiffEndMarker" import { VirtualDiffList } from "./VirtualDiffList" import { isMarkdownFile, MarkdownDiffView } from "./MarkdownDiffView" +import { ImageDiffView } from "./ImageDiffView" import { createDiffRows, diffToken } from "./diff-state" type DiffStyle = "unified" | "split" @@ -253,6 +254,15 @@ export const FullScreenDiffView: Component = (props) => ), ) + const request = (diff: WorktreeFileDiff) => { + if (!props.onRequestDiff || props.loadingFiles?.has(diff.file)) return + if (!isDiffExpandable(diff) || diff.summarized !== true) return + const value = diffToken(diff) + if (requested.get(diff.file) === value) return + requested.set(diff.file, value) + props.onRequestDiff(diff.file) + } + createEffect( on( () => [open(), props.diffs] as const, @@ -261,15 +271,10 @@ export const FullScreenDiffView: Component = (props) => for (const file of requested.keys()) { if (!files.has(file)) requested.delete(file) } - if (!props.onRequestDiff) return for (const file of next) { - if (props.loadingFiles?.has(file)) continue const diff = props.diffs.find((item) => item.file === file) - if (!diff || !isDiffExpandable(diff) || diff.summarized !== true) continue - const value = diffToken(diff) - if (requested.get(file) === value) continue - requested.set(file, value) - props.onRequestDiff(file) + if (!diff || diff.kind === "image") continue + request(diff) } }, { defer: true }, @@ -634,6 +639,10 @@ export const FullScreenDiffView: Component = (props) => const isLoadingDetail = () => props.loadingFiles?.has(diff.file) ?? false const fileCommentCount = () => (commentsByFile().get(diff.file) ?? []).length + createEffect(() => { + if (diff.kind === "image" && open().includes(diff.file)) request(diff) + }) + return ( @@ -671,6 +680,9 @@ export const FullScreenDiffView: Component = (props) => + + {t("agentManager.review.image")} + {t("agentManager.review.largeFileCollapsed")} @@ -752,36 +764,43 @@ export const FullScreenDiffView: Component = (props) => } > - before={{ name: diff.file, contents: diff.before }} - after={{ name: diff.file, contents: diff.after }} - patch={diff.patch} - diffStyle={props.diffStyle} - virtualized={shouldVirtualizeDiff(diff)} - annotations={annotationsForFile(diff.file)} - renderAnnotation={buildAnnotation} - enableGutterUtility={props.canComment !== false} - onGutterUtilityClick={(result) => handleGutterClick(diff.file, result)} - onLineNumberClick={(event) => { - if (event.annotationSide === "deletions") return - props.onOpenFile?.(diff.file, event.lineNumber) - }} - /> + + before={{ name: diff.file, contents: diff.before }} + after={{ name: diff.file, contents: diff.after }} + patch={diff.patch} + diffStyle={props.diffStyle} + virtualized={shouldVirtualizeDiff(diff)} + annotations={annotationsForFile(diff.file)} + renderAnnotation={buildAnnotation} + enableGutterUtility={props.canComment !== false} + onGutterUtilityClick={(result) => handleGutterClick(diff.file, result)} + onLineNumberClick={(event) => { + if (event.annotationSide === "deletions") return + props.onOpenFile?.(diff.file, event.lineNumber) + }} + /> + } + > + handleGutterClick(diff.file, result)} + onLineNumberClick={(event) => { + if (event.annotationSide === "deletions") return + props.onOpenFile?.(diff.file, event.lineNumber) + }} + /> + } > - handleGutterClick(diff.file, result)} - onLineNumberClick={(event) => { - if (event.annotationSide === "deletions") return - props.onOpenFile?.(diff.file, event.lineNumber) - }} - /> + diff --git a/packages/kilo-vscode/webview-ui/diff-viewer/ImageDiffView.tsx b/packages/kilo-vscode/webview-ui/diff-viewer/ImageDiffView.tsx new file mode 100644 index 00000000000..4d4ae85a2cb --- /dev/null +++ b/packages/kilo-vscode/webview-ui/diff-viewer/ImageDiffView.tsx @@ -0,0 +1,80 @@ +import { type Component, createEffect, createSignal, Show } from "solid-js" +import type { DiffImageSide, WorktreeFileDiff } from "../src/types/messages" +import { useLanguage } from "../src/context/language" + +interface ImageDiffViewProps { + diff: WorktreeFileDiff +} + +interface ImagePaneProps { + file: string + label: string + image: DiffImageSide +} + +function size(bytes: number): string { + if (bytes < 1_000) return `${bytes} B` + if (bytes < 1_000_000) return `${(bytes / 1_000).toFixed(1)} KB` + return `${(bytes / 1_000_000).toFixed(1)} MB` +} + +const ImagePane: Component = (props) => { + const { t } = useLanguage() + const [failed, setFailed] = createSignal(false) + + createEffect(() => { + props.image.data + setFailed(false) + }) + + const message = () => { + if (props.image.error === "too-large") { + return t("agentManager.review.imageTooLarge", { size: size(props.image.bytes) }) + } + if (props.image.error === "unreadable" || failed()) return t("agentManager.review.imageUnreadable") + return undefined + } + + return ( +
+
+ {props.label} + {size(props.image.bytes)} +
+
+ {message()}}> + {`${props.file} setFailed(true)} + /> + +
+
+ ) +} + +export const ImageDiffView: Component = (props) => { + const { t } = useLanguage() + const image = () => props.diff.image + const available = () => Boolean(image()?.before || image()?.after) + + return ( + {t("agentManager.review.imageUnavailable")}} + > +
+ + {(side) => } + + + {(side) => } + +
+
+ ) +} diff --git a/packages/kilo-vscode/webview-ui/diff-viewer/diff-open-policy.ts b/packages/kilo-vscode/webview-ui/diff-viewer/diff-open-policy.ts index c59a22c78db..a0513512057 100644 --- a/packages/kilo-vscode/webview-ui/diff-viewer/diff-open-policy.ts +++ b/packages/kilo-vscode/webview-ui/diff-viewer/diff-open-policy.ts @@ -14,7 +14,7 @@ export function shouldVirtualizeDiff(diff: WorktreeFileDiff): boolean { } export function isDiffExpandable(diff: WorktreeFileDiff): boolean { - return diff.summarized === true || Boolean(diff.patch || diff.before || diff.after) + return diff.kind === "image" || diff.summarized === true || Boolean(diff.patch || diff.before || diff.after) } export function sanitizeOpenFiles(diffs: WorktreeFileDiff[], open: string[]): string[] { @@ -27,7 +27,7 @@ export function expandableOpenFiles(diffs: WorktreeFileDiff[]): string[] { } export function initialOpenFiles(diffs: WorktreeFileDiff[]): string[] { - return expandableOpenFiles(diffs) + return diffs.filter((diff) => diff.kind !== "image" && isDiffExpandable(diff)).map((diff) => diff.file) } export function allOpenFiles(diffs: WorktreeFileDiff[], open: string[]): boolean { diff --git a/packages/kilo-vscode/webview-ui/diff-viewer/diff-state.ts b/packages/kilo-vscode/webview-ui/diff-viewer/diff-state.ts index 4d977e13ce0..32ebe6db77a 100644 --- a/packages/kilo-vscode/webview-ui/diff-viewer/diff-state.ts +++ b/packages/kilo-vscode/webview-ui/diff-viewer/diff-state.ts @@ -10,7 +10,8 @@ export function sameDiffMeta(left: WorktreeFileDiff, right: WorktreeFileDiff) { left.tracked === right.tracked && left.generatedLike === right.generatedLike && left.summarized === right.summarized && - left.stamp === right.stamp + left.stamp === right.stamp && + left.kind === right.kind ) } @@ -79,6 +80,7 @@ export function mergeWorktreeDiffs(prev: WorktreeFileDiff[], next: WorktreeFileD existing.before === diff.before && existing.after === diff.after && existing.patch === diff.patch && + existing.image === diff.image && sameDiffMeta(existing, diff) ) return existing @@ -91,12 +93,14 @@ export function mergeWorktreeDiffs(prev: WorktreeFileDiff[], next: WorktreeFileD before: existing.before, after: existing.after, patch: existing.patch, + image: existing.image, summarized: false, } if ( existing.before === merged.before && existing.after === merged.after && existing.patch === merged.patch && + existing.image === merged.image && sameDiffMeta(existing, merged) ) return existing diff --git a/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts b/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts index 0a28d60db91..553997c59ec 100644 --- a/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts +++ b/packages/kilo-vscode/webview-ui/src/types/messages/agent-manager.ts @@ -123,6 +123,20 @@ export interface ExternalWorktreeInfo { branch: string } +export type DiffImageError = "too-large" | "unreadable" + +export interface DiffImageSide { + mime: string + bytes: number + data?: string + error?: DiffImageError +} + +export interface DiffImage { + before?: DiffImageSide + after?: DiffImageSide +} + // Shared FileDiff shape (matches Snapshot.FileDiff from CLI backend) export interface WorktreeFileDiff { file: string @@ -137,6 +151,8 @@ export interface WorktreeFileDiff { generatedLike?: boolean summarized?: boolean stamp?: string + kind?: "image" + image?: DiffImage } export type AgentManagerApplyWorktreeDiffStatus = "checking" | "applying" | "success" | "conflict" | "error" From 148b324c9494949bd92234de836416a7809668ec Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Mon, 15 Jun 2026 16:08:50 +0200 Subject: [PATCH 19/78] fix(vscode): translate image diff labels --- packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts | 6 ++++++ packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts | 6 ++++++ 19 files changed, 114 insertions(+) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts index 22b81da3b39..82f7c1df3ae 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts @@ -180,6 +180,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} مطوي", "agentManager.review.collapsedWithLarge": "{{collapsed}} مطوي، {{large}} كبير", "agentManager.review.largeFileCollapsed": "ملف كبير (مطوي)", + "agentManager.review.image": "صورة", + "agentManager.review.imageBefore": "قبل", + "agentManager.review.imageAfter": "بعد", + "agentManager.review.imageTooLarge": "الصورة كبيرة جدًا بحيث يتعذر معاينتها ({{size}}).", + "agentManager.review.imageUnreadable": "تعذر عرض هذه الصورة.", + "agentManager.review.imageUnavailable": "معاينة الصورة غير متاحة في لقطة الجلسة هذه.", "agentManager.review.endOfLongDiff": "لقد وصلت إلى النهاية!", "agentManager.import.pullRequest": "طلب سحب", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts index aec59b1bcf0..0fc6e14e066 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts @@ -182,6 +182,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} recolhidos", "agentManager.review.collapsedWithLarge": "{{collapsed}} recolhidos, {{large}} grandes", "agentManager.review.largeFileCollapsed": "Arquivo grande (recolhido)", + "agentManager.review.image": "Imagem", + "agentManager.review.imageBefore": "Antes", + "agentManager.review.imageAfter": "Depois", + "agentManager.review.imageTooLarge": "A imagem é muito grande para ser visualizada ({{size}}).", + "agentManager.review.imageUnreadable": "Não foi possível renderizar esta imagem.", + "agentManager.review.imageUnavailable": "A visualização da imagem não está disponível para este instantâneo da sessão.", "agentManager.review.endOfLongDiff": "Você chegou ao fim!", "agentManager.import.pullRequest": "Solicitação de extração", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts index d3b3ca7953b..ddb68c76991 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts @@ -183,6 +183,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} sažeto", "agentManager.review.collapsedWithLarge": "{{collapsed}} sažeto, {{large}} velikih", "agentManager.review.largeFileCollapsed": "Velika datoteka (sažeto)", + "agentManager.review.image": "Slika", + "agentManager.review.imageBefore": "Prije", + "agentManager.review.imageAfter": "Poslije", + "agentManager.review.imageTooLarge": "Slika je prevelika za pregled ({{size}}).", + "agentManager.review.imageUnreadable": "Ova slika se nije mogla prikazati.", + "agentManager.review.imageUnavailable": "Pregled slike nije dostupan za ovaj snimak sesije.", "agentManager.review.endOfLongDiff": "Došli ste do kraja!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts index 194ad09be4a..5089b29a5c2 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts @@ -182,6 +182,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} foldet sammen", "agentManager.review.collapsedWithLarge": "{{collapsed}} foldet sammen, {{large}} store", "agentManager.review.largeFileCollapsed": "Stor fil (sammenklappet)", + "agentManager.review.image": "Billede", + "agentManager.review.imageBefore": "Før", + "agentManager.review.imageAfter": "Efter", + "agentManager.review.imageTooLarge": "Billedet er for stort til at kunne forhåndsvises ({{size}}).", + "agentManager.review.imageUnreadable": "Dette billede kunne ikke gengives.", + "agentManager.review.imageUnavailable": "Forhåndsvisning af billedet er ikke tilgængelig for dette snapshot af sessionen.", "agentManager.review.endOfLongDiff": "Du nåede slutningen!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts index f6643237ba9..bf022edb96c 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts @@ -183,6 +183,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} eingeklappt", "agentManager.review.collapsedWithLarge": "{{collapsed}} eingeklappt, {{large}} groß", "agentManager.review.largeFileCollapsed": "Große Datei (eingeklappt)", + "agentManager.review.image": "Bild", + "agentManager.review.imageBefore": "Vorher", + "agentManager.review.imageAfter": "Nachher", + "agentManager.review.imageTooLarge": "Das Bild ist zu groß für eine Vorschau ({{size}}).", + "agentManager.review.imageUnreadable": "Dieses Bild konnte nicht dargestellt werden.", + "agentManager.review.imageUnavailable": "Für diesen Sitzungssnapshot ist keine Bildvorschau verfügbar.", "agentManager.review.endOfLongDiff": "Du hast das Ende erreicht!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts index bfddc7036fd..dc07e16d7bb 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts @@ -182,6 +182,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} contraídos", "agentManager.review.collapsedWithLarge": "{{collapsed}} contraídos, {{large}} grandes", "agentManager.review.largeFileCollapsed": "Archivo grande (contraído)", + "agentManager.review.image": "Imagen", + "agentManager.review.imageBefore": "Antes", + "agentManager.review.imageAfter": "Después", + "agentManager.review.imageTooLarge": "La imagen es demasiado grande para previsualizarla ({{size}}).", + "agentManager.review.imageUnreadable": "No se pudo renderizar esta imagen.", + "agentManager.review.imageUnavailable": "La vista previa de la imagen no está disponible para esta instantánea de la sesión.", "agentManager.review.endOfLongDiff": "¡Llegaste al final!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts index 435308590a4..8052baa2194 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts @@ -183,6 +183,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} repliés", "agentManager.review.collapsedWithLarge": "{{collapsed}} repliés, {{large}} volumineux", "agentManager.review.largeFileCollapsed": "Fichier volumineux (replié)", + "agentManager.review.image": "Image", + "agentManager.review.imageBefore": "Avant", + "agentManager.review.imageAfter": "Après", + "agentManager.review.imageTooLarge": "L’image est trop volumineuse pour être prévisualisée ({{size}}).", + "agentManager.review.imageUnreadable": "Cette image n’a pas pu être affichée.", + "agentManager.review.imageUnavailable": "L’aperçu de l’image n’est pas disponible pour cet instantané de session.", "agentManager.review.endOfLongDiff": "Vous êtes arrivé à la fin !", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts index 24e5636bb09..e877f1d560d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts @@ -188,6 +188,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} compressi", "agentManager.review.collapsedWithLarge": "{{collapsed}} compressi, {{large}} grandi", "agentManager.review.largeFileCollapsed": "File grande (compresso)", + "agentManager.review.image": "Immagine", + "agentManager.review.imageBefore": "Prima", + "agentManager.review.imageAfter": "Dopo", + "agentManager.review.imageTooLarge": "L'immagine è troppo grande per visualizzare l'anteprima ({{size}}).", + "agentManager.review.imageUnreadable": "Non è stato possibile visualizzare questa immagine.", + "agentManager.review.imageUnavailable": "L'anteprima dell'immagine non è disponibile per questa istantanea della sessione.", "agentManager.review.endOfLongDiff": "Sei arrivato alla fine!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts index 4d37c64e2e3..2935cc9b979 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts @@ -182,6 +182,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} 件折りたたみ", "agentManager.review.collapsedWithLarge": "{{collapsed}} 件折りたたみ、{{large}} 件がサイズ大", "agentManager.review.largeFileCollapsed": "大きなファイル(折りたたみ)", + "agentManager.review.image": "画像", + "agentManager.review.imageBefore": "変更前", + "agentManager.review.imageAfter": "変更後", + "agentManager.review.imageTooLarge": "画像が大きすぎるためプレビューできません({{size}})。", + "agentManager.review.imageUnreadable": "この画像を表示できませんでした。", + "agentManager.review.imageUnavailable": "このセッションスナップショットでは画像プレビューを利用できません。", "agentManager.review.endOfLongDiff": "最後まで到達しました!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts index f7d7e9a2d92..a2764c6f16d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts @@ -182,6 +182,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}}개 접힘", "agentManager.review.collapsedWithLarge": "{{collapsed}}개 접힘, {{large}}개 대용량", "agentManager.review.largeFileCollapsed": "큰 파일(접힘)", + "agentManager.review.image": "이미지", + "agentManager.review.imageBefore": "변경 전", + "agentManager.review.imageAfter": "변경 후", + "agentManager.review.imageTooLarge": "이미지가 너무 커서 미리 볼 수 없습니다({{size}}).", + "agentManager.review.imageUnreadable": "이 이미지를 렌더링할 수 없습니다.", + "agentManager.review.imageUnavailable": "이 세션 스냅샷에서는 이미지 미리보기를 사용할 수 없습니다.", "agentManager.review.endOfLongDiff": "끝까지 도달했습니다!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts index 713c7349005..9dffdf4d70a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts @@ -188,6 +188,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} ingeklapt", "agentManager.review.collapsedWithLarge": "{{collapsed}} ingeklapt, {{large}} groot", "agentManager.review.largeFileCollapsed": "Groot bestand (ingeklapt)", + "agentManager.review.image": "Afbeelding", + "agentManager.review.imageBefore": "Voor", + "agentManager.review.imageAfter": "Na", + "agentManager.review.imageTooLarge": "Afbeelding is te groot voor een voorbeeldweergave ({{size}}).", + "agentManager.review.imageUnreadable": "Deze afbeelding kon niet worden weergegeven.", + "agentManager.review.imageUnavailable": "Afbeeldingsvoorbeeld is niet beschikbaar voor deze sessiesnapshot.", "agentManager.review.endOfLongDiff": "Je hebt het einde bereikt!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts index a412c141caa..be07a49aa25 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts @@ -182,6 +182,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} kollapset", "agentManager.review.collapsedWithLarge": "{{collapsed}} kollapset, {{large}} store", "agentManager.review.largeFileCollapsed": "Stor fil (sammenfoldet)", + "agentManager.review.image": "Bilde", + "agentManager.review.imageBefore": "Før", + "agentManager.review.imageAfter": "Etter", + "agentManager.review.imageTooLarge": "Bildet er for stort til å forhåndsvises ({{size}}).", + "agentManager.review.imageUnreadable": "Dette bildet kunne ikke gjengis.", + "agentManager.review.imageUnavailable": "Forhåndsvisning av bildet er ikke tilgjengelig for dette øyeblikksbildet av økten.", "agentManager.review.endOfLongDiff": "Du nådde slutten!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts index f985f44198b..2eb0f92c84d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts @@ -181,6 +181,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} zwiniętych", "agentManager.review.collapsedWithLarge": "{{collapsed}} zwiniętych, {{large}} dużych", "agentManager.review.largeFileCollapsed": "Duży plik (zwinięty)", + "agentManager.review.image": "Obraz", + "agentManager.review.imageBefore": "Przed", + "agentManager.review.imageAfter": "Po", + "agentManager.review.imageTooLarge": "Obraz jest zbyt duży, aby wyświetlić podgląd ({{size}}).", + "agentManager.review.imageUnreadable": "Nie można wyświetlić tego obrazu.", + "agentManager.review.imageUnavailable": "Podgląd obrazu jest niedostępny dla tej migawki sesji.", "agentManager.review.endOfLongDiff": "Dotarłeś do końca!", "agentManager.import.pullRequest": "Prośba o scalenie", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts index 4b5a8c9ee37..b3d604a8c7f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts @@ -182,6 +182,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} свернуто", "agentManager.review.collapsedWithLarge": "{{collapsed}} свернуто, {{large}} больших", "agentManager.review.largeFileCollapsed": "Большой файл (свернут)", + "agentManager.review.image": "Изображение", + "agentManager.review.imageBefore": "До", + "agentManager.review.imageAfter": "После", + "agentManager.review.imageTooLarge": "Изображение слишком большое для предварительного просмотра ({{size}}).", + "agentManager.review.imageUnreadable": "Не удалось отобразить это изображение.", + "agentManager.review.imageUnavailable": "Предварительный просмотр изображения недоступен для этого снимка сеанса.", "agentManager.review.endOfLongDiff": "Вы дошли до конца!", "agentManager.import.pullRequest": "Запрос на слияние", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts index 26e20065593..a00249118a6 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts @@ -178,6 +178,12 @@ export const dict = { "agentManager.review.collapsedOnly": "ยุบ {{count}} รายการ", "agentManager.review.collapsedWithLarge": "ยุบ {{collapsed}} รายการ, ขนาดใหญ่ {{large}} รายการ", "agentManager.review.largeFileCollapsed": "ไฟล์ขนาดใหญ่ (พับอยู่)", + "agentManager.review.image": "รูปภาพ", + "agentManager.review.imageBefore": "ก่อน", + "agentManager.review.imageAfter": "หลัง", + "agentManager.review.imageTooLarge": "รูปภาพมีขนาดใหญ่เกินกว่าจะแสดงตัวอย่าง ({{size}})", + "agentManager.review.imageUnreadable": "ไม่สามารถแสดงผลรูปภาพนี้ได้", + "agentManager.review.imageUnavailable": "ไม่สามารถแสดงตัวอย่างรูปภาพสำหรับสแนปช็อตของเซสชันนี้ได้", "agentManager.review.endOfLongDiff": "คุณมาถึงท้ายสุดแล้ว!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts index 4ac533f5f4b..1d4951aa42b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts @@ -189,6 +189,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} daraltıldı", "agentManager.review.collapsedWithLarge": "{{collapsed}} daraltıldı, {{large}} büyük", "agentManager.review.largeFileCollapsed": "Büyük dosya (daraltıldı)", + "agentManager.review.image": "Görsel", + "agentManager.review.imageBefore": "Önce", + "agentManager.review.imageAfter": "Sonra", + "agentManager.review.imageTooLarge": "Görsel önizleme için çok büyük ({{size}}).", + "agentManager.review.imageUnreadable": "Bu görsel görüntülenemedi.", + "agentManager.review.imageUnavailable": "Bu oturum anlık görüntüsü için görsel önizlemesi kullanılamıyor.", "agentManager.review.endOfLongDiff": "Sonuna geldiniz!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts index f34fcafe5ce..96cb6a5866f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts @@ -190,6 +190,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} згорнуто", "agentManager.review.collapsedWithLarge": "{{collapsed}} згорнуто, {{large}} великих", "agentManager.review.largeFileCollapsed": "Великий файл (згорнуто)", + "agentManager.review.image": "Зображення", + "agentManager.review.imageBefore": "До", + "agentManager.review.imageAfter": "Після", + "agentManager.review.imageTooLarge": "Зображення завелике для попереднього перегляду ({{size}}).", + "agentManager.review.imageUnreadable": "Не вдалося відобразити це зображення.", + "agentManager.review.imageUnavailable": "Попередній перегляд зображення недоступний для цього знімка сеансу.", "agentManager.review.endOfLongDiff": "Ви дійшли до кінця!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts index fc4460778d5..d64ffdf2c33 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts @@ -177,6 +177,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} 个已折叠", "agentManager.review.collapsedWithLarge": "{{collapsed}} 个已折叠,{{large}} 个过大", "agentManager.review.largeFileCollapsed": "大文件(已折叠)", + "agentManager.review.image": "图片", + "agentManager.review.imageBefore": "之前", + "agentManager.review.imageAfter": "之后", + "agentManager.review.imageTooLarge": "图片过大,无法预览({{size}})。", + "agentManager.review.imageUnreadable": "无法渲染此图片。", + "agentManager.review.imageUnavailable": "此会话快照无法提供图片预览。", "agentManager.review.endOfLongDiff": "你已经到末尾了!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts index 96e306580ae..3d3170591d3 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts @@ -177,6 +177,12 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} 個已摺疊", "agentManager.review.collapsedWithLarge": "{{collapsed}} 個已摺疊,{{large}} 個過大", "agentManager.review.largeFileCollapsed": "大型檔案(已摺疊)", + "agentManager.review.image": "圖片", + "agentManager.review.imageBefore": "變更前", + "agentManager.review.imageAfter": "變更後", + "agentManager.review.imageTooLarge": "圖片太大,無法預覽({{size}})。", + "agentManager.review.imageUnreadable": "無法呈現此圖片。", + "agentManager.review.imageUnavailable": "此工作階段快照無法預覽圖片。", "agentManager.review.endOfLongDiff": "你已經到最後了!", "agentManager.import.pullRequest": "Pull Request", From f52ffab517bfa1aebc48c8c05c48423cd4ab854a Mon Sep 17 00:00:00 2001 From: Z User Date: Mon, 15 Jun 2026 14:09:35 +0000 Subject: [PATCH 20/78] =?UTF-8?q?fix:=20restore=20htmlparser2=20=E2=80=94?= =?UTF-8?q?=20accidentally=20dropped=20during=20rebase=20conflict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit htmlparser2 was added by upstream but lost during rebase conflict resolution in packages/opencode/package.json. It is actively imported at packages/opencode/src/tool/webfetch.ts:3 --- bun.lock | 7 ++++++- packages/opencode/package.json | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bun.lock b/bun.lock index bac65e0f3de..128179d2e13 100644 --- a/bun.lock +++ b/bun.lock @@ -460,6 +460,7 @@ "gitlab-ai-provider": "6.6.0", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", + "htmlparser2": "8.0.2", "iconv-lite": "0.7.2", "ignore": "7.0.5", "immer": "11.1.4", @@ -3056,7 +3057,7 @@ "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], - "htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="], + "htmlparser2": ["htmlparser2@8.0.2", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1", "entities": "^4.4.0" } }, "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA=="], "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], @@ -4602,6 +4603,8 @@ "chalk-template/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "cheerio/htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="], + "cheerio/undici": ["undici@7.25.0", "", {}, "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ=="], "clean-stack/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], @@ -4680,6 +4683,8 @@ "gray-matter/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="], + "htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "isomorphic-git/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], diff --git a/packages/opencode/package.json b/packages/opencode/package.json index cacdb391d79..982e24caa15 100644 --- a/packages/opencode/package.json +++ b/packages/opencode/package.json @@ -142,6 +142,7 @@ "gitlab-ai-provider": "6.6.0", "google-auth-library": "10.5.0", "gray-matter": "4.0.3", + "htmlparser2": "8.0.2", "iconv-lite": "0.7.2", "ignore": "7.0.5", "immer": "11.1.4", From a081fe37f928f624e91e806a8ce7ab622711a8b7 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Mon, 15 Jun 2026 16:20:03 +0200 Subject: [PATCH 21/78] style(vscode): format image diff translations --- packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts | 3 ++- packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts | 3 ++- packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts | 3 ++- packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts | 3 ++- packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts index 0fc6e14e066..b74ac0eab4a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts @@ -187,7 +187,8 @@ export const dict = { "agentManager.review.imageAfter": "Depois", "agentManager.review.imageTooLarge": "A imagem é muito grande para ser visualizada ({{size}}).", "agentManager.review.imageUnreadable": "Não foi possível renderizar esta imagem.", - "agentManager.review.imageUnavailable": "A visualização da imagem não está disponível para este instantâneo da sessão.", + "agentManager.review.imageUnavailable": + "A visualização da imagem não está disponível para este instantâneo da sessão.", "agentManager.review.endOfLongDiff": "Você chegou ao fim!", "agentManager.import.pullRequest": "Solicitação de extração", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts index 5089b29a5c2..aeae9335d86 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts @@ -187,7 +187,8 @@ export const dict = { "agentManager.review.imageAfter": "Efter", "agentManager.review.imageTooLarge": "Billedet er for stort til at kunne forhåndsvises ({{size}}).", "agentManager.review.imageUnreadable": "Dette billede kunne ikke gengives.", - "agentManager.review.imageUnavailable": "Forhåndsvisning af billedet er ikke tilgængelig for dette snapshot af sessionen.", + "agentManager.review.imageUnavailable": + "Forhåndsvisning af billedet er ikke tilgængelig for dette snapshot af sessionen.", "agentManager.review.endOfLongDiff": "Du nåede slutningen!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts index dc07e16d7bb..d9ef8188815 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts @@ -187,7 +187,8 @@ export const dict = { "agentManager.review.imageAfter": "Después", "agentManager.review.imageTooLarge": "La imagen es demasiado grande para previsualizarla ({{size}}).", "agentManager.review.imageUnreadable": "No se pudo renderizar esta imagen.", - "agentManager.review.imageUnavailable": "La vista previa de la imagen no está disponible para esta instantánea de la sesión.", + "agentManager.review.imageUnavailable": + "La vista previa de la imagen no está disponible para esta instantánea de la sesión.", "agentManager.review.endOfLongDiff": "¡Llegaste al final!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts index e877f1d560d..d1162022595 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts @@ -193,7 +193,8 @@ export const dict = { "agentManager.review.imageAfter": "Dopo", "agentManager.review.imageTooLarge": "L'immagine è troppo grande per visualizzare l'anteprima ({{size}}).", "agentManager.review.imageUnreadable": "Non è stato possibile visualizzare questa immagine.", - "agentManager.review.imageUnavailable": "L'anteprima dell'immagine non è disponibile per questa istantanea della sessione.", + "agentManager.review.imageUnavailable": + "L'anteprima dell'immagine non è disponibile per questa istantanea della sessione.", "agentManager.review.endOfLongDiff": "Sei arrivato alla fine!", "agentManager.import.pullRequest": "Pull Request", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts index be07a49aa25..f9a81430a4b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts @@ -187,7 +187,8 @@ export const dict = { "agentManager.review.imageAfter": "Etter", "agentManager.review.imageTooLarge": "Bildet er for stort til å forhåndsvises ({{size}}).", "agentManager.review.imageUnreadable": "Dette bildet kunne ikke gjengis.", - "agentManager.review.imageUnavailable": "Forhåndsvisning av bildet er ikke tilgjengelig for dette øyeblikksbildet av økten.", + "agentManager.review.imageUnavailable": + "Forhåndsvisning av bildet er ikke tilgjengelig for dette øyeblikksbildet av økten.", "agentManager.review.endOfLongDiff": "Du nådde slutten!", "agentManager.import.pullRequest": "Pull Request", From df34a364464cd6e6469f86651206ed2876a9664c Mon Sep 17 00:00:00 2001 From: Z User Date: Tue, 16 Jun 2026 11:29:06 +0000 Subject: [PATCH 22/78] fix: restore storybook addons and revert knip workaround per review feedback Storybook addons (addon-docs, addon-a11y, addon-themes) are configured features, not unused dependencies. Their removal disabled documentation, accessibility, and theme panels rather than pruning dead code. This restores: - kilo-ui: addon-docs, addon-themes, addon-a11y - kilo-vscode: addon-docs, addon-a11y - .storybook/main.ts configs to their original addon arrays - knip.json: reverts story file entry point additions (addons serve as implicit entry points for story files) Remaining removals (34 deps) are unaffected. --- packages/kilo-ui/.storybook/main.ts | 2 +- packages/kilo-ui/package.json | 3 +++ packages/kilo-vscode/.storybook/main.ts | 2 +- packages/kilo-vscode/knip.json | 4 +--- packages/kilo-vscode/package.json | 2 ++ 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/kilo-ui/.storybook/main.ts b/packages/kilo-ui/.storybook/main.ts index 565ac1f1c0e..d47e3caac84 100644 --- a/packages/kilo-ui/.storybook/main.ts +++ b/packages/kilo-ui/.storybook/main.ts @@ -5,7 +5,7 @@ import solidPlugin from "vite-plugin-solid" const config: StorybookConfig = { framework: "storybook-solidjs-vite", stories: ["../src/**/*.stories.@(ts|tsx)"], - addons: [], + addons: ["@storybook/addon-docs", "@storybook/addon-themes", "@storybook/addon-a11y"], refs: {}, viteFinal: async (config) => { return mergeConfig(config, { diff --git a/packages/kilo-ui/package.json b/packages/kilo-ui/package.json index 226f55f3fdb..b68c0fcf20b 100644 --- a/packages/kilo-ui/package.json +++ b/packages/kilo-ui/package.json @@ -96,6 +96,9 @@ }, "devDependencies": { "@playwright/test": "1.57.0", + "@storybook/addon-a11y": "10.2.10", + "@storybook/addon-docs": "10.2.10", + "@storybook/addon-themes": "10.2.10", "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", "storybook": "10.2.10", diff --git a/packages/kilo-vscode/.storybook/main.ts b/packages/kilo-vscode/.storybook/main.ts index 2d368c49bec..300333356fb 100644 --- a/packages/kilo-vscode/.storybook/main.ts +++ b/packages/kilo-vscode/.storybook/main.ts @@ -5,7 +5,7 @@ import solidPlugin from "vite-plugin-solid" const config: StorybookConfig = { framework: "storybook-solidjs-vite", stories: ["../webview-ui/src/stories/**/*.stories.@(ts|tsx)"], - addons: [], + addons: ["@storybook/addon-docs", "@storybook/addon-a11y"], staticDirs: [{ from: "../assets/icons", to: "/icons" }], refs: {}, viteFinal: async (config) => { diff --git a/packages/kilo-vscode/knip.json b/packages/kilo-vscode/knip.json index 67c0c14e45c..78b5a311058 100644 --- a/packages/kilo-vscode/knip.json +++ b/packages/kilo-vscode/knip.json @@ -12,9 +12,7 @@ "src/**/__tests__/**/*.{ts,spec.ts}", "src/**/*.test.ts", "tests/**/*.{ts,mts}", - "script/*.ts", - "webview-ui/src/stories/**/*.stories.@(ts|tsx)", - "webview-ui/src/stories/*.tsx" + "script/*.ts" ], "project": ["src/**/*.ts", "webview-ui/**/*.{ts,tsx}"], "ignoreExportsUsedInFile": true, diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index 780afdebbb3..59edbea0294 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -1051,6 +1051,8 @@ "devDependencies": { "@axe-core/playwright": "4.11.3", "@playwright/test": "1.57.0", + "@storybook/addon-a11y": "10.2.10", + "@storybook/addon-docs": "10.2.10", "@types/diff": "^6.0.0", "@types/mocha": "^10.0.10", "@types/node": "22.x", From 2fa0890928f7dd060125ad4f4083b8bd2bf3e69b Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Tue, 16 Jun 2026 11:49:21 +0200 Subject: [PATCH 23/78] fix(vscode): restore speech input availability --- .../restore-authenticated-speech-input.md | 7 ++ .../code-with-ai/features/speech-to-text.md | 2 +- .../pages/getting-started/settings/index.md | 23 +++---- packages/kilo-vscode/package.json | 4 -- packages/kilo-vscode/src/provider-actions.ts | 16 ++++- .../kilo-vscode/src/speech-to-text/models.ts | 10 +-- .../model-selector-accessibility.spec.ts | 7 ++ .../tests/unit/provider-actions-save.test.ts | 44 +++++++++++++ .../unit/speech-to-text-availability.test.ts | 21 +++--- .../unit/speech-to-text-models-sync.test.ts | 3 +- .../tests/unit/use-speech-to-text.test.ts | 34 +++++++++- .../webview-ui/agent-manager/DiffPanel.tsx | 2 +- .../agent-manager/NewWorktreeDialog.tsx | 2 +- .../diff-viewer/FullScreenDiffView.tsx | 2 +- .../src/components/chat/PromptInput.tsx | 2 +- .../components/settings/ExperimentalTab.tsx | 40 ----------- .../src/components/settings/ModelsTab.tsx | 47 +++++++++++++ .../src/components/settings/ProvidersTab.tsx | 2 +- .../components/speech-to-text/availability.ts | 13 ++-- .../speech-to-text/useSpeechToText.ts | 31 +++++---- .../kilo-vscode/webview-ui/src/i18n/ar.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/br.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/bs.ts | 7 +- .../kilo-vscode/webview-ui/src/i18n/da.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/de.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/en.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/es.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/fr.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/it.ts | 7 +- .../kilo-vscode/webview-ui/src/i18n/ja.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/ko.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/nl.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/no.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/pl.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/ru.ts | 7 +- .../kilo-vscode/webview-ui/src/i18n/th.ts | 7 +- .../kilo-vscode/webview-ui/src/i18n/tr.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/uk.ts | 7 +- .../kilo-vscode/webview-ui/src/i18n/zh.ts | 6 +- .../kilo-vscode/webview-ui/src/i18n/zht.ts | 6 +- .../src/stories/settings.stories.tsx | 11 ++++ .../server/httpapi/groups/kilo-gateway.ts | 17 +++++ .../server/httpapi/handlers/kilo-gateway.ts | 8 +++ .../server/httpapi-exercise-scenarios.ts | 5 ++ .../kilocode/server/httpapi-public.test.ts | 7 ++ .../server/kilo-gateway-statuses.test.ts | 11 ++++ packages/sdk/js/src/v2/gen/sdk.gen.ts | 32 +++++++++ packages/sdk/js/src/v2/gen/types.gen.ts | 31 +++++++++ packages/sdk/openapi.json | 66 +++++++++++++++++++ 49 files changed, 460 insertions(+), 165 deletions(-) create mode 100644 .changeset/restore-authenticated-speech-input.md diff --git a/.changeset/restore-authenticated-speech-input.md b/.changeset/restore-authenticated-speech-input.md new file mode 100644 index 00000000000..6f826ad0660 --- /dev/null +++ b/.changeset/restore-authenticated-speech-input.md @@ -0,0 +1,7 @@ +--- +"@kilocode/cli": patch +"@kilocode/sdk": patch +"kilo-code": patch +--- + +Restore speech input when profile details are unavailable, move transcription model selection to the Models tab, and default transcription to Whisper Large V3 Turbo. diff --git a/packages/kilo-docs/pages/code-with-ai/features/speech-to-text.md b/packages/kilo-docs/pages/code-with-ai/features/speech-to-text.md index c98ae767c66..f95f55bfadb 100644 --- a/packages/kilo-docs/pages/code-with-ai/features/speech-to-text.md +++ b/packages/kilo-docs/pages/code-with-ai/features/speech-to-text.md @@ -41,7 +41,7 @@ Enable and sign in to the Kilo provider to use voice input in prompt fields. Req ## Choose a model -You can optionally choose a transcription model in **Settings** > **Experimental** > **Speech to Text Model**. Kilo stores this choice as `experimental.speech_to_text_model` in your global Kilo CLI config (`~/.config/kilo/kilo.jsonc`). +You can optionally choose a transcription model in **Settings** > **Models** > **Speech to Text Model**. Kilo stores this choice as `experimental.speech_to_text_model` in your global Kilo CLI config (`~/.config/kilo/kilo.jsonc`). --- diff --git a/packages/kilo-docs/pages/getting-started/settings/index.md b/packages/kilo-docs/pages/getting-started/settings/index.md index 79faf946cf5..a3e9ddd5361 100644 --- a/packages/kilo-docs/pages/getting-started/settings/index.md +++ b/packages/kilo-docs/pages/getting-started/settings/index.md @@ -47,6 +47,18 @@ Use **Local Config** or **Global Config** in the Settings header to open the mat If you check config files into version control, make sure they do not contain API keys or other secrets (e.g., `provider.*.options.apiKey`). Use environment variables for credentials instead. {% /callout %} +### Voice Transcription Model + +When the Kilo provider is enabled and you are signed in, choose the transcription model under **Models** > **Speech to Text Model**. This stores `experimental.speech_to_text_model` in your global Kilo CLI config: + +```json +{ + "experimental": { + "speech_to_text_model": "openai/whisper-large-v3-turbo" + } +} +``` + ### Reasoning Blocks Reasoning blocks stay expanded by default in the VS Code chat UI. Enable **Auto-Collapse Reasoning** in the Display tab, or set `auto_collapse_reasoning` in `kilo.jsonc`, to collapse them after the agent finishes writing them: @@ -198,20 +210,9 @@ Available experimental settings include: - **Share mode** - `manual`, `auto`, or `disabled` session sharing - **LSP integration** - expose language server diagnostics to the agent - **Paste summary** - summarize large clipboard pastes before including them -- **Speech to Text Model** - optionally select the transcription model - **Batch tool** - allow the agent to batch multiple tool calls in one step - **OpenTelemetry** - enable Kilo telemetry and optional OTLP export when configured -Voice input appears automatically when the Kilo provider is enabled and you are signed in. Choosing **Speech to Text Model** stores `experimental.speech_to_text_model` in your global Kilo CLI config (`~/.config/kilo/kilo.jsonc`): - -```json -{ - "experimental": { - "speech_to_text_model": "openai/gpt-4o-mini-transcribe" - } -} -``` - Advanced options not exposed in the UI can be configured via the `experimental` key in `kilo.jsonc`: ```json diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index 876b6f9e9f4..59993be026c 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -902,10 +902,6 @@ "default": false, "description": "Enable chat textarea autocomplete" }, - "kilo-code.new.speechToText.model": { - "type": "string", - "description": "Model to use for experimental speech-to-text voice input. Requires Kilo Gateway." - }, "kilo-code.new.claudeCodeCompat": { "type": "boolean", "default": false, diff --git a/packages/kilo-vscode/src/provider-actions.ts b/packages/kilo-vscode/src/provider-actions.ts index 739ff339e9d..650d3e1714a 100644 --- a/packages/kilo-vscode/src/provider-actions.ts +++ b/packages/kilo-vscode/src/provider-actions.ts @@ -9,7 +9,7 @@ import { sanitizeCustomProviderConfig, withCustomProviderDeletions, } from "./shared/custom-provider" -import { isCustomProviderPackage, KILO_AUTO, parseModelString } from "./shared/provider-model" +import { isCustomProviderPackage, KILO_AUTO, KILO_PROVIDER_ID, parseModelString } from "./shared/provider-model" import { configFeatures } from "./features" /** @@ -50,7 +50,7 @@ function same(a: unknown, b: unknown): boolean { return akeys.every((key, index) => key === bkeys[index] && same(a[key], b[key])) } -/** Fetch auth methods alongside the provider list. Auth states default to empty (endpoint not yet available). */ +/** Fetch provider availability and authentication state without exposing stored credentials. */ export async function fetchProviderData(client: KiloClient, dir: string) { const authRequest = typeof client.provider.auth === "function" @@ -59,10 +59,18 @@ export async function fetchProviderData(client: KiloClient, dir: string) { .then((r) => r.data ?? {}) .catch(() => ({})) : Promise.resolve({}) + const kiloRequest = + typeof client.kilo?.authStatus === "function" + ? client.kilo + .authStatus({ directory: dir }, { throwOnError: true }) + .then((r) => (r.data?.authenticated ? (r.data.type ?? null) : null)) + .catch(() => null) + : Promise.resolve(null) - const [{ data: response }, authMethods] = await Promise.all([ + const [{ data: response }, authMethods, kiloAuth] = await Promise.all([ client.provider.list({ directory: dir }, { throwOnError: true }), authRequest, + kiloRequest, ]) const authStates: Record = {} const storedKeys: Record = {} @@ -83,6 +91,8 @@ export async function fetchProviderData(client: KiloClient, dir: string) { delete next.key return next as (typeof response.all)[number] }) + delete authStates[KILO_PROVIDER_ID] + if (kiloAuth) authStates[KILO_PROVIDER_ID] = kiloAuth return { response: { ...response, all }, authMethods, authStates, storedKeys } } diff --git a/packages/kilo-vscode/src/speech-to-text/models.ts b/packages/kilo-vscode/src/speech-to-text/models.ts index be872d81c96..7eab1dfa181 100644 --- a/packages/kilo-vscode/src/speech-to-text/models.ts +++ b/packages/kilo-vscode/src/speech-to-text/models.ts @@ -6,6 +6,11 @@ export interface SpeechToTextModelDef { } const models: SpeechToTextModelDef[] = [ + { + id: "openai/whisper-large-v3-turbo", + label: "Whisper Large V3 Turbo", + provider: "OpenAI-compatible", + }, { id: "openai/gpt-4o-mini-transcribe", label: "GPT-4o Mini Transcribe", @@ -23,11 +28,6 @@ const models: SpeechToTextModelDef[] = [ label: "Whisper 1", provider: "OpenAI", }, - { - id: "openai/whisper-large-v3-turbo", - label: "Whisper Large V3 Turbo", - provider: "OpenAI-compatible", - }, { id: "openai/whisper-large-v3", label: "Whisper Large V3", diff --git a/packages/kilo-vscode/tests/model-selector-accessibility.spec.ts b/packages/kilo-vscode/tests/model-selector-accessibility.spec.ts index dd634e67ff9..fcd2111199e 100644 --- a/packages/kilo-vscode/tests/model-selector-accessibility.spec.ts +++ b/packages/kilo-vscode/tests/model-selector-accessibility.spec.ts @@ -133,6 +133,13 @@ test("settings and mode editing expose distinct model field purposes", async ({ /Override the default model for specific modes/, ) + await load(page, "settings--models-speech-to-text") + const speech = page.getByRole("button", { name: "Speech to Text Model: Chirp 3" }) + await expect(speech).toBeEnabled() + await speech.click() + await page.getByRole("option", { name: "GPT-4o Mini Transcribe (OpenAI)" }).click() + await expect(page.getByRole("button", { name: "Speech to Text Model: GPT-4o Mini Transcribe" })).toBeVisible() + await load(page, "settings--mode-edit-export") await expect(page.getByRole("button", { name: /Model Override:/ })).toHaveAccessibleDescription( "Override the default model for this agent", diff --git a/packages/kilo-vscode/tests/unit/provider-actions-save.test.ts b/packages/kilo-vscode/tests/unit/provider-actions-save.test.ts index 523ba6f94d8..e80edd36879 100644 --- a/packages/kilo-vscode/tests/unit/provider-actions-save.test.ts +++ b/packages/kilo-vscode/tests/unit/provider-actions-save.test.ts @@ -444,6 +444,50 @@ describe("fetchProviderData", () => { expect("key" in item).toBe(false) }) + it("uses local Kilo auth status instead of profile availability", async () => { + const client = { + provider: { + list: async () => ({ + data: { + all: [{ id: "kilo", name: "Kilo Gateway", source: "custom", env: [], models: {} }], + connected: ["kilo"], + default: { kilo: "kilo-auto/frontier" }, + }, + }), + auth: async () => ({ data: {} }), + }, + kilo: { + authStatus: async () => ({ data: { authenticated: true, type: "oauth" } }), + }, + } as unknown as Parameters[0] + + const result = await fetchProviderData(client, "/tmp") + + expect(result.authStates).toEqual({ kilo: "oauth" }) + }) + + it("does not infer Kilo speech access without stored Gateway auth", async () => { + const client = { + provider: { + list: async () => ({ + data: { + all: [{ id: "kilo", name: "Kilo Gateway", source: "config", key: "configured", env: [], models: {} }], + connected: ["kilo"], + default: { kilo: "kilo-auto/frontier" }, + }, + }), + auth: async () => ({ data: {} }), + }, + kilo: { + authStatus: async () => ({ data: { authenticated: false } }), + }, + } as unknown as Parameters[0] + + const result = await fetchProviderData(client, "/tmp") + + expect(result.authStates).toEqual({}) + }) + it("retains stripped keys for providers with a configured baseURL", async () => { const client = { provider: { diff --git a/packages/kilo-vscode/tests/unit/speech-to-text-availability.test.ts b/packages/kilo-vscode/tests/unit/speech-to-text-availability.test.ts index 2e6de323033..83848168963 100644 --- a/packages/kilo-vscode/tests/unit/speech-to-text-availability.test.ts +++ b/packages/kilo-vscode/tests/unit/speech-to-text-availability.test.ts @@ -6,17 +6,20 @@ import { import { DEFAULT_SPEECH_TO_TEXT_MODEL } from "../../src/speech-to-text/models" describe("speech-to-text availability", () => { - const providers = ["kilo"] - const profile = {} - - it("shows speech input by default when Kilo access exists", () => { - expect(canUseSpeechToText({}, providers, profile)).toBe(true) + it("shows speech input for stored Kilo credentials", () => { + expect(canUseSpeechToText({}, { kilo: "oauth" })).toBe(true) + expect(canUseSpeechToText({}, { kilo: "api" })).toBe(true) }) - it("hides speech input without a signed-in, enabled Kilo provider", () => { - expect(canUseSpeechToText({}, [], profile)).toBe(false) - expect(canUseSpeechToText({}, providers, null)).toBe(false) - expect(canUseSpeechToText({ disabled_providers: ["kilo"] }, providers, profile)).toBe(false) + it("hides speech input without usable Kilo credentials", () => { + expect(canUseSpeechToText({}, {})).toBe(false) + expect(canUseSpeechToText({}, { kilo: "wellknown" })).toBe(false) + }) + + it("honors enabled and disabled provider configuration", () => { + expect(canUseSpeechToText({ disabled_providers: ["kilo"] }, { kilo: "oauth" })).toBe(false) + expect(canUseSpeechToText({ enabled_providers: ["openai"] }, { kilo: "oauth" })).toBe(false) + expect(canUseSpeechToText({ enabled_providers: ["kilo"] }, { kilo: "oauth" })).toBe(true) }) it("normalizes configured and unknown transcription models", () => { diff --git a/packages/kilo-vscode/tests/unit/speech-to-text-models-sync.test.ts b/packages/kilo-vscode/tests/unit/speech-to-text-models-sync.test.ts index a09e20e8261..79a75d87256 100644 --- a/packages/kilo-vscode/tests/unit/speech-to-text-models-sync.test.ts +++ b/packages/kilo-vscode/tests/unit/speech-to-text-models-sync.test.ts @@ -6,7 +6,8 @@ import { } from "../../src/speech-to-text/models" describe("speech-to-text model catalog", () => { - it("keeps the first speech model as the fallback default", () => { + it("uses Whisper Large V3 Turbo as the fallback default", () => { + expect(DEFAULT_SPEECH_TO_TEXT_MODEL.id).toBe("openai/whisper-large-v3-turbo") expect(DEFAULT_SPEECH_TO_TEXT_MODEL.id).toBe(SPEECH_TO_TEXT_MODELS[0]?.id) }) diff --git a/packages/kilo-vscode/tests/unit/use-speech-to-text.test.ts b/packages/kilo-vscode/tests/unit/use-speech-to-text.test.ts index 52ac796d67a..ae28f0b4171 100644 --- a/packages/kilo-vscode/tests/unit/use-speech-to-text.test.ts +++ b/packages/kilo-vscode/tests/unit/use-speech-to-text.test.ts @@ -2,8 +2,13 @@ import { describe, expect, it, mock } from "bun:test" import { createRoot } from "solid-js" import type { ExtensionMessage, WebviewMessage } from "../../webview-ui/src/types/messages" +type Toast = { + actions?: Array<{ onClick: string | (() => void) }> +} + +const toasts: Toast[] = [] mock.module("@kilocode/kilo-ui/toast", () => ({ - showToast: () => undefined, + showToast: (toast: Toast) => toasts.push(toast), })) const { useSpeechToText } = await import("../../webview-ui/src/components/speech-to-text/useSpeechToText") @@ -11,6 +16,8 @@ const { useSpeechToText } = await import("../../webview-ui/src/components/speech function setup() { const sent: WebviewMessage[] = [] let handler: ((message: ExtensionMessage) => void) | undefined + let logins = 0 + toasts.length = 0 const root = createRoot((dispose) => ({ dispose, @@ -24,16 +31,37 @@ function setup() { } }, }, - { profileData: () => ({}), goToLogin: () => {} }, + { goToLogin: () => logins++ }, { t: (key) => key }, ), })) const fire = (message: ExtensionMessage) => handler?.(message) - return { ...root, fire, sent } + return { ...root, fire, sent, logins: () => logins } } describe("useSpeechToText", () => { + it("offers sign-in when stored credentials stop authenticating", () => { + const ctx = setup() + + ctx.speech.start({ model: "scribe", insert: () => {} }) + const start = ctx.sent[0] + if (start?.type !== "speechToTextStart") throw new Error("speech start message missing") + + ctx.fire({ + type: "speechToTextError", + requestId: start.requestId, + error: "Unauthorized", + code: "not_authenticated", + }) + const action = toasts[0]?.actions?.find((item) => typeof item.onClick === "function") + if (typeof action?.onClick === "function") action.onClick() + + expect(ctx.logins()).toBe(1) + expect(ctx.speech.error()).toBe("speechToText.error.loginRequired") + ctx.dispose() + }) + it("runs the stop completion after inserting a transcript", () => { const ctx = setup() const text: string[] = [] diff --git a/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx index 78be9646d81..9dfb2d4e28f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx @@ -92,7 +92,7 @@ export const DiffPanel: Component = (props) => { const provider = useProvider() const { config } = useConfig() const speech = useSpeechToText(vscode, server, { t }) - const canUseSpeech = () => canUseSpeechToText(config(), provider.connected(), server.profileData()) + const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates()) const speechModel = () => selectedSpeechToTextModel(config()) const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent) const sendAllKeybind = () => diff --git a/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx b/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx index 0685e6b72b1..25679aad3c0 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/NewWorktreeDialog.tsx @@ -103,7 +103,7 @@ export const NewWorktreeDialog: Component<{ onClose: () => void; defaultBaseBran const [highlightedIndex, setHighlightedIndex] = createSignal(0) const [variant, setVariant] = createSignal(session.currentVariant()) const speech = useSpeechToText(vscode, server, { t }) - const canUseSpeech = () => canUseSpeechToText(config(), provider.connected(), server.profileData()) + const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates()) const speechModel = () => selectedSpeechToTextModel(config()) // Variant list for the currently selected model diff --git a/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx b/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx index 9a881d61244..efb58d344b6 100644 --- a/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx +++ b/packages/kilo-vscode/webview-ui/diff-viewer/FullScreenDiffView.tsx @@ -96,7 +96,7 @@ export const FullScreenDiffView: Component = (props) => const provider = useProvider() const { config } = useConfig() const speech = useSpeechToText(vscode, server, { t }) - const canUseSpeech = () => canUseSpeechToText(config(), provider.connected(), server.profileData()) + const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates()) const speechModel = () => selectedSpeechToTextModel(config()) const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent) const sendAllKeybind = () => diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx index d30f37f1b77..f53a5dcbed4 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx @@ -295,7 +295,7 @@ export const PromptInput: Component = (props) => { const isBusy = () => isPromptBusy(session.status(), !!props.suggesting?.(), !!props.questioning?.(), session.submitting()) const isDisabled = () => !server.isConnected() - const canUseSpeech = () => canUseSpeechToText(config(), provider.connected(), server.profileData()) + const canUseSpeech = () => canUseSpeechToText(config(), provider.authStates()) const speechModel = () => selectedSpeechToTextModel(config()) const hasInput = () => text().trim().length > 0 || imageAttach.images().length > 0 || reviewComments().length > 0 const canSend = () => diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx index 0b4400918d5..30c240b4f97 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/ExperimentalTab.tsx @@ -1,19 +1,13 @@ import { Component, For, Show, createMemo, createSignal, onCleanup, onMount } from "solid-js" import { Switch } from "@kilocode/kilo-ui/switch" -import { Tooltip } from "@kilocode/kilo-ui/tooltip" import { Select } from "@kilocode/kilo-ui/select" import { TextField } from "@kilocode/kilo-ui/text-field" import { Card } from "@kilocode/kilo-ui/card" import { useConfig } from "../../context/config" import { useLanguage } from "../../context/language" -import { useProvider } from "../../context/provider" -import { useServer } from "../../context/server" import { useVSCode } from "../../context/vscode" import type { ExtensionMessage } from "../../types/messages" 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" interface ShareOption { value: string @@ -29,8 +23,6 @@ const SHARE_OPTIONS: ShareOption[] = [ const ExperimentalTab: Component = () => { const { config, updateConfig } = useConfig() const language = useLanguage() - const provider = useProvider() - const server = useServer() const vscode = useVSCode() const [active, setActive] = createSignal(false) @@ -47,8 +39,6 @@ const ExperimentalTab: Component = () => { }) const experimental = createMemo(() => config().experimental ?? {}) - const kiloReady = createMemo(() => hasSpeechToTextAccess(config(), provider.connected(), server.profileData())) - const speechModel = createMemo(() => selectedSpeechToTextModel(config())) const updateExperimental = (key: string, value: unknown) => { updateConfig({ @@ -164,36 +154,6 @@ const ExperimentalTab: Component = () => { - - - item.value} + label={(item) => `${item.label} (${item.provider})`} + onSelect={(item) => + updateConfig({ + experimental: { + ...config().experimental, + speech_to_text_model: item?.value ?? DEFAULT_SPEECH_TO_TEXT_MODEL.id, + }, + }) + } + variant="secondary" + size="small" + triggerVariant="settings" + triggerProps={{ + "aria-label": `${language.t("settings.models.speechToTextModel.title")}: ${speechOption()?.label}`, + }} + disabled={!kiloReady()} + placeholder={DEFAULT_SPEECH_TO_TEXT_MODEL.label} + /> + + { onCleanup(action.dispose) - const kiloLoggedIn = createMemo(() => !!server.profileData()) + const kiloLoggedIn = createMemo(() => !!provider.authStates()[KILO_PROVIDER_ID]) const connectedProviders = createMemo(() => { const ids = visibleConnectedIds(provider.connected(), provider.authStates()) diff --git a/packages/kilo-vscode/webview-ui/src/components/speech-to-text/availability.ts b/packages/kilo-vscode/webview-ui/src/components/speech-to-text/availability.ts index 32a263c2b6c..3c386670b20 100644 --- a/packages/kilo-vscode/webview-ui/src/components/speech-to-text/availability.ts +++ b/packages/kilo-vscode/webview-ui/src/components/speech-to-text/availability.ts @@ -2,18 +2,23 @@ import { KILO_PROVIDER_ID } from "../../../../src/shared/provider-model" import { getSpeechToTextModel } from "../../../../src/speech-to-text/models" type Cfg = { + enabled_providers?: string[] disabled_providers?: string[] experimental?: { speech_to_text_model?: string } } -export function hasSpeechToTextAccess(cfg: Cfg, providers: readonly string[], profile: unknown | null): boolean { - return providers.includes(KILO_PROVIDER_ID) && !cfg.disabled_providers?.includes(KILO_PROVIDER_ID) && !!profile +type AuthState = "api" | "oauth" | "wellknown" + +export function hasSpeechToTextAccess(cfg: Cfg, auth: Readonly>): boolean { + const enabled = !cfg.enabled_providers || cfg.enabled_providers.includes(KILO_PROVIDER_ID) + const type = auth[KILO_PROVIDER_ID] + return enabled && !cfg.disabled_providers?.includes(KILO_PROVIDER_ID) && (type === "api" || type === "oauth") } -export function canUseSpeechToText(cfg: Cfg, providers: readonly string[], profile: unknown | null): boolean { - return hasSpeechToTextAccess(cfg, providers, profile) +export function canUseSpeechToText(cfg: Cfg, auth: Readonly>): boolean { + return hasSpeechToTextAccess(cfg, auth) } export function selectedSpeechToTextModel(cfg: Cfg): string { diff --git a/packages/kilo-vscode/webview-ui/src/components/speech-to-text/useSpeechToText.ts b/packages/kilo-vscode/webview-ui/src/components/speech-to-text/useSpeechToText.ts index a22afef7f8f..7deb0c06721 100644 --- a/packages/kilo-vscode/webview-ui/src/components/speech-to-text/useSpeechToText.ts +++ b/packages/kilo-vscode/webview-ui/src/components/speech-to-text/useSpeechToText.ts @@ -9,7 +9,6 @@ type VSCode = { } type Server = { - profileData: Accessor goToLogin: () => void } @@ -70,6 +69,10 @@ export function useSpeechToText(vscode: VSCode, server: Server, lang: Lang): Spe } if (msg.type === "speechToTextError") { + if (msg.code === "not_authenticated") { + login() + return + } fail(msg.error) return } @@ -98,19 +101,6 @@ export function useSpeechToText(vscode: VSCode, server: Server, lang: Lang): Spe insert = opts.insert setError(undefined) - if (!server.profileData()) { - showToast({ - variant: "error", - title: lang.t("speechToText.error.loginRequired"), - actions: [ - { label: lang.t("common.signIn"), onClick: server.goToLogin }, - { label: lang.t("common.dismiss"), onClick: "dismiss" }, - ], - }) - fail(lang.t("speechToText.error.loginRequired"), false) - return - } - counter++ request = `${prefix}-${counter}` setState("recording") @@ -144,6 +134,19 @@ export function useSpeechToText(vscode: VSCode, server: Server, lang: Lang): Spe setError(undefined) } + function login() { + const message = lang.t("speechToText.error.loginRequired") + showToast({ + variant: "error", + title: message, + actions: [ + { label: lang.t("common.signIn"), onClick: server.goToLogin }, + { label: lang.t("common.dismiss"), onClick: "dismiss" }, + ], + }) + fail(message, false) + } + function fail(message: string, toast = true) { cleanup() setState("error") diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index 02f8c0dc6f8..e119b2c8fae 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -1298,10 +1298,10 @@ export const dict = { "settings.experimental.speechToText.title": "تحويل الصوت إلى نص", "settings.experimental.speechToText.description": "تمكين الإدخال الصوتي في حقول المطالبة باستخدام حساب Kilo الخاص بك من خلال Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "قم بتمكين وتسجيل الدخول إلى مزود Kilo لاستخدام Speech to Text. ميزة Speech to Text مدعومة حاليًا فقط مع Kilo Gateway.", - "settings.experimental.speechToTextModel.title": "نموذج تحويل الصوت إلى نص", - "settings.experimental.speechToTextModel.description": "اختر نموذج نسخ Kilo Gateway للإدخال الصوتي.", + "settings.models.speechToTextModel.title": "نموذج تحويل الصوت إلى نص", + "settings.models.speechToTextModel.description": "اختر نموذج نسخ Kilo Gateway للإدخال الصوتي.", "settings.experimental.continueOnDeny.title": "المتابعة عند الرفض", "settings.experimental.continueOnDeny.description": "متابعة حلقة الوكيل عند رفض الإذن", "settings.experimental.mcpTimeout.title": "مهلة MCP (مللي ثانية)", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index e38574210f2..da360635cbb 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -1329,10 +1329,10 @@ export const dict = { "settings.experimental.speechToText.title": "Fala para texto", "settings.experimental.speechToText.description": "Ative a entrada de voz nos campos de prompt usando sua conta do Kilo por meio do Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Ative e faça login no provedor Kilo para usar o Speech to Text. Atualmente, o Speech to Text é compatível apenas com o Kilo Gateway.", - "settings.experimental.speechToTextModel.title": "Modelo de fala para texto", - "settings.experimental.speechToTextModel.description": + "settings.models.speechToTextModel.title": "Modelo de fala para texto", + "settings.models.speechToTextModel.description": "Escolha o modelo de transcrição do Kilo Gateway para entrada de voz.", "settings.experimental.continueOnDeny.title": "Continuar ao negar", "settings.experimental.continueOnDeny.description": "Continuar o loop do agente quando uma permissão é negada", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index 09bc1ef88a5..55734c100f9 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -1328,11 +1328,10 @@ export const dict = { "settings.experimental.speechToText.title": "Govor u tekst", "settings.experimental.speechToText.description": "Omogućite glasovni unos u poljima za promptove koristeći vaš Kilo račun preko Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Omogućite i prijavite se na Kilo provajder da biste koristili Speech to Text. Speech to Text je trenutno podržan samo uz Kilo Gateway.", - "settings.experimental.speechToTextModel.title": "Model govora u tekst", - "settings.experimental.speechToTextModel.description": - "Odaberite Kilo Gateway model za transkripciju za glasovni unos.", + "settings.models.speechToTextModel.title": "Model govora u tekst", + "settings.models.speechToTextModel.description": "Odaberite Kilo Gateway model za transkripciju za glasovni unos.", "settings.experimental.continueOnDeny.title": "Nastavi pri odbijanju", "settings.experimental.continueOnDeny.description": "Nastavi petlju agenta kada je dozvola odbijena", "settings.experimental.mcpTimeout.title": "MCP istek vremena (ms)", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index 855f8235953..610a769b57c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -1322,10 +1322,10 @@ export const dict = { "settings.experimental.speechToText.title": "Tale til tekst", "settings.experimental.speechToText.description": "Aktivér stemmeinput i prompt-felter ved hjælp af din Kilo-konto gennem Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Aktivér og log ind på Kilo-udbyderen for at bruge Speech to Text. Speech to Text understøttes i øjeblikket kun med Kilo Gateway.", - "settings.experimental.speechToTextModel.title": "Model til tale til tekst", - "settings.experimental.speechToTextModel.description": "Vælg Kilo Gateway-transskriptionsmodellen til stemmeinput.", + "settings.models.speechToTextModel.title": "Model til tale til tekst", + "settings.models.speechToTextModel.description": "Vælg Kilo Gateway-transskriptionsmodellen til stemmeinput.", "settings.experimental.continueOnDeny.title": "Fortsæt ved afvisning", "settings.experimental.continueOnDeny.description": "Fortsæt agentløkken, når en tilladelse afvises", "settings.experimental.mcpTimeout.title": "MCP-timeout (ms)", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index 933f1c9c84f..9404c93acc4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -1345,10 +1345,10 @@ export const dict = { "settings.experimental.speechToText.title": "Sprache zu Text", "settings.experimental.speechToText.description": "Aktivieren Sie die Spracheingabe in Prompt-Feldern mit Ihrem Kilo-Konto über Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Aktivieren Sie den Kilo-Anbieter und melden Sie sich an, um Speech to Text zu verwenden. Speech to Text wird derzeit nur mit Kilo Gateway unterstützt.", - "settings.experimental.speechToTextModel.title": "Sprache-zu-Text-Modell", - "settings.experimental.speechToTextModel.description": + "settings.models.speechToTextModel.title": "Sprache-zu-Text-Modell", + "settings.models.speechToTextModel.description": "Wählen Sie das Kilo Gateway-Transkriptionsmodell für die Spracheingabe.", "settings.experimental.continueOnDeny.title": "Bei Ablehnung fortfahren", "settings.experimental.continueOnDeny.description": diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index 1f487e3049f..ba6a8c2029e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -1305,10 +1305,10 @@ export const dict = { "settings.experimental.speechToText.title": "Speech to Text", "settings.experimental.speechToText.description": "Enable voice input in prompt fields using your Kilo account through Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Enable and sign in to the Kilo provider to use Speech to Text. Speech to Text is currently only supported through Kilo Gateway.", - "settings.experimental.speechToTextModel.title": "Speech to Text Model", - "settings.experimental.speechToTextModel.description": "Choose the Kilo Gateway transcription model for voice input.", + "settings.models.speechToTextModel.title": "Speech to Text Model", + "settings.models.speechToTextModel.description": "Choose the Kilo Gateway transcription model for voice input.", "settings.experimental.continueOnDeny.title": "Continue on Deny", "settings.experimental.continueOnDeny.description": "Continue the agent loop when a permission is denied", "settings.experimental.mcpTimeout.title": "MCP Timeout (ms)", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index 6e69ac5ae69..e0cf93835dc 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -1337,10 +1337,10 @@ export const dict = { "settings.experimental.speechToText.title": "Voz a texto", "settings.experimental.speechToText.description": "Habilita la entrada de voz en los campos de prompt usando tu cuenta de Kilo a través de Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Habilita e inicia sesión en el proveedor Kilo para usar Speech to Text. Actualmente, Speech to Text solo es compatible con Kilo Gateway.", - "settings.experimental.speechToTextModel.title": "Modelo de voz a texto", - "settings.experimental.speechToTextModel.description": + "settings.models.speechToTextModel.title": "Modelo de voz a texto", + "settings.models.speechToTextModel.description": "Elige el modelo de transcripción de Kilo Gateway para la entrada de voz.", "settings.experimental.continueOnDeny.title": "Continuar al denegar", "settings.experimental.continueOnDeny.description": "Continuar el bucle del agente cuando se deniega un permiso", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index f514c29142d..72da1869a21 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -1352,10 +1352,10 @@ export const dict = { "settings.experimental.speechToText.title": "Transcription vocale", "settings.experimental.speechToText.description": "Activez la saisie vocale dans les champs de prompt en utilisant votre compte Kilo via Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Activez et connectez-vous au fournisseur Kilo pour utiliser Speech to Text. Speech to Text n'est actuellement pris en charge qu'avec Kilo Gateway.", - "settings.experimental.speechToTextModel.title": "Modèle de transcription vocale", - "settings.experimental.speechToTextModel.description": + "settings.models.speechToTextModel.title": "Modèle de transcription vocale", + "settings.models.speechToTextModel.description": "Choisissez le modèle de transcription Kilo Gateway pour la saisie vocale.", "settings.experimental.continueOnDeny.title": "Continuer en cas de refus", "settings.experimental.continueOnDeny.description": diff --git a/packages/kilo-vscode/webview-ui/src/i18n/it.ts b/packages/kilo-vscode/webview-ui/src/i18n/it.ts index b1d7a95367d..3c6fdd12cdc 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/it.ts @@ -1535,11 +1535,10 @@ export const dict = { "settings.experimental.speechToText.title": "Da voce a testo", "settings.experimental.speechToText.description": "Abilita input vocale nei campi prompt usando il tuo account Kilo tramite Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Abilita e accedi al provider Kilo per usare Da voce a testo. Da voce a testo è attualmente supportato solo tramite Kilo Gateway.", - "settings.experimental.speechToTextModel.title": "Modello Da voce a testo", - "settings.experimental.speechToTextModel.description": - "Scegli il modello di trascrizione Kilo Gateway per l'input vocale.", + "settings.models.speechToTextModel.title": "Modello Da voce a testo", + "settings.models.speechToTextModel.description": "Scegli il modello di trascrizione Kilo Gateway per l'input vocale.", // Compaction limit "settings.context.compactionLimit.title": "Limite compattazione automatica", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index 373ca32ef55..719e7e46106 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -1316,10 +1316,10 @@ export const dict = { "settings.experimental.speechToText.title": "音声認識", "settings.experimental.speechToText.description": "Kilo Gateway経由でKiloアカウントを使用して、プロンプトフィールドでの音声入力を有効にします。", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Speech to Text を使用するには、Kilo プロバイダーを有効にしてサインインしてください。現在、Speech to Text は Kilo Gateway でのみサポートされています。", - "settings.experimental.speechToTextModel.title": "音声認識モデル", - "settings.experimental.speechToTextModel.description": "音声入力に使用するKilo Gateway文字起こしモデルを選択します。", + "settings.models.speechToTextModel.title": "音声認識モデル", + "settings.models.speechToTextModel.description": "音声入力に使用するKilo Gateway文字起こしモデルを選択します。", "settings.experimental.continueOnDeny.title": "拒否時に続行", "settings.experimental.continueOnDeny.description": "権限が拒否された場合にエージェントループを続行", "settings.experimental.mcpTimeout.title": "MCPタイムアウト(ミリ秒)", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index 509214bf2e0..fdac017fa0b 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -1310,10 +1310,10 @@ export const dict = { "settings.experimental.speechToText.title": "음성 텍스트 변환", "settings.experimental.speechToText.description": "Kilo Gateway를 통해 Kilo 계정을 사용하여 프롬프트 필드에서 음성 입력을 활성화합니다.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Speech to Text를 사용하려면 Kilo 제공자를 활성화하고 로그인하세요. 현재 Speech to Text는 Kilo Gateway에서만 지원됩니다.", - "settings.experimental.speechToTextModel.title": "음성 텍스트 변환 모델", - "settings.experimental.speechToTextModel.description": "음성 입력에 사용할 Kilo Gateway 변환 모델을 선택하세요.", + "settings.models.speechToTextModel.title": "음성 텍스트 변환 모델", + "settings.models.speechToTextModel.description": "음성 입력에 사용할 Kilo Gateway 변환 모델을 선택하세요.", "settings.experimental.continueOnDeny.title": "거부 시 계속", "settings.experimental.continueOnDeny.description": "권한이 거부되면 에이전트 루프 계속", "settings.experimental.mcpTimeout.title": "MCP 타임아웃 (ms)", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts index bc8b5c21263..061cfc9f8d7 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/nl.ts @@ -1323,10 +1323,10 @@ export const dict = { "settings.experimental.speechToText.title": "Spraak naar tekst", "settings.experimental.speechToText.description": "Schakel spraakinvoer in promptvelden in met uw Kilo-account via Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Schakel de Kilo-provider in en meld u aan om Speech to Text te gebruiken. Speech to Text wordt momenteel alleen ondersteund met Kilo Gateway.", - "settings.experimental.speechToTextModel.title": "Spraak-naar-tekst-model", - "settings.experimental.speechToTextModel.description": "Kies het Kilo Gateway-transcriptiemodel voor spraakinvoer.", + "settings.models.speechToTextModel.title": "Spraak-naar-tekst-model", + "settings.models.speechToTextModel.description": "Kies het Kilo Gateway-transcriptiemodel voor spraakinvoer.", "settings.experimental.continueOnDeny.title": "Doorgaan bij weigering", "settings.experimental.continueOnDeny.description": "Ga door met de agent loop wanneer een toestemming wordt geweigerd", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index deb8d4abb87..441ccb1f0ae 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -1287,10 +1287,10 @@ export const dict = { "settings.experimental.speechToText.title": "Tale til tekst", "settings.experimental.speechToText.description": "Aktiver taleinndata i prompt-felt ved å bruke din Kilo-konto gjennom Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Aktiver og logg på Kilo-leverandøren for å bruke Speech to Text. Speech to Text støttes for øyeblikket bare med Kilo Gateway.", - "settings.experimental.speechToTextModel.title": "Tale-til-tekst-modell", - "settings.experimental.speechToTextModel.description": "Velg Kilo Gateway-transkripsjonsmodellen for taleinndata.", + "settings.models.speechToTextModel.title": "Tale-til-tekst-modell", + "settings.models.speechToTextModel.description": "Velg Kilo Gateway-transkripsjonsmodellen for taleinndata.", "settings.experimental.continueOnDeny.title": "Fortsett ved avvisning", "settings.experimental.continueOnDeny.description": "Fortsett agentløkken når en tillatelse avvises", "settings.experimental.mcpTimeout.title": "MCP-tidsavbrudd (ms)", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index b2bf5edb8c3..72b5aa16639 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -1287,10 +1287,10 @@ export const dict = { "settings.experimental.speechToText.title": "Mowa na tekst", "settings.experimental.speechToText.description": "Włącz wprowadzanie głosowe w polach promptów przy użyciu konta Kilo za pośrednictwem Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Włącz i zaloguj się do dostawcy Kilo, aby korzystać ze Speech to Text. Speech to Text jest obecnie obsługiwane tylko z Kilo Gateway.", - "settings.experimental.speechToTextModel.title": "Model mowy na tekst", - "settings.experimental.speechToTextModel.description": + "settings.models.speechToTextModel.title": "Model mowy na tekst", + "settings.models.speechToTextModel.description": "Wybierz model transkrypcji Kilo Gateway dla wprowadzania głosowego.", "settings.experimental.continueOnDeny.title": "Kontynuuj przy odmowie", "settings.experimental.continueOnDeny.description": "Kontynuuj pętlę agenta po odmowie uprawnienia", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index 4ca0f12164b..4a7159224a4 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -1327,11 +1327,10 @@ export const dict = { "settings.experimental.speechToText.title": "Речь в текст", "settings.experimental.speechToText.description": "Включите голосовой ввод в полях запросов, используя вашу учетную запись Kilo через Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Включите провайдер Kilo и выполните вход, чтобы использовать Speech to Text. В настоящее время Speech to Text поддерживается только с Kilo Gateway.", - "settings.experimental.speechToTextModel.title": "Модель речи в текст", - "settings.experimental.speechToTextModel.description": - "Выберите модель транскрипции Kilo Gateway для голосового ввода.", + "settings.models.speechToTextModel.title": "Модель речи в текст", + "settings.models.speechToTextModel.description": "Выберите модель транскрипции Kilo Gateway для голосового ввода.", "settings.experimental.continueOnDeny.title": "Продолжить при отказе", "settings.experimental.continueOnDeny.description": "Продолжить цикл агента при отказе в разрешении", "settings.experimental.mcpTimeout.title": "Таймаут MCP (мс)", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 7bc0a177cb2..297b79124a0 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -1306,11 +1306,10 @@ export const dict = { "settings.experimental.speechToText.title": "แปลงเสียงเป็นข้อความ", "settings.experimental.speechToText.description": "เปิดใช้งานการป้อนข้อมูลด้วยเสียงในช่องพรอมต์โดยใช้บัญชี Kilo ของคุณผ่าน Kilo Gateway", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "เปิดใช้งานและลงชื่อเข้าใช้ผู้ให้บริการ Kilo เพื่อใช้ Speech to Text ปัจจุบัน Speech to Text รองรับเฉพาะกับ Kilo Gateway เท่านั้น", - "settings.experimental.speechToTextModel.title": "โมเดลแปลงเสียงเป็นข้อความ", - "settings.experimental.speechToTextModel.description": - "เลือกโมเดลการถอดเสียง Kilo Gateway สำหรับการป้อนข้อมูลด้วยเสียง", + "settings.models.speechToTextModel.title": "โมเดลแปลงเสียงเป็นข้อความ", + "settings.models.speechToTextModel.description": "เลือกโมเดลการถอดเสียง Kilo Gateway สำหรับการป้อนข้อมูลด้วยเสียง", "settings.experimental.continueOnDeny.title": "ดำเนินต่อเมื่อถูกปฏิเสธ", "settings.experimental.continueOnDeny.description": "ดำเนินลูปเอเจนต์ต่อเมื่อสิทธิ์ถูกปฏิเสธ", "settings.experimental.mcpTimeout.title": "หมดเวลา MCP (มิลลิวินาที)", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts index 13568ff9969..99396b2881f 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/tr.ts @@ -1316,10 +1316,10 @@ export const dict = { "settings.experimental.speechToText.title": "Sesten metne", "settings.experimental.speechToText.description": "Kilo Gateway üzerinden Kilo hesabınızı kullanarak komut alanlarında sesli girişi etkinleştirin.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Speech to Text kullanmak için Kilo sağlayıcısını etkinleştirin ve giriş yapın. Speech to Text şu anda yalnızca Kilo Gateway ile desteklenmektedir.", - "settings.experimental.speechToTextModel.title": "Sesten metne modeli", - "settings.experimental.speechToTextModel.description": "Sesli giriş için Kilo Gateway transkripsiyon modelini seçin.", + "settings.models.speechToTextModel.title": "Sesten metne modeli", + "settings.models.speechToTextModel.description": "Sesli giriş için Kilo Gateway transkripsiyon modelini seçin.", "settings.experimental.continueOnDeny.title": "Reddetme Durumunda Devam Et", "settings.experimental.continueOnDeny.description": "Bir izin reddedildiğinde ajan döngüsüne devam et", "settings.experimental.mcpTimeout.title": "MCP Zaman Aşımı (ms)", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts index f8b2ec4a502..57c05ee0687 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/uk.ts @@ -1315,11 +1315,10 @@ export const dict = { "settings.experimental.speechToText.title": "Мовлення в текст", "settings.experimental.speechToText.description": "Увімкніть голосове введення в полях запитів, використовуючи ваш обліковий запис Kilo через Kilo Gateway.", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "Увімкніть провайдер Kilo та виконайте вхід, щоб використовувати Speech to Text. Наразі Speech to Text підтримується лише з Kilo Gateway.", - "settings.experimental.speechToTextModel.title": "Модель мовлення в текст", - "settings.experimental.speechToTextModel.description": - "Виберіть модель транскрипції Kilo Gateway для голосового введення.", + "settings.models.speechToTextModel.title": "Модель мовлення в текст", + "settings.models.speechToTextModel.description": "Виберіть модель транскрипції Kilo Gateway для голосового введення.", "settings.experimental.continueOnDeny.title": "Продовжувати при відхиленні", "settings.experimental.continueOnDeny.description": "Продовжувати цикл агента, коли дозвіл відхилено", "settings.experimental.mcpTimeout.title": "Тайм-аут MCP (мс)", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index 89fd63765f7..a28cdef048c 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -1286,10 +1286,10 @@ export const dict = { "settings.experimental.codebaseSearch.description": "启用 AI 驱动的自然语言代码库搜索", "settings.experimental.speechToText.title": "语音转文本", "settings.experimental.speechToText.description": "通过 Kilo Gateway 使用您的 Kilo 帐户在提示词字段中启用语音输入。", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "启用并登录 Kilo 提供商以使用 Speech to Text。Speech to Text 目前仅支持通过 Kilo Gateway 使用。", - "settings.experimental.speechToTextModel.title": "语音转文本模型", - "settings.experimental.speechToTextModel.description": "选择用于语音输入的 Kilo Gateway 转录模型。", + "settings.models.speechToTextModel.title": "语音转文本模型", + "settings.models.speechToTextModel.description": "选择用于语音输入的 Kilo Gateway 转录模型。", "settings.experimental.continueOnDeny.title": "拒绝后继续", "settings.experimental.continueOnDeny.description": "权限被拒绝时继续智能体循环", "settings.experimental.mcpTimeout.title": "MCP 超时(毫秒)", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index 31762615e9b..3c60d2557ec 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -1254,10 +1254,10 @@ export const dict = { "settings.experimental.codebaseSearch.description": "啟用 AI 驅動的自然語言程式碼庫搜尋", "settings.experimental.speechToText.title": "語音轉文字", "settings.experimental.speechToText.description": "透過 Kilo Gateway 使用您的 Kilo 帳戶在提示詞欄位中啟用語音輸入。", - "settings.experimental.speechToText.disabledDescription": + "settings.models.speechToText.disabledDescription": "啟用並登入 Kilo 供應商以使用 Speech to Text。Speech to Text 目前僅支援透過 Kilo Gateway 使用。", - "settings.experimental.speechToTextModel.title": "語音轉文字模型", - "settings.experimental.speechToTextModel.description": "選擇用於語音輸入的 Kilo Gateway 轉錄模型。", + "settings.models.speechToTextModel.title": "語音轉文字模型", + "settings.models.speechToTextModel.description": "選擇用於語音輸入的 Kilo Gateway 轉錄模型。", "settings.experimental.continueOnDeny.title": "拒絕後繼續", "settings.experimental.continueOnDeny.description": "權限被拒絕時繼續 Agent 迴圈", "settings.experimental.mcpTimeout.title": "MCP 逾時(毫秒)", diff --git a/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx index 3f960e7fa23..755554ec02c 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx @@ -84,6 +84,17 @@ export const ModelsAccessibleLabels: Story = { ), } +export const ModelsSpeechToText: Story = { + name: "ModelsTab — speech-to-text model", + render: () => ( + +
+ +
+
+ ), +} + function OpenModelPicker(props: { children: any }) { let ref: HTMLDivElement | undefined onMount(() => { diff --git a/packages/opencode/src/kilocode/server/httpapi/groups/kilo-gateway.ts b/packages/opencode/src/kilocode/server/httpapi/groups/kilo-gateway.ts index 9d51df1aac7..fc1bc59ee9e 100644 --- a/packages/opencode/src/kilocode/server/httpapi/groups/kilo-gateway.ts +++ b/packages/opencode/src/kilocode/server/httpapi/groups/kilo-gateway.ts @@ -33,6 +33,11 @@ export const ProfileWithBalance = Schema.Struct({ currentOrgId: Schema.NullOr(Schema.String), }) +export const AuthStatus = Schema.Struct({ + authenticated: Schema.Boolean, + type: Schema.optional(Schema.Literals(["api", "oauth"])), +}) + export const NotificationAction = Schema.Struct({ actionText: Schema.String, actionURL: Schema.String, @@ -241,6 +246,7 @@ export const CloudSessionData = Schema.Struct({ export const KiloGatewayPaths = { modes: `${root}/modes`, profile: `${root}/profile`, + authStatus: `${root}/auth-status`, fim: `${root}/fim`, edit: `${root}/edit`, audioTranscriptions: `${root}/audio/transcriptions`, @@ -268,6 +274,17 @@ export const KiloGatewayApi = HttpApi.make("kilo") description: "Fetch user profile and organizations from Kilo Gateway", }), ), + HttpApiEndpoint.get("authStatus", KiloGatewayPaths.authStatus, { + query: WorkspaceRoutingQuery, + success: described(AuthStatus, "Kilo authentication status"), + error: HttpApiError.BadRequest, + }).annotateMerge( + OpenApi.annotations({ + identifier: "kilo.authStatus", + summary: "Get Kilo authentication status", + description: "Check whether a locally stored Kilo credential can authenticate Gateway requests", + }), + ), HttpApiEndpoint.get("modes", KiloGatewayPaths.modes, { query: WorkspaceRoutingQuery, success: described(OrganizationModes, "Organization modes list"), diff --git a/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts b/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts index 6e57c9cee7a..f6fa2aa97c3 100644 --- a/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts +++ b/packages/opencode/src/kilocode/server/httpapi/handlers/kilo-gateway.ts @@ -73,6 +73,13 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo", return { profile, balance, currentOrgId } }) + const authStatus = Effect.fn("KiloGatewayHttpApi.authStatus")(function* () { + const info = yield* auth.get("kilo").pipe(Effect.mapError(() => new HttpApiError.BadRequest({}))) + const type = getToken(info) && (info?.type === "api" || info?.type === "oauth") ? info.type : undefined + if (!type) return { authenticated: false } + return { authenticated: true, type } + }) + const proxyAuth = Effect.fn("KiloGatewayHttpApi.proxyAuth")(function* () { const info = yield* auth.get("kilo").pipe(Effect.mapError(() => new HttpApiError.Unauthorized({}))) return { @@ -504,6 +511,7 @@ export const kiloGatewayHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilo", return handlers .handle("profile", profile) + .handle("authStatus", authStatus) .handle("modes", modes) .handle("fim", fim) .handle("edit", edit) diff --git a/packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts b/packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts index 6236f5d8a53..6ac923569fa 100644 --- a/packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts +++ b/packages/opencode/test/kilocode/server/httpapi-exercise-scenarios.ts @@ -132,6 +132,11 @@ export const kiloScenarios: Scenario[] = [ http.protected.get("/indexing/models", "indexing.models").json(200, object), http.protected.get("/indexing/warnings", "indexing.warnings").json(200, array), http.protected.get("/kilo/profile", "kilo.profile").probe({ path: "/path" }).status(401), + http.protected.get("/kilo/auth-status", "kilo.authStatus").json(200, (body) => { + object(body) + check(body.authenticated === false, "Kilo auth status should report signed out") + check(body.type === undefined, "Kilo auth status should not expose a credential type while signed out") + }), http.protected.get("/kilo/modes", "kilo.modes").json(200, (body) => { object(body) array(body.modes) diff --git a/packages/opencode/test/kilocode/server/httpapi-public.test.ts b/packages/opencode/test/kilocode/server/httpapi-public.test.ts index 64ffdc1de5b..4f2ddde7980 100644 --- a/packages/opencode/test/kilocode/server/httpapi-public.test.ts +++ b/packages/opencode/test/kilocode/server/httpapi-public.test.ts @@ -17,6 +17,7 @@ type Schema = { items?: Schema properties?: Record type?: string + enum?: string[] minLength?: number maxLength?: number pattern?: string @@ -165,6 +166,12 @@ describe("Kilo PublicApi OpenAPI contract", () => { expect(profile?.balance).toEqual({ anyOf: [expect.objectContaining({ type: "object" }), { type: "null" }] }) expect(profile?.currentOrgId).toEqual({ anyOf: [{ type: "string" }, { type: "null" }] }) + const auth = response(KiloGatewayPaths.authStatus)?.properties + expect(auth).toEqual({ + authenticated: { type: "boolean" }, + type: { type: "string", enum: ["api", "oauth"] }, + }) + const sessions = response(KiloGatewayPaths.cloudSessions)?.properties expect(sessions?.cliSessions?.items?.properties?.title).toEqual({ anyOf: [{ type: "string" }, { type: "null" }], diff --git a/packages/opencode/test/kilocode/server/kilo-gateway-statuses.test.ts b/packages/opencode/test/kilocode/server/kilo-gateway-statuses.test.ts index d3be9a25916..5426a3b105e 100644 --- a/packages/opencode/test/kilocode/server/kilo-gateway-statuses.test.ts +++ b/packages/opencode/test/kilocode/server/kilo-gateway-statuses.test.ts @@ -84,6 +84,17 @@ function post(path: string, body: Record) { } describe("Kilo gateway HttpApi statuses", () => { + it.live("reports locally stored API authentication without a Gateway request", () => + Effect.gen(function* () { + yield* stub(() => Promise.reject(new Error("unexpected Gateway request"))) + + const response = yield* HttpClient.get(KiloGatewayPaths.authStatus) + + expect(response.status).toBe(200) + expect(yield* response.json).toEqual({ authenticated: true, type: "api" }) + }), + ) + it.live("preserves cloud session list rate limits", () => Effect.gen(function* () { yield* stub(() => new Response("rate limited", { status: 429 })) diff --git a/packages/sdk/js/src/v2/gen/sdk.gen.ts b/packages/sdk/js/src/v2/gen/sdk.gen.ts index 636cd78b170..dbf279c1093 100644 --- a/packages/sdk/js/src/v2/gen/sdk.gen.ts +++ b/packages/sdk/js/src/v2/gen/sdk.gen.ts @@ -91,6 +91,8 @@ import type { InstanceDisposeResponses, KiloAudioTranscriptionsErrors, KiloAudioTranscriptionsResponses, + KiloAuthStatusErrors, + KiloAuthStatusResponses, KiloClawChatCredentialsResponses, KiloClawStatusErrors, KiloClawStatusResponses, @@ -6523,6 +6525,36 @@ export class Kilo extends HeyApiClient { }) } + /** + * Get Kilo authentication status + * + * Check whether a locally stored Kilo credential can authenticate Gateway requests + */ + public authStatus( + parameters?: { + directory?: string + workspace?: string + }, + options?: Options, + ) { + const params = buildClientParams( + [parameters], + [ + { + args: [ + { in: "query", key: "directory" }, + { in: "query", key: "workspace" }, + ], + }, + ], + ) + return (options?.client ?? this.client).get({ + url: "/kilo/auth-status", + ...options, + ...params, + }) + } + /** * Get organization custom modes * diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index 90d28d3e8a9..b605d88a2cf 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -8837,6 +8837,37 @@ export type KiloProfileResponses = { export type KiloProfileResponse = KiloProfileResponses[keyof KiloProfileResponses] +export type KiloAuthStatusData = { + body?: never + path?: never + query?: { + directory?: string + workspace?: string + } + url: "/kilo/auth-status" +} + +export type KiloAuthStatusErrors = { + /** + * Bad request + */ + 400: BadRequestError +} + +export type KiloAuthStatusError = KiloAuthStatusErrors[keyof KiloAuthStatusErrors] + +export type KiloAuthStatusResponses = { + /** + * Kilo authentication status + */ + 200: { + authenticated: boolean + type?: "api" | "oauth" + } +} + +export type KiloAuthStatusResponse = KiloAuthStatusResponses[keyof KiloAuthStatusResponses] + export type KiloModesData = { body?: never path?: never diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index bb1370a2899..8243bf7d8e9 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -11382,6 +11382,72 @@ ] } }, + "/kilo/auth-status": { + "get": { + "tags": ["kilo"], + "operationId": "kilo.authStatus", + "parameters": [ + { + "name": "directory", + "in": "query", + "schema": { + "type": "string" + }, + "required": false + }, + { + "name": "workspace", + "in": "query", + "schema": { + "type": "string" + }, + "required": false + } + ], + "responses": { + "200": { + "description": "Kilo authentication status", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "authenticated": { + "type": "boolean" + }, + "type": { + "type": "string", + "enum": ["api", "oauth"] + } + }, + "required": ["authenticated"], + "additionalProperties": false, + "description": "Kilo authentication status" + } + } + } + }, + "400": { + "description": "Bad request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BadRequestError" + } + } + } + } + }, + "description": "Check whether a locally stored Kilo credential can authenticate Gateway requests", + "summary": "Get Kilo authentication status", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createKiloClient } from \"@kilocode/sdk\n\nconst client = createKiloClient()\nawait client.kilo.authStatus({\n ...\n})" + } + ] + } + }, "/kilo/modes": { "get": { "tags": ["kilo"], From e50bb7d1d3a1e2e8acec0e2becfb95f515d31bba Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Tue, 16 Jun 2026 14:11:36 +0200 Subject: [PATCH 24/78] refactor(vscode): simplify speech state lookups --- packages/kilo-vscode/src/provider-actions.ts | 11 ++++------- .../tests/unit/provider-actions-save.test.ts | 9 +++++++++ .../webview-ui/src/components/settings/ModelsTab.tsx | 4 ++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/kilo-vscode/src/provider-actions.ts b/packages/kilo-vscode/src/provider-actions.ts index 650d3e1714a..742380b5604 100644 --- a/packages/kilo-vscode/src/provider-actions.ts +++ b/packages/kilo-vscode/src/provider-actions.ts @@ -59,13 +59,10 @@ export async function fetchProviderData(client: KiloClient, dir: string) { .then((r) => r.data ?? {}) .catch(() => ({})) : Promise.resolve({}) - const kiloRequest = - typeof client.kilo?.authStatus === "function" - ? client.kilo - .authStatus({ directory: dir }, { throwOnError: true }) - .then((r) => (r.data?.authenticated ? (r.data.type ?? null) : null)) - .catch(() => null) - : Promise.resolve(null) + const kiloRequest = client.kilo + .authStatus({ directory: dir }, { throwOnError: true }) + .then((r) => (r.data?.authenticated ? (r.data.type ?? null) : null)) + .catch(() => null) const [{ data: response }, authMethods, kiloAuth] = await Promise.all([ client.provider.list({ directory: dir }, { throwOnError: true }), diff --git a/packages/kilo-vscode/tests/unit/provider-actions-save.test.ts b/packages/kilo-vscode/tests/unit/provider-actions-save.test.ts index e80edd36879..a6499bab1c6 100644 --- a/packages/kilo-vscode/tests/unit/provider-actions-save.test.ts +++ b/packages/kilo-vscode/tests/unit/provider-actions-save.test.ts @@ -54,6 +54,9 @@ function createCtx(existing: ExistingGlobal = { disabled_providers: [] }, merged }), auth: async () => ({ data: {} }), }, + kilo: { + authStatus: async () => ({ data: { authenticated: false } }), + }, global: { config: { get: async () => ({ data: existing }), @@ -435,6 +438,9 @@ describe("fetchProviderData", () => { }), auth: async () => ({ data: {} }), }, + kilo: { + authStatus: async () => ({ data: { authenticated: false } }), + }, } as unknown as Parameters[0] const result = await fetchProviderData(client, "/tmp") @@ -518,6 +524,9 @@ describe("fetchProviderData", () => { }), auth: async () => ({ data: {} }), }, + kilo: { + authStatus: async () => ({ data: { authenticated: false } }), + }, } as unknown as Parameters[0] const result = await fetchProviderData(client, "/tmp") diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx index 53314e25208..abce3c773a4 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx @@ -43,8 +43,8 @@ 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()) ?? SPEECH_TO_TEXT_MODEL_OPTIONS[0], + const speechOption = createMemo(() => + SPEECH_TO_TEXT_MODEL_OPTIONS.find((item) => item.value === speechModel()), ) const kiloReady = createMemo(() => hasSpeechToTextAccess(config(), provider.authStates())) const variantKey = createMemo(() => config().subagent_model ?? undefined) From ac1f0c8bd90578ee86ee4d0ea372f4ceed548750 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Tue, 16 Jun 2026 14:16:28 +0200 Subject: [PATCH 25/78] chore(vscode): format speech model selector --- .../webview-ui/src/components/settings/ModelsTab.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx index abce3c773a4..81f57c39682 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/ModelsTab.tsx @@ -43,9 +43,7 @@ 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 speechOption = createMemo(() => SPEECH_TO_TEXT_MODEL_OPTIONS.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 ?? {})) From 1bffc5337cb121b96b5a946e0bbeaa08df76e2b9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 16 Jun 2026 12:20:21 +0000 Subject: [PATCH 26/78] chore: update kilo-vscode visual regression baselines --- .../settings/models-accessible-labels-chromium-linux.png | 4 ++-- .../settings/models-autocomplete-open-chromium-linux.png | 4 ++-- .../settings/models-speech-to-text-chromium-linux.png | 3 +++ .../settings/settings-panel-chromium-linux.png | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-speech-to-text-chromium-linux.png diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-accessible-labels-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-accessible-labels-chromium-linux.png index db7441c87c1..34dae072479 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-accessible-labels-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-accessible-labels-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bae54b771e2d7da8c8b1dcab42d9d51662ec888ebb618b24af11e597c79df7b7 -size 37836 +oid sha256:9e041b27abbb19de884e1b928d415d49182872b06018411ed589197424a5ac22 +size 48674 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-autocomplete-open-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-autocomplete-open-chromium-linux.png index db7441c87c1..34dae072479 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-autocomplete-open-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-autocomplete-open-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bae54b771e2d7da8c8b1dcab42d9d51662ec888ebb618b24af11e597c79df7b7 -size 37836 +oid sha256:9e041b27abbb19de884e1b928d415d49182872b06018411ed589197424a5ac22 +size 48674 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-speech-to-text-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-speech-to-text-chromium-linux.png new file mode 100644 index 00000000000..866111072a1 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/models-speech-to-text-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ced53d4974eeacc629eede823c7e37191f40ea2816f80f18c1de1a833fbcc614 +size 45341 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/settings-panel-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/settings-panel-chromium-linux.png index 9fb41085b48..545366bac04 100644 --- a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/settings-panel-chromium-linux.png +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/settings/settings-panel-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:57fa5c60dd92a2f4299e58ead8a682c4802de415a496c247af2eba2faf0d63e4 -size 50341 +oid sha256:f4dbd3be2cdf72a789c17076afa73ddeb2eacbe201ace8d8a2657eb95b23a413 +size 53851 From 5686915e01ee5c0b78eae565cf58134356ad791e Mon Sep 17 00:00:00 2001 From: markijbema <624143+markijbema@users.noreply.github.com> Date: Tue, 16 Jun 2026 12:55:41 +0000 Subject: [PATCH 27/78] ci: add jetbrains container image and fix containers.yml trigger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add packages/containers/jetbrains/Dockerfile extending bun-node with Java 21 (apt-get, multi-arch safe) and Gradle 9.4.1 pre-cached in GRADLE_USER_HOME so the wrapper skips the network download - Register the new image in build.ts (after bun-node, before rust) and update README - Fix containers.yml branch trigger: dev → main so images are actually published from the default branch on every relevant push Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .github/workflows/containers.yml | 2 +- packages/containers/README.md | 1 + packages/containers/jetbrains/Dockerfile | 35 ++++++++++++++++++++++++ packages/containers/script/build.ts | 2 +- 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 packages/containers/jetbrains/Dockerfile diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml index 797252b7fd8..5d8c4ad1c18 100644 --- a/.github/workflows/containers.yml +++ b/.github/workflows/containers.yml @@ -3,7 +3,7 @@ name: containers on: push: branches: - - dev + - main paths: - packages/containers/** - .github/workflows/containers.yml diff --git a/packages/containers/README.md b/packages/containers/README.md index 2efd38d7163..b67dcbb2d80 100644 --- a/packages/containers/README.md +++ b/packages/containers/README.md @@ -8,6 +8,7 @@ Images - `base`: Ubuntu 24.04 with common build tools and utilities - `bun-node`: `base` plus Bun and Node.js 24 +- `jetbrains`: `bun-node` plus Java 21 and the pre-cached Gradle distribution - `rust`: `bun-node` plus Rust (stable, minimal profile) - `tauri-linux`: `rust` plus Tauri Linux build dependencies - `publish`: `bun-node` plus Docker CLI and AUR tooling diff --git a/packages/containers/jetbrains/Dockerfile b/packages/containers/jetbrains/Dockerfile new file mode 100644 index 00000000000..707c5be73ca --- /dev/null +++ b/packages/containers/jetbrains/Dockerfile @@ -0,0 +1,35 @@ +# kilocode_change start +ARG REGISTRY=ghcr.io/kilo-org +FROM ${REGISTRY}/build/bun-node:24.04 + +ARG DEBIAN_FRONTEND=noninteractive +ARG GRADLE_VERSION=9.4.1 + +# Install Java 21 (apt handles multi-arch automatically) +RUN apt-get update \ + && apt-get install -y --no-install-recommends openjdk-21-jdk-headless \ + && rm -rf /var/lib/apt/lists/* + +# Create a stable /usr/lib/jvm/java-21 symlink that works on amd64 and arm64 +RUN java_home="$(dirname "$(dirname "$(readlink -f "$(which java)")")")" \ + && ln -nfs "${java_home}" /usr/lib/jvm/java-21 + +ENV JAVA_HOME=/usr/lib/jvm/java-21 + +# Pre-download the exact Gradle distribution used by the wrapper so CI jobs +# skip the download entirely. GRADLE_USER_HOME is set to a location that any +# user (including the GitHub Actions runner) can read. +ENV GRADLE_USER_HOME=/gradle-home + +RUN set -euo pipefail; \ + url="https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip"; \ + hash=$(echo -n "${url}" | md5sum | cut -d' ' -f1); \ + dir="/gradle-home/wrapper/dists/gradle-${GRADLE_VERSION}-bin/${hash}"; \ + mkdir -p "${dir}"; \ + curl -fsSL "${url}" -o "${dir}/gradle-${GRADLE_VERSION}-bin.zip"; \ + unzip -q "${dir}/gradle-${GRADLE_VERSION}-bin.zip" -d "${dir}"; \ + touch "${dir}/gradle-${GRADLE_VERSION}-bin.zip.ok"; \ + rm "${dir}/gradle-${GRADLE_VERSION}-bin.zip"; \ + java -version; \ + "${dir}/gradle-${GRADLE_VERSION}/bin/gradle" --version +# kilocode_change end diff --git a/packages/containers/script/build.ts b/packages/containers/script/build.ts index 82df81eb6ce..ff6054b5dcf 100644 --- a/packages/containers/script/build.ts +++ b/packages/containers/script/build.ts @@ -17,7 +17,7 @@ const manager = pkg.packageManager ?? "" const bun = manager.startsWith("bun@") ? manager.slice(4) : "" if (!bun) throw new Error("packageManager must be bun@") -const images = ["base", "bun-node", "rust", "tauri-linux", "publish"] +const images = ["base", "bun-node", "jetbrains", "rust", "tauri-linux", "publish"] // kilocode_change const setup = async () => { if (!push) return From 4a0117500e4a611034ae4e7544bd05e3e84c67f2 Mon Sep 17 00:00:00 2001 From: markijbema <624143+markijbema@users.noreply.github.com> Date: Tue, 16 Jun 2026 12:58:27 +0000 Subject: [PATCH 28/78] ci: annotate containers.yml branch change with kilocode_change Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .github/workflows/containers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/containers.yml b/.github/workflows/containers.yml index 5d8c4ad1c18..d8b3485035b 100644 --- a/.github/workflows/containers.yml +++ b/.github/workflows/containers.yml @@ -3,7 +3,7 @@ name: containers on: push: branches: - - main + - main # kilocode_change paths: - packages/containers/** - .github/workflows/containers.yml From d319f7468efc6d21db30ba84f02bad4b5c15ac7a Mon Sep 17 00:00:00 2001 From: kirillk Date: Tue, 16 Jun 2026 09:37:49 -0400 Subject: [PATCH 29/78] test(jetbrains): add fakeable UI timers --- .../client/session/SessionSidePanelManager.kt | 21 ++--- .../ai/kilocode/client/session/SessionUi.kt | 9 +-- .../client/session/controller/DelayedState.kt | 12 +-- .../client/session/history/HistoryPanel.kt | 6 +- .../session/ui/empty/EmptySessionPanel.kt | 8 +- .../settings/profile/LoggedOutProfileUi.kt | 8 +- .../client/settings/profile/ProfileUi.kt | 5 +- .../ai/kilocode/client/util/UiTimers.kt | 41 ++++++++++ .../resources/kilo.jetbrains.frontend.xml | 2 + .../session/SessionSidePanelManagerTest.kt | 31 ++++--- .../session/controller/DelayedStateTest.kt | 41 +++++----- .../controller/SessionControllerTestBase.kt | 17 +++- .../kilocode/client/testing/FakeUiTimers.kt | 80 +++++++++++++++++++ 13 files changed, 212 insertions(+), 69 deletions(-) create mode 100644 packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/util/UiTimers.kt create mode 100644 packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeUiTimers.kt diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionSidePanelManager.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionSidePanelManager.kt index 81958b33b5a..29344279472 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionSidePanelManager.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionSidePanelManager.kt @@ -6,6 +6,8 @@ import ai.kilocode.client.app.Workspace import ai.kilocode.client.session.history.HistoryController import ai.kilocode.client.session.history.HistoryPanel import ai.kilocode.client.telemetry.Telemetry +import ai.kilocode.client.util.UiTimer +import ai.kilocode.client.util.UiTimers import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.DataProvider import com.intellij.openapi.application.ApplicationManager @@ -20,7 +22,6 @@ import kotlinx.coroutines.cancel import java.awt.BorderLayout import javax.swing.JComponent import javax.swing.JPanel -import javax.swing.Timer class SessionSidePanelManager( private val project: Project, @@ -31,6 +32,7 @@ class SessionSidePanelManager( private val resolve: (String) -> Workspace = { dir -> service().workspace(dir) }, private val status: () -> Map = { project.service().activity() }, private val history: ((Disposable, (SessionRef) -> Unit, (String) -> Unit) -> JComponent)? = null, + private val timers: UiTimers = service(), ) : SessionManager, Disposable { val component: JPanel = object : JPanel(BorderLayout()), DataProvider { override fun getData(dataId: String): Any? { @@ -42,7 +44,7 @@ class SessionSidePanelManager( private val opened = mutableMapOf() private val all = mutableSetOf() - private val timers = mutableMapOf() + private val activeTimers = mutableMapOf() private var current: SessionUi? = null private var latest: SessionUi? = null private var panel: JComponent? = null @@ -206,24 +208,23 @@ class SessionSidePanelManager( private fun schedule(ui: SessionUi) { cancel(ui) val delay = Registry.intValue("kilo.session.inactive.disposeTimeoutMs").coerceAtLeast(0) - val timer = Timer(delay) { - timers.remove(ui) - if (ui === current || ui !in all) return@Timer + val timer = timers.timer(delay, repeats = false) { + activeTimers.remove(ui) + if (ui === current || ui !in all) return@timer disposeUi(ui) } - timer.isRepeats = false - timers[ui] = timer + activeTimers[ui] = timer timer.start() } private fun cancel(ui: SessionUi) { - timers.remove(ui)?.stop() + activeTimers.remove(ui)?.stop() } override fun dispose() { val items = all.toList() - timers.values.forEach { it.stop() } - timers.clear() + activeTimers.values.forEach { it.stop() } + activeTimers.clear() opened.clear() all.clear() current = null diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt index 98e63cbca2e..3ea02a0c074 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt @@ -41,6 +41,7 @@ import ai.kilocode.client.session.views.question.QuestionView import ai.kilocode.client.settings.profile.UserProfileConfigurable import ai.kilocode.client.telemetry.Telemetry import ai.kilocode.client.ui.layout.Stack +import ai.kilocode.client.util.UiTimers import ai.kilocode.client.vfs.KiloVfsManager import ai.kilocode.log.ChatLogSummary import ai.kilocode.rpc.dto.PromptDto @@ -77,7 +78,6 @@ import java.net.URI import java.nio.file.Path import javax.swing.JComponent import javax.swing.JPanel -import javax.swing.Timer import javax.swing.UIManager /** @@ -97,6 +97,7 @@ class SessionUi( private val manager: SessionManager? = null, private val workspaces: KiloWorkspaceService = service(), private val migration: MigrationUiController = service(), + private val timers: UiTimers = service(), ) : JPanel(BorderLayout()), Disposable, SessionEditorStyleTarget, UiDataProvider { companion object { @@ -139,11 +140,9 @@ class SessionUi( private lateinit var root: SessionRootPanel private lateinit var account: SessionAccountOverlay private lateinit var drop: SessionDropOverlay - private val hide = Timer(HIDE_MS) { - if (disposed || !this::drop.isInitialized) return@Timer + private val hide = timers.timer(HIDE_MS, repeats = false) { + if (disposed || !this::drop.isInitialized) return@timer drop.setActive(false) - }.apply { - isRepeats = false } private lateinit var sessionContent: JPanel diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt index 623524e2c0a..34410bc1d1b 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt @@ -1,18 +1,20 @@ package ai.kilocode.client.session.controller +import ai.kilocode.client.util.UiTimers import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager -import javax.swing.Timer +import com.intellij.openapi.components.service internal class DelayedState( private val ms: Long, + private val timers: UiTimers = service(), ) : Disposable { private val tick = when { ms <= 0 -> 1 ms > Int.MAX_VALUE -> Int.MAX_VALUE else -> ms.coerceAtMost(TICK_MS).toInt() } - private val timer = Timer(tick) { flush() } + private val timer = timers.timer(tick) { flush() } private val pending = mutableListOf>() @Volatile private var alive = true @@ -36,7 +38,7 @@ internal class DelayedState( } } - internal fun active() = timer.isRunning + internal fun active() = timer.isRunning() private fun apply(item: Pending) { if (!alive) return @@ -47,7 +49,7 @@ internal class DelayedState( private fun flush() { if (!alive) return - val now = System.currentTimeMillis() + val now = timers.now() for (item in pending.toList()) { if (item.due > now) continue apply(item) @@ -56,7 +58,7 @@ internal class DelayedState( } private fun due(): Long { - val now = System.currentTimeMillis() + val now = timers.now() return now + ms.coerceAtMost(Long.MAX_VALUE - now) } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryPanel.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryPanel.kt index 34147ce19e0..35550f612f7 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryPanel.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryPanel.kt @@ -7,6 +7,7 @@ import ai.kilocode.client.session.ui.style.SessionEditorStyle import ai.kilocode.client.ui.UiStyle import ai.kilocode.client.ui.HoverIcon import ai.kilocode.client.ui.iconButton +import ai.kilocode.client.util.UiTimers import com.intellij.icons.AllIcons import com.intellij.ide.ui.LafManagerListener import com.intellij.openapi.application.ApplicationManager @@ -15,6 +16,7 @@ import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionPlaces import com.intellij.openapi.actionSystem.DataProvider +import com.intellij.openapi.components.service import com.intellij.openapi.util.Disposer import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.ui.DocumentAdapter @@ -49,7 +51,6 @@ import javax.swing.JList import javax.swing.KeyStroke import javax.swing.ListSelectionModel import javax.swing.SwingUtilities -import javax.swing.Timer import javax.swing.event.DocumentEvent import javax.swing.event.ListDataEvent import javax.swing.event.ListDataListener @@ -59,6 +60,7 @@ class HistoryPanel( private val controller: HistoryController, private val nav: () -> Unit = {}, private val manager: SessionManager? = null, + private val timers: UiTimers = service(), ) : BorderLayoutPanel(), Disposable, DataProvider { private val localSearch = search(controller.local) private val cloudSearch = search(controller.cloud) @@ -83,7 +85,7 @@ class HistoryPanel( .setText(KiloBundle.message("history.tab.cloud")) .setForeSideComponent(back()) private var stale = false - private val timer = Timer(ACTIVITY_MS) { syncActivity() } + private val timer = timers.timer(ACTIVITY_MS) { syncActivity() } private val tabs: JBTabs = JBTabsFactory.createTabs(null, this).apply { presentation.setSingleRow(true) presentation.setTabsPosition(JBTabsPosition.top) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionPanel.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionPanel.kt index 29ad9df649f..9583b5c5a4f 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionPanel.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionPanel.kt @@ -12,10 +12,12 @@ import ai.kilocode.client.ui.layout.HAlign import ai.kilocode.client.ui.layout.Stack import ai.kilocode.client.ui.layout.VAlign import ai.kilocode.client.ui.layout.align +import ai.kilocode.client.util.UiTimers import ai.kilocode.rpc.dto.SessionDto import com.intellij.icons.AllIcons import com.intellij.ide.BrowserUtil import com.intellij.openapi.Disposable +import com.intellij.openapi.components.service import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.IconLoader import com.intellij.util.concurrency.annotations.RequiresEdt @@ -37,7 +39,6 @@ import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import javax.swing.JButton import javax.swing.JComponent -import javax.swing.Timer /** * Empty-session panel. @@ -53,10 +54,11 @@ class EmptySessionPanel( private val activity: () -> Map = { emptyMap() }, private val titles: () -> Map = { emptyMap() }, private val browse: (String) -> Unit = BrowserUtil::browse, + private val timers: UiTimers = service(), ) : BorderLayoutPanel(), Disposable, SessionEditorStyleTarget { val view: Align = align(HAlign.CENTER, VAlign.CENTER) - private val timer = Timer(ACTIVITY_MS) { syncActivity() } + private val timer = timers.timer(ACTIVITY_MS) { syncActivity() } internal val recent = RecentsList(recents, controller) private val historyButton = ShowHistoryButton().apply { @@ -180,7 +182,7 @@ class EmptySessionPanel( internal fun activeView() = getComponent(0) - internal fun text(session: SessionDto, now: Long = System.currentTimeMillis()) = + internal fun text(session: SessionDto, now: Long = timers.now()) = recent.text(session, now) internal fun rendererComponent( diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedOutProfileUi.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedOutProfileUi.kt index 03326e588a7..1540f2e3351 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedOutProfileUi.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedOutProfileUi.kt @@ -4,8 +4,10 @@ import ai.kilocode.client.plugin.KiloBundle import ai.kilocode.client.ui.HoverIcon import ai.kilocode.client.ui.RoundedContentPanel import ai.kilocode.client.ui.UiStyle +import ai.kilocode.client.util.UiTimers import ai.kilocode.rpc.dto.KiloAppStatusDto import com.intellij.icons.AllIcons +import com.intellij.openapi.components.service import com.intellij.openapi.ide.CopyPasteManager import com.intellij.openapi.util.IconLoader import com.intellij.openapi.ui.popup.Balloon @@ -33,7 +35,6 @@ import javax.swing.JButton import javax.swing.JComponent import javax.swing.JPanel import javax.swing.SwingConstants -import javax.swing.Timer internal enum class OutMode { CONNECTING, APP_ERROR, INITIATING, AUTH, LOGIN_ERROR, EMPTY } @@ -47,6 +48,7 @@ internal class LoggedOutProfileUi( private val retry: () -> Unit, private val cancel: () -> Unit, private val browse: (String) -> Unit, + private val timers: UiTimers = service(), ) : JPanel(BorderLayout()) { private val cards = JPanel(CardLayout()) @@ -147,7 +149,7 @@ internal class LoggedOutProfileUi( // -- cached URL for listener/QR deduplication -- private var lastPendingUrl: String? = null - private val timer = Timer(1000) { syncTime() } + private val timer = timers.timer(1000) { syncTime() } init { codePanel.add(codeLabel, BorderLayout.CENTER) @@ -361,7 +363,7 @@ internal class LoggedOutProfileUi( @RequiresEdt private fun syncTime() { - val elapsed = ((System.currentTimeMillis() - pendingStarted) / 1000).toInt() + val elapsed = ((timers.now() - pendingStarted) / 1000).toInt() val remain = (pendingExpires - elapsed).coerceAtLeast(0) val min = remain / 60 val sec = remain % 60 diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/ProfileUi.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/ProfileUi.kt index 110a695264c..b33b58d2435 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/ProfileUi.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/ProfileUi.kt @@ -3,6 +3,7 @@ package ai.kilocode.client.settings.profile import ai.kilocode.client.app.KiloAppService import ai.kilocode.client.plugin.KiloBundle import ai.kilocode.client.telemetry.Telemetry +import ai.kilocode.client.util.UiTimers import ai.kilocode.rpc.dto.KiloAppStateDto import ai.kilocode.rpc.dto.KiloAppStatusDto import ai.kilocode.rpc.dto.ProfileDto @@ -42,6 +43,7 @@ internal class ProfileUi( private val cs: CoroutineScope, private val app: KiloAppService = service(), private val browse: (String) -> Unit = { BrowserUtil.browse(it) }, + private val timers: UiTimers = service(), ) : JPanel(BorderLayout()) { private val cards = JPanel(CardLayout()) @@ -52,6 +54,7 @@ internal class ProfileUi( retry = { app.retryAsync() }, cancel = ::cancel, browse = browse, + timers = timers, ) private val account = LoggedInProfileUi( dashboard = { @@ -191,7 +194,7 @@ internal class ProfileUi( val next = app.startLogin() withContext(edt) { if (id != attempt) return@withContext - login = LoginState.Pending(next, System.currentTimeMillis()) + login = LoginState.Pending(next, timers.now()) sync() browse(next.verificationUrl) } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/util/UiTimers.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/util/UiTimers.kt new file mode 100644 index 00000000000..d3179abe2e1 --- /dev/null +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/util/UiTimers.kt @@ -0,0 +1,41 @@ +package ai.kilocode.client.util + +import javax.swing.Timer + +interface UiTimer { + fun start() + fun stop() + fun restart() + fun isRunning(): Boolean +} + +interface UiTimers { + fun now(): Long + fun timer(ms: Int, repeats: Boolean = true, action: () -> Unit): UiTimer +} + +class SwingUiTimers : UiTimers { + override fun now(): Long = System.currentTimeMillis() + + override fun timer(ms: Int, repeats: Boolean, action: () -> Unit): UiTimer { + val timer = Timer(ms.coerceAtLeast(0)) { action() } + timer.isRepeats = repeats + return SwingUiTimer(timer) + } + + private class SwingUiTimer(private val timer: Timer) : UiTimer { + override fun start() { + timer.start() + } + + override fun stop() { + timer.stop() + } + + override fun restart() { + timer.restart() + } + + override fun isRunning(): Boolean = timer.isRunning + } +} diff --git a/packages/kilo-jetbrains/frontend/src/main/resources/kilo.jetbrains.frontend.xml b/packages/kilo-jetbrains/frontend/src/main/resources/kilo.jetbrains.frontend.xml index b7515a2cd2d..22be49ad6da 100644 --- a/packages/kilo-jetbrains/frontend/src/main/resources/kilo.jetbrains.frontend.xml +++ b/packages/kilo-jetbrains/frontend/src/main/resources/kilo.jetbrains.frontend.xml @@ -9,6 +9,8 @@ + () private val created = mutableListOf>() private val refs = mutableListOf() @@ -60,6 +62,9 @@ class SessionSidePanelManagerTest : BasePlatformTestCase() { override fun setUp() { super.setUp() + timers = FakeUiTimers() + com.intellij.openapi.application.ApplicationManager.getApplication() + .replaceService(UiTimers::class.java, timers, testRootDisposable) scope = CoroutineScope(SupervisorJob()) rpc = FakeSessionRpcApi() sessions = KiloSessionService(project, scope, rpc) @@ -223,7 +228,7 @@ class SessionSidePanelManagerTest : BasePlatformTestCase() { manager.openSession(session("ses_1")) val first = active(manager) manager.openSession(session("ses_2")) - expire(manager, first) + expire() assertFalse(ui.contains(first)) assertEquals(listOf("/test" to "ses_1", "/test" to "ses_2"), created) @@ -236,7 +241,7 @@ class SessionSidePanelManagerTest : BasePlatformTestCase() { manager.openSession(session("ses_1")) val first = active(manager) manager.openSession(session("ses_2")) - expire(manager, first) + expire() manager.openSession(session("ses_1")) assertNotSame(first, active(manager)) @@ -257,7 +262,7 @@ class SessionSidePanelManagerTest : BasePlatformTestCase() { flow.emit(ChatEventDto.MessageUpdated("ses_1", msg("msg_hidden", "ses_1", "assistant"))) flow.emit(ChatEventDto.PartDelta("ses_1", "msg_hidden", "txt_hidden", "text", "stale")) } - expire(manager, first) + expire() manager.openSession(session("ses_1")) val second = active(manager) settle() @@ -590,7 +595,7 @@ class SessionSidePanelManagerTest : BasePlatformTestCase() { first.controller().model.setState(SessionState.AwaitingPermission(permission("ses_1"))) } manager.openSession(session("ses_2")) - expire(manager, first) + expire() assertFalse(ui.contains(first)) assertEquals(emptyMap(), manager.activity()) @@ -605,7 +610,7 @@ class SessionSidePanelManagerTest : BasePlatformTestCase() { first.controller().model.setState(SessionState.Busy("running")) } manager.openSession(session("ses_2")) - expire(manager, first) + expire() assertFalse(ui.contains(first)) } @@ -616,7 +621,7 @@ class SessionSidePanelManagerTest : BasePlatformTestCase() { manager.openSession(session("ses_1")) val first = active(manager) manager.openSession(session("ses_2")) - expire(manager, first) + expire() assertFalse(ui.contains(first)) assertEquals(emptyMap(), manager.activity()) @@ -700,14 +705,8 @@ class SessionSidePanelManagerTest : BasePlatformTestCase() { method.invoke(manager) } - private fun expire(manager: SessionSidePanelManager, ui: JPanel) { - val field = SessionSidePanelManager::class.java.getDeclaredField("timers") - field.isAccessible = true - @Suppress("UNCHECKED_CAST") - val timers = field.get(manager) as Map - val timer = requireNotNull(timers[ui]) { "Expected an inactive session disposal timer" } - timer.stop() - timer.actionListeners.single().actionPerformed(ActionEvent(timer, ActionEvent.ACTION_PERFORMED, "expire")) + private fun expire() { + timers.advanceBy(10) } private fun settle() = kotlinx.coroutines.runBlocking { diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/DelayedStateTest.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/DelayedStateTest.kt index 349171f6549..0e26e234b8e 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/DelayedStateTest.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/DelayedStateTest.kt @@ -1,14 +1,21 @@ package ai.kilocode.client.session.controller +import ai.kilocode.client.testing.FakeUiTimers +import ai.kilocode.client.util.UiTimers import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.util.Disposer +import com.intellij.testFramework.replaceService import com.intellij.testFramework.fixtures.BasePlatformTestCase -import com.intellij.util.ui.UIUtil -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking class DelayedStateTest : BasePlatformTestCase() { private val states = mutableListOf() + private lateinit var timers: FakeUiTimers + + override fun setUp() { + super.setUp() + timers = FakeUiTimers() + ApplicationManager.getApplication().replaceService(UiTimers::class.java, timers, testRootDisposable) + } override fun tearDown() { try { @@ -25,7 +32,7 @@ class DelayedStateTest : BasePlatformTestCase() { val delay = delayed(30) delay.run("loading", { state }) { out.add(it) } - pause(120) + timers.advanceBy(30) assertEquals(listOf("loading"), out) } @@ -37,7 +44,7 @@ class DelayedStateTest : BasePlatformTestCase() { delay.run("loading", { state }) { out.add(it) } edt { state = "loaded" } - pause(120) + timers.advanceBy(30) assertTrue(out.isEmpty()) } @@ -50,7 +57,7 @@ class DelayedStateTest : BasePlatformTestCase() { delay.run("loading", { first }) { out.add("first:$it") } delay.run("connecting", { second }) { out.add("second:$it") } - pause(120) + timers.advanceBy(30) assertEquals(listOf("first:loading", "second:connecting"), out) } @@ -63,7 +70,7 @@ class DelayedStateTest : BasePlatformTestCase() { delay.run("first", { state }) { out.add(it) } edt { state = "second" } delay.run("second", { state }) { out.add(it) } - pause(120) + timers.advanceBy(30) assertEquals(listOf("second"), out) } @@ -75,7 +82,7 @@ class DelayedStateTest : BasePlatformTestCase() { delay.run("loading", { state }) { out.add(it) } delay.cancel() - pause(120) + timers.advanceBy(30) assertTrue(out.isEmpty()) } @@ -85,9 +92,9 @@ class DelayedStateTest : BasePlatformTestCase() { val delay = delayed(30) delay.run("loading", { state }) {} - pause(10) + timers.advanceBy(10) delay.cancel() - pause(10) + timers.advanceBy(10) assertFalse(delay.active()) } @@ -97,7 +104,7 @@ class DelayedStateTest : BasePlatformTestCase() { val delay = delayed(30) delay.run("loading", { state }) {} - pause(120) + timers.advanceBy(30) assertFalse(delay.active()) } @@ -110,7 +117,7 @@ class DelayedStateTest : BasePlatformTestCase() { delay.run("loading", { state }) { out.add(it) } Disposer.dispose(delay) states.remove(delay) - pause(120) + timers.advanceBy(30) assertTrue(out.isEmpty()) } @@ -123,7 +130,7 @@ class DelayedStateTest : BasePlatformTestCase() { delay.run("loading", { state }) { out.add(ApplicationManager.getApplication().isDispatchThread) } - pause(30) + timers.advanceBy(0) assertEquals(listOf(true), out) } @@ -134,14 +141,6 @@ class DelayedStateTest : BasePlatformTestCase() { return state } - private fun pause(ms: Long) = runBlocking { - val tick = 10L - repeat((ms / tick).coerceAtLeast(1).toInt()) { - delay(tick) - edt { UIUtil.dispatchAllInvocationEvents() } - } - } - private fun edt(block: () -> Unit) { ApplicationManager.getApplication().invokeAndWait(block) } diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionControllerTestBase.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionControllerTestBase.kt index 423a13ea49a..dbb5ee48d48 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionControllerTestBase.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionControllerTestBase.kt @@ -8,6 +8,8 @@ import ai.kilocode.client.session.model.SessionState import ai.kilocode.client.testing.FakeAppRpcApi import ai.kilocode.client.testing.FakeWorkspaceRpcApi import ai.kilocode.client.testing.FakeSessionRpcApi +import ai.kilocode.client.testing.FakeUiTimers +import ai.kilocode.client.util.UiTimers import ai.kilocode.client.app.KiloWorkspaceService import ai.kilocode.client.app.Workspace import ai.kilocode.client.session.SessionRef @@ -31,6 +33,7 @@ import ai.kilocode.rpc.dto.TelemetryCaptureDto import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.util.Disposer +import com.intellij.testFramework.replaceService import com.intellij.testFramework.fixtures.BasePlatformTestCase import com.intellij.util.ui.UIUtil import java.awt.event.HierarchyEvent @@ -95,6 +98,7 @@ abstract class SessionControllerTestBase : BasePlatformTestCase() { protected lateinit var app: KiloAppService protected lateinit var workspaces: KiloWorkspaceService protected lateinit var workspace: Workspace + protected lateinit var timers: FakeUiTimers protected lateinit var scope: CoroutineScope protected lateinit var parent: Disposable @@ -104,6 +108,8 @@ abstract class SessionControllerTestBase : BasePlatformTestCase() { rpc = FakeSessionRpcApi() appRpc = FakeAppRpcApi() projectRpc = FakeWorkspaceRpcApi() + timers = FakeUiTimers() + ApplicationManager.getApplication().replaceService(UiTimers::class.java, timers, testRootDisposable) scope = CoroutineScope(SupervisorJob()) parent = Disposer.newDisposable("test") @@ -242,9 +248,14 @@ abstract class SessionControllerTestBase : BasePlatformTestCase() { } protected fun pause(ms: Long) = runBlocking { - val tick = 10L - repeat((ms / tick).coerceAtLeast(1).toInt()) { - delay(tick) + settleFast() + timers.advanceBy(ms) + settleFast() + } + + private suspend fun settleFast() { + repeat(3) { + delay(1) edt { UIUtil.dispatchAllInvocationEvents() } } } diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeUiTimers.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeUiTimers.kt new file mode 100644 index 00000000000..11cf1401b5e --- /dev/null +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeUiTimers.kt @@ -0,0 +1,80 @@ +package ai.kilocode.client.testing + +import ai.kilocode.client.util.UiTimer +import ai.kilocode.client.util.UiTimers +import com.intellij.openapi.application.ApplicationManager + +class FakeUiTimers : UiTimers { + private val timers = mutableListOf() + private var time = 0L + + override fun now(): Long = time + + override fun timer(ms: Int, repeats: Boolean, action: () -> Unit): UiTimer { + val timer = FakeTimer(ms.coerceAtLeast(0), repeats, action) + timers.add(timer) + return timer + } + + fun advanceBy(ms: Long) { + edt { } + time += ms.coerceAtLeast(0) + runDue() + } + + fun runDue() { + while (true) { + val due = timers.filter { it.running && it.due <= time } + if (due.isEmpty()) return + due.forEach { it.fire() } + } + } + + private fun edt(action: () -> Unit) { + val app = ApplicationManager.getApplication() + if (app.isDispatchThread) { + action() + return + } + app.invokeAndWait(action) + } + + private inner class FakeTimer( + private val ms: Int, + private val repeats: Boolean, + private val action: () -> Unit, + ) : UiTimer { + var running = false + private set + var due = Long.MAX_VALUE + private set + + override fun start() { + if (running) return + running = true + due = time + ms + } + + override fun stop() { + running = false + due = Long.MAX_VALUE + } + + override fun restart() { + running = true + due = time + ms + } + + override fun isRunning(): Boolean = running + + fun fire() { + if (!running || due > time) return + if (repeats) { + due += ms.coerceAtLeast(1) + } else { + stop() + } + edt(action) + } + } +} From 0155b496175a47e336087f2f56023c058316acb8 Mon Sep 17 00:00:00 2001 From: kirillk Date: Tue, 16 Jun 2026 09:53:47 -0400 Subject: [PATCH 30/78] test(jetbrains): make UI timers static --- .../client/session/SessionSidePanelManager.kt | 3 +-- .../ai/kilocode/client/session/SessionUi.kt | 3 +-- .../client/session/controller/DelayedState.kt | 12 ++++------ .../client/session/history/HistoryPanel.kt | 4 +--- .../session/ui/empty/EmptySessionPanel.kt | 6 ++--- .../settings/profile/LoggedOutProfileUi.kt | 6 ++--- .../client/settings/profile/ProfileUi.kt | 4 +--- .../ai/kilocode/client/util/UiTimers.kt | 22 +++++++++++++++++-- .../resources/kilo.jetbrains.frontend.xml | 2 -- .../session/SessionSidePanelManagerTest.kt | 4 +--- .../session/controller/DelayedStateTest.kt | 3 +-- .../controller/SessionControllerTestBase.kt | 3 +-- .../kilocode/client/testing/FakeUiTimers.kt | 4 ++-- 13 files changed, 37 insertions(+), 39 deletions(-) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionSidePanelManager.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionSidePanelManager.kt index 29344279472..280155cbd49 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionSidePanelManager.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionSidePanelManager.kt @@ -32,7 +32,6 @@ class SessionSidePanelManager( private val resolve: (String) -> Workspace = { dir -> service().workspace(dir) }, private val status: () -> Map = { project.service().activity() }, private val history: ((Disposable, (SessionRef) -> Unit, (String) -> Unit) -> JComponent)? = null, - private val timers: UiTimers = service(), ) : SessionManager, Disposable { val component: JPanel = object : JPanel(BorderLayout()), DataProvider { override fun getData(dataId: String): Any? { @@ -208,7 +207,7 @@ class SessionSidePanelManager( private fun schedule(ui: SessionUi) { cancel(ui) val delay = Registry.intValue("kilo.session.inactive.disposeTimeoutMs").coerceAtLeast(0) - val timer = timers.timer(delay, repeats = false) { + val timer = UiTimers.timer(delay, repeats = false) { activeTimers.remove(ui) if (ui === current || ui !in all) return@timer disposeUi(ui) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt index 3ea02a0c074..2b028fb3ec0 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt @@ -97,7 +97,6 @@ class SessionUi( private val manager: SessionManager? = null, private val workspaces: KiloWorkspaceService = service(), private val migration: MigrationUiController = service(), - private val timers: UiTimers = service(), ) : JPanel(BorderLayout()), Disposable, SessionEditorStyleTarget, UiDataProvider { companion object { @@ -140,7 +139,7 @@ class SessionUi( private lateinit var root: SessionRootPanel private lateinit var account: SessionAccountOverlay private lateinit var drop: SessionDropOverlay - private val hide = timers.timer(HIDE_MS, repeats = false) { + private val hide = UiTimers.timer(HIDE_MS, repeats = false) { if (disposed || !this::drop.isInitialized) return@timer drop.setActive(false) } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt index 34410bc1d1b..03c7be99a58 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt @@ -3,18 +3,14 @@ package ai.kilocode.client.session.controller import ai.kilocode.client.util.UiTimers import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.components.service -internal class DelayedState( - private val ms: Long, - private val timers: UiTimers = service(), -) : Disposable { +internal class DelayedState(private val ms: Long) : Disposable { private val tick = when { ms <= 0 -> 1 ms > Int.MAX_VALUE -> Int.MAX_VALUE else -> ms.coerceAtMost(TICK_MS).toInt() } - private val timer = timers.timer(tick) { flush() } + private val timer = UiTimers.timer(tick) { flush() } private val pending = mutableListOf>() @Volatile private var alive = true @@ -49,7 +45,7 @@ internal class DelayedState( private fun flush() { if (!alive) return - val now = timers.now() + val now = UiTimers.now() for (item in pending.toList()) { if (item.due > now) continue apply(item) @@ -58,7 +54,7 @@ internal class DelayedState( } private fun due(): Long { - val now = timers.now() + val now = UiTimers.now() return now + ms.coerceAtMost(Long.MAX_VALUE - now) } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryPanel.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryPanel.kt index 35550f612f7..e9b9ddd0095 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryPanel.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryPanel.kt @@ -16,7 +16,6 @@ import com.intellij.openapi.actionSystem.ActionGroup import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.ActionPlaces import com.intellij.openapi.actionSystem.DataProvider -import com.intellij.openapi.components.service import com.intellij.openapi.util.Disposer import com.intellij.util.concurrency.annotations.RequiresEdt import com.intellij.ui.DocumentAdapter @@ -60,7 +59,6 @@ class HistoryPanel( private val controller: HistoryController, private val nav: () -> Unit = {}, private val manager: SessionManager? = null, - private val timers: UiTimers = service(), ) : BorderLayoutPanel(), Disposable, DataProvider { private val localSearch = search(controller.local) private val cloudSearch = search(controller.cloud) @@ -85,7 +83,7 @@ class HistoryPanel( .setText(KiloBundle.message("history.tab.cloud")) .setForeSideComponent(back()) private var stale = false - private val timer = timers.timer(ACTIVITY_MS) { syncActivity() } + private val timer = UiTimers.timer(ACTIVITY_MS) { syncActivity() } private val tabs: JBTabs = JBTabsFactory.createTabs(null, this).apply { presentation.setSingleRow(true) presentation.setTabsPosition(JBTabsPosition.top) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionPanel.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionPanel.kt index 9583b5c5a4f..d96cefb9673 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionPanel.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionPanel.kt @@ -17,7 +17,6 @@ import ai.kilocode.rpc.dto.SessionDto import com.intellij.icons.AllIcons import com.intellij.ide.BrowserUtil import com.intellij.openapi.Disposable -import com.intellij.openapi.components.service import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.IconLoader import com.intellij.util.concurrency.annotations.RequiresEdt @@ -54,11 +53,10 @@ class EmptySessionPanel( private val activity: () -> Map = { emptyMap() }, private val titles: () -> Map = { emptyMap() }, private val browse: (String) -> Unit = BrowserUtil::browse, - private val timers: UiTimers = service(), ) : BorderLayoutPanel(), Disposable, SessionEditorStyleTarget { val view: Align = align(HAlign.CENTER, VAlign.CENTER) - private val timer = timers.timer(ACTIVITY_MS) { syncActivity() } + private val timer = UiTimers.timer(ACTIVITY_MS) { syncActivity() } internal val recent = RecentsList(recents, controller) private val historyButton = ShowHistoryButton().apply { @@ -182,7 +180,7 @@ class EmptySessionPanel( internal fun activeView() = getComponent(0) - internal fun text(session: SessionDto, now: Long = timers.now()) = + internal fun text(session: SessionDto, now: Long = UiTimers.now()) = recent.text(session, now) internal fun rendererComponent( diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedOutProfileUi.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedOutProfileUi.kt index 1540f2e3351..f1246d54a92 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedOutProfileUi.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedOutProfileUi.kt @@ -7,7 +7,6 @@ import ai.kilocode.client.ui.UiStyle import ai.kilocode.client.util.UiTimers import ai.kilocode.rpc.dto.KiloAppStatusDto import com.intellij.icons.AllIcons -import com.intellij.openapi.components.service import com.intellij.openapi.ide.CopyPasteManager import com.intellij.openapi.util.IconLoader import com.intellij.openapi.ui.popup.Balloon @@ -48,7 +47,6 @@ internal class LoggedOutProfileUi( private val retry: () -> Unit, private val cancel: () -> Unit, private val browse: (String) -> Unit, - private val timers: UiTimers = service(), ) : JPanel(BorderLayout()) { private val cards = JPanel(CardLayout()) @@ -149,7 +147,7 @@ internal class LoggedOutProfileUi( // -- cached URL for listener/QR deduplication -- private var lastPendingUrl: String? = null - private val timer = timers.timer(1000) { syncTime() } + private val timer = UiTimers.timer(1000) { syncTime() } init { codePanel.add(codeLabel, BorderLayout.CENTER) @@ -363,7 +361,7 @@ internal class LoggedOutProfileUi( @RequiresEdt private fun syncTime() { - val elapsed = ((timers.now() - pendingStarted) / 1000).toInt() + val elapsed = ((UiTimers.now() - pendingStarted) / 1000).toInt() val remain = (pendingExpires - elapsed).coerceAtLeast(0) val min = remain / 60 val sec = remain % 60 diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/ProfileUi.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/ProfileUi.kt index b33b58d2435..6cfcb2d3bef 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/ProfileUi.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/ProfileUi.kt @@ -43,7 +43,6 @@ internal class ProfileUi( private val cs: CoroutineScope, private val app: KiloAppService = service(), private val browse: (String) -> Unit = { BrowserUtil.browse(it) }, - private val timers: UiTimers = service(), ) : JPanel(BorderLayout()) { private val cards = JPanel(CardLayout()) @@ -54,7 +53,6 @@ internal class ProfileUi( retry = { app.retryAsync() }, cancel = ::cancel, browse = browse, - timers = timers, ) private val account = LoggedInProfileUi( dashboard = { @@ -194,7 +192,7 @@ internal class ProfileUi( val next = app.startLogin() withContext(edt) { if (id != attempt) return@withContext - login = LoginState.Pending(next, timers.now()) + login = LoginState.Pending(next, UiTimers.now()) sync() browse(next.verificationUrl) } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/util/UiTimers.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/util/UiTimers.kt index d3179abe2e1..94d78e5a558 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/util/UiTimers.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/util/UiTimers.kt @@ -1,5 +1,7 @@ package ai.kilocode.client.util +import com.intellij.openapi.Disposable +import com.intellij.openapi.util.Disposer import javax.swing.Timer interface UiTimer { @@ -9,12 +11,28 @@ interface UiTimer { fun isRunning(): Boolean } -interface UiTimers { +interface UiTimerSource { fun now(): Long fun timer(ms: Int, repeats: Boolean = true, action: () -> Unit): UiTimer } -class SwingUiTimers : UiTimers { +object UiTimers : UiTimerSource { + @Volatile private var source: UiTimerSource = SwingUiTimers() + + override fun now(): Long = source.now() + + override fun timer(ms: Int, repeats: Boolean, action: () -> Unit): UiTimer = source.timer(ms, repeats, action) + + fun replace(source: UiTimerSource, parent: Disposable) { + val prev = this.source + this.source = source + Disposer.register(parent) { + if (this.source === source) this.source = prev + } + } +} + +private class SwingUiTimers : UiTimerSource { override fun now(): Long = System.currentTimeMillis() override fun timer(ms: Int, repeats: Boolean, action: () -> Unit): UiTimer { diff --git a/packages/kilo-jetbrains/frontend/src/main/resources/kilo.jetbrains.frontend.xml b/packages/kilo-jetbrains/frontend/src/main/resources/kilo.jetbrains.frontend.xml index 22be49ad6da..b7515a2cd2d 100644 --- a/packages/kilo-jetbrains/frontend/src/main/resources/kilo.jetbrains.frontend.xml +++ b/packages/kilo-jetbrains/frontend/src/main/resources/kilo.jetbrains.frontend.xml @@ -9,8 +9,6 @@ - () private var time = 0L From 8824fa0b35caaa8b9911b6a9bbd5ff5ef02fc2b3 Mon Sep 17 00:00:00 2001 From: Z User Date: Tue, 16 Jun 2026 13:50:01 +0000 Subject: [PATCH 31/78] =?UTF-8?q?fix:=20restore=20@opencode-ai/ui=20in=20k?= =?UTF-8?q?ilo-console=20=E2=80=94=20it=20is=20actively=20imported?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit kilo-console's ProjectConsoleRoute.tsx imports @opencode-ai/ui/file, @opencode-ai/ui/context/file, and @opencode-ai/ui/session-review. The removal was a false positive from depcheck which failed to detect the workspace package references. Count now 33 deps removed. --- packages/kilo-console/package.json | 1 + 1 file changed, 1 insertion(+) mode change 100644 => 100755 packages/kilo-console/package.json diff --git a/packages/kilo-console/package.json b/packages/kilo-console/package.json old mode 100644 new mode 100755 index 999c2cfa2f7..4a9cd9366c7 --- a/packages/kilo-console/package.json +++ b/packages/kilo-console/package.json @@ -14,6 +14,7 @@ "@kilocode/kilo-web-ui": "workspace:*", "@kilocode/sdk": "workspace:*", "@lottiefiles/dotlottie-web": "0.74.0", + "@opencode-ai/ui": "workspace:*", "@solidjs/router": "catalog:", "ghostty-web": "0.4.0", "solid-js": "catalog:", From 42627729b4834efec80d44ad9eabca49584eb659 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Tue, 16 Jun 2026 15:56:44 +0200 Subject: [PATCH 32/78] fix(cli): refine shared index concurrency --- .../src/indexing/cache-manager.ts | 7 + .../src/indexing/interfaces/vector-store.ts | 1 + .../kilo-indexing/src/indexing/manager.ts | 120 ++++++++++++------ .../src/indexing/orchestrator.ts | 14 +- .../src/indexing/processors/file-watcher.ts | 5 +- .../src/indexing/processors/scanner.ts | 1 + .../src/indexing/search-service.ts | 11 +- .../vector-store/lancedb-vector-store.ts | 108 +++++++++------- .../indexing/vector-store/qdrant-client.ts | 30 ++--- .../src/indexing/worktree-overlay.ts | 25 +--- .../test/kilocode/indexing/manager.test.ts | 56 +++++++- .../kilocode/indexing/orchestrator.test.ts | 83 ++++++++++++ .../kilocode/indexing/search-service.test.ts | 48 +++---- .../vector-store/lancedb-vector-store.test.ts | 74 +++++++++-- .../vector-store/qdrant-client.test.ts | 60 +++++++-- .../src/kilocode/indexing-worker-client.ts | 37 +++--- .../opencode/src/kilocode/indexing-worker.ts | 17 +-- .../test/kilocode/indexing-worker.test.ts | 28 +++- 18 files changed, 513 insertions(+), 212 deletions(-) diff --git a/packages/kilo-indexing/src/indexing/cache-manager.ts b/packages/kilo-indexing/src/indexing/cache-manager.ts index a70d591892f..ba5cd3735f1 100644 --- a/packages/kilo-indexing/src/indexing/cache-manager.ts +++ b/packages/kilo-indexing/src/indexing/cache-manager.ts @@ -91,4 +91,11 @@ export class CacheManager implements ICacheManager { const entries = Object.entries(this.fileHashes).sort(([left], [right]) => left.localeCompare(right)) return createHash("sha256").update(JSON.stringify(entries)).digest("hex") } + + async stamp(): Promise { + return fs + .stat(this.cachePath) + .then((value) => `${value.mtimeMs}:${value.ctimeMs}:${value.size}`) + .catch(() => undefined) + } } diff --git a/packages/kilo-indexing/src/indexing/interfaces/vector-store.ts b/packages/kilo-indexing/src/indexing/interfaces/vector-store.ts index 8857ae25db3..7712502f14a 100644 --- a/packages/kilo-indexing/src/indexing/interfaces/vector-store.ts +++ b/packages/kilo-indexing/src/indexing/interfaces/vector-store.ts @@ -96,6 +96,7 @@ export interface VectorStoreSearchResult { export interface Payload { filePath: string + fileHash?: string codeChunk: string startLine: number endLine: number diff --git a/packages/kilo-indexing/src/indexing/manager.ts b/packages/kilo-indexing/src/indexing/manager.ts index 77b5797787d..05e4c5fc15d 100644 --- a/packages/kilo-indexing/src/indexing/manager.ts +++ b/packages/kilo-indexing/src/indexing/manager.ts @@ -16,6 +16,15 @@ import { sanitizeErrorMessage } from "./shared/validation-helpers" import { WorktreeOverlay } from "./worktree-overlay" const log = Log.create({ service: "indexing-manager" }) +const BASELINE_CHECK_INTERVAL = 1_000 +const BASELINE_SIGNATURE_INTERVAL = 30_000 + +type Baseline = { + store?: IVectorStore + signature: string + stamp?: string + overlay?: WorktreeOverlay +} /** * RATIONALE: Removed the static singleton Map and vscode.ExtensionContext. @@ -33,6 +42,9 @@ export class CodeIndexManager { private _cacheManager: CacheManager | undefined private _baselineStore: IVectorStore | undefined private _baselineSignature: string | undefined + private _baselineStamp: string | undefined + private _baselineChecked = 0 + private _baselineSigned = 0 private _baselineRefresh: Promise | undefined private _overlay: WorktreeOverlay | undefined private _isRecoveringFromError = false @@ -449,18 +461,36 @@ export class CodeIndexManager { private async refreshBaseline(): Promise { if (!this.baselinePath || this._disposed) return if (this._baselineRefresh) return this._baselineRefresh + const now = Date.now() + if (now - this._baselineChecked < BASELINE_CHECK_INTERVAL) return + this._baselineChecked = now const baselinePath = this.baselinePath const task = (async () => { const cache = new CacheManager(this.cacheDirectory, baselinePath) + const stamp = await cache.stamp() + const force = now - this._baselineSigned >= BASELINE_SIGNATURE_INTERVAL + if (!force && stamp === this._baselineStamp) return + await cache.initialize() - if (cache.signature() === this._baselineSignature) return + const signature = cache.signature() + this._baselineStamp = stamp + this._baselineSigned = now + if (signature === this._baselineSignature && this._baselineStore) return + + const baseline = await this.createBaseline(this._serviceFactory!) + this._baselineStamp = baseline?.stamp ?? stamp + this._baselineSigned = now + if (!baseline?.store) { + if (!this._baselineStore) this._baselineSignature = signature + return + } log.info("shared indexing baseline changed; rebuilding worktree delta", { workspacePath: this.workspacePath, baselinePath, }) - await this._recreateServices() + await this._recreateServices(baseline) if (this._disposed) return await this._orchestrator?.startIndexing("background") })().finally(() => { @@ -470,11 +500,13 @@ export class CodeIndexManager { return task } - private async createBaseline(factory: CodeIndexServiceFactory) { + private async createBaseline(factory: CodeIndexServiceFactory): Promise { if (!this.baselinePath) return const cache = new CacheManager(this.cacheDirectory, this.baselinePath) await cache.initialize() + const signature = cache.signature() + const stamp = await cache.stamp() const hashes = new Map() for (const [filePath, hash] of Object.entries(cache.getAllHashes())) { const rel = path.relative(this.baselinePath, filePath) @@ -483,47 +515,42 @@ export class CodeIndexManager { } const store = factory.createVectorStore(this.baselinePath) - if (!store.openExisting) throw new Error("The configured vector store cannot open a shared baseline") - // Validate compatibility without keeping every worktree baseline connection open. - await store.openExisting() - await store.close?.() - - return { - store, - signature: cache.signature(), - overlay: new WorktreeOverlay(this.workspacePath, this.baselinePath, hashes), + try { + if (!store.openExisting) throw new Error("The configured vector store cannot open a shared baseline") + // Validate compatibility without keeping every worktree baseline connection open. + await store.openExisting() + await store.close?.() + return { + store, + signature, + stamp, + overlay: new WorktreeOverlay(this.workspacePath, this.baselinePath, hashes), + } + } catch (err) { + await store.close?.() + log.warn("shared indexing baseline is unavailable; using an independent worktree index", { + workspacePath: this.workspacePath, + baselinePath: this.baselinePath, + err, + }) + return { signature, stamp } } } - private async _recreateServices(): Promise { + private async _recreateServices(prepared?: Baseline): Promise { log.info("starting indexing service recreation", { workspacePath: this.workspacePath }) - await this._orchestrator?.shutdown?.() - await this._baselineStore?.close?.() - this._orchestrator = undefined - this._baselineStore = undefined - this._searchService = undefined - - this._serviceFactory = new CodeIndexServiceFactory( + const factory = new CodeIndexServiceFactory( this._configManager!, this.workspacePath, this._cacheManager!, this.cacheDirectory, (event) => this.handleTelemetry(event), ) - const ignoreInstance = await loadIgnore(this.workspacePath) - const config = this._configManager!.getConfig() - const baseline = await this.createBaseline(this._serviceFactory) - this._baselineStore = baseline?.store - this._baselineSignature = baseline?.signature - this._overlay = baseline?.overlay - - const { embedder, vectorStore, scanner, fileWatcher } = this._serviceFactory.createServices( - this._cacheManager!, - ignoreInstance, - ) - fileWatcher.setOverlay?.(this._overlay) + const baseline = prepared ?? (await this.createBaseline(factory)) + const { embedder, vectorStore, scanner, fileWatcher } = factory.createServices(this._cacheManager!, ignoreInstance) + fileWatcher.setOverlay?.(baseline?.overlay) log.info("created indexing services", { workspacePath: this.workspacePath, provider: embedder.embedderInfo.name, @@ -532,13 +559,12 @@ export class CodeIndexManager { }) const shouldValidate = embedder && embedder.embedderInfo.name === config.embedderProvider - if (shouldValidate) { log.info("validating embedder configuration", { workspacePath: this.workspacePath, provider: embedder.embedderInfo.name, }) - const validationResult = await this._serviceFactory.validateEmbedder(embedder) + const validationResult = await factory.validateEmbedder(embedder) if (!validationResult.valid) { const errorMessage = validationResult.error || "Embedder configuration validation failed" this._stateManager.setSystemState("Error", errorMessage) @@ -550,7 +576,7 @@ export class CodeIndexManager { }) } - this._orchestrator = new CodeIndexOrchestrator( + const orchestrator = new CodeIndexOrchestrator( this._configManager!, this._stateManager, this.workspacePath, @@ -559,17 +585,33 @@ export class CodeIndexManager { scanner, fileWatcher, (event) => this.handleTelemetry(event), - this._overlay, + baseline?.overlay, + Boolean(this.baselinePath && !baseline?.store), ) - - this._searchService = new CodeIndexSearchService( + const search = new CodeIndexSearchService( this._configManager!, this._stateManager, embedder, vectorStore, - this._baselineStore && this._overlay ? { store: this._baselineStore, overlay: this._overlay } : undefined, + baseline?.store && baseline.overlay ? { store: baseline.store, overlay: baseline.overlay } : undefined, ) + await this._orchestrator?.shutdown?.() + await this._baselineStore?.close?.() + if (this._disposed) { + await orchestrator.shutdown() + await baseline?.store?.close?.() + return + } + + this._serviceFactory = factory + this._orchestrator = orchestrator + this._searchService = search + this._baselineStore = baseline?.store + this._baselineSignature = baseline?.signature + this._baselineStamp = baseline?.stamp + this._baselineSigned = Date.now() + this._overlay = baseline?.overlay this._stateManager.setSystemState("Standby", "") log.info("indexing services are ready", { workspacePath: this.workspacePath }) } diff --git a/packages/kilo-indexing/src/indexing/orchestrator.ts b/packages/kilo-indexing/src/indexing/orchestrator.ts index 46c9b2b0690..d7ae84e00fa 100644 --- a/packages/kilo-indexing/src/indexing/orchestrator.ts +++ b/packages/kilo-indexing/src/indexing/orchestrator.ts @@ -37,6 +37,7 @@ export class CodeIndexOrchestrator { private readonly fileWatcher: IFileWatcher, private readonly onTelemetry?: IndexingTelemetryReporter, private readonly overlay?: WorktreeOverlay, + private readonly independent = false, ) {} private getTelemetryMeta(): IndexingTelemetryMeta { @@ -212,12 +213,17 @@ export class CodeIndexOrchestrator { baselinePath: this.overlay.baselinePath, files: this.overlay.baseline.size, }) - } else if (collectionCreated) { - await this.cacheManager.clearCacheFile() - log.info("cleared indexing cache after new collection creation", { workspacePath: this.workspacePath }) } - const hasExistingData = this.overlay ? false : await this.vectorStore.hasIndexedData() + const hasExistingData = this.overlay || this.independent ? false : await this.vectorStore.hasIndexedData() + if (!this.overlay && !hasExistingData) { + if (!collectionCreated) await this.vectorStore.clearCollection() + await this.cacheManager.clearCacheFile() + log.info("cleared indexing cache before full scan", { + workspacePath: this.workspacePath, + collectionCreated, + }) + } log.info("checked vector store indexed data", { workspacePath: this.workspacePath, hasExistingData, diff --git a/packages/kilo-indexing/src/indexing/processors/file-watcher.ts b/packages/kilo-indexing/src/indexing/processors/file-watcher.ts index 28ae6f457d0..eda57909ab6 100644 --- a/packages/kilo-indexing/src/indexing/processors/file-watcher.ts +++ b/packages/kilo-indexing/src/indexing/processors/file-watcher.ts @@ -472,7 +472,9 @@ export class FileWatcher implements IFileWatcher { retryCount: this.maxBatchRetries, }) this.emitError("file-watcher:upsert_retry_exhausted", upsertError, this.maxBatchRetries) - throw new Error(`Failed to upsert batch after ${this.maxBatchRetries} retries: ${upsertError.message}`) + throw new Error( + `Failed to upsert batch after ${this.maxBatchRetries} retries: ${upsertError.message}`, + ) } this.emitRetry(retryCount, batch.length, upsertError) await new Promise((resolve) => @@ -735,6 +737,7 @@ export class FileWatcher implements IFileWatcher { vector, payload: { filePath: generateRelativeFilePath(normalizedAbsolutePath, this.workspacePath), + fileHash: block.fileHash, codeChunk: block.content, startLine: block.start_line, endLine: block.end_line, diff --git a/packages/kilo-indexing/src/indexing/processors/scanner.ts b/packages/kilo-indexing/src/indexing/processors/scanner.ts index 6f96096fe93..564c0d47c60 100644 --- a/packages/kilo-indexing/src/indexing/processors/scanner.ts +++ b/packages/kilo-indexing/src/indexing/processors/scanner.ts @@ -612,6 +612,7 @@ export class DirectoryScanner implements IDirectoryScanner { vector, payload: { filePath: generateRelativeFilePath(normalizedAbsolutePath, scanWorkspace), + fileHash: block.fileHash, codeChunk: block.content, startLine: block.start_line, endLine: block.end_line, diff --git a/packages/kilo-indexing/src/indexing/search-service.ts b/packages/kilo-indexing/src/indexing/search-service.ts index 71da8834985..139c1a41a85 100644 --- a/packages/kilo-indexing/src/indexing/search-service.ts +++ b/packages/kilo-indexing/src/indexing/search-service.ts @@ -58,7 +58,7 @@ export class CodeIndexSearchService { return search(maxResults) })() const base = (async () => { - const checks = new Map>() + const checks = new Map>() const search = async (limit: number): Promise => { const results = await this.baseline!.store.search(vector, normalizedPrefix, minScore, limit) const accepted = await Promise.all( @@ -73,12 +73,9 @@ export class CodeIndexSearchService { const [baseline, current] = await Promise.all([base, delta]) const merged = new Map() const key = (result: VectorStoreSearchResult) => - [ - result.payload?.filePath, - result.payload?.startLine, - result.payload?.endLine, - result.payload?.codeChunk, - ].join("\0") + [result.payload?.filePath, result.payload?.startLine, result.payload?.endLine, result.payload?.codeChunk].join( + "\0", + ) for (const result of baseline) merged.set(key(result), result) for (const result of current) { diff --git a/packages/kilo-indexing/src/indexing/vector-store/lancedb-vector-store.ts b/packages/kilo-indexing/src/indexing/vector-store/lancedb-vector-store.ts index c5405d6dafc..f97363af68c 100644 --- a/packages/kilo-indexing/src/indexing/vector-store/lancedb-vector-store.ts +++ b/packages/kilo-indexing/src/indexing/vector-store/lancedb-vector-store.ts @@ -10,8 +10,20 @@ import type { EmbeddingProfile } from "../embedding-profile" import { loadLanceDB } from "./lancedb-loader" const log = Log.create({ service: "lancedb-store" }) +let nativeQueue = Promise.resolve() +function native(run: () => Promise): Promise { + const task = nativeQueue.then(run) + nativeQueue = task.then( + () => undefined, + () => undefined, + ) + return task +} + +const SCHEMA = "2" const KEY = { + schema: "index_schema", size: "vector_size", complete: "indexing_complete", provider: "embedding_provider", @@ -74,19 +86,16 @@ export class LanceDBVectorStore implements IVectorStore { * @returns The LanceDB connection. */ private async getDb(): Promise { - if (this.db) { - return this.db - } + if (this.db) return this.db - const lancedb = await this.loadLanceDBModule() + return native(async () => { + if (this.db) return this.db + const lancedb = await this.loadLanceDBModule() - // Create parent directory if needed - if (!fs.existsSync(this.dbPath)) { - fs.mkdirSync(this.dbPath, { recursive: true }) - } - - this.db = await lancedb.connect(this.dbPath) - return this.db as Connection + if (!fs.existsSync(this.dbPath)) fs.mkdirSync(this.dbPath, { recursive: true }) + this.db = await lancedb.connect(this.dbPath) + return this.db as Connection + }) } /** @@ -102,7 +111,7 @@ export class LanceDBVectorStore implements IVectorStore { try { // Try to open existing table - const table = await db.openTable(this.vectorTableName) + const table = await native(() => db.openTable(this.vectorTableName)) this.table = table return table } catch (error) { @@ -121,6 +130,7 @@ export class LanceDBVectorStore implements IVectorStore { id: "sample", vector: new Array(this.vectorSize).fill(0), filePath: "sample", + fileHash: "sample", codeChunk: "sample", startLine: 0, endLine: 0, @@ -134,6 +144,10 @@ export class LanceDBVectorStore implements IVectorStore { */ private _createMetadataData() { return [ + { + key: KEY.schema, + value: SCHEMA, + }, { key: KEY.size, value: String(this.vectorSize), @@ -162,7 +176,7 @@ export class LanceDBVectorStore implements IVectorStore { * @param db The LanceDB connection. */ private async _createVectorTable(db: Connection): Promise { - this.table = await db.createTable(this.vectorTableName, this._createSampleData()) + this.table = await native(() => db.createTable(this.vectorTableName, this._createSampleData())) if (this.table) { await this.table.delete("id = 'sample'") } @@ -173,7 +187,7 @@ export class LanceDBVectorStore implements IVectorStore { * @param db The LanceDB connection. */ private async _createMetadataTable(db: Connection): Promise { - await db.createTable(this.metadataTableName, this._createMetadataData()) + await native(() => db.createTable(this.metadataTableName, this._createMetadataData())) } /** @@ -194,15 +208,10 @@ export class LanceDBVectorStore implements IVectorStore { * @returns The stored vector size, or null if not found. */ private async _getStoredVectorSize(db: Connection): Promise { - try { - const value = await this._getMetadataValue(db, KEY.size) - if (value === undefined) return null - const dim = this._parseNumber(value) - return dim ?? null - } catch (error) { - log.warn("Failed to read metadata table", { error }) - return null - } + const value = await this._getMetadataValue(db, KEY.size) + if (value === undefined) return null + const dim = this._parseNumber(value) + return dim ?? null } private isValidMetadataKey(key: string): boolean { @@ -219,27 +228,22 @@ export class LanceDBVectorStore implements IVectorStore { if (!this.isValidMetadataKey(key)) { throw new Error(`Invalid metadata key: ${key}`) } - const metadataTable = await db.openTable(this.metadataTableName) + const metadataTable = await native(() => db.openTable(this.metadataTableName)) const rows = await metadataTable.query().where(`key = '${key}'`).toArray() return rows.length > 0 ? rows[0].value : undefined } private async _getStoredEmbeddingProfile(db: Connection): Promise { - try { - const provider = await this._getMetadataValue(db, KEY.provider) - const modelId = await this._getMetadataValue(db, KEY.model) - const dimension = await this._getMetadataValue(db, KEY.dimension) - if (typeof provider !== "string" || typeof modelId !== "string") return undefined - const dim = this._parseNumber(dimension) - if (!dim) return undefined - return { - provider: provider as EmbeddingProfile["provider"], - modelId, - dimension: dim, - } - } catch (error) { - log.warn("Failed to read embedding profile metadata", { error }) - return undefined + const provider = await this._getMetadataValue(db, KEY.provider) + const modelId = await this._getMetadataValue(db, KEY.model) + const dimension = await this._getMetadataValue(db, KEY.dimension) + if (typeof provider !== "string" || typeof modelId !== "string") return undefined + const dim = this._parseNumber(dimension) + if (!dim) return undefined + return { + provider: provider as EmbeddingProfile["provider"], + modelId, + dimension: dim, } } @@ -265,9 +269,11 @@ export class LanceDBVectorStore implements IVectorStore { throw new Error("Baseline LanceDB embedding profile does not match the worktree") } + const schema = await this._getMetadataValue(db, KEY.schema) + if (String(schema) !== SCHEMA) throw new Error("Baseline LanceDB index schema does not match the worktree") const complete = await this._getMetadataValue(db, KEY.complete) if (String(complete) !== "true") throw new Error("Baseline LanceDB index is not complete") - this.table = await db.openTable(this.vectorTableName) + this.table = await native(() => db.openTable(this.vectorTableName)) } async initialize(): Promise { @@ -293,12 +299,13 @@ export class LanceDBVectorStore implements IVectorStore { return true } - this.table = await db.openTable(this.vectorTableName) + this.table = await native(() => db.openTable(this.vectorTableName)) const storedVectorSize = metadataTableExists ? await this._getStoredVectorSize(db) : null + const storedSchema = metadataTableExists ? await this._getMetadataValue(db, KEY.schema) : undefined const pointCount = await this.table.countRows() - if (storedVectorSize === null || storedVectorSize !== this.vectorSize) { + if (String(storedSchema) !== SCHEMA || storedVectorSize === null || storedVectorSize !== this.vectorSize) { needsRecreation = true } @@ -363,6 +370,7 @@ export class LanceDBVectorStore implements IVectorStore { id: point.id, vector: point.vector, filePath: point.payload.filePath, + fileHash: point.payload.fileHash, codeChunk: point.payload.codeChunk, startLine: point.payload.startLine, endLine: point.payload.endLine, @@ -411,7 +419,7 @@ export class LanceDBVectorStore implements IVectorStore { if (!payload) { return false } - const validKeys = ["filePath", "codeChunk", "startLine", "endLine"] + const validKeys = ["filePath", "fileHash", "codeChunk", "startLine", "endLine"] const hasValidKeys = validKeys.every((key) => key in payload) return hasValidKeys } @@ -450,6 +458,7 @@ export class LanceDBVectorStore implements IVectorStore { score: 1 - result._distance, // Convert distance to similarity score payload: { filePath: result.filePath, + fileHash: result.fileHash, codeChunk: result.codeChunk, startLine: result.startLine, endLine: result.endLine, @@ -520,7 +529,7 @@ export class LanceDBVectorStore implements IVectorStore { const tableNames = await db.tableNames() if (tableNames.includes(this.metadataTableName)) { - const metadataTable = await db.openTable(this.metadataTableName) + const metadataTable = await native(() => db.openTable(this.metadataTableName)) await metadataTable.delete("true") } } catch (metadataError) { @@ -593,7 +602,7 @@ export class LanceDBVectorStore implements IVectorStore { }) return false } - const metadataTable = await db.openTable(this.metadataTableName) + const metadataTable = await native(() => db.openTable(this.metadataTableName)) const metadataResults = await metadataTable.query().where(`key = '${KEY.complete}'`).toArray() const indexed = metadataResults.length > 0 ? String(metadataResults[0].value) === "true" : false log.info("LanceDB indexing metadata evaluated", { @@ -603,8 +612,8 @@ export class LanceDBVectorStore implements IVectorStore { }) return indexed } catch (error) { - log.warn("Failed to check if collection has data", { error }) - return false + log.error("Failed to check if collection has data", { error }) + throw error } } @@ -619,6 +628,7 @@ export class LanceDBVectorStore implements IVectorStore { } private async _persistEmbeddingProfile(metadataTable: Table): Promise { + await this._upsertMetadata(metadataTable, KEY.schema, SCHEMA) await this._upsertMetadata(metadataTable, KEY.provider, this.profile.provider) await this._upsertMetadata(metadataTable, KEY.model, this.profile.modelId) await this._upsertMetadata(metadataTable, KEY.dimension, this.profile.dimension) @@ -632,7 +642,7 @@ export class LanceDBVectorStore implements IVectorStore { async markIndexingComplete(): Promise { try { const db = await this.getDb() - const metadataTable = await db.openTable(this.metadataTableName) + const metadataTable = await native(() => db.openTable(this.metadataTableName)) await this._persistEmbeddingProfile(metadataTable) await this._upsertMetadata(metadataTable, KEY.complete, "true") log.info("Marked indexing as complete") @@ -649,7 +659,7 @@ export class LanceDBVectorStore implements IVectorStore { async markIndexingIncomplete(): Promise { try { const db = await this.getDb() - const metadataTable = await db.openTable(this.metadataTableName) + const metadataTable = await native(() => db.openTable(this.metadataTableName)) await this._persistEmbeddingProfile(metadataTable) await this._upsertMetadata(metadataTable, KEY.complete, "false") log.info("Marked indexing as incomplete (in progress)") diff --git a/packages/kilo-indexing/src/indexing/vector-store/qdrant-client.ts b/packages/kilo-indexing/src/indexing/vector-store/qdrant-client.ts index 7082a0a4062..53a4df91561 100644 --- a/packages/kilo-indexing/src/indexing/vector-store/qdrant-client.ts +++ b/packages/kilo-indexing/src/indexing/vector-store/qdrant-client.ts @@ -9,7 +9,9 @@ import type { EmbeddingProfile } from "../embedding-profile" const log = Log.create({ service: "qdrant-store" }) +const SCHEMA = 2 const KEY = { + schema: "index_schema", complete: "indexing_complete", provider: "embedding_provider", model: "embedding_model_id", @@ -221,12 +223,12 @@ export class QdrantVectorStore implements IVectorStore { }) } - private async recreateCollectionForProfile(stored?: EmbeddingProfile): Promise { + private async recreateCollectionForCompatibility(stored?: EmbeddingProfile): Promise { const from = stored ? `${stored.provider}:${stored.modelId}:${stored.dimension}` : "missing embedding metadata on populated collection" const to = `${this.profile.provider}:${this.profile.modelId}:${this.profile.dimension}` - log.warn(`Collection ${this.collectionName} embedding profile changed (${from} -> ${to}). Recreating collection.`) + log.warn(`Collection ${this.collectionName} is incompatible (${from} -> ${to}). Recreating collection.`) await this.client.deleteCollection(this.collectionName) await new Promise((resolve) => setTimeout(resolve, 100)) @@ -258,6 +260,7 @@ export class QdrantVectorStore implements IVectorStore { if (!profile || !this.isProfileMatch(profile)) { throw new Error("Baseline Qdrant embedding profile does not match the worktree") } + if (payload?.[KEY.schema] !== SCHEMA) throw new Error("Baseline Qdrant index schema does not match the worktree") if (payload?.[KEY.complete] !== true) throw new Error("Baseline Qdrant index is not complete") } @@ -299,8 +302,8 @@ export class QdrantVectorStore implements IVectorStore { } else { const payload = await this.getMetadataPayload() const profile = this.getStoredProfile(payload) - created = - !profile || !this.isProfileMatch(profile) ? await this.recreateCollectionForProfile(profile) : false + const compatible = payload?.[KEY.schema] === SCHEMA && profile && this.isProfileMatch(profile) + created = compatible ? false : await this.recreateCollectionForCompatibility(profile) } } else { // Exists but wrong vector size, recreate with enhanced error handling @@ -474,7 +477,7 @@ export class QdrantVectorStore implements IVectorStore { if (!payload) { return false } - const validKeys = ["filePath", "codeChunk", "startLine", "endLine"] + const validKeys = ["filePath", "fileHash", "codeChunk", "startLine", "endLine"] const hasValidKeys = validKeys.every((key) => key in payload) return hasValidKeys } @@ -544,7 +547,7 @@ export class QdrantVectorStore implements IVectorStore { exact: false, }, with_payload: { - include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], + include: ["filePath", "fileHash", "codeChunk", "startLine", "endLine", "pathSegments"], }, } @@ -676,14 +679,7 @@ export class QdrantVectorStore implements IVectorStore { */ async hasIndexedData(): Promise { try { - const collectionInfo = await this.getCollectionInfo() - if (!collectionInfo) { - log.info("Qdrant collection has no indexed data", { - collection: this.collectionName, - reason: "collection_missing", - }) - return false - } + const collectionInfo = await this.client.getCollection(this.collectionName) // Check if the collection has any points indexed const pointsCount = collectionInfo.points_count ?? 0 if (pointsCount === 0) { @@ -713,8 +709,8 @@ export class QdrantVectorStore implements IVectorStore { log.info("No indexing metadata marker found. Using backward compatibility mode (checking points_count > 0).") return pointsCount > 0 } catch (error) { - log.warn("Failed to check if collection has data", { error }) - return false + log.error("Failed to check if collection has data", { error }) + throw error } } @@ -731,6 +727,7 @@ export class QdrantVectorStore implements IVectorStore { vector: new Array(this.vectorSize).fill(0), payload: { type: "metadata", + [KEY.schema]: SCHEMA, [KEY.complete]: true, [KEY.provider]: this.profile.provider, [KEY.model]: this.profile.modelId, @@ -761,6 +758,7 @@ export class QdrantVectorStore implements IVectorStore { vector: new Array(this.vectorSize).fill(0), payload: { type: "metadata", + [KEY.schema]: SCHEMA, [KEY.complete]: false, [KEY.provider]: this.profile.provider, [KEY.model]: this.profile.modelId, diff --git a/packages/kilo-indexing/src/indexing/worktree-overlay.ts b/packages/kilo-indexing/src/indexing/worktree-overlay.ts index 28efc0fd298..741bf4ae807 100644 --- a/packages/kilo-indexing/src/indexing/worktree-overlay.ts +++ b/packages/kilo-indexing/src/indexing/worktree-overlay.ts @@ -64,35 +64,22 @@ export class WorktreeOverlay { this.ready = true } - async baselineResult( - result: VectorStoreSearchResult, - checks: Map>, - ): Promise { + async baselineResult(result: VectorStoreSearchResult, checks: Map>): Promise { const filePath = result.payload?.filePath if (typeof filePath !== "string") return false const rel = this.relative(filePath) if (!rel || this.shadows.has(rel) || this.blocked.has(rel)) return false const expected = this.baseline.get(rel) - if (!expected) return false + if (!expected || result.payload?.fileHash !== expected) return false const existing = checks.get(rel) const valid = existing ?? - Promise.all([ - readFile(path.join(this.baselinePath, ...rel.split("/")), "utf-8"), - readFile(path.join(this.workspacePath, ...rel.split("/")), "utf-8"), - ]) - .then(([baseline, current]) => { - const hash = (content: string) => createHash("sha256").update(content).digest("hex") - return { valid: hash(baseline) === expected && hash(current) === expected, content: current } - }) - .catch(() => ({ valid: false, content: "" })) + readFile(path.join(this.workspacePath, ...rel.split("/")), "utf-8") + .then((content) => createHash("sha256").update(content).digest("hex") === expected) + .catch(() => false) checks.set(rel, valid) - const checked = await valid - if (!checked.valid) return false - - const chunk = result.payload?.codeChunk - return typeof chunk === "string" && checked.content.includes(chunk) + return valid } deltaResult(result: VectorStoreSearchResult): boolean { diff --git a/packages/kilo-indexing/test/kilocode/indexing/manager.test.ts b/packages/kilo-indexing/test/kilocode/indexing/manager.test.ts index 4999d52df4d..9eabad3c61e 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/manager.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/manager.test.ts @@ -1,4 +1,5 @@ -import { describe, expect, test } from "bun:test" +import { describe, expect, spyOn, test } from "bun:test" +import { CacheManager } from "../../../src/indexing/cache-manager" import { CodeIndexManager } from "../../../src/indexing/manager" import type { IndexingConfigInput } from "../../../src/indexing/config-manager" import type { IndexingTelemetryEvent, IndexingTelemetryTrigger } from "../../../src/indexing/interfaces/telemetry" @@ -73,6 +74,59 @@ function createStartError(location = "orchestrator:startIndexing"): IndexingTele } describe("CodeIndexManager", () => { + test("falls back when the shared baseline is not ready", async () => { + const mgr = new CodeIndexManager("/tmp/worktree", "/tmp/cache", "/tmp/main") + let closed = 0 + const data = mgr as unknown as { + createBaseline(factory: { createVectorStore(): unknown }): Promise<{ store?: unknown }> + } + const baseline = await data.createBaseline({ + createVectorStore() { + return { + async openExisting() { + throw new Error("baseline rebuilding") + }, + async close() { + closed += 1 + }, + } + }, + }) + + expect(baseline.store).toBeUndefined() + expect(closed).toBe(1) + }) + + test("throttles unchanged baseline cache checks between searches", async () => { + const mgr = new CodeIndexManager("/tmp/worktree", "/tmp/cache", "/tmp/main") + const data = createData(mgr) as Data & { + _baselineStamp: string + _baselineSigned: number + _orchestrator: { state: string } + _searchService: { searchIndex(): Promise<[]> } + } + data._baselineStamp = "same" + data._baselineSigned = Date.now() + data._orchestrator = { state: "Indexed" } + data._searchService = { + async searchIndex() { + return [] + }, + } + const stamp = spyOn(CacheManager.prototype, "stamp").mockResolvedValue("same") + const initialize = spyOn(CacheManager.prototype, "initialize").mockResolvedValue() + + try { + await mgr.searchIndex("first") + await mgr.searchIndex("second") + expect(stamp).toHaveBeenCalledTimes(1) + expect(initialize).not.toHaveBeenCalled() + } finally { + stamp.mockRestore() + initialize.mockRestore() + } + }) + test("returns standby state before services are initialized", () => { const mgr = new CodeIndexManager("/tmp/ws", "/tmp/cache") const data = mgr as unknown as { diff --git a/packages/kilo-indexing/test/kilocode/indexing/orchestrator.test.ts b/packages/kilo-indexing/test/kilocode/indexing/orchestrator.test.ts index d2f43697190..4bef1912c7b 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/orchestrator.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/orchestrator.test.ts @@ -272,6 +272,89 @@ describe("CodeIndexOrchestrator telemetry", () => { expect(store.closeCount).toBe(1) }) + test("clears stale vectors and hashes before rebuilding an incomplete store", async () => { + const cache = { + clears: 0, + async clearCacheFile() { + this.clears += 1 + }, + async flush() {}, + } + const store = new Store(false, false) + const orchestrator = new CodeIndexOrchestrator( + createConfig(), + new CodeIndexStateManager(), + "/tmp/ws", + cache as unknown as CacheManager, + store as unknown as IVectorStore, + new Scanner(1, 1, 1) as unknown as DirectoryScanner, + new Watcher() as unknown as IFileWatcher, + ) + + await orchestrator.startIndexing("background") + + expect(store.clearCount).toBe(1) + expect(cache.clears).toBe(1) + expect(orchestrator.state).toBe("Indexed") + }) + + test("does not clear data when index completeness cannot be read", async () => { + const cache = { + clears: 0, + async clearCacheFile() { + this.clears += 1 + }, + } + const store = new Store(true, false) + store.hasIndexedData = async () => { + throw new Error("metadata unavailable") + } + const orchestrator = new CodeIndexOrchestrator( + createConfig(), + new CodeIndexStateManager(), + "/tmp/ws", + cache as unknown as CacheManager, + store as unknown as IVectorStore, + new Scanner(1, 1, 1) as unknown as DirectoryScanner, + new Watcher() as unknown as IFileWatcher, + ) + + await orchestrator.startIndexing("background") + + expect(store.clearCount).toBe(0) + expect(cache.clears).toBe(0) + expect(orchestrator.state).toBe("Error") + }) + + test("rebuilds a complete independent index when the shared baseline is unavailable", async () => { + const cache = { + clears: 0, + async clearCacheFile() { + this.clears += 1 + }, + async flush() {}, + } + const store = new Store(true, false) + const orchestrator = new CodeIndexOrchestrator( + createConfig(), + new CodeIndexStateManager(), + "/tmp/ws", + cache as unknown as CacheManager, + store as unknown as IVectorStore, + new Scanner(1, 1, 1) as unknown as DirectoryScanner, + new Watcher() as unknown as IFileWatcher, + undefined, + undefined, + true, + ) + + await orchestrator.startIndexing("background") + + expect(store.clearCount).toBe(1) + expect(cache.clears).toBe(1) + expect(orchestrator.state).toBe("Indexed") + }) + test("preserves cache and collection data on retryable start failures", async () => { const events: IndexingTelemetryEvent[] = [] const cache = { diff --git a/packages/kilo-indexing/test/kilocode/indexing/search-service.test.ts b/packages/kilo-indexing/test/kilocode/indexing/search-service.test.ts index 1c83734ce0a..9447aa688fa 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/search-service.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/search-service.test.ts @@ -9,10 +9,10 @@ import { CodeIndexSearchService } from "../../../src/indexing/search-service" import { CodeIndexStateManager } from "../../../src/indexing/state-manager" import { WorktreeOverlay } from "../../../src/indexing/worktree-overlay" -const result = (filePath: string, score: number, codeChunk = filePath): VectorStoreSearchResult => ({ +const result = (filePath: string, score: number, codeChunk = filePath, fileHash?: string): VectorStoreSearchResult => ({ id: `${filePath}:${score}`, score, - payload: { filePath, codeChunk, startLine: 1, endLine: 1 }, + payload: { filePath, fileHash, codeChunk, startLine: 1, endLine: 1 }, }) const store = (results: VectorStoreSearchResult[], limits: number[]): IVectorStore => @@ -79,7 +79,7 @@ describe("CodeIndexSearchService worktree search", () => { embedder(calls), store([result("src/changed.ts", 0.9, "worktree")], deltaLimits), { - store: store([result("src/changed.ts", 0.99), result("src/base.ts", 0.8)], baseLimits), + store: store([result("src/changed.ts", 0.99), result("src/base.ts", 0.8, "src/base.ts", hash)], baseLimits), overlay, }, ) @@ -105,7 +105,17 @@ describe("CodeIndexSearchService worktree search", () => { const state = new CodeIndexStateManager() state.setSystemState("Indexed") const service = new CodeIndexSearchService(config(), state, embedder([]), store([], []), { - store: store([result("file.ts", 0.99, "export const value = 'primary-only'")], []), + store: store( + [ + result( + "file.ts", + 0.99, + "export const value = 'primary-only'", + createHash("sha256").update("export const value = 'primary-only'").digest("hex"), + ), + ], + [], + ), overlay, }) @@ -144,24 +154,18 @@ describe("CodeIndexSearchService worktree search", () => { const limits: number[] = [] const state = new CodeIndexStateManager() state.setSystemState("Indexed") - const service = new CodeIndexSearchService( - config(), - state, - embedder([]), - store([], []), - { - store: store( - [ - result("src/a.ts", 0.99), - result("src/b.ts", 0.98), - result("src/c.ts", 0.8), - result("src/d.ts", 0.7), - ], - limits, - ), - overlay, - }, - ) + const service = new CodeIndexSearchService(config(), state, embedder([]), store([], []), { + store: store( + [ + result("src/a.ts", 0.99), + result("src/b.ts", 0.98), + result("src/c.ts", 0.8, "src/c.ts", hash("src/c.ts")), + result("src/d.ts", 0.7, "src/d.ts", hash("src/d.ts")), + ], + limits, + ), + overlay, + }) const results = await service.searchIndex("query") diff --git a/packages/kilo-indexing/test/kilocode/indexing/vector-store/lancedb-vector-store.test.ts b/packages/kilo-indexing/test/kilocode/indexing/vector-store/lancedb-vector-store.test.ts index 7355abb8301..2ac7fdeeab6 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/vector-store/lancedb-vector-store.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/vector-store/lancedb-vector-store.test.ts @@ -173,6 +173,29 @@ describe("LocalVectorStore", () => { expect(mockLoadLanceDB).toHaveBeenCalledTimes(1) expect(mockLanceDBModule.connect).not.toHaveBeenCalled() }) + + test("serializes native connections across stores", async () => { + const other = new LanceDBVectorStore(path.join("mock", "other"), vectorSize, dbDirectory) + store["db"] = null + other["lancedbModule"] = mockLanceDBModule + let active = 0 + let maximum = 0 + mockLanceDBModule.connect.mockImplementation(async () => { + active += 1 + maximum = Math.max(maximum, active) + await Bun.sleep(10) + active -= 1 + return mockDb + }) + + try { + await Promise.all([store.collectionExists(), other.collectionExists()]) + } finally { + await other.close() + } + + expect(maximum).toBe(1) + }) }) describe("initialize", () => { @@ -183,7 +206,9 @@ describe("LocalVectorStore", () => { modelId: "", dimension: vectorSize, }) - store["_getMetadataValue"] = mock().mockResolvedValue("true") + store["_getMetadataValue"] = mock((_: unknown, key: string) => + Promise.resolve(key === "index_schema" ? "2" : "true"), + ) await store.openExisting() @@ -211,19 +236,49 @@ describe("LocalVectorStore", () => { expect(mockDb.dropTable).toHaveBeenCalled() }) - test("should not recreate if vector size matches", async () => { + test("should not recreate if vector size and schema match", async () => { mockDb.tableNames.mockResolvedValue(["vector", "metadata"]) mockDb.openTable.mockResolvedValue(mockTable) store["_getStoredVectorSize"] = mock().mockResolvedValue(vectorSize) + store["_getMetadataValue"] = mock().mockResolvedValue("2") const result = await store.initialize() expect(result).toBe(false) }) + test("recreates an index using the legacy payload schema", async () => { + mockDb.tableNames.mockResolvedValue(["vector", "metadata"]) + mockDb.openTable.mockResolvedValue(mockTable) + store["_getStoredVectorSize"] = mock().mockResolvedValue(vectorSize) + store["_getMetadataValue"] = mock().mockResolvedValue("1") + + expect(await store.initialize()).toBe(true) + expect(mockDb.dropTable).toHaveBeenCalledTimes(2) + }) + test("should throw error on LanceDB failure", async () => { mockDb.tableNames.mockRejectedValue(new Error("fail")) await expect(store.initialize()).rejects.toThrow() }) + test("does not recreate when vector metadata cannot be read", async () => { + store["_getStoredVectorSize"] = mock().mockRejectedValue(new Error("metadata unavailable")) + + await expect(store.initialize()).rejects.toThrow("metadata unavailable") + expect(mockDb.dropTable).not.toHaveBeenCalled() + expect(mockDb.createTable).not.toHaveBeenCalled() + }) + + test("does not recreate when profile metadata cannot be read", async () => { + mockTable.countRows.mockResolvedValue(1) + store["_getStoredVectorSize"] = mock().mockResolvedValue(vectorSize) + store["_getMetadataValue"] = mock().mockResolvedValue("2") + store["_getStoredEmbeddingProfile"] = mock().mockRejectedValue(new Error("profile unavailable")) + + await expect(store.initialize()).rejects.toThrow("profile unavailable") + expect(mockDb.dropTable).not.toHaveBeenCalled() + expect(mockDb.createTable).not.toHaveBeenCalled() + }) + test("should recreate tables when stored embedding identity differs", async () => { const identity = { provider: "openai", @@ -332,7 +387,7 @@ describe("LocalVectorStore", () => { { id: "123e4567-e89b-12d3-a456-426614174000", vector: [1, 2, 3], - payload: { filePath: "a", codeChunk: "b", startLine: 1, endLine: 2 }, + payload: { filePath: "a", fileHash: "hash-a", codeChunk: "b", startLine: 1, endLine: 2 }, }, ] mockTable.delete.mockResolvedValue(undefined) @@ -347,7 +402,7 @@ describe("LocalVectorStore", () => { { id: "123e4567-e89b-12d3-a456-426614174000", vector: [1, 2, 3], - payload: { filePath: "a", codeChunk: "b", startLine: 1, endLine: 2 }, + payload: { filePath: "a", fileHash: "hash-a", codeChunk: "b", startLine: 1, endLine: 2 }, }, ] mockTable.delete.mockResolvedValue(undefined) @@ -365,7 +420,7 @@ describe("LocalVectorStore", () => { distanceRange: distanceRangeSpy, limit: mock().mockReturnThis(), toArray: mock().mockResolvedValue([ - { id: "2", _distance: 0.2, filePath: "a", codeChunk: "c", startLine: 3, endLine: 4 }, + { id: "2", _distance: 0.2, filePath: "a", fileHash: "hash-a", codeChunk: "c", startLine: 3, endLine: 4 }, ]), }) const results = await store.search([1, 2, 3], "a", 0.7, 1) @@ -389,7 +444,7 @@ describe("LocalVectorStore", () => { distanceRange: distanceRangeSpy, limit: mock().mockReturnThis(), toArray: mock().mockResolvedValue([ - { id: "2", _distance: 0.2, filePath: "a", codeChunk: "c", startLine: 3, endLine: 4 }, + { id: "2", _distance: 0.2, filePath: "a", fileHash: "hash-a", codeChunk: "c", startLine: 3, endLine: 4 }, ]), }) const results = await store.search([1, 2, 3], "a", 0.1, 2) @@ -506,7 +561,7 @@ describe("LocalVectorStore", () => { }) test("should return true for valid payload", () => { - const payload: Payload = { filePath: "a", codeChunk: "b", startLine: 1, endLine: 2 } + const payload: Payload = { filePath: "a", fileHash: "hash-a", codeChunk: "b", startLine: 1, endLine: 2 } expect(store["isPayloadValid"](payload)).toBe(true) }) }) @@ -606,6 +661,7 @@ describe("LocalVectorStore", () => { vector: [1, 2, 3], payload: { filePath: "test.ts", + fileHash: "hash-test", codeChunk: "code", startLine: 1, endLine: 2, @@ -625,12 +681,12 @@ describe("LocalVectorStore", () => { { id: "123e4567-e89b-12d3-a456-426614174000", vector: [1, 2, 3], - payload: { filePath: "a", codeChunk: "b", startLine: 1, endLine: 2 }, + payload: { filePath: "a", fileHash: "hash-a", codeChunk: "b", startLine: 1, endLine: 2 }, }, { id: "' OR '1'='1", vector: [4, 5, 6], - payload: { filePath: "c", codeChunk: "d", startLine: 3, endLine: 4 }, + payload: { filePath: "c", fileHash: "hash-c", codeChunk: "d", startLine: 3, endLine: 4 }, }, ] diff --git a/packages/kilo-indexing/test/kilocode/indexing/vector-store/qdrant-client.test.ts b/packages/kilo-indexing/test/kilocode/indexing/vector-store/qdrant-client.test.ts index 36739fb3556..9ecb36221b2 100644 --- a/packages/kilo-indexing/test/kilocode/indexing/vector-store/qdrant-client.test.ts +++ b/packages/kilo-indexing/test/kilocode/indexing/vector-store/qdrant-client.test.ts @@ -510,6 +510,7 @@ describe("QdrantVectorStore", () => { mockRetrieve.mockResolvedValue([ { payload: { + index_schema: 2, indexing_complete: true, embedding_provider: "openai", embedding_model_id: "", @@ -600,6 +601,33 @@ describe("QdrantVectorStore", () => { expect(mockCreatePayloadIndex).toHaveBeenCalledTimes(6) }) + test("recreates a populated collection using the legacy payload schema", async () => { + mockGetCollection + .mockResolvedValueOnce({ + points_count: 7, + config: { params: { vectors: { size: mockVectorSize } } }, + } as any) + .mockRejectedValueOnce({ response: { status: 404 }, message: "Not found" }) + mockRetrieve.mockResolvedValue([ + { + payload: { + index_schema: 1, + indexing_complete: true, + embedding_provider: "openai", + embedding_model_id: "", + embedding_dimension: mockVectorSize, + }, + }, + ] as any) + mockDeleteCollection.mockResolvedValue(true as any) + mockCreateCollection.mockResolvedValue(true as any) + mockCreatePayloadIndex.mockResolvedValue({} as any) + + expect(await vectorStore.initialize()).toBe(true) + expect(mockDeleteCollection).toHaveBeenCalledTimes(1) + expect(mockCreateCollection).toHaveBeenCalledTimes(1) + }) + test("should recreate collection when stored embedding identity mismatches", async () => { const identity = { provider: "openai", @@ -1279,6 +1307,7 @@ describe("QdrantVectorStore", () => { score: 0.85, payload: { filePath: "src/test.ts", + fileHash: "test-hash", codeChunk: "test code", startLine: 1, endLine: 5, @@ -1290,6 +1319,7 @@ describe("QdrantVectorStore", () => { score: 0.75, payload: { filePath: "src/utils.ts", + fileHash: "test-hash", codeChunk: "utility code", startLine: 10, endLine: 15, @@ -1314,7 +1344,7 @@ describe("QdrantVectorStore", () => { exact: false, }, with_payload: { - include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], + include: ["filePath", "fileHash", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) expect(callArgs.filter).toEqual({ @@ -1334,6 +1364,7 @@ describe("QdrantVectorStore", () => { score: 0.85, payload: { filePath: "src/components/Button.tsx", + fileHash: "test-hash", codeChunk: "button code", startLine: 1, endLine: 5, @@ -1353,7 +1384,7 @@ describe("QdrantVectorStore", () => { score_threshold: DEFAULT_SEARCH_MIN_SCORE, limit: DEFAULT_MAX_SEARCH_RESULTS, params: { hnsw_ef: 128, exact: false }, - with_payload: { include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"] }, + with_payload: { include: ["filePath", "fileHash", "codeChunk", "startLine", "endLine", "pathSegments"] }, }) expect(callArgs2.filter).toEqual({ must: [ @@ -1385,7 +1416,7 @@ describe("QdrantVectorStore", () => { exact: false, }, with_payload: { - include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], + include: ["filePath", "fileHash", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) expect(callArgs3.filter).toEqual({ @@ -1412,7 +1443,7 @@ describe("QdrantVectorStore", () => { exact: false, }, with_payload: { - include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], + include: ["filePath", "fileHash", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) expect(callArgs4.filter).toEqual({ @@ -1429,6 +1460,7 @@ describe("QdrantVectorStore", () => { score: 0.85, payload: { filePath: "src/test.ts", + fileHash: "test-hash", codeChunk: "test code", startLine: 1, endLine: 5, @@ -1446,6 +1478,7 @@ describe("QdrantVectorStore", () => { score: 0.55, payload: { filePath: "src/test2.ts", + fileHash: "test-hash", codeChunk: "test code 2", startLine: 10, endLine: 15, @@ -1472,6 +1505,7 @@ describe("QdrantVectorStore", () => { score: 0.85, payload: { filePath: "src/test.ts", + fileHash: "test-hash", codeChunk: "test code", startLine: 1, endLine: 5, @@ -1492,6 +1526,7 @@ describe("QdrantVectorStore", () => { score: 0.55, payload: { filePath: "src/test2.ts", + fileHash: "test-hash", codeChunk: "test code 2", startLine: 10, endLine: 15, @@ -1540,7 +1575,7 @@ describe("QdrantVectorStore", () => { exact: false, }, with_payload: { - include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], + include: ["filePath", "fileHash", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) expect(callArgs5.filter).toEqual({ @@ -1588,6 +1623,7 @@ describe("QdrantVectorStore", () => { score: 0.85, payload: { filePath: "src/test.ts", + fileHash: "test-hash", codeChunk: "test code", startLine: 1, endLine: 5, @@ -1611,7 +1647,7 @@ describe("QdrantVectorStore", () => { exact: false, }, with_payload: { - include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], + include: ["filePath", "fileHash", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) expect(callArgs7.filter).toEqual({ @@ -1640,7 +1676,7 @@ describe("QdrantVectorStore", () => { exact: false, }, with_payload: { - include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], + include: ["filePath", "fileHash", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) expect(callArgs6.filter).toEqual({ @@ -1667,7 +1703,7 @@ describe("QdrantVectorStore", () => { exact: false, }, with_payload: { - include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], + include: ["filePath", "fileHash", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) expect(callArgs8.filter).toEqual({ @@ -1694,7 +1730,7 @@ describe("QdrantVectorStore", () => { exact: false, }, with_payload: { - include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], + include: ["filePath", "fileHash", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) expect(callArgs9.filter).toEqual({ @@ -1721,7 +1757,7 @@ describe("QdrantVectorStore", () => { exact: false, }, with_payload: { - include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], + include: ["filePath", "fileHash", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) expect(callArgs10.filter).toEqual({ @@ -1748,7 +1784,7 @@ describe("QdrantVectorStore", () => { exact: false, }, with_payload: { - include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], + include: ["filePath", "fileHash", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) expect(callArgs11.filter).toEqual({ @@ -1781,7 +1817,7 @@ describe("QdrantVectorStore", () => { exact: false, }, with_payload: { - include: ["filePath", "codeChunk", "startLine", "endLine", "pathSegments"], + include: ["filePath", "fileHash", "codeChunk", "startLine", "endLine", "pathSegments"], }, }) expect(callArgs12.filter).toEqual({ diff --git a/packages/opencode/src/kilocode/indexing-worker-client.ts b/packages/opencode/src/kilocode/indexing-worker-client.ts index 1cc67c36f4c..0f055c5db13 100644 --- a/packages/opencode/src/kilocode/indexing-worker-client.ts +++ b/packages/opencode/src/kilocode/indexing-worker-client.ts @@ -170,28 +170,31 @@ export namespace IndexingWorker { ) }, search(query, directoryPrefix) { - return call( - state, - { type: "request", key, method: "search", input: { query, directoryPrefix } }, - (message) => { - if (message.ok && message.method === "search") return message.value - throw new Error("Unexpected indexing worker search response.") - }, - ) + return call(state, { type: "request", key, method: "search", input: { query, directoryPrefix } }, (message) => { + if (message.ok && message.method === "search") return message.value + throw new Error("Unexpected indexing worker search response.") + }) }, async dispose() { if (!active || state.stopped) return active = false - await withTimeout( - call(state, { type: "request", key, method: "dispose", input: undefined }, (message) => { - if (message.ok && message.method === "dispose") return message.value - throw new Error("Unexpected indexing worker dispose response.") - }), - 5000, - "Indexing worker reset timed out", - ).catch((err) => { + if (state.hosts.get(key) === host) state.hosts.delete(key) + if (pool.get(key) === host) pool.delete(key) + try { + await withTimeout( + call(state, { type: "request", key, method: "dispose", input: undefined }, (message) => { + if (message.ok && message.method === "dispose") return message.value + throw new Error("Unexpected indexing worker dispose response.") + }), + 5000, + "Indexing worker reset timed out", + ) + } catch (err) { callbacks.failure(err) - }) + } finally { + if (state.hosts.get(key) === host) state.hosts.delete(key) + if (pool.get(key) === host) pool.delete(key) + } }, } state.hosts.set(key, host) diff --git a/packages/opencode/src/kilocode/indexing-worker.ts b/packages/opencode/src/kilocode/indexing-worker.ts index bfbca31e99f..a37827a6310 100644 --- a/packages/opencode/src/kilocode/indexing-worker.ts +++ b/packages/opencode/src/kilocode/indexing-worker.ts @@ -13,7 +13,6 @@ type Entry = { const managers = new Map() const context = new AsyncLocalStorage() const queues = new Map>() -let initQueue = Promise.resolve() function send(message: Result | Event) { postMessage(message) @@ -75,7 +74,9 @@ async function handle(request: Request) { } if (request.method === "search") { - const value = await managers.get(request.key)?.manager.searchIndex(request.input.query, request.input.directoryPrefix) + const value = await managers + .get(request.key) + ?.manager.searchIndex(request.input.query, request.input.directoryPrefix) send({ type: "result", id: request.id, method: "search", ok: true, value: value ?? [] }) return } @@ -90,17 +91,7 @@ async function handle(request: Request) { onmessage = (event: MessageEvent) => { const request = event.data const prior = queues.get(request.key) ?? Promise.resolve() - const task = prior.then(() => - context.run(request.key, () => { - if (request.method !== "init") return handle(request) - const next = initQueue.then(() => handle(request)) - initQueue = next.then( - () => undefined, - () => undefined, - ) - return next - }), - ) + const task = prior.then(() => context.run(request.key, () => handle(request))) const queued = task.finally(() => { if (queues.get(request.key) === queued) queues.delete(request.key) }) diff --git a/packages/opencode/test/kilocode/indexing-worker.test.ts b/packages/opencode/test/kilocode/indexing-worker.test.ts index 5107495c910..a3c44002ad6 100644 --- a/packages/opencode/test/kilocode/indexing-worker.test.ts +++ b/packages/opencode/test/kilocode/indexing-worker.test.ts @@ -55,7 +55,29 @@ test("routes multiple directories through the shared indexing worker", async () expect(failures).toEqual([]) }) -test("reuses enabled workers across provider initialization errors", async () => { +test("allows same-directory recreation while disposal is pending", async () => { + await using tmp = await tmpdir() + const hooks = { + status() {}, + telemetry() {}, + warning() {}, + log() {}, + failure() {}, + } + const first = IndexingWorker.create(tmp.path, tmp.path, hooks) + await first.init({ enabled: false, embedderProvider: "openai" }) + + const disposing = first.dispose() + const second = IndexingWorker.create(tmp.path, tmp.path, hooks) + const status = await second.init({ enabled: false, embedderProvider: "openai" }) + await disposing + await second.dispose() + + expect(second).not.toBe(first) + expect(status.state).toBe("Disabled") +}) + +test("releases enabled workers after provider initialization errors", async () => { await using tmp = await tmpdir() const drivers = new Set() @@ -107,6 +129,6 @@ test("reuses enabled workers across provider initialization errors", async () => await engine.dispose() expect(status.state).toBe("Disabled") - expect(drivers.has(engine)).toBe(true) - expect(drivers.size).toBe(1) + expect(drivers.has(engine)).toBe(false) + expect(drivers.size).toBe(3) }) From 23d60433c1dad80ef020674b9ef76d677849aefd Mon Sep 17 00:00:00 2001 From: kirillk Date: Tue, 16 Jun 2026 10:52:15 -0400 Subject: [PATCH 33/78] test(jetbrains): inject UI timers per test --- .../client/session/SessionSidePanelManager.kt | 17 ++++++++------ .../ai/kilocode/client/session/SessionUi.kt | 6 ++++- .../client/session/SessionUiFactory.kt | 4 ++++ .../client/session/controller/DelayedState.kt | 12 ++++++---- .../session/controller/SessionController.kt | 5 +++- .../client/session/history/HistoryPanel.kt | 4 +++- .../session/ui/empty/EmptySessionPanel.kt | 6 +++-- .../settings/profile/LoggedOutProfileUi.kt | 6 +++-- .../client/settings/profile/ProfileUi.kt | 5 +++- .../ai/kilocode/client/util/UiTimers.kt | 18 --------------- .../session/SessionSidePanelManagerTest.kt | 23 +++++++++++++------ .../session/controller/DelayedStateTest.kt | 10 ++++---- .../controller/SessionControllerTestBase.kt | 9 ++++---- .../{FakeUiTimers.kt => TestUiTimers.kt} | 2 +- 14 files changed, 71 insertions(+), 56 deletions(-) rename packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/{FakeUiTimers.kt => TestUiTimers.kt} (98%) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionSidePanelManager.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionSidePanelManager.kt index 280155cbd49..d880bc32225 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionSidePanelManager.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionSidePanelManager.kt @@ -7,6 +7,7 @@ import ai.kilocode.client.session.history.HistoryController import ai.kilocode.client.session.history.HistoryPanel import ai.kilocode.client.telemetry.Telemetry import ai.kilocode.client.util.UiTimer +import ai.kilocode.client.util.UiTimerSource import ai.kilocode.client.util.UiTimers import com.intellij.openapi.Disposable import com.intellij.openapi.actionSystem.DataProvider @@ -26,12 +27,14 @@ import javax.swing.JPanel class SessionSidePanelManager( private val project: Project, private val root: Workspace, - private val create: (Project, Workspace, SessionManager, SessionRef?) -> SessionUi = { project, workspace, manager, ref -> - service().create(project, workspace, manager, ref) - }, + private val create: (Project, Workspace, SessionManager, SessionRef?, UiTimerSource) -> SessionUi = + { project, workspace, manager, ref, timers -> + service().create(project, workspace, manager, ref, timers) + }, private val resolve: (String) -> Workspace = { dir -> service().workspace(dir) }, private val status: () -> Map = { project.service().activity() }, private val history: ((Disposable, (SessionRef) -> Unit, (String) -> Unit) -> JComponent)? = null, + private val timers: UiTimerSource = UiTimers, ) : SessionManager, Disposable { val component: JPanel = object : JPanel(BorderLayout()), DataProvider { override fun getData(dataId: String): Any? { @@ -54,7 +57,7 @@ class SessionSidePanelManager( val active = current if (active?.blank == true) return register(active) - show(create(project, root, this, null)) + show(create(project, root, this, null, timers)) } override fun openSession(ref: SessionRef) { @@ -101,7 +104,7 @@ class SessionSidePanelManager( is SessionRef.Local -> ref.session?.directory?.let(resolve) ?: root is SessionRef.Cloud -> root } - return create(project, workspace, this, ref).also { + return create(project, workspace, this, ref, timers).also { all.add(it) opened[ref.key] = it val local = (ref as? SessionRef.Local)?.session?.id @@ -148,7 +151,7 @@ class SessionSidePanelManager( deleted = this::removeSession, ) Disposer.register(this) { cs.cancel() } - return HistoryPanel(this, controller, nav = this::back, manager = this).component + return HistoryPanel(this, controller, nav = this::back, manager = this, timers = timers).component } private fun back() { @@ -207,7 +210,7 @@ class SessionSidePanelManager( private fun schedule(ui: SessionUi) { cancel(ui) val delay = Registry.intValue("kilo.session.inactive.disposeTimeoutMs").coerceAtLeast(0) - val timer = UiTimers.timer(delay, repeats = false) { + val timer = timers.timer(delay, repeats = false) { activeTimers.remove(ui) if (ui === current || ui !in all) return@timer disposeUi(ui) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt index 2b028fb3ec0..7772ad1dda0 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUi.kt @@ -41,6 +41,7 @@ import ai.kilocode.client.session.views.question.QuestionView import ai.kilocode.client.settings.profile.UserProfileConfigurable import ai.kilocode.client.telemetry.Telemetry import ai.kilocode.client.ui.layout.Stack +import ai.kilocode.client.util.UiTimerSource import ai.kilocode.client.util.UiTimers import ai.kilocode.client.vfs.KiloVfsManager import ai.kilocode.log.ChatLogSummary @@ -97,6 +98,7 @@ class SessionUi( private val manager: SessionManager? = null, private val workspaces: KiloWorkspaceService = service(), private val migration: MigrationUiController = service(), + private val timers: UiTimerSource = UiTimers, ) : JPanel(BorderLayout()), Disposable, SessionEditorStyleTarget, UiDataProvider { companion object { @@ -133,13 +135,14 @@ class SessionUi( afterUpdate = { if (!opening) scroll.followBottom(it) }, loaded = ::onSessionLoaded, openProfileAction = ::openProfileSettings, + timers = timers, ) private lateinit var root: SessionRootPanel private lateinit var account: SessionAccountOverlay private lateinit var drop: SessionDropOverlay - private val hide = UiTimers.timer(HIDE_MS, repeats = false) { + private val hide = timers.timer(HIDE_MS, repeats = false) { if (disposed || !this::drop.isInitialized) return@timer drop.setActive(false) } @@ -416,6 +419,7 @@ class SessionUi( history = { manager?.showHistory() }, activity = { manager?.activity() ?: sessions.activity() }, titles = { manager?.titles().orEmpty() }, + timers = timers, ) empty = panel scroll.show(panel.view) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUiFactory.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUiFactory.kt index 52735be09d5..48ebfa2f5a0 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUiFactory.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/SessionUiFactory.kt @@ -3,6 +3,8 @@ package ai.kilocode.client.session import ai.kilocode.client.app.KiloAppService import ai.kilocode.client.app.KiloSessionService import ai.kilocode.client.app.Workspace +import ai.kilocode.client.util.UiTimerSource +import ai.kilocode.client.util.UiTimers import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.project.Project @@ -19,6 +21,7 @@ class SessionUiFactory( workspace: Workspace, manager: SessionManager, ref: SessionRef? = null, + timers: UiTimerSource = UiTimers, ): SessionUi = SessionUi( project = project, workspace = workspace, @@ -27,6 +30,7 @@ class SessionUiFactory( cs = scope(), ref = ref, manager = manager, + timers = timers, ) fun scope(): CoroutineScope { diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt index 03c7be99a58..851c31a6306 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt @@ -1,16 +1,20 @@ package ai.kilocode.client.session.controller +import ai.kilocode.client.util.UiTimerSource import ai.kilocode.client.util.UiTimers import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager -internal class DelayedState(private val ms: Long) : Disposable { +internal class DelayedState( + private val ms: Long, + private val timers: UiTimerSource = UiTimers, +) : Disposable { private val tick = when { ms <= 0 -> 1 ms > Int.MAX_VALUE -> Int.MAX_VALUE else -> ms.coerceAtMost(TICK_MS).toInt() } - private val timer = UiTimers.timer(tick) { flush() } + private val timer = timers.timer(tick) { flush() } private val pending = mutableListOf>() @Volatile private var alive = true @@ -45,7 +49,7 @@ internal class DelayedState(private val ms: Long) : Disposable { private fun flush() { if (!alive) return - val now = UiTimers.now() + val now = timers.now() for (item in pending.toList()) { if (item.due > now) continue apply(item) @@ -54,7 +58,7 @@ internal class DelayedState(private val ms: Long) : Disposable { } private fun due(): Long { - val now = UiTimers.now() + val now = timers.now() return now + ms.coerceAtMost(Long.MAX_VALUE - now) } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/SessionController.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/SessionController.kt index 49d265623b2..7048f8e9aa7 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/SessionController.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/SessionController.kt @@ -23,6 +23,8 @@ import ai.kilocode.client.session.model.Text import ai.kilocode.client.plugin.KiloPluginSettings import ai.kilocode.client.session.SessionRef import ai.kilocode.client.telemetry.Telemetry +import ai.kilocode.client.util.UiTimerSource +import ai.kilocode.client.util.UiTimers import ai.kilocode.rpc.dto.ChatEventDto import ai.kilocode.rpc.dto.ConfigWarningDto import ai.kilocode.rpc.dto.ConfigUpdateDto @@ -89,6 +91,7 @@ class SessionController( private val loaded: (Boolean) -> Unit = {}, private val openProfileAction: () -> Unit = {}, private val telemetry: (String, Map) -> Unit = { event, props -> Telemetry.send(event, props) }, + private val timers: UiTimerSource = UiTimers, ) : Disposable { private data class OrganizationTarget(val org: String?) @@ -138,7 +141,7 @@ class SessionController( private var viewState: SessionControllerEvent.ViewChanged? = null private var connectionState: SessionControllerEvent.ConnectionChanged? = null private var connectionTargetState: SessionControllerEvent.ConnectionChanged? = null - private val connectionDelay = DelayedState(displayMs) + private val connectionDelay = DelayedState(displayMs, timers) private var acctState: SessionControllerEvent.AccountOverlayChanged = SessionControllerEvent.AccountOverlayChanged.Hide private var acctAllowed = false diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryPanel.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryPanel.kt index e9b9ddd0095..3d969a291e7 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryPanel.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/history/HistoryPanel.kt @@ -7,6 +7,7 @@ import ai.kilocode.client.session.ui.style.SessionEditorStyle import ai.kilocode.client.ui.UiStyle import ai.kilocode.client.ui.HoverIcon import ai.kilocode.client.ui.iconButton +import ai.kilocode.client.util.UiTimerSource import ai.kilocode.client.util.UiTimers import com.intellij.icons.AllIcons import com.intellij.ide.ui.LafManagerListener @@ -59,6 +60,7 @@ class HistoryPanel( private val controller: HistoryController, private val nav: () -> Unit = {}, private val manager: SessionManager? = null, + private val timers: UiTimerSource = UiTimers, ) : BorderLayoutPanel(), Disposable, DataProvider { private val localSearch = search(controller.local) private val cloudSearch = search(controller.cloud) @@ -83,7 +85,7 @@ class HistoryPanel( .setText(KiloBundle.message("history.tab.cloud")) .setForeSideComponent(back()) private var stale = false - private val timer = UiTimers.timer(ACTIVITY_MS) { syncActivity() } + private val timer = timers.timer(ACTIVITY_MS) { syncActivity() } private val tabs: JBTabs = JBTabsFactory.createTabs(null, this).apply { presentation.setSingleRow(true) presentation.setTabsPosition(JBTabsPosition.top) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionPanel.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionPanel.kt index d96cefb9673..f277884c261 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionPanel.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/empty/EmptySessionPanel.kt @@ -12,6 +12,7 @@ import ai.kilocode.client.ui.layout.HAlign import ai.kilocode.client.ui.layout.Stack import ai.kilocode.client.ui.layout.VAlign import ai.kilocode.client.ui.layout.align +import ai.kilocode.client.util.UiTimerSource import ai.kilocode.client.util.UiTimers import ai.kilocode.rpc.dto.SessionDto import com.intellij.icons.AllIcons @@ -53,10 +54,11 @@ class EmptySessionPanel( private val activity: () -> Map = { emptyMap() }, private val titles: () -> Map = { emptyMap() }, private val browse: (String) -> Unit = BrowserUtil::browse, + private val timers: UiTimerSource = UiTimers, ) : BorderLayoutPanel(), Disposable, SessionEditorStyleTarget { val view: Align = align(HAlign.CENTER, VAlign.CENTER) - private val timer = UiTimers.timer(ACTIVITY_MS) { syncActivity() } + private val timer = timers.timer(ACTIVITY_MS) { syncActivity() } internal val recent = RecentsList(recents, controller) private val historyButton = ShowHistoryButton().apply { @@ -180,7 +182,7 @@ class EmptySessionPanel( internal fun activeView() = getComponent(0) - internal fun text(session: SessionDto, now: Long = UiTimers.now()) = + internal fun text(session: SessionDto, now: Long = timers.now()) = recent.text(session, now) internal fun rendererComponent( diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedOutProfileUi.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedOutProfileUi.kt index f1246d54a92..31af273a4bd 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedOutProfileUi.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/LoggedOutProfileUi.kt @@ -4,6 +4,7 @@ import ai.kilocode.client.plugin.KiloBundle import ai.kilocode.client.ui.HoverIcon import ai.kilocode.client.ui.RoundedContentPanel import ai.kilocode.client.ui.UiStyle +import ai.kilocode.client.util.UiTimerSource import ai.kilocode.client.util.UiTimers import ai.kilocode.rpc.dto.KiloAppStatusDto import com.intellij.icons.AllIcons @@ -47,6 +48,7 @@ internal class LoggedOutProfileUi( private val retry: () -> Unit, private val cancel: () -> Unit, private val browse: (String) -> Unit, + private val timers: UiTimerSource = UiTimers, ) : JPanel(BorderLayout()) { private val cards = JPanel(CardLayout()) @@ -147,7 +149,7 @@ internal class LoggedOutProfileUi( // -- cached URL for listener/QR deduplication -- private var lastPendingUrl: String? = null - private val timer = UiTimers.timer(1000) { syncTime() } + private val timer = timers.timer(1000) { syncTime() } init { codePanel.add(codeLabel, BorderLayout.CENTER) @@ -361,7 +363,7 @@ internal class LoggedOutProfileUi( @RequiresEdt private fun syncTime() { - val elapsed = ((UiTimers.now() - pendingStarted) / 1000).toInt() + val elapsed = ((timers.now() - pendingStarted) / 1000).toInt() val remain = (pendingExpires - elapsed).coerceAtLeast(0) val min = remain / 60 val sec = remain % 60 diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/ProfileUi.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/ProfileUi.kt index 6cfcb2d3bef..096f57fdbd7 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/ProfileUi.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/profile/ProfileUi.kt @@ -3,6 +3,7 @@ package ai.kilocode.client.settings.profile import ai.kilocode.client.app.KiloAppService import ai.kilocode.client.plugin.KiloBundle import ai.kilocode.client.telemetry.Telemetry +import ai.kilocode.client.util.UiTimerSource import ai.kilocode.client.util.UiTimers import ai.kilocode.rpc.dto.KiloAppStateDto import ai.kilocode.rpc.dto.KiloAppStatusDto @@ -43,6 +44,7 @@ internal class ProfileUi( private val cs: CoroutineScope, private val app: KiloAppService = service(), private val browse: (String) -> Unit = { BrowserUtil.browse(it) }, + private val timers: UiTimerSource = UiTimers, ) : JPanel(BorderLayout()) { private val cards = JPanel(CardLayout()) @@ -53,6 +55,7 @@ internal class ProfileUi( retry = { app.retryAsync() }, cancel = ::cancel, browse = browse, + timers = timers, ) private val account = LoggedInProfileUi( dashboard = { @@ -192,7 +195,7 @@ internal class ProfileUi( val next = app.startLogin() withContext(edt) { if (id != attempt) return@withContext - login = LoginState.Pending(next, UiTimers.now()) + login = LoginState.Pending(next, timers.now()) sync() browse(next.verificationUrl) } diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/util/UiTimers.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/util/UiTimers.kt index 94d78e5a558..88e4b0074e8 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/util/UiTimers.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/util/UiTimers.kt @@ -1,7 +1,5 @@ package ai.kilocode.client.util -import com.intellij.openapi.Disposable -import com.intellij.openapi.util.Disposer import javax.swing.Timer interface UiTimer { @@ -17,22 +15,6 @@ interface UiTimerSource { } object UiTimers : UiTimerSource { - @Volatile private var source: UiTimerSource = SwingUiTimers() - - override fun now(): Long = source.now() - - override fun timer(ms: Int, repeats: Boolean, action: () -> Unit): UiTimer = source.timer(ms, repeats, action) - - fun replace(source: UiTimerSource, parent: Disposable) { - val prev = this.source - this.source = source - Disposer.register(parent) { - if (this.source === source) this.source = prev - } - } -} - -private class SwingUiTimers : UiTimerSource { override fun now(): Long = System.currentTimeMillis() override fun timer(ms: Int, repeats: Boolean, action: () -> Unit): UiTimer { diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionSidePanelManagerTest.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionSidePanelManagerTest.kt index 79164073b48..0ff015a00d2 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionSidePanelManagerTest.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/SessionSidePanelManagerTest.kt @@ -16,9 +16,8 @@ import ai.kilocode.client.session.model.QuestionItem import ai.kilocode.client.session.model.SessionState import ai.kilocode.client.testing.FakeAppRpcApi import ai.kilocode.client.testing.FakeSessionRpcApi -import ai.kilocode.client.testing.FakeUiTimers +import ai.kilocode.client.testing.TestUiTimers import ai.kilocode.client.testing.FakeWorkspaceRpcApi -import ai.kilocode.client.util.UiTimers import ai.kilocode.rpc.dto.ChatEventDto import ai.kilocode.rpc.dto.CloudSessionDto import ai.kilocode.rpc.dto.KiloAppStateDto @@ -53,7 +52,7 @@ class SessionSidePanelManagerTest : BasePlatformTestCase() { private lateinit var workspace: Workspace private lateinit var sessions: KiloSessionService private lateinit var app: KiloAppService - private lateinit var timers: FakeUiTimers + private lateinit var timers: TestUiTimers private val managers = mutableListOf() private val created = mutableListOf>() private val refs = mutableListOf() @@ -61,8 +60,7 @@ class SessionSidePanelManagerTest : BasePlatformTestCase() { override fun setUp() { super.setUp() - timers = FakeUiTimers() - UiTimers.replace(timers, testRootDisposable) + timers = TestUiTimers() scope = CoroutineScope(SupervisorJob()) rpc = FakeSessionRpcApi() sessions = KiloSessionService(project, scope, rpc) @@ -631,7 +629,7 @@ class SessionSidePanelManagerTest : BasePlatformTestCase() { val manager = SessionSidePanelManager( project = project, root = workspace, - create = { project, workspace, owner, ref -> + create = { project, workspace, owner, ref, timers -> val id = when (ref) { is SessionRef.Local -> ref.id is SessionRef.Cloud -> ref.key @@ -639,7 +637,17 @@ class SessionSidePanelManagerTest : BasePlatformTestCase() { } created.add(workspace.directory to id) refs.add(ref) - SessionUi(project, workspace, sessions, app, scope, ref = ref, manager = owner, workspaces = workspaces).also { + SessionUi( + project, + workspace, + sessions, + app, + scope, + ref = ref, + manager = owner, + workspaces = workspaces, + timers = timers, + ).also { ui.add(it) Disposer.register(it) { ui.remove(it) } } @@ -647,6 +655,7 @@ class SessionSidePanelManagerTest : BasePlatformTestCase() { resolve = { workspaces.workspace(it) }, status = { sessions.activity() }, history = history, + timers = timers, ) managers.add(manager) return manager diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/DelayedStateTest.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/DelayedStateTest.kt index cb41d496b42..7b060ef0daf 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/DelayedStateTest.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/DelayedStateTest.kt @@ -1,19 +1,17 @@ package ai.kilocode.client.session.controller -import ai.kilocode.client.testing.FakeUiTimers -import ai.kilocode.client.util.UiTimers +import ai.kilocode.client.testing.TestUiTimers import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.util.Disposer import com.intellij.testFramework.fixtures.BasePlatformTestCase class DelayedStateTest : BasePlatformTestCase() { private val states = mutableListOf() - private lateinit var timers: FakeUiTimers + private lateinit var timers: TestUiTimers override fun setUp() { super.setUp() - timers = FakeUiTimers() - UiTimers.replace(timers, testRootDisposable) + timers = TestUiTimers() } override fun tearDown() { @@ -135,7 +133,7 @@ class DelayedStateTest : BasePlatformTestCase() { } private fun delayed(ms: Long): DelayedState { - val state = DelayedState(ms) + val state = DelayedState(ms, timers) states.add(state) return state } diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionControllerTestBase.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionControllerTestBase.kt index f1edc386590..d55076fb621 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionControllerTestBase.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/session/controller/SessionControllerTestBase.kt @@ -8,8 +8,7 @@ import ai.kilocode.client.session.model.SessionState import ai.kilocode.client.testing.FakeAppRpcApi import ai.kilocode.client.testing.FakeWorkspaceRpcApi import ai.kilocode.client.testing.FakeSessionRpcApi -import ai.kilocode.client.testing.FakeUiTimers -import ai.kilocode.client.util.UiTimers +import ai.kilocode.client.testing.TestUiTimers import ai.kilocode.client.app.KiloWorkspaceService import ai.kilocode.client.app.Workspace import ai.kilocode.client.session.SessionRef @@ -97,7 +96,7 @@ abstract class SessionControllerTestBase : BasePlatformTestCase() { protected lateinit var app: KiloAppService protected lateinit var workspaces: KiloWorkspaceService protected lateinit var workspace: Workspace - protected lateinit var timers: FakeUiTimers + protected lateinit var timers: TestUiTimers protected lateinit var scope: CoroutineScope protected lateinit var parent: Disposable @@ -107,8 +106,7 @@ abstract class SessionControllerTestBase : BasePlatformTestCase() { rpc = FakeSessionRpcApi() appRpc = FakeAppRpcApi() projectRpc = FakeWorkspaceRpcApi() - timers = FakeUiTimers() - UiTimers.replace(timers, testRootDisposable) + timers = TestUiTimers() scope = CoroutineScope(SupervisorJob()) parent = Disposer.newDisposable("test") @@ -174,6 +172,7 @@ abstract class SessionControllerTestBase : BasePlatformTestCase() { beforeUpdate = beforeUpdate, afterUpdate = afterUpdate, telemetry = { event, props -> appRpc.telemetry.add(TelemetryCaptureDto(event, props)) }, + timers = timers, ) controllers.add(m) roots[m] = root diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeUiTimers.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/TestUiTimers.kt similarity index 98% rename from packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeUiTimers.kt rename to packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/TestUiTimers.kt index 18984605855..42f5929888e 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeUiTimers.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/TestUiTimers.kt @@ -4,7 +4,7 @@ import ai.kilocode.client.util.UiTimer import ai.kilocode.client.util.UiTimerSource import com.intellij.openapi.application.ApplicationManager -class FakeUiTimers : UiTimerSource { +class TestUiTimers : UiTimerSource { private val timers = mutableListOf() private var time = 0L From f89bbe339096631ca21f55dd850025b0fe6f8cc3 Mon Sep 17 00:00:00 2001 From: kirillk Date: Tue, 16 Jun 2026 11:30:44 -0400 Subject: [PATCH 34/78] test(jetbrains): tighten test UI timers --- .../client/session/controller/DelayedState.kt | 3 --- .../ai/kilocode/client/testing/TestUiTimers.kt | 13 +++++++------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt index 851c31a6306..960bf3b57a9 100644 --- a/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt +++ b/packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/controller/DelayedState.kt @@ -74,9 +74,6 @@ internal class DelayedState( override fun dispose() { alive = false cancel() - edt { - timer.stop() - } } private data class Pending( diff --git a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/TestUiTimers.kt b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/TestUiTimers.kt index 42f5929888e..13c6d7ede54 100644 --- a/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/TestUiTimers.kt +++ b/packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/TestUiTimers.kt @@ -5,15 +5,13 @@ import ai.kilocode.client.util.UiTimerSource import com.intellij.openapi.application.ApplicationManager class TestUiTimers : UiTimerSource { - private val timers = mutableListOf() + private val timers = linkedSetOf() private var time = 0L override fun now(): Long = time override fun timer(ms: Int, repeats: Boolean, action: () -> Unit): UiTimer { - val timer = FakeTimer(ms.coerceAtLeast(0), repeats, action) - timers.add(timer) - return timer + return TestTimer(ms.coerceAtLeast(0), repeats, action) } fun advanceBy(ms: Long) { @@ -39,7 +37,7 @@ class TestUiTimers : UiTimerSource { app.invokeAndWait(action) } - private inner class FakeTimer( + private inner class TestTimer( private val ms: Int, private val repeats: Boolean, private val action: () -> Unit, @@ -53,16 +51,19 @@ class TestUiTimers : UiTimerSource { if (running) return running = true due = time + ms + timers.add(this) } override fun stop() { running = false due = Long.MAX_VALUE + timers.remove(this) } override fun restart() { running = true due = time + ms + timers.add(this) } override fun isRunning(): Boolean = running @@ -70,7 +71,7 @@ class TestUiTimers : UiTimerSource { fun fire() { if (!running || due > time) return if (repeats) { - due += ms.coerceAtLeast(1) + due = time + ms.coerceAtLeast(1) } else { stop() } From c961f66bd4f38c922640d721d1ebdbcc9f97c0a6 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Tue, 16 Jun 2026 11:43:14 +0200 Subject: [PATCH 35/78] feat(agent-manager): add worktree and session search --- .changeset/search-agent-manager-sidebar.md | 5 + packages/kilo-vscode/package.json | 11 + .../src/agent-manager/format-keybinding.ts | 1 + packages/kilo-vscode/src/extension.ts | 3 + .../kilo-vscode/tests/accessibility.spec.ts | 58 ++++ .../tests/unit/agent-manager-arch.test.ts | 2 +- .../tests/unit/extension-arch.test.ts | 11 + .../tests/unit/format-keybinding.test.ts | 10 +- .../tests/unit/sidebar-search.test.ts | 154 ++++++++++ .../agent-manager/AgentManagerApp.tsx | 93 ++++-- .../agent-manager/CurrentTabsMenu.tsx | 288 ------------------ .../agent-manager/SidebarSearchMenu.tsx | 165 ++++++++++ .../agent-manager/agent-manager.css | 220 ++++++------- .../webview-ui/agent-manager/i18n/ar.ts | 6 +- .../webview-ui/agent-manager/i18n/br.ts | 7 +- .../webview-ui/agent-manager/i18n/bs.ts | 6 +- .../webview-ui/agent-manager/i18n/da.ts | 8 +- .../webview-ui/agent-manager/i18n/de.ts | 7 +- .../webview-ui/agent-manager/i18n/en.ts | 6 +- .../webview-ui/agent-manager/i18n/es.ts | 7 +- .../webview-ui/agent-manager/i18n/fr.ts | 7 +- .../webview-ui/agent-manager/i18n/it.ts | 7 +- .../webview-ui/agent-manager/i18n/ja.ts | 7 +- .../webview-ui/agent-manager/i18n/ko.ts | 6 +- .../webview-ui/agent-manager/i18n/nl.ts | 6 +- .../webview-ui/agent-manager/i18n/no.ts | 6 +- .../webview-ui/agent-manager/i18n/pl.ts | 7 +- .../webview-ui/agent-manager/i18n/ru.ts | 7 +- .../webview-ui/agent-manager/i18n/th.ts | 6 +- .../webview-ui/agent-manager/i18n/tr.ts | 7 +- .../webview-ui/agent-manager/i18n/uk.ts | 7 +- .../webview-ui/agent-manager/i18n/zh.ts | 6 +- .../webview-ui/agent-manager/i18n/zht.ts | 6 +- .../webview-ui/agent-manager/shortcuts.ts | 1 + .../agent-manager/sidebar-search.ts | 225 ++++++++++++++ .../agent-manager/tab-rendering.tsx | 41 ++- .../agent-manager/terminal/TerminalTab.tsx | 2 +- .../src/stories/agent-manager.stories.tsx | 134 +++++++- 38 files changed, 1055 insertions(+), 501 deletions(-) create mode 100644 .changeset/search-agent-manager-sidebar.md create mode 100644 packages/kilo-vscode/tests/unit/sidebar-search.test.ts delete mode 100644 packages/kilo-vscode/webview-ui/agent-manager/CurrentTabsMenu.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/SidebarSearchMenu.tsx create mode 100644 packages/kilo-vscode/webview-ui/agent-manager/sidebar-search.ts diff --git a/.changeset/search-agent-manager-sidebar.md b/.changeset/search-agent-manager-sidebar.md new file mode 100644 index 00000000000..e47bae5fc41 --- /dev/null +++ b/.changeset/search-agent-manager-sidebar.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Search and switch between local sessions, worktrees, and their sessions from the Agent Manager sidebar. diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index 876b6f9e9f4..a065675d6ce 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -235,6 +235,11 @@ "title": "Agent Manager: Next Tab", "category": "Kilo Code" }, + { + "command": "kilo-code.new.agentManager.search", + "title": "Agent Manager: Search Worktrees and Sessions", + "category": "Kilo Code" + }, { "command": "kilo-code.new.agentManager.showTerminal", "title": "Agent Manager: Focus Terminal", @@ -629,6 +634,12 @@ "mac": "cmd+alt+right", "when": "activeWebviewPanelId == 'kilo-code.new.AgentManagerPanel'" }, + { + "command": "kilo-code.new.agentManager.search", + "key": "ctrl+f", + "mac": "cmd+f", + "when": "activeWebviewPanelId == 'kilo-code.new.AgentManagerPanel' && !terminalFocus" + }, { "command": "kilo-code.new.agentManager.showTerminal", "key": "ctrl+/", diff --git a/packages/kilo-vscode/src/agent-manager/format-keybinding.ts b/packages/kilo-vscode/src/agent-manager/format-keybinding.ts index daa15a3af59..c6a4b05f150 100644 --- a/packages/kilo-vscode/src/agent-manager/format-keybinding.ts +++ b/packages/kilo-vscode/src/agent-manager/format-keybinding.ts @@ -67,6 +67,7 @@ export function buildKeybindingMap( // Ensure fallback bindings are always present (may be missing from // cached packageJSON if the extension hasn't been fully reloaded) + if (!bindings.search) bindings.search = formatKeybinding(mac ? "cmd+f" : "ctrl+f", mac) if (!bindings.runScript) bindings.runScript = formatKeybinding(mac ? "cmd+e" : "ctrl+e", mac) if (!bindings.toggleDiff) bindings.toggleDiff = formatKeybinding(mac ? "cmd+d" : "ctrl+d", mac) if (!bindings.showShortcuts) bindings.showShortcuts = formatKeybinding(mac ? "cmd+shift+/" : "ctrl+shift+/", mac) diff --git a/packages/kilo-vscode/src/extension.ts b/packages/kilo-vscode/src/extension.ts index 35dc3073d2b..815124b225a 100644 --- a/packages/kilo-vscode/src/extension.ts +++ b/packages/kilo-vscode/src/extension.ts @@ -435,6 +435,9 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand("kilo-code.new.agentManager.nextTab", () => { agentManagerProvider.postMessage({ type: "action", action: "tabNext" }) }), + vscode.commands.registerCommand("kilo-code.new.agentManager.search", () => { + agentManagerProvider.postMessage({ type: "action", action: "search" }) + }), vscode.commands.registerCommand("kilo-code.new.agentManager.showTerminal", () => { // Route through the webview so it can reach into the active session // state and open the VS Code integrated terminal for it. diff --git a/packages/kilo-vscode/tests/accessibility.spec.ts b/packages/kilo-vscode/tests/accessibility.spec.ts index 4600a9f0cd6..2f1565ab4df 100644 --- a/packages/kilo-vscode/tests/accessibility.spec.ts +++ b/packages/kilo-vscode/tests/accessibility.spec.ts @@ -13,6 +13,7 @@ const STORIES = [ { id: "settings--providers-configure", name: "Settings / providers empty state" }, { id: "marketplace--skills-tab-empty", name: "Marketplace / skills empty state" }, { id: "marketplace--agents-tab-empty", name: "Marketplace / agents empty state" }, + { id: "agentmanager--sidebar-search-open", name: "Agent Manager / sidebar search" }, ] function url(id: string) { @@ -61,4 +62,61 @@ test.describe("webview accessibility ratchet", () => { await page.keyboard.press("Enter") await expect(login).toHaveAttribute("data-keyboard-activated", "true") }) + + test("Agent Manager sidebar search filters and selects with the keyboard", async ({ page }) => { + await open(page, "agentmanager--sidebar-search-open") + + const trigger = page.getByRole("button", { name: "Search worktrees and sessions" }) + const input = page.getByPlaceholder("Search worktrees and sessions", { exact: true }) + const prompt = page.getByRole("textbox", { name: "Story prompt" }) + await expect(input).toBeFocused() + await expect(page.locator('[data-slot="list-header"]')).toHaveText(["SESSIONS", "LOCAL & WORKTREES"]) + + await input.fill("Render") + await expect(page.locator('[data-slot="sidebar-search-result"]').first()).toContainText( + "Render images in diff viewer", + ) + await input.fill("rndr img") + await expect(page.locator('[data-slot="sidebar-search-result"]').first()).toContainText( + "Render images in diff viewer", + ) + + await input.fill("main") + await expect(page.locator('[data-slot="sidebar-search-result"]').first()).toContainText("local") + await page.keyboard.press("Enter") + await expect(page.locator('[data-slot="sidebar-search-selection"]')).toHaveText("local") + await expect(prompt).toBeFocused() + + await trigger.click() + await input.fill("Build grouped") + await expect(page.getByText("Build grouped worktree search", { exact: true })).toBeVisible() + + await page.keyboard.press("Enter") + await expect(page.locator('[data-slot="sidebar-search-selection"]')).toHaveText("session:session-build") + await expect(input).toBeHidden() + await expect(prompt).toBeFocused() + + await trigger.click() + await input.fill("local indexing") + await expect(page.getByText("Investigate local indexing", { exact: true })).toBeVisible() + await page.keyboard.press("Enter") + await expect(page.locator('[data-slot="sidebar-search-selection"]')).toHaveText("session:session-local") + + await trigger.click() + await input.fill("does not exist") + await expect(page.locator('[data-slot="list-empty-state"]')).toBeVisible() + await page.keyboard.press("Escape") + await expect(prompt).toBeFocused() + + await trigger.click() + await expect(input).toBeFocused() + await page.locator(".am-section-label").click() + await expect(prompt).toBeFocused() + + await trigger.hover() + await expect( + page.getByText("Searches the local workspace, local sessions, worktrees, and their sessions", { exact: true }), + ).toBeVisible() + await expect(page.getByText("⌘F", { exact: true })).toBeVisible() + }) }) diff --git a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts index d45c3873e2e..9f2e8e89048 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts @@ -35,7 +35,7 @@ const TSX_FILES = [ path.join(ROOT, "webview-ui/agent-manager/ApplyDialog.tsx"), path.join(ROOT, "webview-ui/agent-manager/WorktreeItem.tsx"), path.join(ROOT, "webview-ui/agent-manager/SectionHeader.tsx"), - path.join(ROOT, "webview-ui/agent-manager/CurrentTabsMenu.tsx"), + path.join(ROOT, "webview-ui/agent-manager/SidebarSearchMenu.tsx"), path.join(ROOT, "webview-ui/agent-manager/SidebarToggleButton.tsx"), path.join(ROOT, "webview-ui/agent-manager/tab-rendering.tsx"), path.join(ROOT, "webview-ui/agent-manager/terminal/TerminalTab.tsx"), diff --git a/packages/kilo-vscode/tests/unit/extension-arch.test.ts b/packages/kilo-vscode/tests/unit/extension-arch.test.ts index 174989a2611..f2d07811517 100644 --- a/packages/kilo-vscode/tests/unit/extension-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/extension-arch.test.ts @@ -103,6 +103,17 @@ describe("Extension — package.json command sync", () => { `Commands without "kilo-code.new." prefix — use the namespaced form:\n` + bad.map((b) => ` - ${b}`).join("\n"), ).toEqual([]) }) + + it("scopes Agent Manager search to the panel and leaves the integrated terminal alone", () => { + const binding = pkg.contributes?.keybindings?.find( + (item: { command: string }) => item.command === "kilo-code.new.agentManager.search", + ) + expect(binding).toMatchObject({ + key: "ctrl+f", + mac: "cmd+f", + when: "activeWebviewPanelId == 'kilo-code.new.AgentManagerPanel' && !terminalFocus", + }) + }) }) // --------------------------------------------------------------------------- diff --git a/packages/kilo-vscode/tests/unit/format-keybinding.test.ts b/packages/kilo-vscode/tests/unit/format-keybinding.test.ts index 564b8a9ef3a..8320b94cd08 100644 --- a/packages/kilo-vscode/tests/unit/format-keybinding.test.ts +++ b/packages/kilo-vscode/tests/unit/format-keybinding.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "bun:test" -import { formatKeybinding } from "../../src/agent-manager/format-keybinding" +import { buildKeybindingMap, formatKeybinding } from "../../src/agent-manager/format-keybinding" describe("formatKeybinding", () => { describe("mac", () => { @@ -70,3 +70,11 @@ describe("formatKeybinding", () => { }) }) }) + +describe("buildKeybindingMap", () => { + it("maps the configurable Agent Manager search shortcut", () => { + const bindings = [{ command: "kilo-code.new.agentManager.search", key: "ctrl+f", mac: "cmd+f" }] + expect(buildKeybindingMap(bindings, true).search).toBe("⌘F") + expect(buildKeybindingMap(bindings, false).search).toBe("Ctrl+F") + }) +}) diff --git a/packages/kilo-vscode/tests/unit/sidebar-search.test.ts b/packages/kilo-vscode/tests/unit/sidebar-search.test.ts new file mode 100644 index 00000000000..9eeccde33b0 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/sidebar-search.test.ts @@ -0,0 +1,154 @@ +import { describe, expect, it } from "bun:test" +import type { SectionState, SessionInfo, WorktreeState } from "../../webview-ui/src/types/messages" +import { buildSidebarSearch } from "../../webview-ui/agent-manager/sidebar-search" +import { buildShortcutCategories } from "../../webview-ui/agent-manager/shortcuts" + +const session = (id: string, title: string, updatedAt: string, parentID?: string): SessionInfo => ({ + id, + title, + parentID, + createdAt: "2026-06-01T00:00:00.000Z", + updatedAt, +}) + +const worktree = (id: string, branch: string, sectionId?: string): WorktreeState => ({ + id, + branch, + path: `/tmp/${id}`, + parentBranch: "main", + createdAt: "2026-06-01T00:00:00.000Z", + sectionId, +}) + +const section: SectionState = { + id: "section-polish", + name: "Polish", + color: "Blue", + order: 0, + collapsed: true, +} + +const build = (overrides?: Partial[0]>) => + buildSidebarSearch({ + worktrees: [ + { + worktree: worktree("wt-search", "feat/sidebar-search", section.id), + label: "Agent Manager search", + sessions: [ + session("busy-session", "Build grouped search", "2026-06-02T00:00:00.000Z"), + session("recent-session", "Review search UI", "2026-06-03T00:00:00.000Z"), + ], + }, + { + worktree: worktree("wt-other", "fix/other"), + label: "Other worktree", + sessions: [session("other-session", "Other session", "2026-06-04T00:00:00.000Z")], + }, + ], + sections: [section], + local: [session("local-session", "Local investigation", "2026-06-05T00:00:00.000Z")], + localLabel: "local", + localBranch: "main", + untitled: "Untitled", + pending: (id) => id.startsWith("pending:"), + status: (id) => (id === "busy-session" ? "busy" : "idle"), + busy: () => false, + localBusy: false, + ...overrides, + }) + +describe("buildSidebarSearch", () => { + it("indexes worktree sessions with section context and excludes non-root tabs", () => { + const items = build({ + worktrees: [ + { + worktree: worktree("wt-search", "feat/sidebar-search", section.id), + label: "Agent Manager search", + sessions: [ + session("session", "Build grouped search", "2026-06-02T00:00:00.000Z"), + session("pending:1", "New Session", "2026-06-03T00:00:00.000Z"), + session("child", "Subagent", "2026-06-04T00:00:00.000Z", "session"), + ], + }, + ], + local: [], + }) + + expect(items).toHaveLength(3) + expect(items[0]).toMatchObject({ + kind: "session", + sessionId: "session", + worktreeId: "wt-search", + meta: ["Polish", "Agent Manager search", "feat/sidebar-search"], + section: { id: section.id, color: "Blue", collapsed: true }, + }) + expect(items[0]?.search).toContain("Build grouped search Agent Manager search feat/sidebar-search Polish") + expect(items[1]).toMatchObject({ kind: "local", title: "local", meta: ["main"], count: 0 }) + expect(items[2]).toMatchObject({ kind: "worktree", worktreeId: "wt-search", count: 1, visible: false }) + }) + + it("ranks attention and progress before recency within each result group", () => { + const items = build() + + expect(items.map((item) => item.key)).toEqual([ + "session:busy-session", + "session:local-session", + "session:other-session", + "session:recent-session", + "worktree:wt-search", + "local", + "worktree:wt-other", + ]) + expect(items[0]).toMatchObject({ state: "busy", updatedAt: "2026-06-02T00:00:00.000Z" }) + expect(items[1]).toMatchObject({ location: "local", meta: ["local"] }) + expect(items[4]).toMatchObject({ state: "busy", updatedAt: "2026-06-03T00:00:00.000Z" }) + }) + + it("uses expanded sidebar visibility before recency as a tie-breaker", () => { + const items = build({ + worktrees: [ + { + worktree: worktree("wt-hidden", "feat/hidden", section.id), + label: "Same task hidden", + sessions: [session("hidden", "Same task", "2026-06-05T00:00:00.000Z")], + }, + { + worktree: worktree("wt-visible", "feat/visible"), + label: "Same task visible", + sessions: [session("visible", "Same task", "2026-06-01T00:00:00.000Z")], + }, + ], + local: [], + status: () => "idle", + }) + + expect(items.filter((item) => item.kind === "session").map((item) => item.sessionId)).toEqual(["visible", "hidden"]) + expect(items.find((item) => item.key === "session:hidden")).toMatchObject({ visible: false }) + expect(items.find((item) => item.key === "session:visible")).toMatchObject({ visible: true }) + }) + + it("avoids repeating a worktree label that matches the session title", () => { + const items = build({ + worktrees: [ + { + worktree: worktree("wt-owned", "feat/owned", section.id), + label: "Owned context", + sessions: [session("owned", "Owned context", "2026-06-02T00:00:00.000Z")], + }, + ], + local: [], + }) + + expect(items[0]).toMatchObject({ kind: "session", meta: ["Polish", "feat/owned"] }) + }) +}) + +describe("sidebar search shortcut", () => { + it("appears in the quick-switch keyboard shortcuts section", () => { + const categories = buildShortcutCategories({ search: "⌘F", jumpTo1: "⌘1" }, (key) => key) + expect(categories[0]?.shortcuts[0]).toEqual({ + label: "agentManager.sidebarSearch.label", + binding: "⌘F", + }) + }) +}) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 7f9ee074816..455de4b781d 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -101,7 +101,7 @@ import { reorderTabs, applyTabOrder, firstOrderedTitle } from "./tab-order" import { createTabOrderSync } from "./tab-order-sync" import { ConstrainDragYAxis } from "./sortable-tab" import { isTerminalTabId, createTerminalState, createTerminalHandlers, createTerminalMessageHandler } from "./terminal" -import { renderTab, renderTerminalLayer, renderNewTabButton } from "./tab-rendering" +import { focusCurrentTab, renderTab, renderTerminalLayer, renderNewTabButton } from "./tab-rendering" import { useTabScroll } from "./tab-scroll" import { DiffPanel } from "./DiffPanel" import { createRevertFile } from "./revert-file" @@ -110,7 +110,8 @@ import { ApplyDialog } from "./ApplyDialog" import { groupApplyConflicts } from "./apply-conflicts" import type { ReviewComment } from "../diff-viewer/review-comments" import { clearReviewComposer, createReviewComposer } from "../diff-viewer/review-annotations" -import { CurrentTabsMenu, createCurrentTabItems, focusCurrentTab } from "./CurrentTabsMenu" +import { SidebarSearchMenu, type SidebarSearchMenuRef } from "./SidebarSearchMenu" +import { createSidebarSearch, type SidebarSearchItem } from "./sidebar-search" import { BranchSelect } from "../src/components/shared/BranchSelect" import { WorktreeItem } from "./WorktreeItem" import SectionHeader from "./SectionHeader" @@ -171,6 +172,7 @@ const defaultBindings: Record = { nextSession: isMac ? "⌘⌥↓" : "Ctrl+Alt+↓", previousTab: isMac ? "⌘⌥←" : "Ctrl+Alt+←", nextTab: isMac ? "⌘⌥→" : "Ctrl+Alt+→", + search: isMac ? "⌘F" : "Ctrl+F", showTerminal: isMac ? "⌘/" : "Ctrl+/", newTerminal: isMac ? "⌘⇧T" : "Ctrl+Shift+T", runScript: isMac ? "⌘E" : "Ctrl+E", @@ -197,6 +199,7 @@ const AgentManagerContent: Component = () => { const session = useSession() const vscode = useVSCode() const dialog = useDialog() + let sidebarSearchMenu: SidebarSearchMenuRef | undefined const [kb, setKb] = createSignal>(defaultBindings) @@ -964,6 +967,37 @@ const AgentManagerContent: Component = () => { return true } + const sidebarSearch = createSidebarSearch({ + worktrees: sortedWorktrees, + sections, + local: localSessions, + localBranch: repoBranch, + selection, + sessionId: session.currentSessionID, + statuses: session.allStatusMap, + permissions: session.permissions, + questions: session.questions, + label: worktreeLabel, + sessions: sessionsForWorktree, + pending: isPending, + busy: (id) => busyWorktrees().has(id) || (runStatuses()[id]?.state ?? "idle") !== "idle", + localBusy: isLocalBusy, + t, + }) + const focusSidebarSearchItem = (item: SidebarSearchItem) => { + if (item.section?.collapsed) + vscode.postMessage({ type: "agentManager.toggleSectionCollapsed", sectionId: item.section.id }) + setHistory(false) + if (item.kind === "local") return selectLocal() + if (item.kind === "worktree") return selectWorktree(item.worktreeId) + if (item.location === "local") selectLocal() + if (item.location === "worktree" && item.worktreeId) selectWorktree(item.worktreeId) + terms.setActiveId(undefined) + setReviewActive(false) + setActivePendingId(undefined) + session.selectSession(item.sessionId) + } + const cycleAgent = (direction: 1 | -1) => { const available = session.agents().filter((a) => a.mode !== "subagent" && !a.hidden) if (available.length <= 1) return @@ -990,7 +1024,13 @@ const AgentManagerContent: Component = () => { else if (msg.action === "sessionNext") navigate("down") else if (msg.action === "tabPrevious") navigateTab("left") else if (msg.action === "tabNext") navigateTab("right") - else if (msg.action === "showTerminal") { + else if (msg.action === "search") { + if (!sidebarCollapsed()) sidebarSearchMenu?.open() + else { + expandSidebar() + requestAnimationFrame(() => sidebarSearchMenu?.open()) + } + } else if (msg.action === "showTerminal") { // Cmd+/ opens the legacy VS Code integrated terminal for the // active session (or local). The new xterm tab affordance has // its own keybind (Cmd+Shift+T) so both coexist. @@ -2027,20 +2067,6 @@ const AgentManagerContent: Component = () => { activateTerminal: termHandlers.activate, }) - const tabMenuItems = createCurrentTabItems({ - tabIds, - tabLookup, - statusMap: session.allStatusMap, - permissions: session.permissions, - questions: session.questions, - visibleTabId, - terms, - reviewId: REVIEW_TAB_ID, - isTerminal: isTerminalTabId, - isPending, - t, - }) - // Close the currently active tab via keyboard shortcut. // If no tabs remain, fall through to close the selected worktree. const closeActiveTab = () => { @@ -2203,8 +2229,25 @@ const AgentManagerContent: Component = () => {
- -
+
+ { + sidebarSearchMenu = value + }} + items={sidebarSearch.items} + current={sidebarSearch.current} + keybind={kb().search ?? ""} + labels={{ + search: t("agentManager.sidebarSearch.label"), + scope: t("agentManager.sidebarSearch.scope"), + sessions: t("agentManager.section.sessions"), + contexts: t("agentManager.sidebarSearch.contexts"), + waiting: t("agentManager.tabsMenu.status.waiting"), + retry: t("agentManager.tabsMenu.status.retry"), + }} + onSelect={focusSidebarSearchItem} + /> +
{ -
-
+ +
{
-
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/CurrentTabsMenu.tsx b/packages/kilo-vscode/webview-ui/agent-manager/CurrentTabsMenu.tsx deleted file mode 100644 index b1369f75318..00000000000 --- a/packages/kilo-vscode/webview-ui/agent-manager/CurrentTabsMenu.tsx +++ /dev/null @@ -1,288 +0,0 @@ -/** @jsxImportSource solid-js */ - -import { For, Show, createEffect, createMemo, createSignal } from "solid-js" -import type { Accessor, Component } from "solid-js" -import { Popover } from "@kilocode/kilo-ui/popover" -import { Spinner } from "@kilocode/kilo-ui/spinner" -import { Tooltip } from "@kilocode/kilo-ui/tooltip" -import type { PermissionRequest, QuestionRequest, SessionInfo, SessionStatusInfo } from "../src/types/messages" -import type { TerminalStateControls } from "./terminal" - -interface CurrentTabItem { - id: string - title: string - status?: string - working: boolean - tone: "active" | "busy" | "waiting" | "idle" -} - -interface CurrentTabItemsDeps { - tabIds: Accessor - tabLookup: Accessor> - statusMap: Accessor> - permissions: Accessor - questions: Accessor - visibleTabId: Accessor - terms: TerminalStateControls - reviewId: string - isTerminal: (id: string) => boolean - isPending: (id: string) => boolean - t: (key: string) => string -} - -interface FocusTabDeps { - id: string - terms: TerminalStateControls - isTerminal: (id: string) => boolean - isPending: (id: string) => boolean - reviewId: string - reviewOpen: Accessor - setReviewOpen: (open: boolean) => void - setReviewActive: (active: boolean) => void - tabLookup: Accessor> - setActivePendingId: (id: string | undefined) => void - clearSession: () => void - selectSession: (id: string) => void - activateTerminal: (id: string) => void -} - -export function focusCurrentTab(deps: FocusTabDeps) { - if (deps.isTerminal(deps.id)) { - deps.activateTerminal(deps.id) - return - } - deps.terms.setActiveId(undefined) - if (deps.id === deps.reviewId) { - if (!deps.reviewOpen()) deps.setReviewOpen(true) - deps.setReviewActive(true) - return - } - const target = deps.tabLookup().get(deps.id) - if (!target) return - deps.setReviewActive(false) - if (deps.isPending(target.id)) { - deps.setActivePendingId(target.id) - deps.clearSession() - return - } - deps.setActivePendingId(undefined) - deps.selectSession(target.id) -} - -export const createCurrentTabItems = (deps: CurrentTabItemsDeps): Accessor => - createMemo(() => { - const statuses = deps.statusMap() - const perms = deps.permissions() - const qs = deps.questions() - return deps - .tabIds() - .map((id) => buildItem(id, statuses, perms, qs, deps)) - .filter((item): item is CurrentTabItem => item !== undefined) - }) - -const basicItem = (id: string, title: string, deps: CurrentTabItemsDeps): CurrentTabItem => ({ - id, - title, - working: false, - tone: id === deps.visibleTabId() ? "active" : "idle", -}) - -function buildReviewItem(id: string, deps: CurrentTabItemsDeps) { - return basicItem(id, deps.t("session.tab.review"), deps) -} - -function buildTerminalItem(id: string, deps: CurrentTabItemsDeps) { - const term = deps.terms.lookup().get(id) - if (!term) return undefined - return basicItem(id, term.title, deps) -} - -function buildPendingItem(tab: SessionInfo, deps: CurrentTabItemsDeps) { - return basicItem(tab.id, tab.title || deps.t("agentManager.session.newSession"), deps) -} - -function buildSessionItem( - tab: SessionInfo, - status: SessionStatusInfo | undefined, - blocked: boolean, - deps: CurrentTabItemsDeps, -) { - const working = !blocked && (status?.type === "busy" || status?.type === "retry") - return { - id: tab.id, - title: tab.title || deps.t("agentManager.session.untitled"), - status: statusLabel(tab.id, blocked, status, deps), - working, - tone: statusTone(tab.id, blocked, working, deps), - } satisfies CurrentTabItem -} - -function buildItem( - id: string, - statuses: Record, - perms: PermissionRequest[], - qs: QuestionRequest[], - deps: CurrentTabItemsDeps, -): CurrentTabItem | undefined { - if (id === deps.reviewId) return buildReviewItem(id, deps) - if (deps.isTerminal(id)) return buildTerminalItem(id, deps) - const tab = deps.tabLookup().get(id) - if (!tab) return undefined - if (deps.isPending(id)) return buildPendingItem(tab, deps) - const blocked = perms.some((p) => p.sessionID === id) || qs.some((q) => q.sessionID === id) - return buildSessionItem(tab, statuses[id], blocked, deps) -} - -function statusLabel(id: string, blocked: boolean, status: SessionStatusInfo | undefined, deps: CurrentTabItemsDeps) { - if (blocked) return deps.t("agentManager.tabsMenu.status.waiting") - if (status?.type === "retry") return deps.t("agentManager.tabsMenu.status.retry") - return undefined -} - -function statusTone(id: string, blocked: boolean, working: boolean, deps: CurrentTabItemsDeps): CurrentTabItem["tone"] { - if (id === deps.visibleTabId()) return "active" - if (blocked) return "waiting" - if (working) return "busy" - return "idle" -} - -interface CurrentTabsMenuProps { - items: Accessor - label: string - searchLabel: string - emptyLabel: string - activeId: Accessor - onSelect: (id: string) => void -} - -function SearchIcon() { - return ( - - ) -} - -export const CurrentTabsMenu: Component = (props) => { - const [search, setSearch] = createSignal("") - const [open, setOpen] = createSignal(false) - const [mark, setMark] = createSignal(0) - let input: HTMLInputElement | undefined - - const focus = () => { - input?.focus({ preventScroll: true }) - } - - createEffect(() => { - if (!open()) return - setSearch("") - queueMicrotask(focus) - }) - - const rows = createMemo(() => { - const q = search().trim().toLowerCase() - if (!q) return props.items() - return props.items().filter((item) => item.title.toLowerCase().includes(q)) - }) - - createEffect(() => { - if (!open()) return - search() - setMark(0) - }) - - const select = (id: string) => { - props.onSelect(id) - setOpen(false) - } - - const move = (dir: 1 | -1) => { - const len = rows().length - if (len === 0) return - setMark((prev) => (prev + dir + len) % len) - } - - const onKeyDown = (e: KeyboardEvent) => { - e.stopPropagation() - if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return - if (e.key === "ArrowDown") { - e.preventDefault() - move(1) - return - } - if (e.key === "ArrowUp") { - e.preventDefault() - move(-1) - return - } - if (e.key === "Enter") { - e.preventDefault() - const item = rows()[mark()] - if (item) select(item.id) - return - } - if (e.key === "Escape") { - e.preventDefault() - setOpen(false) - } - } - - return ( - - - - } - > - -
- 0} fallback={
{props.emptyLabel}
}> - - {(item) => ( - - )} - -
-
-
- ) -} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/SidebarSearchMenu.tsx b/packages/kilo-vscode/webview-ui/agent-manager/SidebarSearchMenu.tsx new file mode 100644 index 00000000000..7da9c9cb79f --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/SidebarSearchMenu.tsx @@ -0,0 +1,165 @@ +/** @jsxImportSource solid-js */ + +import { Show, createEffect, createSignal } from "solid-js" +import type { Accessor, Component } from "solid-js" +import { Icon } from "@kilocode/kilo-ui/icon" +import { List } from "@kilocode/kilo-ui/list" +import type { ListRef } from "@kilocode/kilo-ui/list" +import { Popover } from "@kilocode/kilo-ui/popover" +import { Spinner } from "@kilocode/kilo-ui/spinner" +import { TooltipKeybind } from "@kilocode/kilo-ui/tooltip" +import { formatRelativeDate } from "../src/utils/date" +import { colorCss } from "./section-colors" +import type { SidebarSearchItem } from "./sidebar-search" + +export interface SidebarSearchMenuRef { + open: () => void +} + +interface SidebarSearchMenuProps { + items: Accessor + current: Accessor + labels: { search: string; scope: string; contexts: string; sessions: string; waiting: string; retry: string } + keybind: string + ref?: (value: SidebarSearchMenuRef) => void + onSelect: (item: SidebarSearchItem) => void + defaultOpen?: boolean + portal?: boolean +} + +export const SidebarSearchMenu: Component = (props) => { + const [open, setOpen] = createSignal(props.defaultOpen ?? false) + const [query, setQuery] = createSignal("") + const [notice, setNotice] = createSignal("") + let list: ListRef | undefined + let root: HTMLDivElement | undefined + + const focus = () => + queueMicrotask(() => { + list?.setFilter("") + root?.querySelector("input")?.focus({ preventScroll: true }) + }) + + createEffect(() => { + if (open()) focus() + }) + + const reveal = () => { + setOpen(true) + focus() + } + props.ref?.({ open: reveal }) + + const refocus = () => + requestAnimationFrame(() => window.dispatchEvent(new CustomEvent("focusPrompt", { detail: { restore: true } }))) + const close = (next: boolean) => { + setOpen(next) + if (!next) refocus() + } + + const select = (item: SidebarSearchItem) => { + setOpen(false) + props.onSelect(item) + refocus() + } + + return ( + + } + > + + + + ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css index 7eea66cc458..9403d9c7a54 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -1371,165 +1371,167 @@ body.am-wt-dragging-active * { padding-right: 4px; border-left: 1px solid var(--border-weak-base); } - -.am-tabs-menu-trigger { +.am-sidebar-search-trigger { display: inline-flex; - align-items: center; - justify-content: center; width: 24px; height: 24px; + flex-shrink: 0; + align-items: center; + justify-content: center; padding: 0; border: none; border-radius: var(--radius-sm); background: transparent; color: var(--text-weak); cursor: pointer; - flex-shrink: 0; } -.am-tabs-menu-trigger:hover, -[data-slot="popover-trigger"][data-expanded] .am-tabs-menu-trigger { +.am-sidebar-search-trigger:hover, +.am-sidebar-search-trigger[data-expanded] { background: var(--surface-inset-base-hover); color: var(--text-base); } -.am-tabs-search-icon { - width: 16px; - height: 16px; - fill: none; - stroke: currentColor; - stroke-width: 1.35; - stroke-linecap: round; - stroke-linejoin: round; -} - -.am-tabs-menu[data-component="popover-content"] { - width: 300px; - max-height: min(360px, calc(100vh - 64px)); +.am-sidebar-search-popover[data-component="popover-content"] { + width: min(360px, calc(100vw - 24px)); + max-height: min(480px, calc(100vh - 64px)); padding: 0; overflow: hidden; } -.am-tabs-menu [data-slot="popover-body"] { +.am-sidebar-search-popover [data-slot="popover-body"] { padding: 0; } -.am-tabs-menu-search { - display: flex; - align-items: center; - padding: 8px 12px; - border-bottom: 1px solid var(--border-base); +.am-sidebar-search [data-component="list"] { + max-height: min(440px, calc(100vh - 88px)); + gap: 0; + padding: 8px 0 0; +} + +.am-sidebar-search [data-slot="list-search-wrapper"] { + margin: 0 0 4px; + padding: 0 8px; +} + +.am-sidebar-search [data-slot="list-search"] { + background: var(--surface-inset-base); +} + +.am-sidebar-search [data-slot="list-scroll"] { + max-height: 360px; + gap: 2px; + padding: 0 4px; +} + +.am-sidebar-search [data-slot="list-group"]:last-child { + padding-bottom: 4px; +} + +.am-sidebar-search [data-slot="list-header"] { + padding: 7px 8px 4px; color: var(--text-weaker); + font-size: var(--kilo-font-size-10); + font-weight: 600; + letter-spacing: 0.06em; } -.am-tabs-menu-search-input { - flex: 1; - min-width: 0; - border: none; - background: transparent; - color: var(--text-base); - font-family: inherit; - font-size: var(--kilo-font-size-12); - outline: none; - user-select: text; +.am-sidebar-search [data-slot="list-item"] { + min-height: 46px; + padding: 6px 8px; } -.am-tabs-menu-search-input::placeholder { - color: var(--vscode-input-placeholderForeground, var(--text-weaker)); +.am-sidebar-search [data-slot="list-item"][data-active="true"], +.am-sidebar-search [data-slot="list-item"][data-selected="true"] { + background: color-mix(in srgb, var(--text-base) 10%, transparent); } -.am-tabs-menu-list { - max-height: 300px; - overflow-y: auto; - padding: 4px; -} - -.am-tabs-menu-item { +.am-sidebar-search-result { display: flex; + width: 100%; + min-width: 0; align-items: center; gap: 8px; - width: 100%; - min-height: 34px; - padding: 6px 10px; - border: none; - border-radius: var(--radius-sm); - background: transparent; - color: var(--text-base); - cursor: pointer; - font-family: inherit; - font-size: var(--kilo-font-size-12); - text-align: left; } -.am-tabs-menu-item:hover { - background: var(--surface-inset-base-hover); -} - -.am-tabs-menu-item-marked:not(.am-tabs-menu-item-active) { - background: var(--surface-inset-base-hover); -} - -.am-tabs-menu-item-active, -.am-tabs-menu-item-active:hover { - background: var(--surface-interactive-base); - color: var(--text-on-interactive-base); -} - -.am-tabs-menu-indicator { +.am-sidebar-search-icon { display: inline-flex; + width: 18px; + height: 18px; + flex-shrink: 0; align-items: center; justify-content: center; - width: 16px; - height: 16px; - flex-shrink: 0; + color: var(--text-weak); } -.am-tabs-menu-spinner { +.am-sidebar-search-spinner { width: 14px; height: 14px; } -.am-tabs-menu-status { - display: inline-flex; - align-items: center; - justify-content: flex-end; - flex-shrink: 0; - margin-left: auto; - color: var(--text-weaker); - font-size: var(--kilo-font-size-10); - line-height: 1; - text-transform: uppercase; -} - -.am-tabs-menu-status[data-tone="active"] { - color: var(--text-weak); -} - -.am-tabs-menu-status[data-tone="busy"] { - color: var(--vscode-testing-iconPassed, #34d399); -} - -.am-tabs-menu-status[data-tone="waiting"] { - color: #fbbf24; -} - -.am-tabs-menu-item-active .am-tabs-menu-status { - color: color-mix(in srgb, var(--text-on-interactive-base) 70%, transparent); -} - -.am-tabs-menu-title { - flex: 1; +.am-sidebar-search-copy { + display: flex; min-width: 0; + flex: 1; + flex-direction: column; + gap: 1px; + text-align: left; +} + +.am-sidebar-search-title, +.am-sidebar-search-meta { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.am-tabs-menu-empty { - padding: 14px 10px; - color: var(--text-weaker); +.am-sidebar-search-title { + color: var(--text-base); font-size: var(--kilo-font-size-12); - text-align: center; + font-weight: 500; + line-height: 1.35; +} + +.am-sidebar-search-meta { + display: flex; + align-items: center; + gap: 5px; + color: var(--text-base); + font-size: var(--kilo-font-size-10); + font-weight: 400; + line-height: 1.35; +} + +.am-sidebar-search-swatch { + width: 6px; + height: 6px; + flex-shrink: 0; + border-radius: 50%; +} + +.am-sidebar-search-status, +.am-sidebar-search-count, +.am-sidebar-search-time { + flex-shrink: 0; + margin-left: auto; + color: var(--text-base); + font-size: var(--kilo-font-size-10); + font-weight: 400; +} + +.am-sidebar-search-status { + color: #fbbf24; + text-transform: uppercase; +} + +.am-sidebar-search-count { + display: inline-flex; + min-width: 18px; + height: 18px; + align-items: center; + justify-content: center; + border-radius: 9px; + background: var(--surface-inset-base); } /* Empty worktree state */ diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts index 22b81da3b39..31a4eafa4fa 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts @@ -51,11 +51,11 @@ export const dict = { "agentManager.tab.terminal": "الطرفية", "agentManager.tab.openTerminal": "فتح الطرفية", "agentManager.tab.newOptions": "المزيد من خيارات علامات التبويب الجديدة", - "agentManager.tabsMenu.label": "البحث في علامات التبويب المفتوحة", - "agentManager.tabsMenu.search": "البحث في علامات التبويب...", - "agentManager.tabsMenu.empty": "لا توجد علامات تبويب مطابقة", "agentManager.tabsMenu.status.waiting": "انتظار", "agentManager.tabsMenu.status.retry": "إعادة", + "agentManager.sidebarSearch.label": "البحث في Worktrees والجلسات", + "agentManager.sidebarSearch.scope": "يبحث في مساحة العمل المحلية والجلسات المحلية وWorktrees وجلساتها", + "agentManager.sidebarSearch.contexts": "محلي & WORKTREES", "agentManager.terminal.new": "علامة تبويب جديدة للمحطة الطرفية", "agentManager.terminal.ended": "انتهت المحطة الطرفية — أغلق علامة التبويب للإخفاء", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts index aec59b1bcf0..581b4316c46 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts @@ -51,11 +51,12 @@ export const dict = { "agentManager.tab.terminal": "Terminal", "agentManager.tab.openTerminal": "Abrir Terminal", "agentManager.tab.newOptions": "Mais opções de nova aba", - "agentManager.tabsMenu.label": "Pesquisar abas abertas", - "agentManager.tabsMenu.search": "Pesquisar abas...", - "agentManager.tabsMenu.empty": "Nenhuma aba correspondente", "agentManager.tabsMenu.status.waiting": "Espera", "agentManager.tabsMenu.status.retry": "Repetir", + "agentManager.sidebarSearch.label": "Pesquisar Worktrees e sessões", + "agentManager.sidebarSearch.scope": + "Pesquisa o espaço de trabalho local, as sessões locais, os Worktrees e suas sessões", + "agentManager.sidebarSearch.contexts": "LOCAL & WORKTREES", "agentManager.terminal.new": "Nova aba de terminal", "agentManager.terminal.ended": "terminal encerrado — feche a aba para dispensar", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts index d3b3ca7953b..b327a7d336b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts @@ -51,11 +51,11 @@ export const dict = { "agentManager.tab.terminal": "Terminal", "agentManager.tab.openTerminal": "Otvori Terminal", "agentManager.tab.newOptions": "Više opcija nove kartice", - "agentManager.tabsMenu.label": "Pretraži otvorene kartice", - "agentManager.tabsMenu.search": "Pretraži kartice...", - "agentManager.tabsMenu.empty": "Nema podudarnih kartica", "agentManager.tabsMenu.status.waiting": "Čeka", "agentManager.tabsMenu.status.retry": "Pokušaj", + "agentManager.sidebarSearch.label": "Pretraži Worktree-ove i sesije", + "agentManager.sidebarSearch.scope": "Pretražuje lokalni radni prostor, lokalne sesije, Worktree-ove i njihove sesije", + "agentManager.sidebarSearch.contexts": "LOKALNO & WORKTREES", "agentManager.terminal.new": "Nova kartica terminala", "agentManager.terminal.ended": "terminal je završen — zatvorite karticu da biste odbacili", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts index 194ad09be4a..2a4a252b04a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts @@ -51,11 +51,13 @@ export const dict = { "agentManager.tab.terminal": "Terminal", "agentManager.tab.openTerminal": "Åbn Terminal", "agentManager.tab.newOptions": "Flere nye faneindstillinger", - "agentManager.tabsMenu.label": "Søg i åbne faner", - "agentManager.tabsMenu.search": "Søg faner...", - "agentManager.tabsMenu.empty": "Ingen matchende faner", + "agentManager.tabsMenu.status.waiting": "Venter", "agentManager.tabsMenu.status.retry": "Igen", + "agentManager.sidebarSearch.label": "Søg i Worktrees og sessioner", + "agentManager.sidebarSearch.scope": + "Søger i det lokale arbejdsområde, lokale sessioner, Worktrees og deres sessioner", + "agentManager.sidebarSearch.contexts": "LOKAL & WORKTREES", "agentManager.terminal.new": "Ny terminalfane", "agentManager.terminal.ended": "terminal afsluttet — luk fanen for at fjerne", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts index f6643237ba9..4da2c390252 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts @@ -51,11 +51,12 @@ export const dict = { "agentManager.tab.terminal": "Terminal", "agentManager.tab.openTerminal": "Terminal öffnen", "agentManager.tab.newOptions": "Weitere Tab-Optionen", - "agentManager.tabsMenu.label": "Offene Tabs suchen", - "agentManager.tabsMenu.search": "Tabs suchen...", - "agentManager.tabsMenu.empty": "Keine passenden Tabs", "agentManager.tabsMenu.status.waiting": "Warten", "agentManager.tabsMenu.status.retry": "Erneut", + "agentManager.sidebarSearch.label": "Worktrees und Sitzungen durchsuchen", + "agentManager.sidebarSearch.scope": + "Durchsucht den lokalen Arbeitsbereich, lokale Sitzungen, Worktrees und deren Sitzungen", + "agentManager.sidebarSearch.contexts": "LOKAL & WORKTREES", "agentManager.terminal.new": "Neuer Terminal-Tab", "agentManager.terminal.ended": "Terminal beendet — Tab schließen zum Verwerfen", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts index cc94798638a..50a17af9cd3 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts @@ -55,11 +55,11 @@ export const dict = { "agentManager.tab.terminal": "Terminal", "agentManager.tab.openTerminal": "Open Terminal", "agentManager.tab.newOptions": "More new-tab options", - "agentManager.tabsMenu.label": "Search and switch tabs", - "agentManager.tabsMenu.search": "Search tabs...", - "agentManager.tabsMenu.empty": "No matching tabs", "agentManager.tabsMenu.status.waiting": "Wait", "agentManager.tabsMenu.status.retry": "Retry", + "agentManager.sidebarSearch.label": "Search worktrees and sessions", + "agentManager.sidebarSearch.scope": "Searches the local workspace, local sessions, worktrees, and their sessions", + "agentManager.sidebarSearch.contexts": "LOCAL & WORKTREES", "agentManager.terminal.new": "New Terminal Tab", "agentManager.terminal.ended": "terminal ended — close tab to dismiss", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts index bfddc7036fd..77bd55dff62 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts @@ -51,11 +51,12 @@ export const dict = { "agentManager.tab.terminal": "Terminal", "agentManager.tab.openTerminal": "Abrir Terminal", "agentManager.tab.newOptions": "Más opciones de nueva pestaña", - "agentManager.tabsMenu.label": "Buscar pestañas abiertas", - "agentManager.tabsMenu.search": "Buscar pestañas...", - "agentManager.tabsMenu.empty": "No hay pestañas coincidentes", "agentManager.tabsMenu.status.waiting": "Espera", "agentManager.tabsMenu.status.retry": "Reintento", + "agentManager.sidebarSearch.label": "Buscar Worktrees y sesiones", + "agentManager.sidebarSearch.scope": + "Busca en el espacio de trabajo local, las sesiones locales, los Worktrees y sus sesiones", + "agentManager.sidebarSearch.contexts": "LOCAL & WORKTREES", "agentManager.terminal.new": "Nueva pestaña de terminal", "agentManager.terminal.ended": "terminal finalizado — cierra la pestaña para descartar", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts index 435308590a4..3b08c297232 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts @@ -51,11 +51,12 @@ export const dict = { "agentManager.tab.terminal": "Terminal", "agentManager.tab.openTerminal": "Ouvrir le Terminal", "agentManager.tab.newOptions": "Plus d'options de nouvel onglet", - "agentManager.tabsMenu.label": "Rechercher les onglets ouverts", - "agentManager.tabsMenu.search": "Rechercher des onglets...", - "agentManager.tabsMenu.empty": "Aucun onglet correspondant", "agentManager.tabsMenu.status.waiting": "Attente", "agentManager.tabsMenu.status.retry": "Réessai", + "agentManager.sidebarSearch.label": "Rechercher des Worktrees et des sessions", + "agentManager.sidebarSearch.scope": + "Recherche dans l'espace de travail local, les sessions locales, les Worktrees et leurs sessions", + "agentManager.sidebarSearch.contexts": "LOCAL & WORKTREES", "agentManager.terminal.new": "Nouvel onglet de terminal", "agentManager.terminal.ended": "terminal terminé — fermez l'onglet pour ignorer", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts index 24e5636bb09..c23f104e002 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts @@ -55,11 +55,12 @@ export const dict = { "agentManager.tab.terminal": "Terminale", "agentManager.tab.openTerminal": "Apri terminale", "agentManager.tab.newOptions": "Altre opzioni nuova scheda", - "agentManager.tabsMenu.label": "Cerca e cambia scheda", - "agentManager.tabsMenu.search": "Cerca schede...", - "agentManager.tabsMenu.empty": "Nessuna scheda corrispondente", "agentManager.tabsMenu.status.waiting": "Attendi", "agentManager.tabsMenu.status.retry": "Riprova", + "agentManager.sidebarSearch.label": "Cerca Worktree e sessioni", + "agentManager.sidebarSearch.scope": + "Cerca nell'area di lavoro locale, nelle sessioni locali, nei Worktree e nelle relative sessioni", + "agentManager.sidebarSearch.contexts": "LOCALE & WORKTREE", "agentManager.terminal.new": "Nuova scheda terminale", "agentManager.terminal.ended": "terminale terminato - chiudi la scheda per nasconderlo", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts index 4d37c64e2e3..843a477f316 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts @@ -51,11 +51,12 @@ export const dict = { "agentManager.tab.terminal": "ターミナル", "agentManager.tab.openTerminal": "ターミナルを開く", "agentManager.tab.newOptions": "新しいタブのその他のオプション", - "agentManager.tabsMenu.label": "開いているタブを検索", - "agentManager.tabsMenu.search": "タブを検索...", - "agentManager.tabsMenu.empty": "一致するタブがありません", "agentManager.tabsMenu.status.waiting": "待機", "agentManager.tabsMenu.status.retry": "再試行", + "agentManager.sidebarSearch.label": "Worktreeとセッションを検索", + "agentManager.sidebarSearch.scope": + "ローカルワークスペース、ローカルセッション、Worktree、および各Worktreeのセッションを検索", + "agentManager.sidebarSearch.contexts": "ローカル & WORKTREES", "agentManager.terminal.new": "新しいターミナルタブ", "agentManager.terminal.ended": "ターミナルが終了しました — タブを閉じて破棄", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts index f7d7e9a2d92..fe9fb1b264e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts @@ -51,11 +51,11 @@ export const dict = { "agentManager.tab.terminal": "터미널", "agentManager.tab.openTerminal": "터미널 열기", "agentManager.tab.newOptions": "더 많은 새 탭 옵션", - "agentManager.tabsMenu.label": "열린 탭 검색", - "agentManager.tabsMenu.search": "탭 검색...", - "agentManager.tabsMenu.empty": "일치하는 탭 없음", "agentManager.tabsMenu.status.waiting": "대기", "agentManager.tabsMenu.status.retry": "재시도", + "agentManager.sidebarSearch.label": "Worktree 및 세션 검색", + "agentManager.sidebarSearch.scope": "로컬 워크스페이스, 로컬 세션, Worktree 및 각 Worktree의 세션 검색", + "agentManager.sidebarSearch.contexts": "로컬 & WORKTREES", "agentManager.terminal.new": "새 터미널 탭", "agentManager.terminal.ended": "터미널 종료됨 — 탭을 닫아 해제", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts index 713c7349005..fa25158b058 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts @@ -55,11 +55,11 @@ export const dict = { "agentManager.tab.terminal": "Terminal", "agentManager.tab.openTerminal": "Terminal openen", "agentManager.tab.newOptions": "Meer opties voor nieuwe tabblad", - "agentManager.tabsMenu.label": "Open tabbladen zoeken", - "agentManager.tabsMenu.search": "Tabbladen zoeken...", - "agentManager.tabsMenu.empty": "Geen overeenkomende tabbladen", "agentManager.tabsMenu.status.waiting": "Wacht", "agentManager.tabsMenu.status.retry": "Opnieuw", + "agentManager.sidebarSearch.label": "Worktrees en sessies doorzoeken", + "agentManager.sidebarSearch.scope": "Doorzoekt de lokale werkruimte, lokale sessies, Worktrees en hun sessies", + "agentManager.sidebarSearch.contexts": "LOKAAL & WORKTREES", "agentManager.terminal.new": "Nieuw terminaltabblad", "agentManager.terminal.ended": "terminal beëindigd — sluit tabblad om te negeren", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts index a412c141caa..8a8ead40090 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts @@ -51,11 +51,11 @@ export const dict = { "agentManager.tab.terminal": "Terminal", "agentManager.tab.openTerminal": "Åpne Terminal", "agentManager.tab.newOptions": "Flere alternativer for ny fane", - "agentManager.tabsMenu.label": "Søk i åpne faner", - "agentManager.tabsMenu.search": "Søk i faner...", - "agentManager.tabsMenu.empty": "Ingen treffende faner", "agentManager.tabsMenu.status.waiting": "Venter", "agentManager.tabsMenu.status.retry": "Igjen", + "agentManager.sidebarSearch.label": "Søk i Worktrees og økter", + "agentManager.sidebarSearch.scope": "Søker i det lokale arbeidsområdet, lokale økter, Worktrees og øktene deres", + "agentManager.sidebarSearch.contexts": "LOKAL & WORKTREES", "agentManager.terminal.new": "Ny terminalfane", "agentManager.terminal.ended": "terminal avsluttet — lukk fanen for å avvise", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts index f985f44198b..db4b81199be 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts @@ -51,11 +51,12 @@ export const dict = { "agentManager.tab.terminal": "Terminal", "agentManager.tab.openTerminal": "Otwórz Terminal", "agentManager.tab.newOptions": "Więcej opcji nowej karty", - "agentManager.tabsMenu.label": "Szukaj otwartych kart", - "agentManager.tabsMenu.search": "Szukaj kart...", - "agentManager.tabsMenu.empty": "Brak pasujących kart", "agentManager.tabsMenu.status.waiting": "Czeka", "agentManager.tabsMenu.status.retry": "Ponów", + "agentManager.sidebarSearch.label": "Wyszukaj Worktree i sesje", + "agentManager.sidebarSearch.scope": + "Przeszukuje lokalny obszar roboczy, lokalne sesje, Worktree i przypisane do nich sesje", + "agentManager.sidebarSearch.contexts": "LOKALNE & WORKTREES", "agentManager.terminal.new": "Nowa karta terminala", "agentManager.terminal.ended": "terminal zakończony — zamknij kartę, aby zamknąć", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts index 4b5a8c9ee37..0b11255a5cb 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts @@ -51,11 +51,12 @@ export const dict = { "agentManager.tab.terminal": "Терминал", "agentManager.tab.openTerminal": "Открыть терминал", "agentManager.tab.newOptions": "Другие параметры новой вкладки", - "agentManager.tabsMenu.label": "Искать открытые вкладки", - "agentManager.tabsMenu.search": "Поиск вкладок...", - "agentManager.tabsMenu.empty": "Нет подходящих вкладок", "agentManager.tabsMenu.status.waiting": "Ожидание", "agentManager.tabsMenu.status.retry": "Повтор", + "agentManager.sidebarSearch.label": "Поиск по Worktree и сессиям", + "agentManager.sidebarSearch.scope": + "Поиск локального рабочего пространства, локальных сессий, Worktree и связанных с ними сессий", + "agentManager.sidebarSearch.contexts": "ЛОКАЛЬНЫЙ & WORKTREES", "agentManager.terminal.new": "Новая вкладка терминала", "agentManager.terminal.ended": "терминал завершен — закройте вкладку, чтобы скрыть", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts index 26e20065593..fc9f8366057 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts @@ -51,11 +51,11 @@ export const dict = { "agentManager.tab.terminal": "เทอร์มินัล", "agentManager.tab.openTerminal": "เปิดเทอร์มินัล", "agentManager.tab.newOptions": "ตัวเลือกแท็บใหม่เพิ่มเติม", - "agentManager.tabsMenu.label": "ค้นหาแท็บที่เปิดอยู่", - "agentManager.tabsMenu.search": "ค้นหาแท็บ...", - "agentManager.tabsMenu.empty": "ไม่มีแท็บที่ตรงกัน", "agentManager.tabsMenu.status.waiting": "รอ", "agentManager.tabsMenu.status.retry": "ลองใหม่", + "agentManager.sidebarSearch.label": "ค้นหา Worktree และเซสชัน", + "agentManager.sidebarSearch.scope": "ค้นหาพื้นที่ทำงานในเครื่อง เซสชันในเครื่อง Worktree และเซสชันของ Worktree", + "agentManager.sidebarSearch.contexts": "ในเครื่อง & WORKTREES", "agentManager.terminal.new": "แท็บเทอร์มินัลใหม่", "agentManager.terminal.ended": "เทอร์มินัลสิ้นสุด — ปิดแท็บเพื่อยกเลิก", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts index 4ac533f5f4b..8c4142ecf3e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts @@ -55,11 +55,12 @@ export const dict = { "agentManager.tab.terminal": "Terminal", "agentManager.tab.openTerminal": "Terminali Aç", "agentManager.tab.newOptions": "Daha fazla yeni sekme seçeneği", - "agentManager.tabsMenu.label": "Açık sekmeleri ara", - "agentManager.tabsMenu.search": "Sekmelerde ara...", - "agentManager.tabsMenu.empty": "Eşleşen sekme yok", "agentManager.tabsMenu.status.waiting": "Bekliyor", "agentManager.tabsMenu.status.retry": "Yeniden", + "agentManager.sidebarSearch.label": "Worktree'leri ve oturumları ara", + "agentManager.sidebarSearch.scope": + "Yerel çalışma alanını, yerel oturumları, Worktree'leri ve bunların oturumlarını arar", + "agentManager.sidebarSearch.contexts": "YEREL & WORKTREE'LER", "agentManager.terminal.new": "Yeni Terminal Sekmesi", "agentManager.terminal.ended": "terminal sona erdi — kapatmak için sekmeyi kapatın", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts index f34fcafe5ce..2df141cea0f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts @@ -55,11 +55,12 @@ export const dict = { "agentManager.tab.terminal": "Термінал", "agentManager.tab.openTerminal": "Відкрити термінал", "agentManager.tab.newOptions": "Інші параметри нової вкладки", - "agentManager.tabsMenu.label": "Шукати відкриті вкладки", - "agentManager.tabsMenu.search": "Шукати вкладки...", - "agentManager.tabsMenu.empty": "Немає відповідних вкладок", "agentManager.tabsMenu.status.waiting": "Очікує", "agentManager.tabsMenu.status.retry": "Повтор", + "agentManager.sidebarSearch.label": "Пошук робочих дерев і сесій", + "agentManager.sidebarSearch.scope": + "Пошук локального робочого простору, локальних сесій, робочих дерев і призначених їм сесій", + "agentManager.sidebarSearch.contexts": "ЛОКАЛЬНИЙ & РОБОЧІ ДЕРЕВА", "agentManager.terminal.new": "Нова вкладка термінала", "agentManager.terminal.ended": "термінал завершено — закрийте вкладку, щоб відхилити", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts index fc4460778d5..79d3aaeaaa7 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts @@ -51,11 +51,11 @@ export const dict = { "agentManager.tab.terminal": "终端", "agentManager.tab.openTerminal": "打开终端", "agentManager.tab.newOptions": "更多新建标签页选项", - "agentManager.tabsMenu.label": "搜索打开的标签页", - "agentManager.tabsMenu.search": "搜索标签页...", - "agentManager.tabsMenu.empty": "无匹配标签页", "agentManager.tabsMenu.status.waiting": "等待", "agentManager.tabsMenu.status.retry": "重试", + "agentManager.sidebarSearch.label": "搜索 Worktree 和会话", + "agentManager.sidebarSearch.scope": "搜索本地工作区、本地会话、Worktree 及其会话", + "agentManager.sidebarSearch.contexts": "本地 & WORKTREES", "agentManager.terminal.new": "新建终端标签页", "agentManager.terminal.ended": "终端已结束 — 关闭标签页以消除", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts index 96e306580ae..249732d322b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts @@ -51,11 +51,11 @@ export const dict = { "agentManager.tab.terminal": "終端機", "agentManager.tab.openTerminal": "開啟終端機", "agentManager.tab.newOptions": "更多新增分頁選項", - "agentManager.tabsMenu.label": "搜尋開啟的分頁", - "agentManager.tabsMenu.search": "搜尋分頁...", - "agentManager.tabsMenu.empty": "沒有相符的分頁", "agentManager.tabsMenu.status.waiting": "等待", "agentManager.tabsMenu.status.retry": "重試", + "agentManager.sidebarSearch.label": "搜尋 Worktree 與工作階段", + "agentManager.sidebarSearch.scope": "搜尋本機工作區、本機工作階段、Worktree 及其工作階段", + "agentManager.sidebarSearch.contexts": "本機 & WORKTREES", "agentManager.terminal.new": "新增終端分頁", "agentManager.terminal.ended": "終端已結束 — 關閉分頁以消除", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/shortcuts.ts b/packages/kilo-vscode/webview-ui/agent-manager/shortcuts.ts index 8f21d698a11..e265a524d14 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/shortcuts.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/shortcuts.ts @@ -16,6 +16,7 @@ export function buildShortcutCategories( { title: t("agentManager.shortcuts.category.quickSwitch"), shortcuts: [ + { label: t("agentManager.sidebarSearch.label"), binding: bindings.search ?? "" }, { label: t("agentManager.shortcuts.jumpToItem"), binding: (() => { diff --git a/packages/kilo-vscode/webview-ui/agent-manager/sidebar-search.ts b/packages/kilo-vscode/webview-ui/agent-manager/sidebar-search.ts new file mode 100644 index 00000000000..7be92e0b37e --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/sidebar-search.ts @@ -0,0 +1,225 @@ +import { createMemo } from "solid-js" +import type { Accessor } from "solid-js" +import type { + PermissionRequest, + QuestionRequest, + SectionState, + SessionInfo, + SessionStatusInfo, + WorktreeState, +} from "../src/types/messages" +import { LOCAL } from "./navigate" + +export type SidebarSearchState = "idle" | "busy" | "retry" | "waiting" + +type SearchItem = { + key: string + title: string + meta: string[] + search: string + updatedAt: string + state: SidebarSearchState + visible: boolean + section?: SectionState +} + +export type SidebarSearchItem = + | (SearchItem & { + kind: "local" + group: "contexts" + count: number + }) + | (SearchItem & { + kind: "worktree" + group: "contexts" + worktreeId: string + count: number + }) + | (SearchItem & { + kind: "session" + group: "sessions" + sessionId: string + location: "local" | "worktree" + worktreeId?: string + }) + +export interface SidebarSearchWorktree { + worktree: WorktreeState + label: string + sessions: SessionInfo[] +} + +interface SidebarSearchInput { + worktrees: SidebarSearchWorktree[] + sections: SectionState[] + local: SessionInfo[] + localLabel: string + localBranch?: string + untitled: string + pending: (id: string) => boolean + status: (id: string) => SidebarSearchState + busy: (id: string) => boolean + localBusy: boolean +} + +const root = (item: SessionInfo) => !item.parentID +const same = (a: string, b: string) => a.trim().toLowerCase() === b.trim().toLowerCase() +const score = (state: SidebarSearchState) => (state === "waiting" ? 3 : state === "idle" ? 0 : 2) +const newest = (items: SessionInfo[], fallback: string) => + items.reduce((latest, item) => (item.updatedAt > latest ? item.updatedAt : latest), fallback) + +export function sortSidebarSearch(a: SidebarSearchItem, b: SidebarSearchItem) { + return ( + score(b.state) - score(a.state) || + Number(b.visible) - Number(a.visible) || + b.updatedAt.localeCompare(a.updatedAt) || + a.title.localeCompare(b.title) + ) +} + +export function buildSidebarSearch(input: SidebarSearchInput): SidebarSearchItem[] { + const sections = new Map(input.sections.map((item) => [item.id, item])) + const owned = new Set(input.worktrees.flatMap((item) => item.sessions.map((session) => session.id))) + const local = input.local.filter((session) => root(session) && !input.pending(session.id) && !owned.has(session.id)) + const sessions: SidebarSearchItem[] = local.map((session) => ({ + key: `session:${session.id}`, + kind: "session" as const, + group: "sessions" as const, + title: session.title || input.untitled, + meta: [input.localLabel], + search: [session.title || input.untitled, input.localLabel, session.id].join(" "), + sessionId: session.id, + location: "local" as const, + updatedAt: session.updatedAt, + state: input.status(session.id), + visible: true, + })) + const localState = local.map((session) => input.status(session.id)).sort((a, b) => score(b) - score(a))[0] ?? "idle" + const contexts: SidebarSearchItem[] = [ + { + key: LOCAL, + kind: "local", + group: "contexts", + title: input.localLabel, + meta: input.localBranch ? [input.localBranch] : [], + search: [input.localLabel, input.localBranch].filter(Boolean).join(" "), + updatedAt: newest(local, ""), + state: input.localBusy && localState === "idle" ? "busy" : localState, + visible: true, + count: local.length, + }, + ] + + for (const item of input.worktrees) { + const wt = item.worktree + const section = wt.sectionId ? sections.get(wt.sectionId) : undefined + const roots = item.sessions.filter((session) => root(session) && !input.pending(session.id)) + + for (const session of roots) { + const title = session.title || input.untitled + const meta = [section?.name, !same(item.label, title) ? item.label : undefined, wt.branch].filter( + (value): value is string => !!value, + ) + sessions.push({ + key: `session:${session.id}`, + kind: "session", + group: "sessions", + title, + meta, + search: [title, item.label, wt.branch, section?.name, session.id].filter(Boolean).join(" "), + sessionId: session.id, + location: "worktree", + worktreeId: wt.id, + updatedAt: session.updatedAt, + state: input.status(session.id), + visible: !section?.collapsed, + section, + }) + } + + const state = roots.map((session) => input.status(session.id)).sort((a, b) => score(b) - score(a))[0] ?? "idle" + contexts.push({ + key: `worktree:${wt.id}`, + kind: "worktree", + group: "contexts", + title: item.label, + meta: [section?.name, wt.branch].filter((value): value is string => !!value), + search: [item.label, wt.branch, section?.name, wt.prNumber ? `#${wt.prNumber}` : undefined, wt.id] + .filter(Boolean) + .join(" "), + worktreeId: wt.id, + updatedAt: newest(roots, wt.createdAt), + state: input.busy(wt.id) && state === "idle" ? "busy" : state, + visible: !section?.collapsed, + section, + count: roots.length, + }) + } + + // The List keeps this order for an empty query and as the tie-break order for equally relevant fuzzy matches. + return [...sessions.sort(sortSidebarSearch), ...contexts.sort(sortSidebarSearch)] +} + +interface SidebarSearchDeps { + worktrees: Accessor + sections: Accessor + local: Accessor + localBranch: Accessor + selection: Accessor + sessionId: Accessor + statuses: Accessor> + permissions: Accessor + questions: Accessor + label: (worktree: WorktreeState) => string + sessions: (id: string) => SessionInfo[] + pending: (id: string) => boolean + busy: (id: string) => boolean + localBusy: Accessor + t: (key: string) => string +} + +export function createSidebarSearch(deps: SidebarSearchDeps) { + const items = createMemo(() => { + const statuses = deps.statuses() + const blocked = new Set([ + ...deps.permissions().map((item) => item.sessionID), + ...deps.questions().map((item) => item.sessionID), + ]) + return buildSidebarSearch({ + worktrees: deps.worktrees().map((worktree) => ({ + worktree, + label: deps.label(worktree), + sessions: deps.sessions(worktree.id), + })), + sections: deps.sections(), + local: deps.local(), + localLabel: deps.t("agentManager.local"), + localBranch: deps.localBranch(), + untitled: deps.t("agentManager.session.untitled"), + pending: deps.pending, + status: (id) => { + if (blocked.has(id)) return "waiting" + const status = statuses[id]?.type + return status === "busy" || status === "retry" ? status : "idle" + }, + busy: deps.busy, + localBusy: deps.localBusy(), + }) + }) + + const current = createMemo(() => { + const id = deps.sessionId() + const selection = deps.selection() + const active = items().find( + (item) => + item.kind === "session" && + item.sessionId === id && + ((item.location === "local" && selection === LOCAL) || item.worktreeId === selection), + ) + if (active || !selection) return active + if (selection === LOCAL) return items().find((item) => item.kind === "local") + return items().find((item) => item.kind === "worktree" && item.worktreeId === selection) + }) + + return { items, current } +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/tab-rendering.tsx b/packages/kilo-vscode/webview-ui/agent-manager/tab-rendering.tsx index 9271a274d7c..6e0e133c4f7 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/tab-rendering.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/tab-rendering.tsx @@ -8,7 +8,7 @@ */ import { Show } from "solid-js" -import type { JSX } from "solid-js" +import type { Accessor, JSX } from "solid-js" import { IconButton } from "@kilocode/kilo-ui/icon-button" import { DropdownMenu } from "@kilocode/kilo-ui/dropdown-menu" import { Icon } from "@kilocode/kilo-ui/icon" @@ -19,6 +19,45 @@ import { isTerminalTabId, renderTerminalTab } from "./terminal" import type { SessionInfo } from "../src/types/messages" import { parseBindingTokens } from "./keybind-tokens" +interface FocusTabDeps { + id: string + terms: TerminalStateControls + isTerminal: (id: string) => boolean + isPending: (id: string) => boolean + reviewId: string + reviewOpen: Accessor + setReviewOpen: (open: boolean) => void + setReviewActive: (active: boolean) => void + tabLookup: Accessor> + setActivePendingId: (id: string | undefined) => void + clearSession: () => void + selectSession: (id: string) => void + activateTerminal: (id: string) => void +} + +export function focusCurrentTab(deps: FocusTabDeps) { + if (deps.isTerminal(deps.id)) { + deps.activateTerminal(deps.id) + return + } + deps.terms.setActiveId(undefined) + if (deps.id === deps.reviewId) { + if (!deps.reviewOpen()) deps.setReviewOpen(true) + deps.setReviewActive(true) + return + } + const target = deps.tabLookup().get(deps.id) + if (!target) return + deps.setReviewActive(false) + if (deps.isPending(target.id)) { + deps.setActivePendingId(target.id) + deps.clearSession() + return + } + deps.setActivePendingId(undefined) + deps.selectSession(target.id) +} + export interface TabRenderDeps { terms: TerminalStateControls REVIEW_TAB_ID: string diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx b/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx index 622fa520705..7b43afc7752 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx @@ -109,7 +109,7 @@ function isAgentManagerShortcut(e: KeyboardEvent): boolean { if (!e.metaKey && !e.ctrlKey) return false const key = e.key.toLowerCase() if (e.altKey && ["arrowleft", "arrowright", "arrowup", "arrowdown"].includes(key)) return true - if (["t", "w", "n", "d", "e"].includes(key)) return true + if (["t", "w", "n", "d", "e", "f"].includes(key)) return true if (e.shiftKey && ["w", "n", "o", "m", "/", "?"].includes(key)) return true if (/^[1-9]$/.test(key)) return true if (key === "/") return true diff --git a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx index 8b387cd3cde..fbad72deba7 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx @@ -15,12 +15,15 @@ import { registerVscodeToolOverrides } from "../components/chat/VscodeToolOverri import { SessionContext } from "../context/session" import { ServerContext } from "../context/server" import { WorktreeModeProvider } from "../context/worktree-mode" +import { SidebarSearchMenu } from "../../agent-manager/SidebarSearchMenu" +import { SidebarToggleButton } from "../../agent-manager/SidebarToggleButton" +import type { SidebarSearchItem } from "../../agent-manager/sidebar-search" import { Button } from "@kilocode/kilo-ui/button" import { IconButton } from "@kilocode/kilo-ui/icon-button" import { Icon } from "@kilocode/kilo-ui/icon" import { TooltipKeybind } from "@kilocode/kilo-ui/tooltip" import { ContextMenu } from "@kilocode/kilo-ui/context-menu" -import { createSignal, type JSX } from "solid-js" +import { createSignal, onCleanup, onMount, type JSX } from "solid-js" import type { WorktreeFileDiff, WorktreeState, WorktreeGitStats, PRStatus } from "../types/messages" import type { ReviewComment } from "../../diff-viewer/review-comments" import "../../agent-manager/agent-manager.css" @@ -823,18 +826,9 @@ const MockReviewTab = (props: { active?: boolean }) => (
) -const MockTabsSearchButton = () => ( - -) - const MockTabLeading = () => (
- + {}} />
) @@ -934,3 +928,121 @@ export const TabBarSingleTab: Story = { ), } + +const searchSection = { id: "polish", name: "Polish", color: "Blue", order: 0, collapsed: false } +const slackedSection = { id: "slacked", name: "SLACKED", color: "Yellow", order: 1, collapsed: false } +const sidebarSearchItems: SidebarSearchItem[] = [ + { + key: "session:session-build", + kind: "session", + group: "sessions", + title: "Build grouped worktree search", + meta: ["Polish", "Agent Manager search", "feat/sidebar-search"], + search: "Build grouped worktree search Agent Manager search feat/sidebar-search Polish", + sessionId: "session-build", + location: "worktree", + worktreeId: "wt-search", + updatedAt: new Date(Date.now() - 3 * 60_000).toISOString(), + state: "busy", + visible: true, + section: searchSection, + }, + { + key: "session:session-local", + kind: "session", + group: "sessions", + title: "Investigate local indexing", + meta: ["local"], + search: "Investigate local indexing local", + sessionId: "session-local", + location: "local", + updatedAt: new Date(Date.now() - 8 * 60_000).toISOString(), + state: "idle", + visible: true, + }, + { + key: "session:session-render", + kind: "session", + group: "sessions", + title: "Render images in diff viewer", + meta: ["SLACKED", "images diff viewer", "utopian-approval"], + search: "Render images in diff viewer SLACKED images diff viewer utopian-approval", + sessionId: "session-render", + location: "worktree", + worktreeId: "wt-render", + updatedAt: new Date(Date.now() - 60 * 60_000).toISOString(), + state: "idle", + visible: true, + section: slackedSection, + }, + { + key: "local", + kind: "local", + group: "contexts", + title: "local", + meta: ["main"], + search: "local main", + updatedAt: new Date(Date.now() - 3 * 60_000).toISOString(), + state: "idle", + visible: true, + count: 2, + }, + { + key: "worktree:wt-search", + kind: "worktree", + group: "contexts", + title: "Agent Manager search", + meta: ["Polish", "feat/sidebar-search"], + search: "Agent Manager search Polish feat/sidebar-search", + worktreeId: "wt-search", + updatedAt: new Date(Date.now() - 3 * 60_000).toISOString(), + state: "busy", + visible: true, + section: searchSection, + count: 2, + }, +] + +export const SidebarSearchOpen: Story = { + name: "Sidebar search — worktrees and sessions", + render: () => { + const [selected, setSelected] = createSignal("worktree:wt-search") + let prompt!: HTMLTextAreaElement + const refocus = () => requestAnimationFrame(() => prompt.focus()) + onMount(() => { + window.addEventListener("focusPrompt", refocus) + onCleanup(() => window.removeEventListener("focusPrompt", refocus)) + }) + return ( + +
+
+ +
+ sidebarSearchItems} + keybind="⌘F" + current={() => sidebarSearchItems.find((item) => item.key === selected())} + labels={{ + search: "Search worktrees and sessions", + scope: "Searches the local workspace, local sessions, worktrees, and their sessions", + contexts: "LOCAL & WORKTREES", + sessions: "SESSIONS", + waiting: "Wait", + retry: "Retry", + }} + onSelect={(item) => setSelected(item.key)} + defaultOpen + portal={false} + /> +
+
+ + {selected()} + +