diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index b3a49424fbd..5c5ca6159a6 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -7,6 +7,7 @@ on: jobs: sync: + if: github.repository == 'Kilo-Org/kilocode' runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: write diff --git a/.github/workflows/close-stale-prs.yml b/.github/workflows/close-stale-prs.yml index 34beac1596d..38d0d3627a5 100644 --- a/.github/workflows/close-stale-prs.yml +++ b/.github/workflows/close-stale-prs.yml @@ -17,6 +17,7 @@ permissions: jobs: close-stale-prs: + if: github.repository == 'Kilo-Org/kilocode' runs-on: blacksmith-2vcpu-ubuntu-2404 # kilocode_change timeout-minutes: 15 steps: diff --git a/.github/workflows/compliance-close.yml b/.github/workflows/compliance-close.yml index 6ce16d8e02b..d56d3b4d971 100644 --- a/.github/workflows/compliance-close.yml +++ b/.github/workflows/compliance-close.yml @@ -13,7 +13,7 @@ permissions: jobs: close-non-compliant: - if: false # kilocode_change - disabled: not needed in kilocode repo + if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo runs-on: ubuntu-latest steps: - name: Close non-compliant issues and PRs after 2 hours diff --git a/.github/workflows/docs-check-links.yml b/.github/workflows/docs-check-links.yml index f83c7960f10..75b47a67077 100644 --- a/.github/workflows/docs-check-links.yml +++ b/.github/workflows/docs-check-links.yml @@ -14,6 +14,7 @@ on: jobs: link-checker: + if: github.repository == 'Kilo-Org/kilocode' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/generate.yml b/.github/workflows/generate.yml index bbc06efec62..25b03a7bfc2 100644 --- a/.github/workflows/generate.yml +++ b/.github/workflows/generate.yml @@ -7,6 +7,7 @@ on: jobs: generate: + if: github.repository == 'Kilo-Org/kilocode' runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: write diff --git a/.github/workflows/nix-eval.yml b/.github/workflows/nix-eval.yml index abce8d05ab0..dbe5f0bfd05 100644 --- a/.github/workflows/nix-eval.yml +++ b/.github/workflows/nix-eval.yml @@ -16,6 +16,7 @@ permissions: jobs: nix-eval: + if: github.repository == 'Kilo-Org/kilocode' runs-on: blacksmith-4vcpu-ubuntu-2404 timeout-minutes: 15 steps: diff --git a/.github/workflows/nix-hashes.yml b/.github/workflows/nix-hashes.yml index 65d1c151bc9..cd1c053433a 100644 --- a/.github/workflows/nix-hashes.yml +++ b/.github/workflows/nix-hashes.yml @@ -21,6 +21,7 @@ jobs: # Native runners required: bun install cross-compilation flags (--os/--cpu) # do not produce byte-identical node_modules as native installs. compute-hash: + if: github.repository == 'Kilo-Org/kilocode' strategy: fail-fast: false matrix: @@ -76,7 +77,7 @@ jobs: update-hashes: needs: compute-hash - if: github.event_name != 'pull_request' + if: github.repository == 'Kilo-Org/kilocode' && github.event_name != 'pull_request' runs-on: blacksmith-4vcpu-ubuntu-2404 steps: diff --git a/.github/workflows/pr-management.yml b/.github/workflows/pr-management.yml index 8e5dfe9b8ec..7abe0a0ac18 100644 --- a/.github/workflows/pr-management.yml +++ b/.github/workflows/pr-management.yml @@ -7,7 +7,7 @@ on: jobs: # kilocode_change start check-author: - if: false # kilocode_change - disabled: not needed in kilocode repo + if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo uses: ./.github/workflows/check-org-member.yml with: username: ${{ github.event.pull_request.user.login }} @@ -17,7 +17,7 @@ jobs: check-duplicates: needs: check-author - if: false # kilocode_change - disabled: not needed in kilocode repo + if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo # kilocode_change end runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: @@ -89,7 +89,7 @@ jobs: fi add-contributor-label: - if: false # kilocode_change - disabled: not needed in kilocode repo + if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo runs-on: blacksmith-2vcpu-ubuntu-2404 # kilocode_change permissions: pull-requests: write diff --git a/.github/workflows/pr-standards.yml b/.github/workflows/pr-standards.yml index 98b0c32e57c..75fb650144d 100644 --- a/.github/workflows/pr-standards.yml +++ b/.github/workflows/pr-standards.yml @@ -7,7 +7,7 @@ on: jobs: # kilocode_change start check-author: - if: false # kilocode_change - disabled: not needed in kilocode repo + if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo uses: ./.github/workflows/check-org-member.yml with: username: ${{ github.event.pull_request.user.login }} @@ -17,7 +17,7 @@ jobs: check-standards: needs: check-author - if: false # kilocode_change - disabled: not needed in kilocode repo + if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo # kilocode_change end runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: @@ -141,7 +141,7 @@ jobs: console.log('PR meets all standards'); check-compliance: - if: false # kilocode_change - disabled: not needed in kilocode repo + if: github.repository == 'Kilo-Org/kilocode' && false # kilocode_change - disabled: not needed in kilocode repo runs-on: ubuntu-latest permissions: contents: read diff --git a/.github/workflows/stale-issues.yml b/.github/workflows/stale-issues.yml index d86ef0ad0bb..51c649d823c 100644 --- a/.github/workflows/stale-issues.yml +++ b/.github/workflows/stale-issues.yml @@ -11,6 +11,7 @@ env: jobs: stale: + if: github.repository == 'Kilo-Org/kilocode' runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: issues: write diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml index 45be522c483..b767380365e 100644 --- a/.github/workflows/triage.yml +++ b/.github/workflows/triage.yml @@ -6,6 +6,7 @@ on: jobs: triage: + if: github.repository == 'Kilo-Org/kilocode' runs-on: blacksmith-4vcpu-ubuntu-2404 permissions: contents: read diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml index 4d23526c065..d57764c67c5 100644 --- a/.github/workflows/visual-regression.yml +++ b/.github/workflows/visual-regression.yml @@ -2,18 +2,33 @@ name: Visual Regression Tests on: pull_request: - paths: - - "packages/kilo-ui/**" - - "packages/ui/**" - - "packages/util/**" - - "packages/sdk/js/**" - - "packages/kilo-vscode/webview-ui/**" - - "packages/kilo-vscode/.storybook/**" - - "packages/kilo-vscode/tests/visual-regression*" - - ".github/workflows/visual-regression.yml" + types: [opened, synchronize, reopened] jobs: + check-paths: + name: Check changed paths + runs-on: ubuntu-latest + outputs: + matched: ${{ steps.filter.outputs.matched }} + steps: + - uses: actions/checkout@v4 + - uses: Kilo-Org/paths-filter@master + id: filter + with: + filters: | + matched: + - "packages/kilo-ui/**" + - "packages/ui/**" + - "packages/util/**" + - "packages/sdk/js/**" + - "packages/kilo-vscode/webview-ui/**" + - "packages/kilo-vscode/.storybook/**" + - "packages/kilo-vscode/tests/visual-regression*" + - ".github/workflows/visual-regression.yml" + visual-regression: + needs: check-paths + if: needs.check-paths.outputs.matched == 'true' name: Visual Regression (kilo-ui) runs-on: blacksmith-4vcpu-ubuntu-2404 timeout-minutes: 15 @@ -23,7 +38,7 @@ jobs: uses: actions/checkout@v4 with: lfs: true - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.BOT_PAT }} ref: ${{ github.head_ref }} - name: Setup Bun @@ -77,6 +92,8 @@ jobs: - name: Commit and push new baselines (if any) id: commit-baselines + env: + GH_TOKEN: ${{ secrets.BOT_PAT }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" @@ -106,6 +123,8 @@ jobs: retention-days: 7 visual-regression-vscode: + needs: check-paths + if: needs.check-paths.outputs.matched == 'true' name: Visual Regression (kilo-vscode webview) runs-on: blacksmith-4vcpu-ubuntu-2404 timeout-minutes: 15 @@ -115,7 +134,7 @@ jobs: uses: actions/checkout@v4 with: lfs: true - token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.BOT_PAT }} ref: ${{ github.head_ref }} - name: Setup Bun @@ -169,6 +188,8 @@ jobs: - name: Commit and push new baselines (if any) id: commit-baselines-vscode + env: + GH_TOKEN: ${{ secrets.BOT_PAT }} run: | git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" diff --git a/packages/kilo-gateway/src/server/routes.ts b/packages/kilo-gateway/src/server/routes.ts index 19a239f35ff..b5231f245af 100644 --- a/packages/kilo-gateway/src/server/routes.ts +++ b/packages/kilo-gateway/src/server/routes.ts @@ -19,6 +19,7 @@ type Validator = any type Resolver = any type Errors = any type Auth = any +type ModelCache = { clear: (providerID: string) => void } type Z = any interface KiloRoutesDeps extends ImportDeps { @@ -28,6 +29,7 @@ interface KiloRoutesDeps extends ImportDeps { resolver: Resolver errors: Errors Auth: Auth + ModelCache: ModelCache z: Z } @@ -72,6 +74,7 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { Bus, SessionCreatedEvent, Identifier, + ModelCache, } = deps const Organization = z.object({ @@ -200,6 +203,8 @@ export function createKiloRoutes(deps: KiloRoutesDeps) { ...(organizationId && { accountId: organizationId }), }) + ModelCache.clear("kilo") + return c.json(true) }, ) diff --git a/packages/kilo-ui/src/components/basic-tool.css b/packages/kilo-ui/src/components/basic-tool.css index 7891116b576..52b6958e408 100644 --- a/packages/kilo-ui/src/components/basic-tool.css +++ b/packages/kilo-ui/src/components/basic-tool.css @@ -59,7 +59,7 @@ html[data-theme="kilo-vscode"] [data-component="tool-trigger"] { } /* Card styling for tool calls in the VS Code sidebar theme */ -html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"] { +html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-type="tool"] { border-radius: var(--radius-md); border: 1px solid var(--border-weak-base, var(--vscode-panel-border)); overflow: hidden; diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index 3656e92f186..f43f25129d7 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -1612,6 +1612,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper console.log("[Kilo New] KiloProvider: 🔐 Login successful") + await this.client.global.dispose().catch((e: unknown) => + console.warn("[Kilo New] KiloProvider: global.dispose() after login failed:", e), + ) + // Step 4: Fetch profile and push to webview const { data: profileData } = await this.client.kilo.profile(undefined, { throwOnError: true }) this.postMessage({ type: "profileData", data: profileData }) @@ -1657,6 +1661,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper return } + await this.client.global.dispose().catch((e: unknown) => + console.warn("[Kilo New] KiloProvider: global.dispose() after org switch failed:", e), + ) + // Org switch succeeded — refresh profile and providers independently (best-effort) try { const profileResult = await sdkClient.kilo.profile() @@ -1709,6 +1717,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper type: "profileData", data: null, }) + + await this.client.global.dispose().catch((e: unknown) => + console.warn("[Kilo New] KiloProvider: global.dispose() after logout failed:", e), + ) + } catch (error) { console.error("[Kilo New] KiloProvider: ❌ Logout failed:", error) this.postMessage({ @@ -1716,6 +1729,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper message: getErrorMessage(error) || "Failed to logout", }) } + + } /** @@ -1802,6 +1817,21 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper return this.connectionService.resolveEventSessionId(event) } + /** + * Re-fetch all server-side state after an auth change (login/logout/org switch). + * After instance.dispose() clears the server cache, the next request to each + * endpoint will re-initialize with the current auth state. + * This mirrors the TUI's sync.bootstrap() pattern. + */ + private async reloadAfterAuthChange(): Promise { + await Promise.all([ + this.fetchAndSendProviders(), + this.fetchAndSendAgents(), + this.fetchAndSendConfig(), + this.fetchAndSendNotifications(), + ]) + } + /** * Handle SSE events from the CLI backend. * Filters events by project ID and tracked session IDs so each webview only sees its own sessions. @@ -1827,8 +1857,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper // Refresh provider and agent lists when the server signals a state disposal if (event.type === "server.instance.disposed" || event.type === "global.disposed") { - void this.fetchAndSendProviders() - void this.fetchAndSendAgents() + void this.reloadAfterAuthChange(); return } diff --git a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts index 39393f3f066..59d50dff70f 100644 --- a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts +++ b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts @@ -67,11 +67,45 @@ export class WorktreeManager { this.log = log } + // --------------------------------------------------------------------------- + // Per-project git operation mutex + // --------------------------------------------------------------------------- + + // Serializes git-writing operations per repository root so concurrent + // callers (e.g. multi-version worktree creation) don't hit index.lock + // conflicts. Operations on different repositories proceed in parallel. + private static locks = new Map>() + + private withGitLock(fn: () => Promise): Promise { + const key = this.root + const prev = WorktreeManager.locks.get(key) ?? Promise.resolve() + const result = prev.then(fn) + const barrier = result.then( + () => {}, + () => {}, + ) + WorktreeManager.locks.set(key, barrier) + return result + } + + // --------------------------------------------------------------------------- + // Public API (acquires git lock) + // --------------------------------------------------------------------------- + async createWorktree(params: { prompt?: string existingBranch?: string baseBranch?: string branchName?: string + }): Promise { + return this.withGitLock(() => this.createWorktreeImpl(params)) + } + + private async createWorktreeImpl(params: { + prompt?: string + existingBranch?: string + baseBranch?: string + branchName?: string }): Promise { const repo = await this.git.checkIsRepo() if (!repo) @@ -108,7 +142,7 @@ export class WorktreeManager { if (fs.existsSync(worktreePath)) { this.log(`Worktree directory exists, cleaning up before re-creation: ${worktreePath}`) - await this.removeWorktree(worktreePath) + await this.removeWorktreeImpl(worktreePath) } try { @@ -154,6 +188,10 @@ export class WorktreeManager { * that git doesn't know about. */ async removeWorktree(worktreePath: string): Promise { + return this.withGitLock(() => this.removeWorktreeImpl(worktreePath)) + } + + private async removeWorktreeImpl(worktreePath: string): Promise { const clean = await this.git.raw(["worktree", "remove", worktreePath]).then( () => true, () => false, @@ -440,6 +478,10 @@ export class WorktreeManager { } async createFromPR(url: string): Promise { + return this.withGitLock(() => this.createFromPRImpl(url)) + } + + private async createFromPRImpl(url: string): Promise { const parsed = parsePRUrl(url) if (!parsed) throw new Error("Invalid PR URL. Expected: https://github.com/owner/repo/pull/123") @@ -462,7 +504,7 @@ export class WorktreeManager { await this.git.raw(["branch", branch, `${forkOwner}/${info.headRefName}`]) } - return this.createWorktree({ existingBranch: branch }) + return this.createWorktreeImpl({ existingBranch: branch }) } private async fetchPRInfo(parsed: { owner: string; repo: string; number: number }): Promise { diff --git a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/FillInTheMiddle.ts b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/FillInTheMiddle.ts index dbf9dfb28ba..7127b9de7e9 100644 --- a/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/FillInTheMiddle.ts +++ b/packages/kilo-vscode/src/services/autocomplete/classic-auto-complete/FillInTheMiddle.ts @@ -83,12 +83,7 @@ export class FimPromptBuilder { response += text } logtime("prep fim") - const usageInfo = await model.generateFimResponse( - formattedPrefix, - prunedSuffix, - onChunk, - signal, - ) + const usageInfo = await model.generateFimResponse(formattedPrefix, prunedSuffix, onChunk, signal) logtime("fim network") console.log("[FIM] response:", response) diff --git a/packages/kilo-vscode/src/services/cli-backend/sdk-sse-adapter.ts b/packages/kilo-vscode/src/services/cli-backend/sdk-sse-adapter.ts index 77b32c9b277..e1fd9a54b08 100644 --- a/packages/kilo-vscode/src/services/cli-backend/sdk-sse-adapter.ts +++ b/packages/kilo-vscode/src/services/cli-backend/sdk-sse-adapter.ts @@ -27,201 +27,199 @@ export type SSEStateHandler = (state: "connecting" | "connected" | "disconnected * straightforward improvement. */ export class SdkSSEAdapter { - private readonly handlers = new Set() - private readonly errorHandlers = new Set() - private readonly stateHandlers = new Set() + private readonly handlers = new Set() + private readonly errorHandlers = new Set() + private readonly stateHandlers = new Set() - private abortController: AbortController | null = null - private heartbeatTimer: ReturnType | null = null + private abortController: AbortController | null = null + private heartbeatTimer: ReturnType | null = null - private static readonly HEARTBEAT_TIMEOUT_MS = 90_000 - private static readonly RECONNECT_DELAY_MS = 250 + private static readonly HEARTBEAT_TIMEOUT_MS = 90_000 + private static readonly RECONNECT_DELAY_MS = 250 - constructor(private readonly client: KiloClient) {} + constructor(private readonly client: KiloClient) {} - // ── Lifecycle ────────────────────────────────────────────────────── + // ── Lifecycle ────────────────────────────────────────────────────── - /** - * Start consuming the global SSE stream. - * Calling `connect()` while already connected is a no-op. - */ - connect(): void { - if (this.abortController) { - console.log("[Kilo New] SSE: ⚠️ Already connected, skipping") - return - } + /** + * Start consuming the global SSE stream. + * Calling `connect()` while already connected is a no-op. + */ + connect(): void { + if (this.abortController) { + console.log("[Kilo New] SSE: ⚠️ Already connected, skipping") + return + } - console.log("[Kilo New] SSE: 🔌 connect() called") - this.abortController = new AbortController() - console.log('[Kilo New] SSE: 🔄 Setting state to "connecting"') - this.notifyState("connecting") - void this.consumeLoop(this.abortController.signal).catch((err) => { - console.error("[Kilo New] SSE: Unhandled error in consumeLoop:", err) - this.notifyError(err instanceof Error ? err : new Error(String(err))) - }) - } + console.log("[Kilo New] SSE: 🔌 connect() called") + this.abortController = new AbortController() + console.log('[Kilo New] SSE: 🔄 Setting state to "connecting"') + this.notifyState("connecting") + void this.consumeLoop(this.abortController.signal).catch((err) => { + console.error("[Kilo New] SSE: Unhandled error in consumeLoop:", err) + this.notifyError(err instanceof Error ? err : new Error(String(err))) + }) + } - /** - * Stop consuming the SSE stream and abort any in-flight request. - */ - disconnect(): void { - console.log("[Kilo New] SSE: 🔌 disconnect() called") - this.abortController?.abort() - this.abortController = null - this.clearHeartbeat() - } + /** + * Stop consuming the SSE stream and abort any in-flight request. + */ + disconnect(): void { + console.log("[Kilo New] SSE: 🔌 disconnect() called") + this.abortController?.abort() + this.abortController = null + this.clearHeartbeat() + } - /** - * Disconnect and clear all registered handlers. - */ - dispose(): void { - this.disconnect() - this.handlers.clear() - this.errorHandlers.clear() - this.stateHandlers.clear() - } + /** + * Disconnect and clear all registered handlers. + */ + dispose(): void { + this.disconnect() + this.handlers.clear() + this.errorHandlers.clear() + this.stateHandlers.clear() + } - // ── Pub/sub ──────────────────────────────────────────────────────── + // ── Pub/sub ──────────────────────────────────────────────────────── - onEvent(handler: SSEEventHandler): () => void { - this.handlers.add(handler) - return () => { - this.handlers.delete(handler) - } - } + onEvent(handler: SSEEventHandler): () => void { + this.handlers.add(handler) + return () => { + this.handlers.delete(handler) + } + } - onError(handler: SSEErrorHandler): () => void { - this.errorHandlers.add(handler) - return () => { - this.errorHandlers.delete(handler) - } - } + onError(handler: SSEErrorHandler): () => void { + this.errorHandlers.add(handler) + return () => { + this.errorHandlers.delete(handler) + } + } - onStateChange(handler: SSEStateHandler): () => void { - this.stateHandlers.add(handler) - return () => { - this.stateHandlers.delete(handler) - } - } + onStateChange(handler: SSEStateHandler): () => void { + this.stateHandlers.add(handler) + return () => { + this.stateHandlers.delete(handler) + } + } - // ── Internal ─────────────────────────────────────────────────────── + // ── Internal ─────────────────────────────────────────────────────── - /** - * Main reconnection loop — mirrors the pattern in `global-sdk.tsx`. - */ - private async consumeLoop(signal: AbortSignal): Promise { - while (!signal.aborted) { - const attempt = new AbortController() + /** + * Main reconnection loop — mirrors the pattern in `global-sdk.tsx`. + */ + private async consumeLoop(signal: AbortSignal): Promise { + while (!signal.aborted) { + const attempt = new AbortController() - // Forward the outer abort to the per-attempt controller so - // `disconnect()` cancels the current fetch immediately. - const onAbort = () => attempt.abort() - signal.addEventListener("abort", onAbort) + // Forward the outer abort to the per-attempt controller so + // `disconnect()` cancels the current fetch immediately. + const onAbort = () => attempt.abort() + signal.addEventListener("abort", onAbort) - try { - console.log("[Kilo New] SSE: 🎬 Calling SDK global.event()...") - const events = await this.client.global.event({ - signal: attempt.signal, - onSseError: (error) => { - if (signal.aborted) { - return - } - console.error("[Kilo New] SSE: ❌ SDK SSE error callback:", error) - this.notifyError(error instanceof Error ? error : new Error(String(error))) - }, - }) + try { + console.log("[Kilo New] SSE: 🎬 Calling SDK global.event()...") + const events = await this.client.global.event({ + signal: attempt.signal, + onSseError: (error) => { + if (signal.aborted) { + return + } + console.error("[Kilo New] SSE: ❌ SDK SSE error callback:", error) + this.notifyError(error instanceof Error ? error : new Error(String(error))) + }, + }) - console.log("[Kilo New] SSE: ✅ Stream opened successfully") - this.notifyState("connected") - this.resetHeartbeat(attempt) + console.log("[Kilo New] SSE: ✅ Stream opened successfully") + this.notifyState("connected") + this.resetHeartbeat(attempt) - for await (const event of events.stream) { - if (signal.aborted) { - break - } + for await (const event of events.stream) { + if (signal.aborted) { + break + } - this.resetHeartbeat(attempt) + this.resetHeartbeat(attempt) - // The SDK yields GlobalEvent = { directory, payload: Event }. - const globalEvent = event as GlobalEvent - console.log("[Kilo New] SSE: 📨 Event:", globalEvent.payload.type) - this.notifyEvent(globalEvent.payload) - } + // The SDK yields GlobalEvent = { directory, payload: Event }. + const globalEvent = event as GlobalEvent + console.log("[Kilo New] SSE: 📨 Event:", globalEvent.payload.type) + this.notifyEvent(globalEvent.payload) + } - console.log("[Kilo New] SSE: 📭 Stream ended normally") - } catch (error) { - if (!signal.aborted) { - console.error("[Kilo New] SSE: ❌ Stream error:", error) - this.notifyError(error instanceof Error ? error : new Error(String(error))) - } - } finally { - signal.removeEventListener("abort", onAbort) - this.clearHeartbeat() - } + console.log("[Kilo New] SSE: 📭 Stream ended normally") + } catch (error) { + if (!signal.aborted) { + console.error("[Kilo New] SSE: ❌ Stream error:", error) + this.notifyError(error instanceof Error ? error : new Error(String(error))) + } + } finally { + signal.removeEventListener("abort", onAbort) + this.clearHeartbeat() + } - if (signal.aborted) { - break - } + if (signal.aborted) { + break + } - console.log( - `[Kilo New] SSE: 🔄 Reconnecting in ${SdkSSEAdapter.RECONNECT_DELAY_MS}ms...`, - ) - this.notifyState("connecting") - await new Promise((resolve) => setTimeout(resolve, SdkSSEAdapter.RECONNECT_DELAY_MS)) - } + console.log(`[Kilo New] SSE: 🔄 Reconnecting in ${SdkSSEAdapter.RECONNECT_DELAY_MS}ms...`) + this.notifyState("connecting") + await new Promise((resolve) => setTimeout(resolve, SdkSSEAdapter.RECONNECT_DELAY_MS)) + } - this.notifyState("disconnected") - } + this.notifyState("disconnected") + } - /** - * Reset the heartbeat timer. If no event arrives within the timeout - * window the per-attempt controller is aborted, causing the - * `for await` loop to exit and the outer loop to reconnect. - */ - private resetHeartbeat(attempt: AbortController): void { - this.clearHeartbeat() - this.heartbeatTimer = setTimeout(() => { - console.log("[Kilo New] SSE: ⏰ Heartbeat timeout — aborting stale connection") - attempt.abort() - }, SdkSSEAdapter.HEARTBEAT_TIMEOUT_MS) - } + /** + * Reset the heartbeat timer. If no event arrives within the timeout + * window the per-attempt controller is aborted, causing the + * `for await` loop to exit and the outer loop to reconnect. + */ + private resetHeartbeat(attempt: AbortController): void { + this.clearHeartbeat() + this.heartbeatTimer = setTimeout(() => { + console.log("[Kilo New] SSE: ⏰ Heartbeat timeout — aborting stale connection") + attempt.abort() + }, SdkSSEAdapter.HEARTBEAT_TIMEOUT_MS) + } - private clearHeartbeat(): void { - if (this.heartbeatTimer) { - clearTimeout(this.heartbeatTimer) - this.heartbeatTimer = null - } - } + private clearHeartbeat(): void { + if (this.heartbeatTimer) { + clearTimeout(this.heartbeatTimer) + this.heartbeatTimer = null + } + } - // ── Notify helpers ───────────────────────────────────────────────── + // ── Notify helpers ───────────────────────────────────────────────── - private notifyEvent(event: Event): void { - for (const handler of this.handlers) { - try { - handler(event) - } catch (error) { - console.error("[Kilo New] SSE: Error in event handler:", error) - } - } - } + private notifyEvent(event: Event): void { + for (const handler of this.handlers) { + try { + handler(event) + } catch (error) { + console.error("[Kilo New] SSE: Error in event handler:", error) + } + } + } - private notifyError(error: Error): void { - for (const handler of this.errorHandlers) { - try { - handler(error) - } catch (err) { - console.error("[Kilo New] SSE: Error in error handler:", err) - } - } - } + private notifyError(error: Error): void { + for (const handler of this.errorHandlers) { + try { + handler(error) + } catch (err) { + console.error("[Kilo New] SSE: Error in error handler:", err) + } + } + } - private notifyState(state: "connecting" | "connected" | "disconnected"): void { - for (const handler of this.stateHandlers) { - try { - handler(state) - } catch (error) { - console.error("[Kilo New] SSE: Error in state handler:", error) - } - } - } + private notifyState(state: "connecting" | "connected" | "disconnected"): void { + for (const handler of this.stateHandlers) { + try { + handler(state) + } catch (error) { + console.error("[Kilo New] SSE: Error in state handler:", error) + } + } + } } diff --git a/packages/kilo-vscode/tests/package.json b/packages/kilo-vscode/tests/package.json index 5ffd9800b97..3dbc1ca591c 100644 --- a/packages/kilo-vscode/tests/package.json +++ b/packages/kilo-vscode/tests/package.json @@ -1 +1,3 @@ -{ "type": "module" } +{ + "type": "module" +} diff --git a/packages/kilo-vscode/tests/setup/vscode-mock.ts b/packages/kilo-vscode/tests/setup/vscode-mock.ts index 3e73903ffeb..adf1dfafda8 100644 --- a/packages/kilo-vscode/tests/setup/vscode-mock.ts +++ b/packages/kilo-vscode/tests/setup/vscode-mock.ts @@ -28,7 +28,11 @@ const mockVscode = { extensions: { getExtension: (id: string) => { if (id === "vscode.git") return undefined - return { packageJSON: { version: "test", contributes: { configuration: { properties: {} } } }, isActive: true, exports: undefined } + return { + packageJSON: { version: "test", contributes: { configuration: { properties: {} } } }, + isActive: true, + exports: undefined, + } }, }, env: { @@ -63,7 +67,10 @@ const mockVscode = { CodeAction: class { command?: { command: string; title: string } isPreferred?: boolean - constructor(public title: string, public kind: { value: string }) {} + constructor( + public title: string, + public kind: { value: string }, + ) {} }, CodeActionKind: { QuickFix: kind("quickfix"), @@ -78,10 +85,16 @@ const mockVscode = { constructor(public uri: { scheme: string; fsPath: string }) {} }, Position: class { - constructor(public line: number, public character: number) {} + constructor( + public line: number, + public character: number, + ) {} }, Range: class { - constructor(public start: { line: number; character: number }, public end: { line: number; character: number }) {} + constructor( + public start: { line: number; character: number }, + public end: { line: number; character: number }, + ) {} }, Disposable: class { constructor(private callback: () => void = noop) {} diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png index f93a836f20b..1c8fa34857d 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d385bd579ac5587b1f70416196f77b6465d6a69fedf7270b889eedb29d0ac73d -size 14864 +oid sha256:a3774fc422d473693e38fafba61c00632fd49d2a0c61d2332b9c31482af78a2b +size 15268 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png new file mode 100644 index 00000000000..490684425ab --- /dev/null +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-with-messages-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d69f44620c8e3a2f8d54ec97903c95cde04bef4655fc3b280a318e6da3cb0b5 +size 9082 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-multi-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-multi-chromium-linux.png index c1626253e08..42623d20283 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-multi-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-multi-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db0467fd219dfc19b4b27e32ff29c7f81e68091c58bb6b9209e6baa4e5f396ac -size 19507 +oid sha256:b8b1ba56d6632bfcbd0e4c5b0c1514cd80832339dde0c86568572ada656ca33d +size 24134 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-single-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-single-chromium-linux.png index d113e984886..21f6afd99c2 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-single-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-single-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:04d857626cd157bf7f3325f50f48f5c9f95302a836f873cd9249aabbb6b22c8e -size 25422 +oid sha256:278855516fe11d95b9de942d9690531c114b0a0b9ac0239c2ef31f82b420bd7e +size 32467 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/chat-busy-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/chat-busy-chromium-linux.png index 116b32df4ce..e3da1f8b30a 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/chat-busy-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/chat-busy-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee4898a628110edf572fa27433bbb905d260225be0dd2154ccdfd4e4a2ea4c1e -size 4415 +oid sha256:bcafa00cb615abbf253220f3b1c3163056bd18e1f5bf9632bc7229738936eff6 +size 4311 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/inline-question-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/inline-question-chromium-linux.png index 9ffe917bfc9..d25eccfaf9e 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/inline-question-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/inline-question-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:22e44ea6da3326afe29e5eba35c3df0602ee84506f91d4d449c8cf770cd741b4 -size 28468 +oid sha256:f6193ba79b4d5dda29bf39d1127dacd7e332755d1f6f42dd67ff667088975659 +size 35678 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/multiple-tool-calls-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/multiple-tool-calls-chromium-linux.png index 60ef12e51a0..bd787278cb7 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/multiple-tool-calls-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/multiple-tool-calls-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd8a53356f0aa0df5c07246296dc20273f5380a012cd579885b1974e3aeb99e1 -size 8496 +oid sha256:8f7fd08433524c113a2d7b3fed8bef5c023d9a451621f658b836c7967ca954aa +size 8310 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-chromium-linux.png index a085f20934e..2cc64b57e70 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:36abb08cb4cc89b797d49ce2639c6f6e1086d83a6d373bf7821c1035253018b7 -size 11181 +oid sha256:2a225a66c7981745089a46f20841d5f214d23d905b8b31194cfba9f278a3763d +size 10965 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/question-dismissed-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/question-dismissed-chromium-linux.png new file mode 100644 index 00000000000..f7e30521e5a --- /dev/null +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/question-dismissed-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:607707e2201d1e50369a5897266136387b2444570b61ac2062c658335a0cdcc7 +size 5055 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/tool-cards-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/tool-cards-chromium-linux.png index 5112b15c21e..492118610c0 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/tool-cards-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/tool-cards-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:64ff66248b1c07209757eb6e27ccac4967650b144317ee21076ba82c129bc17b -size 8846 +oid sha256:d2c75a7bb06035f07f364c7fb7a1eb0b813663658d018e4eea881c48cc0e276c +size 8698 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/shared/model-selector-no-providers-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/shared/model-selector-no-providers-chromium-linux.png index ea6ca1082d8..f597731e231 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/shared/model-selector-no-providers-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/shared/model-selector-no-providers-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:56517eed0845a01559a613124e5cf8f48945bbfc6f0fb6e3274ca5607954cb0c -size 1293 +oid sha256:e0f7858b22d02fc7f30de4faae3f1f95ad7022e5e86741304b67d0f28f423e71 +size 1296 diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index bcf4a052052..53299f60f86 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -329,12 +329,12 @@ const AgentManagerContent: Component = () => { const [diffLoading, setDiffLoading] = createSignal(false) const [diffWidth, setDiffWidth] = createSignal(Math.round(window.innerWidth * 0.5)) - // Full-screen review state (in-memory, per worktree) - const [reviewOpenByWorktree, setReviewOpenByWorktree] = createSignal>({}) - const [reviewCommentsByWorktree, setReviewCommentsByWorktree] = createSignal>({}) + // Full-screen review state (in-memory, per sidebar context: local/worktree) + const [reviewOpenByContext, setReviewOpenByContext] = createSignal>({}) + const [reviewCommentsByContext, setReviewCommentsByContext] = createSignal>({}) const [reviewActive, setReviewActive] = createSignal(false) const [reviewDiffStyle, setReviewDiffStyle] = createSignal<"unified" | "split">("unified") - // reviewOpen (memo below) controls tab presence for selected worktree. + // reviewOpen (memo below) controls tab presence for selected context. // Per-worktree git stats (diff additions/deletions, commits missing from origin) const [worktreeStats, setWorktreeStats] = createSignal>({}) @@ -358,33 +358,33 @@ const AgentManagerContent: Component = () => { const reviewOpen = createMemo(() => { const sel = selection() - if (!sel || sel === LOCAL) return false - return reviewOpenByWorktree()[sel] === true + if (sel === null) return false + return reviewOpenByContext()[sel] === true }) - const setReviewOpenForWorktree = (worktreeId: string, open: boolean) => { - setReviewOpenByWorktree((prev) => { - if (prev[worktreeId] === open) return prev - return { ...prev, [worktreeId]: open } + const setReviewOpenForContext = (context: string, open: boolean) => { + setReviewOpenByContext((prev) => { + if (prev[context] === open) return prev + return { ...prev, [context]: open } }) } const setReviewOpenForSelection = (open: boolean) => { const sel = selection() - if (!sel || sel === LOCAL) return - setReviewOpenForWorktree(sel, open) + if (sel === null) return + setReviewOpenForContext(sel, open) } const reviewComments = createMemo(() => { const sel = selection() - if (!sel || sel === LOCAL) return [] as ReviewComment[] - return reviewCommentsByWorktree()[sel] ?? [] + if (sel === null) return [] as ReviewComment[] + return reviewCommentsByContext()[sel] ?? [] }) const setReviewCommentsForSelection = (comments: ReviewComment[]) => { const sel = selection() - if (!sel || sel === LOCAL) return - setReviewCommentsByWorktree((prev) => ({ ...prev, [sel]: comments })) + if (sel === null) return + setReviewCommentsByContext((prev) => ({ ...prev, [sel]: comments })) } const applyStateForSelection = createMemo(() => { @@ -633,14 +633,14 @@ const AgentManagerContent: Component = () => { createEffect(() => { const ids = new Set(worktrees().map((wt) => wt.id)) - setReviewOpenByWorktree((prev) => { - const next = Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))) + setReviewOpenByContext((prev) => { + const next = Object.fromEntries(Object.entries(prev).filter(([id]) => id === LOCAL || ids.has(id))) if (Object.keys(next).length === Object.keys(prev).length) return prev return next }) - setReviewCommentsByWorktree((prev) => { - const next = Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))) + setReviewCommentsByContext((prev) => { + const next = Object.fromEntries(Object.entries(prev).filter(([id]) => id === LOCAL || ids.has(id))) if (Object.keys(next).length === Object.keys(prev).length) return prev return next }) @@ -722,7 +722,7 @@ const AgentManagerContent: Component = () => { createEffect(() => { const sel = selection() - if (!sel || sel === LOCAL) { + if (sel === null) { if (reviewActive()) setReviewActive(false) return } @@ -903,6 +903,7 @@ const AgentManagerContent: Component = () => { session.clearCurrentSession() vscode.postMessage({ type: "agentManager.showExistingLocalTerminal" }) } + setReviewActive(remembered === REVIEW_TAB_ID && reviewOpenByContext()[LOCAL] === true) } const selectWorktree = (worktreeId: string) => { @@ -919,7 +920,7 @@ const AgentManagerContent: Component = () => { } else { session.setCurrentSessionID(undefined) } - setReviewActive(remembered === REVIEW_TAB_ID && reviewOpenByWorktree()[worktreeId] === true) + setReviewActive(remembered === REVIEW_TAB_ID && reviewOpenByContext()[worktreeId] === true) } onMount(() => { @@ -1288,9 +1289,13 @@ const AgentManagerContent: Component = () => { return } if (review) { - // Review tab is open but no specific session — try using any session in the worktree - const sel = selection() - if (sel && sel !== LOCAL) { + // Review tab is open but no specific session — use local sentinel for local, + // or any session in the selected worktree. + if (sel === LOCAL) { + vscode.postMessage({ type: "agentManager.startDiffWatch", sessionId: LOCAL }) + return + } + if (sel) { const managed = managedSessions().find((ms) => ms.worktreeId === sel) if (managed) { vscode.postMessage({ type: "agentManager.startDiffWatch", sessionId: managed.id }) @@ -1311,9 +1316,9 @@ const AgentManagerContent: Component = () => { const openReviewTab = () => { const sel = selection() - if (!sel || sel === LOCAL) return + if (sel === null) return setDiffOpen(false) - setReviewOpenForWorktree(sel, true) + setReviewOpenForContext(sel, true) setReviewActive(true) } @@ -1333,16 +1338,18 @@ const AgentManagerContent: Component = () => { setReviewOpenForSelection(false) } - // Data for the review tab: use current session's diff data, or first available for the worktree + // Data for the review tab: use local diff data for local context, + // current session for selected worktree context, or first available in that worktree. const reviewDiffs = createMemo(() => { const data = diffDatas() const sel = selection() const id = session.currentSessionID() + if (sel === LOCAL) return data[LOCAL] ?? [] if (id && data[id]) { const current = managedSessions().find((s) => s.id === id) - if (sel && sel !== LOCAL && current?.worktreeId === sel) return data[id]! + if (sel && current?.worktreeId === sel) return data[id]! } - if (!sel || sel === LOCAL) return [] + if (!sel) return [] const ids = managedSessions() .filter((s) => s.worktreeId === sel) .map((s) => s.id) @@ -1569,8 +1576,9 @@ const AgentManagerContent: Component = () => { const tabIds = createMemo(() => { const ids = activeTabs().map((s) => s.id) const sel = selection() - if (!sel || sel === LOCAL) return ids + if (sel === null) return ids const current = reviewOpen() ? [...ids, REVIEW_TAB_ID] : ids + if (sel === LOCAL) return current return applyTabOrder( current.map((id) => ({ id })), worktreeTabOrder()[sel], @@ -1588,7 +1596,12 @@ const AgentManagerContent: Component = () => { if (typeof from !== "string" || typeof to !== "string") return const sel = selection() if (sel === LOCAL) { - setLocalSessionIDs((prev) => reorderTabs(prev, from, to) ?? prev) + setLocalSessionIDs((prev) => { + const ids = reviewOpen() ? [...prev, REVIEW_TAB_ID] : prev + const reordered = reorderTabs(ids, from, to) + if (!reordered) return prev + return reordered.filter((id) => id !== REVIEW_TAB_ID) + }) return } if (sel) { @@ -2037,7 +2050,7 @@ const AgentManagerContent: Component = () => { placement="top" > { ) })()} +
+
+ + {t("agentManager.worktree.doubleClickRename")} +
@@ -2418,7 +2436,7 @@ const AgentManagerContent: Component = () => { ) })()} - + { comments={reviewComments()} onCommentsChange={setReviewCommentsForSelection} onClose={() => setDiffOpen(false)} - onExpand={selection() !== LOCAL ? openReviewTab : undefined} + onExpand={selection() !== null ? openReviewTab : undefined} onOpenFile={(file) => { const id = session.currentSessionID() if (id) vscode.postMessage({ type: "agentManager.openFile", sessionId: id, filePath: file }) 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 546c992380a..6a2a75b1bf3 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -37,6 +37,7 @@ width: 100%; text-align: left; color: var(--text-base); + font-family: inherit; font-size: var(--font-size-base); flex-shrink: 0; } @@ -50,15 +51,17 @@ } .am-local-item-active { - background: var(--surface-base-active); + background: var(--surface-interactive-base); + color: var(--text-on-interactive-base); } .am-local-item-active .am-local-icon { - color: var(--text-base); + color: var(--text-on-interactive-base); } .am-local-item-active .am-local-branch { - color: var(--text-weak); + color: var(--text-on-interactive-base); + opacity: 0.7; } .am-local-item-active .am-stat-files { @@ -469,6 +472,7 @@ button.am-section-toggle:hover .am-section-label { width: 100%; text-align: left; color: var(--text-base); + font-family: inherit; font-size: var(--font-size-base); } @@ -481,8 +485,8 @@ button.am-section-toggle:hover .am-section-label { } .am-item-active { - background: var(--surface-base-active); - color: var(--text-base); + background: var(--surface-interactive-base); + color: var(--text-on-interactive-base); } .am-item-title { @@ -502,7 +506,8 @@ button.am-section-toggle:hover .am-section-label { } .am-item-active .am-item-time { - color: var(--text-weaker); + color: var(--text-on-interactive-base); + opacity: 0.7; } /* Promote button on session rows */ @@ -2042,6 +2047,18 @@ button.am-section-toggle:hover .am-section-label { justify-content: flex-start; } +.am-hover-card-hint { + display: flex; + align-items: center; + gap: 6px; + color: var(--text-weaker); + font-size: 11px; +} + +.am-hover-card-hint [data-component="icon"] { + opacity: 0.8; +} + /* Keyboard shortcuts dialog */ .am-shortcuts { 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 55be84d8b15..f73fb6a4dc6 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts @@ -131,10 +131,10 @@ export const dict = { "agentManager.review.sendToChat": "Pošalji u chat", "agentManager.review.collapsedOnly": "{{count}} sažeto", "agentManager.review.collapsedWithLarge": "{{collapsed}} sažeto, {{large}} velikih", - "agentManager.review.largeFileCollapsed": "Velika datoteka (skupljeno)", - "agentManager.review.endOfLongDiff": "Stigli ste do kraja!", + "agentManager.review.largeFileCollapsed": "Velika datoteka (sažeto)", + "agentManager.review.endOfLongDiff": "Došli ste do kraja!", - "agentManager.import.pullRequest": "Zahtjev za povlačenje", + "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "Zalijepite PR URL...", "agentManager.import.open": "Otvori", "agentManager.import.branches": "Branchevi", 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 3908a05b7a4..3bd7b05d8e6 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts @@ -131,10 +131,10 @@ export const dict = { "agentManager.review.sendToChat": "Send til chat", "agentManager.review.collapsedOnly": "{{count}} foldet sammen", "agentManager.review.collapsedWithLarge": "{{collapsed}} foldet sammen, {{large}} store", - "agentManager.review.largeFileCollapsed": "Stor fil (kollapset)", - "agentManager.review.endOfLongDiff": "Du er nået til slutningen!", + "agentManager.review.largeFileCollapsed": "Stor fil (sammenklappet)", + "agentManager.review.endOfLongDiff": "Du nåede slutningen!", - "agentManager.import.pullRequest": "Fletteanmodning", + "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "Indsæt PR URL...", "agentManager.import.open": "Åbn", "agentManager.import.branches": "Branches", 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 941738cf845..ce3ebe65b0b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts @@ -132,10 +132,10 @@ export const dict = { "agentManager.review.sendToChat": "An Chat senden", "agentManager.review.collapsedOnly": "{{count}} eingeklappt", "agentManager.review.collapsedWithLarge": "{{collapsed}} eingeklappt, {{large}} groß", - "agentManager.review.largeFileCollapsed": "Große Datei (zugeklappt)", + "agentManager.review.largeFileCollapsed": "Große Datei (eingeklappt)", "agentManager.review.endOfLongDiff": "Du hast das Ende erreicht!", - "agentManager.import.pullRequest": "Zusammenführungsanfrage", + "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "PR-URL einfügen...", "agentManager.import.open": "Öffnen", "agentManager.import.branches": "Branches", 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 66e812107e8..a1710c34add 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts @@ -133,9 +133,9 @@ 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.endOfLongDiff": "¡Has llegado al final!", + "agentManager.review.endOfLongDiff": "¡Llegaste al final!", - "agentManager.import.pullRequest": "Solicitud de extracción", + "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "Pegar URL del PR...", "agentManager.import.open": "Abrir", "agentManager.import.branches": "Branches", 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 7ffdf72ba99..f13e015067e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts @@ -133,10 +133,10 @@ export const dict = { "agentManager.review.sendToChat": "Envoyer au chat", "agentManager.review.collapsedOnly": "{{count}} repliés", "agentManager.review.collapsedWithLarge": "{{collapsed}} repliés, {{large}} volumineux", - "agentManager.review.largeFileCollapsed": "Fichier volumineux (réduit)", + "agentManager.review.largeFileCollapsed": "Fichier volumineux (replié)", "agentManager.review.endOfLongDiff": "Vous êtes arrivé à la fin !", - "agentManager.import.pullRequest": "Demande d'extraction", + "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "Coller l'URL du PR...", "agentManager.import.open": "Ouvrir", "agentManager.import.branches": "Branches", 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 39131586fbb..3e574cc7103 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts @@ -133,10 +133,10 @@ export const dict = { "agentManager.review.sendToChat": "チャットに送信", "agentManager.review.collapsedOnly": "{{count}} 件折りたたみ", "agentManager.review.collapsedWithLarge": "{{collapsed}} 件折りたたみ、{{large}} 件がサイズ大", - "agentManager.review.largeFileCollapsed": "大きなファイル (折りたたみ済み)", + "agentManager.review.largeFileCollapsed": "大きなファイル(折りたたみ)", "agentManager.review.endOfLongDiff": "最後まで到達しました!", - "agentManager.import.pullRequest": "プルリクエスト", + "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "PR URLを貼り付け...", "agentManager.import.open": "開く", "agentManager.import.branches": "ブランチ", 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 eebd6b006b3..25d2193dc12 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts @@ -132,10 +132,10 @@ export const dict = { "agentManager.review.sendToChat": "채팅으로 보내기", "agentManager.review.collapsedOnly": "{{count}}개 접힘", "agentManager.review.collapsedWithLarge": "{{collapsed}}개 접힘, {{large}}개 대용량", - "agentManager.review.largeFileCollapsed": "큰 파일 (접힘)", - "agentManager.review.endOfLongDiff": "끝까지 확인하셨습니다!", + "agentManager.review.largeFileCollapsed": "큰 파일(접힘)", + "agentManager.review.endOfLongDiff": "끝까지 도달했습니다!", - "agentManager.import.pullRequest": "풀 리퀘스트", + "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "PR URL 붙여넣기...", "agentManager.import.open": "열기", "agentManager.import.branches": "브랜치", 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 7e3e4e106cf..4eb89250525 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts @@ -131,10 +131,10 @@ export const dict = { "agentManager.review.sendToChat": "Send til chat", "agentManager.review.collapsedOnly": "{{count}} kollapset", "agentManager.review.collapsedWithLarge": "{{collapsed}} kollapset, {{large}} store", - "agentManager.review.largeFileCollapsed": "Stor fil (kollapset)", - "agentManager.review.endOfLongDiff": "Du har nådd slutten!", + "agentManager.review.largeFileCollapsed": "Stor fil (sammenfoldet)", + "agentManager.review.endOfLongDiff": "Du nådde slutten!", - "agentManager.import.pullRequest": "Fletteforespørsel", + "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "Lim inn PR URL...", "agentManager.import.open": "Åpne", "agentManager.import.branches": "Branches", 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 c42e42b6a42..fcfd147f853 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts @@ -132,10 +132,10 @@ export const dict = { "agentManager.review.sendToChat": "ส่งไปยังแชท", "agentManager.review.collapsedOnly": "ยุบ {{count}} รายการ", "agentManager.review.collapsedWithLarge": "ยุบ {{collapsed}} รายการ, ขนาดใหญ่ {{large}} รายการ", - "agentManager.review.largeFileCollapsed": "ไฟล์ขนาดใหญ่ (ย่อไว้)", - "agentManager.review.endOfLongDiff": "คุณมาถึงส่วนท้ายแล้ว!", + "agentManager.review.largeFileCollapsed": "ไฟล์ขนาดใหญ่ (พับอยู่)", + "agentManager.review.endOfLongDiff": "คุณมาถึงท้ายสุดแล้ว!", - "agentManager.import.pullRequest": "คำขอดึง", + "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "วาง URL ของ PR...", "agentManager.import.open": "เปิด", "agentManager.import.branches": "Branches", 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 9731ccd1f96..0676c721b4f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts @@ -131,9 +131,9 @@ export const dict = { "agentManager.review.collapsedOnly": "{{count}} 个已折叠", "agentManager.review.collapsedWithLarge": "{{collapsed}} 个已折叠,{{large}} 个过大", "agentManager.review.largeFileCollapsed": "大文件(已折叠)", - "agentManager.review.endOfLongDiff": "您已到达末尾!", + "agentManager.review.endOfLongDiff": "你已经到末尾了!", - "agentManager.import.pullRequest": "拉取请求", + "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "粘贴 PR URL...", "agentManager.import.open": "打开", "agentManager.import.branches": "分支", 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 6751d9232b7..9d4e5d946e6 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts @@ -130,10 +130,10 @@ export const dict = { "agentManager.review.sendToChat": "傳送到聊天", "agentManager.review.collapsedOnly": "{{count}} 個已摺疊", "agentManager.review.collapsedWithLarge": "{{collapsed}} 個已摺疊,{{large}} 個過大", - "agentManager.review.largeFileCollapsed": "大檔案(已摺疊)", - "agentManager.review.endOfLongDiff": "您已到達底部!", + "agentManager.review.largeFileCollapsed": "大型檔案(已摺疊)", + "agentManager.review.endOfLongDiff": "你已經到最後了!", - "agentManager.import.pullRequest": "拉取請求", + "agentManager.import.pullRequest": "Pull Request", "agentManager.import.pastePrUrl": "貼上 PR URL...", "agentManager.import.open": "開啟", "agentManager.import.branches": "分支", diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx index f93c9cdb459..163b343f0c7 100644 --- a/packages/kilo-vscode/webview-ui/src/App.tsx +++ b/packages/kilo-vscode/webview-ui/src/App.tsx @@ -170,7 +170,7 @@ const AppContent: Component = () => { const handleViewAction = (action: string) => { switch (action) { case "plusButtonClicked": - session.clearCurrentSession() + window.dispatchEvent(new CustomEvent("newTaskRequest")) setCurrentView("newTask") break case "marketplaceButtonClicked": diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx index 9abcc24eb37..ca7ee8d5c34 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx @@ -60,8 +60,7 @@ export const AssistantMessage: Component = (props) => { } // Questions linked to this message (rendered after the last part) - const questionForMessage = () => - questions().find((q) => q.tool!.messageID === props.message.id) + const questionForMessage = () => questions().find((q) => q.tool!.messageID === props.message.id) const [responding, setResponding] = createSignal(false) @@ -79,7 +78,7 @@ export const AssistantMessage: Component = (props) => { const perm = () => permissionForPart(part) return ( -
+
= (props) => { /> {(p) => ( -
+
e.stopPropagation()}> 0}>
@@ -97,13 +96,28 @@ export const AssistantMessage: Component = (props) => {
- - -
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index 0f8ea5bf2d8..b5be42e3c01 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -3,7 +3,7 @@ * Main chat container that combines all chat components */ -import { Component, For, Show, createSignal, onCleanup, onMount } from "solid-js" +import { Component, For, Show, createSignal, createEffect, on, onCleanup, onMount } from "solid-js" import { Button } from "@kilocode/kilo-ui/button" import { BasicTool } from "@kilocode/kilo-ui/basic-tool" import { TaskHeader } from "./TaskHeader" @@ -23,6 +23,8 @@ export const ChatView: Component = (props) => { const language = useLanguage() const id = () => session.currentSessionID() + const hasMessages = () => session.messages().length > 0 + const idle = () => session.status() !== "busy" const sessionQuestions = () => session.questions().filter((q) => q.sessionID === id()) const sessionPermissions = () => session.permissions().filter((p) => p.sessionID === id()) @@ -30,6 +32,17 @@ export const ChatView: Component = (props) => { const permissionRequest = () => sessionPermissions().find((p) => !p.tool) const blocked = () => sessionPermissions().length > 0 || sessionQuestions().length > 0 + // When a bottom-dock permission/question disappears while the session is busy, + // the scroll container grows taller. Dispatch a custom event so MessageList can + // resume auto-scroll. + createEffect( + on(blocked, (isBlocked, wasBlocked) => { + if (wasBlocked && !isBlocked && !idle()) { + window.dispatchEvent(new CustomEvent("resumeAutoScroll")) + } + }), + ) + const [responding, setResponding] = createSignal(false) onMount(() => { @@ -102,6 +115,19 @@ export const ChatView: Component = (props) => {
)} + +
+ +
+
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx index 98ddfe8d5af..6764da91916 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx @@ -7,7 +7,7 @@ * Shows recent sessions in the empty state for quick resumption. */ -import { Component, For, Show, createEffect, createMemo, JSX } from "solid-js" +import { Component, For, Show, createEffect, createMemo, onCleanup, JSX } from "solid-js" import { Spinner } from "@kilocode/kilo-ui/spinner" import { Button } from "@kilocode/kilo-ui/button" import { useDialog } from "@kilocode/kilo-ui/context/dialog" @@ -48,6 +48,11 @@ export const MessageList: Component = (props) => { overflowAnchor: "dynamic", }) + // Resume auto-scroll when a bottom-dock permission/question is dismissed + const onResumeAutoScroll = () => autoScroll.resume() + window.addEventListener("resumeAutoScroll", onResumeAutoScroll) + onCleanup(() => window.removeEventListener("resumeAutoScroll", onResumeAutoScroll)) + let loaded = false createEffect(() => { if (!loaded && server.isConnected() && session.sessions().length === 0) { 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 0bffbc552ce..a1eca6e2d2e 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx @@ -72,6 +72,16 @@ export const PromptInput: Component = () => { window.addEventListener("focusPrompt", onFocusPrompt) onCleanup(() => window.removeEventListener("focusPrompt", onFocusPrompt)) + // Start a new task, carrying over the current prompt text (without auto-sending it) + const onNewTaskRequest = () => { + const prompt = text().trim() + // Pre-populate the draft for the new (empty) session so the effect restores it + if (prompt) drafts.set("__new__", prompt) + session.clearCurrentSession() + } + window.addEventListener("newTaskRequest", onNewTaskRequest) + onCleanup(() => window.removeEventListener("newTaskRequest", onNewTaskRequest)) + const isBusy = () => session.status() === "busy" const isDisabled = () => !server.isConnected() const canSend = () => (text().trim().length > 0 || imageAttach.images().length > 0) && !isBusy() && !isDisabled() 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 bb8abd929b7..ee97a5dcb10 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx @@ -1,12 +1,13 @@ /** * QuestionDock component * Displays question requests from the AI assistant inline above the prompt input. - * Uses kilo-ui's data-component/data-slot CSS pattern for styling. + * Uses kilo-ui's DockPrompt component for proper surface styling. */ import { Component, For, Show, createMemo, createEffect } from "solid-js" import { createStore } from "solid-js/store" import { Button } from "@kilocode/kilo-ui/button" +import { DockPrompt } from "@kilocode/kilo-ui/dock-prompt" import { Icon } from "@kilocode/kilo-ui/icon" import { useSession } from "../../context/session" import { useLanguage } from "../../context/language" @@ -148,10 +149,11 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) => } return ( -
-
-
-
+
e.stopPropagation()}> +
{summary()}
@@ -177,62 +179,65 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
-
- - -
-
{question()?.question}
- {language.t("ui.question.singleHint")}
} - > -
{language.t("ui.question.multiHint")}
+ + } + footer={ + <> + +
+ 0}> + -
- - {(opt, i) => { - const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false - return ( - - ) - }} - - + selectTab(store.tab + 1)} + disabled={store.sending || (!confirm() && (store.answers[store.tab]?.length ?? 0) === 0)} + > + {last() && single() + ? language.t("ui.common.submit") + : last() + ? language.t("common.review") + : language.t("ui.common.next")} + + } + > + + +
+ + } + > + +
{question()?.question}
+ {language.t("ui.question.singleHint")}
}> +
{language.t("ui.question.multiHint")}
+
+
+ + {(opt, i) => { + const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false + return ( - -
- setTimeout(() => el.focus(), 0)} - type="text" - data-slot="custom-input" - placeholder={language.t("ui.question.custom.placeholder")} - value={input()} - disabled={store.sending} - onInput={(e) => { - const inputs = [...store.custom] - inputs[store.tab] = e.currentTarget.value - setStore("custom", inputs) - }} - /> - - -
-
- -
-
- - - -
-
{language.t("ui.messagePart.review.title")}
- - {(q, index) => { - const value = () => store.answers[index()]?.join(", ") ?? "" - const answered = () => Boolean(value()) - return ( -
- {q.question} - - {answered() ? value() : language.t("ui.question.review.notAnswered")} - -
- ) - }} -
-
-
-
- -
- -
- 0}> - - - selectTab(store.tab + 1)} - disabled={store.sending || (!confirm() && (store.answers[store.tab]?.length ?? 0) === 0)} - > - {last() && single() - ? language.t("ui.common.submit") - : last() - ? language.t("common.review") - : language.t("ui.common.next")} - - } - > - + ) + }} + + + + +
+ setTimeout(() => el.focus(), 0)} + type="text" + data-slot="custom-input" + placeholder={language.t("ui.question.custom.placeholder")} + value={input()} + disabled={store.sending} + onInput={(e) => { + const inputs = [...store.custom] + inputs[store.tab] = e.currentTarget.value + setStore("custom", inputs) + }} + /> + + +
+
-
-
+ + + +
+
{language.t("ui.messagePart.review.title")}
+ + {(q, index) => { + const value = () => store.answers[index()]?.join(", ") ?? "" + const answered = () => Boolean(value()) + return ( +
+ {q.question} + + {answered() ? value() : language.t("ui.question.review.notAnswered")} + +
+ ) + }} +
+
+
+
) } diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts index d624712866c..290837a1b1b 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ar.ts @@ -43,6 +43,7 @@ export const dict = { "command.language.set": "استخدام اللغة: {{language}}", "command.session.new": "جلسة جديدة", + "command.session.new.task": "مهمة جديدة", "command.file.open": "فتح ملف", "command.tab.close": "إغلاق علامة التبويب", "command.context.addSelection": "إضافة التحديد إلى السياق", @@ -971,7 +972,6 @@ export const dict = { // Agent Manager strings live in webview-ui/agent-manager/i18n/ar.ts - "question.summary": "{{n}} من {{total}} أسئلة", "common.review": "مراجعة", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/br.ts b/packages/kilo-vscode/webview-ui/src/i18n/br.ts index 92f5ef7f23d..fadb33f8772 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/br.ts @@ -43,6 +43,7 @@ export const dict = { "command.language.set": "Usar idioma: {{language}}", "command.session.new": "Nova sessão", + "command.session.new.task": "Nova tarefa", "command.file.open": "Abrir arquivo", "command.tab.close": "Fechar aba", "command.context.addSelection": "Adicionar seleção ao contexto", @@ -985,7 +986,6 @@ export const dict = { // Agent Manager strings live in webview-ui/agent-manager/i18n/br.ts - "question.summary": "{{n}} de {{total}} perguntas", "common.review": "Revisar", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts index dc0e18c819b..5bd3f7f64f9 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/bs.ts @@ -43,6 +43,7 @@ export const dict = { "command.language.set": "Koristi jezik: {{language}}", "command.session.new": "Nova sesija", + "command.session.new.task": "Novi zadatak", "command.file.open": "Otvori datoteku", "command.tab.close": "Zatvori karticu", "command.context.addSelection": "Dodaj odabir u kontekst", @@ -984,7 +985,6 @@ export const dict = { // Agent Manager strings live in webview-ui/agent-manager/i18n/bs.ts - "question.summary": "{{n}} od {{total}} pitanja", "common.review": "Pregled", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/da.ts b/packages/kilo-vscode/webview-ui/src/i18n/da.ts index b32906e1dc9..e5109827edd 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/da.ts @@ -43,6 +43,7 @@ export const dict = { "command.language.set": "Brug sprog: {{language}}", "command.session.new": "Ny session", + "command.session.new.task": "Ny opgave", "command.file.open": "Åbn fil", "command.tab.close": "Luk fane", "command.context.addSelection": "Tilføj markering til kontekst", @@ -980,7 +981,6 @@ export const dict = { // Agent Manager strings live in webview-ui/agent-manager/i18n/da.ts - "question.summary": "{{n}} af {{total}} spørgsmål", "common.review": "Gennemgå", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/de.ts b/packages/kilo-vscode/webview-ui/src/i18n/de.ts index 88eff4e62b6..f8aeedc00e2 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/de.ts @@ -47,6 +47,7 @@ export const dict = { "command.language.set": "Sprache verwenden: {{language}}", "command.session.new": "Neue Sitzung", + "command.session.new.task": "Neue Aufgabe", "command.file.open": "Datei öffnen", "command.tab.close": "Tab schließen", "command.context.addSelection": "Auswahl zum Kontext hinzufügen", @@ -997,7 +998,6 @@ export const dict = { // Agent Manager strings live in webview-ui/agent-manager/i18n/de.ts - "question.summary": "{{n}} von {{total}} Fragen", "common.review": "Überprüfen", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/en.ts b/packages/kilo-vscode/webview-ui/src/i18n/en.ts index b9b7ef70d4a..cf8a9123e86 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/en.ts @@ -43,6 +43,7 @@ export const dict = { "command.language.set": "Use language: {{language}}", "command.session.new": "New session", + "command.session.new.task": "New task", "command.file.open": "Open file", "command.tab.close": "Close tab", "command.context.addSelection": "Add selection to context", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/es.ts b/packages/kilo-vscode/webview-ui/src/i18n/es.ts index c83e1969793..e2fcae70ba1 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/es.ts @@ -43,6 +43,7 @@ export const dict = { "command.language.set": "Usar idioma: {{language}}", "command.session.new": "Nueva sesión", + "command.session.new.task": "Nueva tarea", "command.file.open": "Abrir archivo", "command.tab.close": "Cerrar pestaña", "command.context.addSelection": "Añadir selección al contexto", @@ -987,7 +988,6 @@ export const dict = { "profile.personalAccount": "Cuenta personal", // Agent Manager strings live in webview-ui/agent-manager/i18n/es.ts - "question.summary": "{{n}} de {{total}} preguntas", "common.review": "Revisar", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts index ae077e4fb39..c8c0b376a4f 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/fr.ts @@ -43,6 +43,7 @@ export const dict = { "command.language.set": "Utiliser la langue : {{language}}", "command.session.new": "Nouvelle session", + "command.session.new.task": "Nouvelle tâche", "command.file.open": "Ouvrir un fichier", "command.tab.close": "Fermer l'onglet", "command.context.addSelection": "Ajouter la sélection au contexte", @@ -71,7 +72,8 @@ export const dict = { "command.permissions.autoaccept.enable": "Accepter automatiquement les modifications", "command.permissions.autoaccept.disable": "Arrêter l'acceptation automatique des modifications", "command.workspace.toggle": "Basculer les espaces de travail", - "command.workspace.toggle.description": "Activer ou désactiver les espaces de travail multiples dans la barre latérale", + "command.workspace.toggle.description": + "Activer ou désactiver les espaces de travail multiples dans la barre latérale", "command.session.undo": "Annuler", "command.session.undo.description": "Annuler le dernier message", "command.session.redo": "Rétablir", @@ -995,7 +997,6 @@ export const dict = { "profile.personalAccount": "Compte personnel", // Agent Manager strings live in webview-ui/agent-manager/i18n/fr.ts - "question.summary": "{{n}} sur {{total}} questions", "common.review": "Réviser", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts index 8b51301ab34..d73838ef306 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ja.ts @@ -43,6 +43,7 @@ export const dict = { "command.language.set": "言語を使用: {{language}}", "command.session.new": "新しいセッション", + "command.session.new.task": "新しいタスク", "command.file.open": "ファイルを開く", "command.tab.close": "タブを閉じる", "command.context.addSelection": "選択範囲をコンテキストに追加", @@ -979,7 +980,6 @@ export const dict = { "profile.personalAccount": "個人アカウント", // Agent Manager strings live in webview-ui/agent-manager/i18n/ja.ts - "question.summary": "{{total}} 問中 {{n}} 問目", "common.review": "確認", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts index a49d9c991e6..689c864b39f 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ko.ts @@ -47,6 +47,7 @@ export const dict = { "command.language.set": "언어 사용: {{language}}", "command.session.new": "새 세션", + "command.session.new.task": "새 작업", "command.file.open": "파일 열기", "command.tab.close": "탭 닫기", "command.context.addSelection": "선택 영역을 컨텍스트에 추가", @@ -976,7 +977,6 @@ export const dict = { "profile.personalAccount": "개인 계정", // Agent Manager strings live in webview-ui/agent-manager/i18n/ko.ts - "question.summary": "{{total}}개 질문 중 {{n}}번째", "common.review": "검토", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/no.ts b/packages/kilo-vscode/webview-ui/src/i18n/no.ts index 77ffea3cccb..8badc1290d9 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/no.ts @@ -46,6 +46,7 @@ export const dict = { "command.language.set": "Bruk språk: {{language}}", "command.session.new": "Ny sesjon", + "command.session.new.task": "Ny oppgave", "command.file.open": "Åpne fil", "command.tab.close": "Lukk fane", "command.context.addSelection": "Legg til markering i kontekst", @@ -981,7 +982,6 @@ export const dict = { "profile.personalAccount": "Personlig konto", // Agent Manager strings live in webview-ui/agent-manager/i18n/no.ts - "question.summary": "{{n}} av {{total}} spørsmål", "common.review": "Gjennomgå", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts index cbe5d0e6662..7e159f26048 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/pl.ts @@ -43,6 +43,7 @@ export const dict = { "command.language.set": "Użyj języka: {{language}}", "command.session.new": "Nowa sesja", + "command.session.new.task": "Nowe zadanie", "command.file.open": "Otwórz plik", "command.tab.close": "Zamknij kartę", "command.context.addSelection": "Dodaj zaznaczenie do kontekstu", @@ -982,7 +983,6 @@ export const dict = { "profile.personalAccount": "Konto osobiste", // Agent Manager strings live in webview-ui/agent-manager/i18n/pl.ts - "question.summary": "{{n}} z {{total}} pytań", "common.review": "Przejrzyj", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts index ba24a453d82..99ffd1b6d2b 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/ru.ts @@ -43,6 +43,7 @@ export const dict = { "command.language.set": "Использовать язык: {{language}}", "command.session.new": "Новая сессия", + "command.session.new.task": "Новая задача", "command.file.open": "Открыть файл", "command.tab.close": "Закрыть вкладку", "command.context.addSelection": "Добавить выделение в контекст", @@ -984,7 +985,6 @@ export const dict = { "profile.personalAccount": "Личный аккаунт", // Agent Manager strings live in webview-ui/agent-manager/i18n/ru.ts - "question.summary": "{{n}} из {{total}} вопросов", "common.review": "Просмотр", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/th.ts b/packages/kilo-vscode/webview-ui/src/i18n/th.ts index 52e94770f7b..0fc0bf2776e 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/th.ts @@ -43,6 +43,7 @@ export const dict = { "command.language.set": "ใช้ภาษา: {{language}}", "command.session.new": "เซสชันใหม่", + "command.session.new.task": "งานใหม่", "command.file.open": "เปิดไฟล์", "command.tab.close": "ปิดแท็บ", "command.context.addSelection": "เพิ่มส่วนที่เลือกไปยังบริบท", @@ -972,7 +973,6 @@ export const dict = { "profile.personalAccount": "บัญชีส่วนตัว", // Agent Manager strings live in webview-ui/agent-manager/i18n/th.ts - "question.summary": "{{n}} จาก {{total}} คำถาม", "common.review": "ตรวจสอบ", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts index b3b2ee2c9f6..57353016d3d 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zh.ts @@ -47,6 +47,7 @@ export const dict = { "command.language.set": "使用语言:{{language}}", "command.session.new": "新建会话", + "command.session.new.task": "新建任务", "command.file.open": "打开文件", "command.tab.close": "关闭标签页", "command.context.addSelection": "将所选内容添加到上下文", @@ -967,7 +968,6 @@ export const dict = { "profile.personalAccount": "个人账户", // Agent Manager strings live in webview-ui/agent-manager/i18n/zh.ts - "question.summary": "第 {{n}} / {{total}} 个问题", "common.review": "审查", diff --git a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts index 9678d6a8bca..521b923115a 100644 --- a/packages/kilo-vscode/webview-ui/src/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/src/i18n/zht.ts @@ -47,6 +47,7 @@ export const dict = { "command.language.set": "使用語言:{{language}}", "command.session.new": "新增工作階段", + "command.session.new.task": "新增任務", "command.file.open": "開啟檔案", "command.tab.close": "關閉分頁", "command.context.addSelection": "將選取內容加入上下文", @@ -968,7 +969,6 @@ export const dict = { "profile.personalAccount": "個人帳戶", // Agent Manager strings live in webview-ui/agent-manager/i18n/zht.ts - "question.summary": "第 {{n}} / {{total}} 個問題", "common.review": "審查", diff --git a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx index c9ac76a851a..f60b512d6f0 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx @@ -8,9 +8,10 @@ */ import type { Meta, StoryObj } from "storybook-solidjs-vite" -import { StoryProviders } from "./StoryProviders" +import { StoryProviders, mockSessionValue } from "./StoryProviders" import { ChatView } from "../components/chat/ChatView" import { QuestionDock } from "../components/chat/QuestionDock" +import { SessionContext } from "../context/session" import type { QuestionRequest } from "../types/messages" const SESSION_ID = "story-session-chat-001" @@ -69,7 +70,7 @@ const multiQuestion: QuestionRequest = { const meta: Meta = { title: "Chat", - parameters: { layout: "padded" }, + parameters: { layout: "fullscreen" }, } export default meta type Story = StoryObj @@ -82,13 +83,35 @@ export const ChatViewIdle: Story = { name: "ChatView — idle (empty)", render: () => ( -
+
), } +/** ChatView with messages — shows the full-width "New task" button above the prompt */ +export const ChatViewWithMessages: Story = { + name: "ChatView — with messages (shows New Task button)", + render: () => { + const session = { + ...mockSessionValue({ id: SESSION_ID, status: "idle" }), + messages: () => [{ id: "msg-001" }] as any[], + totalCost: () => 0.0012, + contextUsage: () => ({ tokens: 512, percentage: 6 }), + } + return ( + + +
+ +
+
+
+ ) + }, +} + // --------------------------------------------------------------------------- // QuestionDock stories // --------------------------------------------------------------------------- @@ -97,7 +120,7 @@ export const QuestionDockSingle: Story = { name: "QuestionDock — single question", render: () => ( -
+
@@ -108,7 +131,7 @@ export const QuestionDockMulti: Story = { name: "QuestionDock — multi-question wizard", render: () => ( -
+
diff --git a/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx index dca22dcd92c..aab5bac37ff 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx @@ -231,6 +231,23 @@ const questionToolPart: ToolPart = { }, } +const questionDismissedPart: ToolPart = { + id: "part-question-dismissed-001", + sessionID: SESSION_ID, + messageID: ASST_MSG_ID, + type: "tool", + callID: "call-question-dismissed-001", + tool: "question", + state: { + status: "error", + input: { question: "Which testing framework?", options: [] }, + error: "Error: User dismissed this question", + title: "Question dismissed", + metadata: {}, + time: { start: now - 2000, end: now - 1500 }, + }, +} + // --------------------------------------------------------------------------- // Data helpers // --------------------------------------------------------------------------- @@ -244,9 +261,7 @@ function dataWith(parts: any[], permissions?: PermissionRequest[]) { part: { [ASST_MSG_ID]: parts, }, - permission: permissions - ? { [SESSION_ID]: permissions } - : {}, + permission: permissions ? { [SESSION_ID]: permissions } : {}, } } @@ -318,9 +333,7 @@ export const PermissionDock: Story = { > 0}>
- - {(pattern) => {pattern}} - + {(pattern) => {pattern}}
@@ -461,3 +474,19 @@ export const InlineQuestion: Story = { ) }, } + +// --------------------------------------------------------------------------- +// 9. Dismissed question (right-aligned "Questions dismissed" text) +// --------------------------------------------------------------------------- + +export const QuestionDismissed: Story = { + name: "Question Dismissed", + render: () => { + const data = dataWith([textPart, questionDismissedPart]) + return ( + + + + ) + }, +} diff --git a/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx index 6f1941e6573..c517ac9ef6a 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx @@ -9,7 +9,7 @@ import { ModelSelectorBase } from "../components/shared/ModelSelector" const meta: Meta = { title: "Shared", - parameters: { layout: "padded" }, + parameters: { layout: "fullscreen" }, } export default meta type Story = StoryObj diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index ab73a8fef8e..ee9dca5d81e 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -41,6 +41,11 @@ border-top: 1px solid var(--vscode-panel-border); } +/* New Task Button */ +.new-task-button-wrapper { + padding: 8px 12px 0; +} + /* ============================================ Message List ============================================ */ @@ -907,6 +912,10 @@ padding: 0 4px; } +.vscode-session-turn + .vscode-session-turn { + margin-top: 12px; +} + .vscode-session-turn-user { width: 100%; } @@ -932,14 +941,10 @@ } /* ============================================ - Question Prompt + Question Prompt (VS Code overrides) ============================================ */ -[data-component="question-prompt"] { - [data-slot="question-header"] { - padding: 0; - } - +[data-component="dock-prompt"][data-kind="question"] { [data-slot="question-progress-nav"] { display: inline-flex; align-items: center; @@ -967,12 +972,6 @@ } } - [data-slot="question-footer"] { - padding: 8px; - padding-top: 8px; - margin-top: 0; - } - [data-slot="question-footer"] > [data-component="button"][data-variant="ghost"] { border: none; box-shadow: none; @@ -992,155 +991,11 @@ color: var(--text-weaker); } - [data-slot="question-text"] { - font-size: var(--font-size-base); - padding: 0 2px; - } - - [data-slot="question-hint"] { - font-size: var(--font-size-small); - padding: 0 2px; - } - - [data-slot="question-options"] { - margin-top: 8px; - gap: 4px; - padding: 1px 1px 4px; - } - - [data-slot="question-option"] { - display: flex; - align-items: flex-start; - gap: 8px; - padding: 6px 8px; - background-color: var(--surface-raised-stronger-non-alpha); - border: 1px solid var(--border-weak-base); - border-radius: var(--radius-lg); - box-shadow: none; - text-align: left; - width: 100%; - cursor: pointer; - transition: - background-color 0.15s ease, - border-color 0.15s ease, - box-shadow 0.15s ease; - - &:hover:not([data-picked="true"]) { - background-color: var(--background-base); - } - - &[data-picked="true"] { - background-color: var(--surface-interactive-weak); - border-color: transparent; - box-shadow: var(--shadow-xs-border-hover); - } - - &:disabled { - cursor: not-allowed; - opacity: 0.6; - } - } - - [data-slot="question-option-check"] { - display: inline-flex; - transform: translateY(2px); - } - - [data-slot="question-option-box"] { - width: 16px; - height: 16px; - padding: 2px; - border-radius: var(--radius-sm); - border: 1px solid var(--border-weak-base); - display: inline-flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - background-color: transparent; - transition: - background-color 0.15s ease, - border-color 0.15s ease; - - [data-component="icon"] { - opacity: 0; - color: var(--icon-base); - } - - &[data-type="radio"] { - border-radius: 999px; - } - - [data-slot="question-option-radio-dot"] { - width: 6px; - height: 6px; - border-radius: 999px; - background-color: var(--background-stronger); - opacity: 0; - } - - &[data-picked="true"] { - border-color: var(--icon-interactive-base); - - [data-component="icon"] { - opacity: 1; - color: var(--icon-invert-base); - } - - &[data-type="checkbox"] { - background-color: var(--icon-interactive-base); - } - - &[data-type="radio"] { - background-color: var(--icon-interactive-base); - [data-slot="question-option-radio-dot"] { - opacity: 1; - } - } - } - } - - [data-slot="question-option-main"] { - display: flex; - flex-direction: column; - gap: 2px; - min-width: 0; - flex: 1; - } - - [data-slot="option-label"] { - font-family: var(--font-family-sans); - font-size: var(--font-size-base); - font-weight: var(--font-weight-medium); - line-height: var(--line-height-large); - color: var(--text-strong); - } - - [data-slot="option-description"] { - font-family: var(--font-family-sans); - font-size: var(--font-size-small); - font-weight: var(--font-weight-regular); - line-height: var(--line-height-large); - color: var(--text-base); - min-width: 0; - white-space: normal; - overflow: visible; - text-overflow: clip; - } - - [data-slot="question-option"][data-custom="true"] { - [data-slot="option-description"] { - overflow: visible; - text-overflow: clip; - white-space: normal; - overflow-wrap: anywhere; - } - } - [data-slot="question-review"] { display: flex; flex-direction: column; gap: 8px; - padding: 4px 2px; + padding: 4px 10px; } [data-slot="review-title"] { diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index a2049a7a39a..62671ba25cc 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -273,6 +273,7 @@ export namespace Server { Bus, // kilocode_change SessionCreatedEvent: Session.Event.Created, // kilocode_change Identifier, // kilocode_change + ModelCache, // kilocode_change }), ) // kilocode_change end diff --git a/packages/opencode/test/kilocode/model-cache-org.test.ts b/packages/opencode/test/kilocode/model-cache-org.test.ts index 7b70e0c8dfa..bf2b1adbedf 100644 --- a/packages/opencode/test/kilocode/model-cache-org.test.ts +++ b/packages/opencode/test/kilocode/model-cache-org.test.ts @@ -121,3 +121,52 @@ test("model fetch without OAuth accountId does not set kilocodeOrganizationId", }, }) }) + +test("ModelCache.clear removes cached entry so next fetch hits the network", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://app.kilo.ai/config.json", + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + init: async () => { + await Auth.set("kilo", { + type: "oauth", + access: "token-clear-test", + refresh: "refresh-clear", + expires: Date.now() + 3600000, + accountId: "org-clear", + }) + }, + fn: async () => { + // Populate cache + captured = undefined + ModelCache.clear("kilo") + await ModelCache.fetch("kilo") + expect(captured).toBeDefined() + + // Verify cache is populated — second fetch should NOT call fetchKiloModels + captured = undefined + await ModelCache.fetch("kilo") + expect(captured).toBeUndefined() + expect(ModelCache.get("kilo")).toBeDefined() + + // Clear the cache + ModelCache.clear("kilo") + + // get() should return undefined after clear + expect(ModelCache.get("kilo")).toBeUndefined() + + // Next fetch should call fetchKiloModels again + captured = undefined + await ModelCache.fetch("kilo") + expect(captured).toBeDefined() + }, + }) +}) diff --git a/packages/ui/src/components/basic-tool.css b/packages/ui/src/components/basic-tool.css index 1240ad7b995..69790904245 100644 --- a/packages/ui/src/components/basic-tool.css +++ b/packages/ui/src/components/basic-tool.css @@ -115,6 +115,7 @@ } &.subagent-link { + cursor: pointer; color: var(--text-interactive-base); text-decoration: none; text-underline-offset: 2px; diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 84dbc83c1a8..a56362d1825 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -964,22 +964,29 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) { const render = ToolRegistry.render(part.tool) ?? GenericTool + const dismissed = createMemo( + () => + part.tool === "question" && + part.state.status === "error" && + typeof part.state.error === "string" && + part.state.error.includes("dismissed this question"), + ) + return ( + +
+ + {i18n.t("ui.tool.questions")} dismissed + +
+
+
{(error) => { const cleaned = error().replace("Error: ", "") - if (part.tool === "question" && cleaned.includes("dismissed this question")) { - return ( -
- - {i18n.t("ui.tool.questions")} dismissed - -
- ) - } const [title, ...rest] = cleaned.split(": ") return ( @@ -1016,6 +1023,7 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
+
) }