diff --git a/.changeset/adaptive-opus-five.md b/.changeset/adaptive-opus-five.md deleted file mode 100644 index 9d8cf6c1b3..0000000000 --- a/.changeset/adaptive-opus-five.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Support adaptive thinking levels for Claude Opus and Sonnet 5 and later. diff --git a/.changeset/fix-scoped-mode-cycling.md b/.changeset/fix-scoped-mode-cycling.md deleted file mode 100644 index d71009746d..0000000000 --- a/.changeset/fix-scoped-mode-cycling.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Update the visible agent mode when cycling modes in Kilo sidebars and pending session tabs. diff --git a/.changeset/fix-vscode-settings-save.md b/.changeset/fix-vscode-settings-save.md deleted file mode 100644 index c7ed178ad9..0000000000 --- a/.changeset/fix-vscode-settings-save.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Fix settings changes sometimes failing to save and apply in VS Code. diff --git a/.changeset/fresh-rc-upgrades.md b/.changeset/fresh-rc-upgrades.md new file mode 100644 index 0000000000..61a54ea4e2 --- /dev/null +++ b/.changeset/fresh-rc-upgrades.md @@ -0,0 +1,5 @@ +--- +"@kilocode/cli": patch +--- + +Keep RC installations up to date when a newer stable CLI release is published. diff --git a/.changeset/fuzzy-tildes-smile.md b/.changeset/fuzzy-tildes-smile.md deleted file mode 100644 index 39c76209f8..0000000000 --- a/.changeset/fuzzy-tildes-smile.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Preserve parenthesized tilde expressions as literal text in rendered chat messages. diff --git a/.changeset/ingest-shutdown-flush.md b/.changeset/ingest-shutdown-flush.md deleted file mode 100644 index 94ff86a5b1..0000000000 --- a/.changeset/ingest-shutdown-flush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Fix session transcripts losing their final messages when the CLI exits — pending uploads are now flushed on shutdown and as soon as a session closes. diff --git a/.changeset/jetbrains-bundled-cli.md b/.changeset/jetbrains-bundled-cli.md deleted file mode 100644 index d0239ff176..0000000000 --- a/.changeset/jetbrains-bundled-cli.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": minor ---- - -Publish a signed GitHub-hosted JetBrains plugin build with the CLI bundled for offline installation. diff --git a/.changeset/jetbrains-queued-prompts.md b/.changeset/jetbrains-queued-prompts.md deleted file mode 100644 index b22d8c8353..0000000000 --- a/.changeset/jetbrains-queued-prompts.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/kilo-jetbrains": patch ---- - -Allow sending prompts while a session is busy and show queued prompts with a remove action. diff --git a/.changeset/opencode-v1-17-5-to-v1-17-9.md b/.changeset/opencode-v1-17-5-to-v1-17-9.md deleted file mode 100644 index 5a019637c3..0000000000 --- a/.changeset/opencode-v1-17-5-to-v1-17-9.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -"@kilocode/cli": patch -"kilo-code": patch ---- - -Changes from opencode v1.17.5 to v1.17.9 upstream: - -- Core Bugfixes: Improved MCP server compatibility by declaring Kilo's supported client capabilities. -- Core Bugfixes: Plugin client requests now reuse the active server instead of assuming the default local port. -- Core Bugfixes: ACP shell tool calls now show the command and working directory from the start. -- Core Bugfixes: Plugin-provided shell environment variables now apply to PTY sessions. -- Core Bugfixes: OpenAI-compatible providers now accept MCP tool schemas that previously failed validation. (@jquense) -- Core Bugfixes: Cloudflare AI Gateway now receives the configured API key correctly. (@keefetang) -- Core Bugfixes: MCP tools without declared schema properties now work with providers that expect object properties. -- Core Bugfixes: Long-running MCP tools now keep their timeout alive when they report progress. (@Nomadcxx) -- Core Bugfixes: The MCP OAuth callback server now shuts down once authorization finishes or is cancelled. -- Core Bugfixes: MCP tool failures now surface the server's error text instead of a generic failure. -- Core Bugfixes: MCP OAuth error pages now escape provider error text correctly. -- Core Bugfixes: Honor configured agent step limits by forcing a final text response instead of failing mid-run. -- Core Bugfixes: Queue steering prompts before dismissing pending questions so the previous turn cannot resume first. -- Core Bugfixes: Prevent local server credentials from leaking into spawned PTY processes. -- Core Bugfixes: Fix Devstral model detection when provider IDs use different casing. (@Robin1987China) -- Core Bugfixes: Pass configured custom headers to Copilot model requests. -- Core Improvements: MCP servers can now receive the current workspace as a client root. -- Core Improvements: Session timelines load much faster and avoid flicker or scroll jumps. -- Core Improvements: Add `high` and `max` thinking variants for GLM-5.2 across supported providers. (@imranshaiedi-byte) -- Core Improvements: Stop wrapping follow-up user messages in a steering reminder so prompt caching stays effective. -- TUI Bugfixes: MCP debug now uses the SDK's latest protocol version. -- TUI Bugfixes: Only show the background subagent shortcut when the server supports it. -- UI Bugfixes: Render completed Mermaid blocks from diagram source instead of fenced Markdown. diff --git a/.changeset/persian-language.md b/.changeset/persian-language.md new file mode 100644 index 0000000000..9e07d9e8b8 --- /dev/null +++ b/.changeset/persian-language.md @@ -0,0 +1,5 @@ +--- +"kilo-code": minor +--- + +Add Persian (Farsi) as a UI language, including right-to-left layout. Contributed by Babak Safabahar. diff --git a/.changeset/pwsh-permission-fail-closed.md b/.changeset/pwsh-permission-fail-closed.md deleted file mode 100644 index cb140f33cd..0000000000 --- a/.changeset/pwsh-permission-fail-closed.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Fix bash permission rules being bypassed on PowerShell for commands containing a bare `--` such as `git checkout -- `. Commands the shell parser cannot parse now get checked against their raw command text instead of executing without a permission check. diff --git a/.changeset/quiet-json-events.md b/.changeset/quiet-json-events.md deleted file mode 100644 index bc7f513506..0000000000 --- a/.changeset/quiet-json-events.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Emit each agent event once from `kilo run --format json`. diff --git a/.changeset/stalled-provider-first-byte.md b/.changeset/stalled-provider-first-byte.md deleted file mode 100644 index 2d11921fd6..0000000000 --- a/.changeset/stalled-provider-first-byte.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Bound the wait for a provider's first response byte by the request timeout. A provider that accepts a request and returns headers but never sends body data now fails and retries instead of leaving the turn hanging after a tool call completes. The same `timeout` value now covers both the connection phase and the wait for the first byte as a single deadline; streaming responses that have already produced data are unaffected. diff --git a/.changeset/steady-cli-subprocess-tests.md b/.changeset/steady-cli-subprocess-tests.md deleted file mode 100644 index e95a9d4c73..0000000000 --- a/.changeset/steady-cli-subprocess-tests.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@kilocode/cli": patch ---- - -Stabilize cross-platform CLI subprocess tests under constrained CI runners diff --git a/.changeset/steady-editor-tabs.md b/.changeset/steady-editor-tabs.md deleted file mode 100644 index 84e5f2b0b5..0000000000 --- a/.changeset/steady-editor-tabs.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Open Kilo chats, settings, and files as tabs in the selected editor pane without creating, locking, or resizing editor panes. diff --git a/.changeset/tidy-mice-report.md b/.changeset/tidy-mice-report.md new file mode 100644 index 0000000000..3ba5fbb6e9 --- /dev/null +++ b/.changeset/tidy-mice-report.md @@ -0,0 +1,5 @@ +--- +"@kilocode/kilo-telemetry": patch +--- + +Include the host operating system name, version, and architecture in telemetry events. diff --git a/.changeset/tui-variant-shortcut-hint.md b/.changeset/tui-variant-shortcut-hint.md deleted file mode 100644 index 8891876bdb..0000000000 --- a/.changeset/tui-variant-shortcut-hint.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"kilo-code": patch ---- - -Show the `Ctrl+T` variant cycling shortcut in the TUI prompt hint row whenever the active model exposes reasoning variants, as the first hint before the agent and command palette hints diff --git a/.github/workflows/test-jetbrains.yml b/.github/workflows/test-jetbrains.yml index 1dde3d1727..cec4ca2dbd 100644 --- a/.github/workflows/test-jetbrains.yml +++ b/.github/workflows/test-jetbrains.yml @@ -73,6 +73,8 @@ jobs: - name: Run JetBrains unit tests run: bun script/test-ci.ts working-directory: packages/kilo-jetbrains + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Publish JetBrains unit reports if: always() diff --git a/bun.lock b/bun.lock index fd599605c4..d98ffedb22 100644 --- a/bun.lock +++ b/bun.lock @@ -32,7 +32,7 @@ }, "packages/core": { "name": "@opencode-ai/core", - "version": "7.4.16", + "version": "7.4.17", "bin": { "opencode": "./bin/opencode", }, @@ -127,7 +127,7 @@ }, "packages/effect-drizzle-sqlite": { "name": "@opencode-ai/effect-drizzle-sqlite", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "drizzle-orm": "catalog:", "effect": "catalog:", @@ -141,7 +141,7 @@ }, "packages/effect-sqlite-node": { "name": "@opencode-ai/effect-sqlite-node", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "effect": "catalog:", }, @@ -153,7 +153,7 @@ }, "packages/http-recorder": { "name": "@opencode-ai/http-recorder", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@effect/platform-node": "4.0.0-beta.74", "@effect/platform-node-shared": "4.0.0-beta.74", @@ -174,7 +174,7 @@ }, "packages/kilo-console": { "name": "@kilocode/kilo-console", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@kilocode/kilo-indexing": "workspace:*", "@kilocode/kilo-web-ui": "workspace:*", @@ -197,7 +197,7 @@ }, "packages/kilo-docs": { "name": "@kilocode/kilo-docs", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@docsearch/css": "^4", "@docsearch/js": "^4", @@ -227,7 +227,7 @@ }, "packages/kilo-gateway": { "name": "@kilocode/kilo-gateway", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@ai-sdk/alibaba": "1.0.17", "@ai-sdk/anthropic": "3.0.71", @@ -263,7 +263,7 @@ }, "packages/kilo-i18n": { "name": "@kilocode/kilo-i18n", - "version": "7.4.16", + "version": "7.4.17", "devDependencies": { "@tsconfig/node22": "catalog:", "@types/bun": "catalog:", @@ -273,7 +273,7 @@ }, "packages/kilo-indexing": { "name": "@kilocode/kilo-indexing", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@aws-sdk/client-bedrock-runtime": "3.1005.0", "@aws-sdk/credential-provider-ini": "3.972.31", @@ -305,11 +305,11 @@ }, "packages/kilo-jetbrains": { "name": "@kilocode/kilo-jetbrains", - "version": "7.4.15", + "version": "7.4.16", }, "packages/kilo-memory": { "name": "@kilocode/kilo-memory", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "effect": "catalog:", "zod": "catalog:", @@ -323,7 +323,7 @@ }, "packages/kilo-sandbox": { "name": "@kilocode/sandbox", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@anthropic-ai/sandbox-runtime": "catalog:", "effect": "catalog:", @@ -338,7 +338,7 @@ }, "packages/kilo-telemetry": { "name": "@kilocode/kilo-telemetry", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@kilocode/kilo-gateway": "workspace:*", "posthog-node": "4.4.0", @@ -352,7 +352,7 @@ }, "packages/kilo-ui": { "name": "@kilocode/kilo-ui", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "0.13.11", @@ -389,7 +389,7 @@ }, "packages/kilo-vscode": { "name": "kilo-code", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@anthropic-ai/sdk": "^0.39.0", "@kilocode/kilo-gateway": "workspace:*", @@ -458,7 +458,7 @@ }, "packages/kilo-web-ui": { "name": "@kilocode/kilo-web-ui", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@kilocode/kilo-ui": "workspace:*", "@kobalte/core": "catalog:", @@ -475,7 +475,7 @@ }, "packages/llm": { "name": "@opencode-ai/llm", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@smithy/eventstream-codec": "4.2.14", "@smithy/util-utf8": "4.2.2", @@ -493,7 +493,7 @@ }, "packages/opencode": { "name": "@kilocode/cli", - "version": "7.4.16", + "version": "7.4.17", "bin": { "kilo": "./bin/kilo", "kilocode": "./bin/kilo", @@ -660,7 +660,7 @@ }, "packages/plugin": { "name": "@kilocode/plugin", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@kilocode/sdk": "workspace:*", "effect": "catalog:", @@ -688,7 +688,7 @@ }, "packages/plugin-atomic-chat": { "name": "@kilocode/plugin-atomic-chat", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@kilocode/plugin": "workspace:*", }, @@ -702,7 +702,7 @@ }, "packages/script": { "name": "@opencode-ai/script", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "semver": "^7.6.3", }, @@ -713,7 +713,7 @@ }, "packages/sdk/js": { "name": "@kilocode/sdk", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "cross-spawn": "catalog:", }, @@ -728,7 +728,7 @@ }, "packages/server": { "name": "@opencode-ai/server", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@opencode-ai/core": "workspace:*", "drizzle-orm": "catalog:", @@ -742,7 +742,7 @@ }, "packages/storybook": { "name": "@opencode-ai/storybook", - "version": "7.4.16", + "version": "7.4.17", "devDependencies": { "@opencode-ai/ui": "workspace:*", "@solidjs/meta": "catalog:", @@ -765,7 +765,7 @@ }, "packages/tui": { "name": "@opencode-ai/tui", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@kilocode/plugin": "workspace:*", "@kilocode/sdk": "workspace:*", @@ -792,7 +792,7 @@ }, "packages/ui": { "name": "@opencode-ai/ui", - "version": "7.4.16", + "version": "7.4.17", "dependencies": { "@kilocode/sdk": "workspace:*", "@kobalte/core": "catalog:", @@ -845,22 +845,22 @@ }, }, "trustedDependencies": [ - "web-tree-sitter", "esbuild", - "tree-sitter-bash", "protobufjs", + "web-tree-sitter", + "tree-sitter-bash", ], "patchedDependencies": { - "virtua@0.49.1": "patches/virtua@0.49.1.patch", - "mammoth@1.12.0": "patches/mammoth@1.12.0.patch", - "@ff-labs/fff-bun@0.9.4": "patches/@ff-labs%2Ffff-bun@0.9.4.patch", - "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", - "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch", - "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", - "pacote@21.5.1": "patches/pacote@21.5.1.patch", - "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", - "@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch", "@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch", + "@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch", + "@ff-labs/fff-bun@0.9.4": "patches/@ff-labs%2Ffff-bun@0.9.4.patch", + "pacote@21.5.1": "patches/pacote@21.5.1.patch", + "@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch", + "@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch", + "@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch", + "virtua@0.49.1": "patches/virtua@0.49.1.patch", + "@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch", + "mammoth@1.12.0": "patches/mammoth@1.12.0.patch", }, "overrides": { "@effect/platform-node-shared": "4.0.0-beta.74", diff --git a/flake.nix b/flake.nix index 7383da725d..280d60035c 100644 --- a/flake.nix +++ b/flake.nix @@ -21,59 +21,7 @@ devShells = forEachSystem (pkgs: { default = let - # Pin bun to the version declared in package.json (packageManager: "bun@1.3.14"). - # The locked nixpkgs revision ships 1.3.11, so we fetch the official release directly. - bun = - let - sources = { - "aarch64-linux" = { - name = "bun-linux-aarch64"; - hash = "sha256-on/7Y6gxA3WDbg1vZorhf6jY0YuIw3yCHGUzGXOhmjs="; - }; - "x86_64-linux" = { - name = "bun-linux-x64"; - hash = "sha256-lR7iruhV8IWVruxiJSJqKY0/6oOj3NZGXAnLzN9+hI8="; - }; - "aarch64-darwin" = { - name = "bun-darwin-aarch64"; - hash = "sha256-2LliIYKK1vl6x6wKt+lYcjQa92MAHogD6CZ2UsJlJiA="; - }; - "x86_64-darwin" = { - name = "bun-darwin-x64"; - hash = "sha256-QYPfM3RiPlurMVxUfPoJdFM81FfYa3O2OfeoeXTNZjM="; - }; - }; - source = - sources.${pkgs.stdenv.hostPlatform.system} - or (throw "Unsupported system for bun: ${pkgs.stdenv.hostPlatform.system}"); - in - pkgs.stdenv.mkDerivation rec { - pname = "bun"; - version = "1.3.14"; - src = pkgs.fetchurl { - url = "https://github.com/oven-sh/bun/releases/download/bun-v${version}/${source.name}.zip"; - inherit (source) hash; - }; - nativeBuildInputs = [ - pkgs.unzip - ] ++ pkgs.lib.optional pkgs.stdenv.isLinux pkgs.autoPatchelfHook; - buildInputs = pkgs.lib.optionals pkgs.stdenv.isLinux [ pkgs.stdenv.cc.cc.lib ]; - dontConfigure = true; - dontBuild = true; - installPhase = '' - runHook preInstall - install -Dm755 bun $out/bin/bun - ln -s $out/bin/bun $out/bin/bunx - runHook postInstall - ''; - meta = { - description = "Fast all-in-one JavaScript runtime"; - homepage = "https://bun.sh"; - license = pkgs.lib.licenses.mit; - mainProgram = "bun"; - platforms = builtins.attrNames sources; - }; - }; + bun = pkgs.callPackage ./nix/bun.nix { }; kilo-dev = pkgs.writeShellScriptBin "kilo-dev" '' set -euo pipefail @@ -279,11 +227,12 @@ packages = forEachSystem ( pkgs: let + bun = pkgs.callPackage ./nix/bun.nix { }; node_modules = pkgs.callPackage ./nix/node_modules.nix { - inherit rev; + inherit bun rev; }; kilo = pkgs.callPackage ./nix/kilo.nix { - inherit node_modules; + inherit bun node_modules; }; in { diff --git a/nix/bun.nix b/nix/bun.nix new file mode 100644 index 0000000000..a880895349 --- /dev/null +++ b/nix/bun.nix @@ -0,0 +1,60 @@ +{ + lib, + stdenv, + fetchurl, + unzip, + autoPatchelfHook, +}: +let + package = lib.pipe ../package.json [ + builtins.readFile + builtins.fromJSON + ]; + version = lib.removePrefix "bun@" package.packageManager; + sources = { + "aarch64-linux" = { + name = "bun-linux-aarch64"; + hash = "sha256-on/7Y6gxA3WDbg1vZorhf6jY0YuIw3yCHGUzGXOhmjs="; + }; + "x86_64-linux" = { + name = "bun-linux-x64"; + hash = "sha256-lR7iruhV8IWVruxiJSJqKY0/6oOj3NZGXAnLzN9+hI8="; + }; + "aarch64-darwin" = { + name = "bun-darwin-aarch64"; + hash = "sha256-2LliIYKK1vl6x6wKt+lYcjQa92MAHogD6CZ2UsJlJiA="; + }; + "x86_64-darwin" = { + name = "bun-darwin-x64"; + hash = "sha256-QYPfM3RiPlurMVxUfPoJdFM81FfYa3O2OfeoeXTNZjM="; + }; + }; + source = + sources.${stdenv.hostPlatform.system} + or (throw "Unsupported system for bun: ${stdenv.hostPlatform.system}"); +in +stdenv.mkDerivation { + pname = "bun"; + inherit version; + src = fetchurl { + url = "https://github.com/oven-sh/bun/releases/download/bun-v${version}/${source.name}.zip"; + inherit (source) hash; + }; + nativeBuildInputs = [ unzip ] ++ lib.optional stdenv.isLinux autoPatchelfHook; + buildInputs = lib.optionals stdenv.isLinux [ stdenv.cc.cc.lib ]; + dontConfigure = true; + dontBuild = true; + installPhase = '' + runHook preInstall + install -Dm755 bun $out/bin/bun + ln -s $out/bin/bun $out/bin/bunx + runHook postInstall + ''; + meta = { + description = "Fast all-in-one JavaScript runtime"; + homepage = "https://bun.sh"; + license = lib.licenses.mit; + mainProgram = "bun"; + platforms = builtins.attrNames sources; + }; +} diff --git a/package.json b/package.json index 2cbfccad00..d50ed093f2 100644 --- a/package.json +++ b/package.json @@ -171,6 +171,6 @@ "pacote@21.5.1": "patches/pacote@21.5.1.patch", "mammoth@1.12.0": "patches/mammoth@1.12.0.patch" }, - "version": "7.4.16", + "version": "7.4.17", "peerDependencies": {} } diff --git a/packages/core/package.json b/packages/core/package.json index ddb4d33d35..e64a66062d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.4.16", + "version": "7.4.17", "name": "@opencode-ai/core", "type": "module", "license": "MIT", diff --git a/packages/effect-drizzle-sqlite/package.json b/packages/effect-drizzle-sqlite/package.json index 920418a989..bdc5267013 100644 --- a/packages/effect-drizzle-sqlite/package.json +++ b/packages/effect-drizzle-sqlite/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.4.16", + "version": "7.4.17", "name": "@opencode-ai/effect-drizzle-sqlite", "type": "module", "license": "MIT", diff --git a/packages/effect-sqlite-node/package.json b/packages/effect-sqlite-node/package.json index 66a2274823..c5863bfad0 100644 --- a/packages/effect-sqlite-node/package.json +++ b/packages/effect-sqlite-node/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.4.16", + "version": "7.4.17", "name": "@opencode-ai/effect-sqlite-node", "type": "module", "license": "MIT", diff --git a/packages/extensions/zed/extension.toml b/packages/extensions/zed/extension.toml index d31c75a3d8..a912fd5940 100644 --- a/packages/extensions/zed/extension.toml +++ b/packages/extensions/zed/extension.toml @@ -1,7 +1,7 @@ id = "kilo" name = "Kilo" description = "The open source coding agent." -version = "7.4.16" +version = "7.4.17" schema_version = 1 authors = ["Anomaly"] repository = "https://github.com/Kilo-Org/kilocode" @@ -11,26 +11,26 @@ name = "Kilo" icon = "./icons/opencode.svg" [agent_servers.opencode.targets.darwin-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.16/opencode-darwin-arm64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.17/opencode-darwin-arm64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.darwin-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.16/opencode-darwin-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.17/opencode-darwin-x64.zip" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-aarch64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.16/opencode-linux-arm64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.17/opencode-linux-arm64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.linux-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.16/opencode-linux-x64.tar.gz" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.17/opencode-linux-x64.tar.gz" cmd = "./opencode" args = ["acp"] [agent_servers.opencode.targets.windows-x86_64] -archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.16/opencode-windows-x64.zip" +archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.17/opencode-windows-x64.zip" cmd = "./opencode.exe" args = ["acp"] diff --git a/packages/http-recorder/package.json b/packages/http-recorder/package.json index fec3e374d0..46de980c7a 100644 --- a/packages/http-recorder/package.json +++ b/packages/http-recorder/package.json @@ -1,6 +1,6 @@ { "$schema": "https://json.schemastore.org/package.json", - "version": "7.4.16", + "version": "7.4.17", "name": "@opencode-ai/http-recorder", "description": "Record and replay Effect HTTP client traffic with deterministic cassettes", "type": "module", diff --git a/packages/kilo-console/package.json b/packages/kilo-console/package.json index 82ed878f47..53ac288500 100755 --- a/packages/kilo-console/package.json +++ b/packages/kilo-console/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-console", - "version": "7.4.16", + "version": "7.4.17", "private": true, "type": "module", "scripts": { diff --git a/packages/kilo-docs/package.json b/packages/kilo-docs/package.json index cff09854e6..742763dd4d 100644 --- a/packages/kilo-docs/package.json +++ b/packages/kilo-docs/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-docs", - "version": "7.4.16", + "version": "7.4.17", "private": true, "scripts": { "dev": "next dev --webpack --port 3002", diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/side-terminal-panel-empty-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/side-terminal-panel-empty-chromium-linux.png new file mode 100644 index 0000000000..7515cef4a3 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/agentmanager/side-terminal-panel-empty-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31c246e1baf581b431033ee2b2ce1c5080cca02d034b42d4dbce20f9acb5f33f +size 9538 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/prompt-rail-many-prompts-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/prompt-rail-many-prompts-chromium-linux.png new file mode 100644 index 0000000000..df4e9c7068 --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/prompt-rail-many-prompts-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:51adb9e31ce0bc82981b0f20f895ce4ede3f92828546115f929942ef93bf0812 +size 11204 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/prompt-rail-sidebar-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/prompt-rail-sidebar-chromium-linux.png new file mode 100644 index 0000000000..94024986cd --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/prompt-rail-sidebar-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5658ed9e5266311c4239b7cd470d2f772d1cc0b3fd6c6b00337d4f3141a3a4d +size 11950 diff --git a/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/prompt-rail-wide-chromium-linux.png b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/prompt-rail-wide-chromium-linux.png new file mode 100644 index 0000000000..07e3e945ef --- /dev/null +++ b/packages/kilo-docs/public/img/screenshot-tests/kilo-vscode/visual-regression/chat/prompt-rail-wide-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:095ed97dc46498be6fd75483b24357ac0740b6f2c1544f6c103a1dac360e1a7e +size 11202 diff --git a/packages/kilo-docs/source-links.md b/packages/kilo-docs/source-links.md index e9b3199d5e..a786a86d27 100644 --- a/packages/kilo-docs/source-links.md +++ b/packages/kilo-docs/source-links.md @@ -155,8 +155,6 @@ - -- - - - diff --git a/packages/kilo-gateway/package.json b/packages/kilo-gateway/package.json index 37461419a9..15fac962bb 100644 --- a/packages/kilo-gateway/package.json +++ b/packages/kilo-gateway/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-gateway", - "version": "7.4.16", + "version": "7.4.17", "type": "module", "license": "MIT", "description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration", diff --git a/packages/kilo-i18n/package.json b/packages/kilo-i18n/package.json index f690fe2f5f..e50df8969b 100644 --- a/packages/kilo-i18n/package.json +++ b/packages/kilo-i18n/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-i18n", - "version": "7.4.16", + "version": "7.4.17", "type": "module", "license": "MIT", "description": "Kilo-specific i18n translations and overrides", diff --git a/packages/kilo-i18n/src/ar.ts b/packages/kilo-i18n/src/ar.ts index 13170b89c6..b538f126a6 100644 --- a/packages/kilo-i18n/src/ar.ts +++ b/packages/kilo-i18n/src/ar.ts @@ -1,12 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "يمنحك Kilo Gateway الوصول إلى مجموعة مختارة من النماذج الموثوقة والمحسّنة لوكلاء البرمجة.", - "provider.connect.kiloGateway.line2": - "بمفتاح API واحد، ستحصل على إمكانية الوصول إلى نماذج مثل Claude وGPT وGemini وGLM والمزيد.", - "provider.connect.kiloGateway.visit.prefix": "قم بزيارة ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " للحصول على مفتاح API الخاص بك.", "provider.connect.kiloGateway.byok.prefix": "للحصول على المزيد من إحصائيات الاستخدام، استخدم ", "provider.connect.kiloGateway.byok.link": "BYOK عبر Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": ".", @@ -24,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "وصول موحد إلى نماذج الذكاء الاصطناعي مع توجيه ذكي", // Reasoning block label - "ui.permission.run": "تشغيل", "ui.reasoning.label": "الاستدلال", // Marketplace - "marketplace.tab.skills": "المهارات", - "marketplace.tab.mcpServers": "خوادم MCP", - "marketplace.category.all": "الكل", - "marketplace.placeholder": "سيتم تنفيذه لاحقاً", "marketplace.card.installed": "مثبت", "marketplace.card.install": "تثبيت", "marketplace.card.remove": "إزالة", @@ -44,11 +32,9 @@ export const dict = { "marketplace.install.scope.global": "عالمي", "marketplace.install.scope.project.description": "هذا المشروع فقط. يمكن إضافة الملفات المثبتة إلى نظام التحكم في الإصدارات ومشاركتها مع فريقك.", - "marketplace.install.scope.global.description": - "جميع المشاريع على هذا الجهاز. تُحفظ في إعدادات المستخدم الخاصة بك.", + "marketplace.install.scope.global.description": "جميع المشاريع على هذا الجهاز. تُحفظ في إعدادات المستخدم الخاصة بك.", "marketplace.install.destination": "وجهة التثبيت", - "marketplace.install.about.mcp": - "يمنح خادم MCP منصة Kilo أدوات إضافية للعمل مع الخدمات الخارجية أو البرامج المحلية.", + "marketplace.install.about.mcp": "يمنح خادم MCP منصة Kilo أدوات إضافية للعمل مع الخدمات الخارجية أو البرامج المحلية.", "marketplace.install.about.agent": "يضيف الوكيل دورًا قابلاً لإعادة الاستخدام بتعليماته وأذوناته الخاصة.", "marketplace.install.about.skill": "تضيف المهارة تعليمات وموارد خاصة بمهام معينة يمكن لـ Kilo تحميلها عند الحاجة.", "marketplace.install.mcp.warning": @@ -72,8 +58,6 @@ export const dict = { "هل أنت متأكد أنك تريد إزالة هذا الـ {{type}}؟ سيؤدي هذا إلى إزالته من تكوين {{scope}} الخاص بك.", "marketplace.remove.cancel": "إلغاء", "marketplace.remove.confirm.button": "إزالة", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "وكلاء", "marketplace.search": "بحث...", "marketplace.filter.all": "جميع العناصر", "marketplace.filter.notInstalled": "غير مثبت", @@ -81,12 +65,10 @@ export const dict = { "marketplace.empty": "لم يتم العثور على عناصر", "marketplace.empty.relevant": "لم يتم العثور على عناصر سوق ذات صلة بمساحة العمل هذه.", "marketplace.badge.mcpServer": "خادم MCP", - "marketplace.badge.mode": "وضع", "marketplace.card.by": "بواسطة {{author}}", "marketplace.install.method": "طريقة التثبيت", "marketplace.install.parameters": "المعلمات", "marketplace.install.optional": "(اختياري)", - "marketplace.install.required": "{{name}} مطلوب", "marketplace.scope.project": "مشروع", "marketplace.scope.global": "عام", "marketplace.remove.type.mcp": "خادم MCP", @@ -128,8 +110,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "فتح في عارض الفروقات", - "ui.messagePart.shell.command": "الأمر", - "ui.messagePart.shell.output": "المخرجات", "ui.messagePart.openInEditor": "فتح في المحرر", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/br.ts b/packages/kilo-i18n/src/br.ts index 3e91b3d765..3117506790 100644 --- a/packages/kilo-i18n/src/br.ts +++ b/packages/kilo-i18n/src/br.ts @@ -1,12 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "O Kilo Gateway oferece acesso a um conjunto selecionado de modelos confiáveis e otimizados para agentes de codificação.", - "provider.connect.kiloGateway.line2": - "Com uma única chave de API, você terá acesso a modelos como Claude, GPT, Gemini, GLM e mais.", - "provider.connect.kiloGateway.visit.prefix": "Visite ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " para obter sua chave de API.", "provider.connect.kiloGateway.byok.prefix": "Para mais estatísticas de uso, utilize ", "provider.connect.kiloGateway.byok.link": "BYOK via Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": ".", @@ -24,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "Acesso unificado a modelos de IA com roteamento inteligente", // Reasoning block label - "ui.permission.run": "Executar", "ui.reasoning.label": "Raciocínio", // Marketplace - "marketplace.tab.skills": "Skills", - "marketplace.tab.mcpServers": "Servidores MCP", - "marketplace.category.all": "Todos", - "marketplace.placeholder": "A ser implementado", "marketplace.card.installed": "Instalado", "marketplace.card.install": "Instalar", "marketplace.card.remove": "Remover", @@ -59,7 +47,8 @@ export const dict = { "marketplace.install.learnMore": "Saiba como funcionam as instalações do Marketplace", "marketplace.install.learnMcp": "Saiba mais sobre MCP", "marketplace.install.installedAt": "Instalado em {{path}}", - "marketplace.intro": "Instale agentes, habilidades e ferramentas MCP reutilizáveis em um projeto ou em todos os projetos.", + "marketplace.intro": + "Instale agentes, habilidades e ferramentas MCP reutilizáveis em um projeto ou em todos os projetos.", "marketplace.intro.learnMore": "Sobre o Marketplace", "marketplace.install.prerequisites": "Pré-requisitos", "marketplace.install.installing": "Instalando...", @@ -73,8 +62,6 @@ export const dict = { "Tem certeza que deseja remover este {{type}}? Isso o removerá da sua configuração {{scope}}.", "marketplace.remove.cancel": "Cancelar", "marketplace.remove.confirm.button": "Remover", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "Agentes", "marketplace.search": "Pesquisar...", "marketplace.filter.all": "Todos os Itens", "marketplace.filter.notInstalled": "Não Instalado", @@ -82,12 +69,10 @@ export const dict = { "marketplace.empty": "Nenhum item encontrado", "marketplace.empty.relevant": "Nenhum item relevante do marketplace foi encontrado para este espaço de trabalho.", "marketplace.badge.mcpServer": "Servidor MCP", - "marketplace.badge.mode": "Modo", "marketplace.card.by": "por {{author}}", "marketplace.install.method": "Método de Instalação", "marketplace.install.parameters": "Parâmetros", "marketplace.install.optional": "(opcional)", - "marketplace.install.required": "{{name}} é obrigatório", "marketplace.scope.project": "projeto", "marketplace.scope.global": "global", "marketplace.remove.type.mcp": "servidor MCP", @@ -129,8 +114,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "Abrir no Visualizador de Diferenças", - "ui.messagePart.shell.command": "Comando", - "ui.messagePart.shell.output": "Saída", "ui.messagePart.openInEditor": "Abrir no Editor", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/bs.ts b/packages/kilo-i18n/src/bs.ts index d1aece561e..49cbbea5ef 100644 --- a/packages/kilo-i18n/src/bs.ts +++ b/packages/kilo-i18n/src/bs.ts @@ -1,12 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway omogućava pristup pažljivo odabranom skupu pouzdanih modela optimizovanih za rad sa agentima za kodiranje.", - "provider.connect.kiloGateway.line2": - "Sa jednim API ključem dobijate pristup modelima kao što su Claude, GPT, Gemini, GLM i drugi.", - "provider.connect.kiloGateway.visit.prefix": "Posjetite ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " da preuzmete svoj API ključ.", "provider.connect.kiloGateway.byok.prefix": "Za više statistika korištenja, koristite ", "provider.connect.kiloGateway.byok.link": "BYOK putem Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": ".", @@ -29,14 +22,9 @@ export const dict = { "desktop.cli.installed.message": "CLI je instaliran u {{path}}\n\nRestartuj terminal da bi koristio komandu 'kilo'.", // Reasoning block label - "ui.permission.run": "Pokreni", "ui.reasoning.label": "Rezonovanje", // Marketplace - "marketplace.tab.skills": "Vještine", - "marketplace.tab.mcpServers": "MCP Serveri", - "marketplace.category.all": "Sve", - "marketplace.placeholder": "Biće implementirano", "marketplace.card.installed": "Instalirano", "marketplace.card.install": "Instaliraj", "marketplace.card.remove": "Ukloni", @@ -78,8 +66,6 @@ export const dict = { "Jeste li sigurni da želite ukloniti ovaj {{type}}? Ovo će ga ukloniti iz vaše {{scope}} konfiguracije.", "marketplace.remove.cancel": "Otkaži", "marketplace.remove.confirm.button": "Ukloni", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "Agenti", "marketplace.search": "Pretraži...", "marketplace.filter.all": "Sve stavke", "marketplace.filter.notInstalled": "Nije instalirano", @@ -87,12 +73,10 @@ export const dict = { "marketplace.empty": "Nema pronađenih stavki", "marketplace.empty.relevant": "Nisu pronađene relevantne stavke marketplacea za ovaj radni prostor.", "marketplace.badge.mcpServer": "MCP Server", - "marketplace.badge.mode": "Režim", "marketplace.card.by": "od {{author}}", "marketplace.install.method": "Metoda instalacije", "marketplace.install.parameters": "Parametri", "marketplace.install.optional": "(opciono)", - "marketplace.install.required": "{{name}} je obavezno", "marketplace.scope.project": "projekat", "marketplace.scope.global": "globalno", "marketplace.remove.type.mcp": "MCP server", @@ -134,8 +118,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "Otvori u pregledniku razlika", - "ui.messagePart.shell.command": "Naredba", - "ui.messagePart.shell.output": "Izlaz", "ui.messagePart.openInEditor": "Otvori u editoru", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/da.ts b/packages/kilo-i18n/src/da.ts index 3147ec1eee..960a5b4158 100644 --- a/packages/kilo-i18n/src/da.ts +++ b/packages/kilo-i18n/src/da.ts @@ -1,12 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway giver dig adgang til et udvalgt sæt pålidelige, optimerede modeller til kodningsagenter.", - "provider.connect.kiloGateway.line2": - "Med en enkelt API-nøgle får du adgang til modeller som Claude, GPT, Gemini, GLM og flere.", - "provider.connect.kiloGateway.visit.prefix": "Besøg ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " for at hente din API-nøgle.", "provider.connect.kiloGateway.byok.prefix": "For flere brugsstatistikker, brug ", "provider.connect.kiloGateway.byok.link": "BYOK via Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": ".", @@ -24,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "Samlet adgang til AI-modeller med smart routing", // Reasoning block label - "ui.permission.run": "Kør", "ui.reasoning.label": "Ræsonnement", // Marketplace - "marketplace.tab.skills": "Skills", - "marketplace.tab.mcpServers": "MCP-servere", - "marketplace.category.all": "Alle", - "marketplace.placeholder": "Skal implementeres", "marketplace.card.installed": "Installeret", "marketplace.card.install": "Installer", "marketplace.card.remove": "Fjern", @@ -44,8 +32,7 @@ export const dict = { "marketplace.install.scope.global": "Global", "marketplace.install.scope.project.description": "Kun dette projekt. De installerede filer kan føjes til versionsstyring og deles med dit team.", - "marketplace.install.scope.global.description": - "Alle projekter på denne maskine. Gemmes i din brugerkonfiguration.", + "marketplace.install.scope.global.description": "Alle projekter på denne maskine. Gemmes i din brugerkonfiguration.", "marketplace.install.destination": "Installationsplacering", "marketplace.install.about.mcp": "En MCP-server giver Kilo yderligere værktøjer til at arbejde med eksterne tjenester eller lokale programmer.", @@ -73,8 +60,6 @@ export const dict = { "Er du sikker på, at du vil fjerne denne {{type}}? Dette vil fjerne den fra din {{scope}} konfiguration.", "marketplace.remove.cancel": "Annuller", "marketplace.remove.confirm.button": "Fjern", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "Agenter", "marketplace.search": "Søg...", "marketplace.filter.all": "Alle elementer", "marketplace.filter.notInstalled": "Ikke installeret", @@ -82,12 +67,10 @@ export const dict = { "marketplace.empty": "Ingen elementer fundet", "marketplace.empty.relevant": "Der blev ikke fundet relevante marketplace-elementer for dette arbejdsområde.", "marketplace.badge.mcpServer": "MCP-server", - "marketplace.badge.mode": "Tilstand", "marketplace.card.by": "af {{author}}", "marketplace.install.method": "Installationsmetode", "marketplace.install.parameters": "Parametre", "marketplace.install.optional": "(valgfrit)", - "marketplace.install.required": "{{name}} er påkrævet", "marketplace.scope.project": "projekt", "marketplace.scope.global": "global", "marketplace.remove.type.mcp": "MCP-server", @@ -129,8 +112,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "Åbn i Diff-visning", - "ui.messagePart.shell.command": "Kommando", - "ui.messagePart.shell.output": "Output", "ui.messagePart.openInEditor": "Åbn i editor", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/de.ts b/packages/kilo-i18n/src/de.ts index 8e4fe468a5..d939be4748 100644 --- a/packages/kilo-i18n/src/de.ts +++ b/packages/kilo-i18n/src/de.ts @@ -1,12 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway bietet Ihnen Zugang zu einer kuratierten Auswahl zuverlässiger, optimierter Modelle für Coding-Agenten.", - "provider.connect.kiloGateway.line2": - "Mit einem einzigen API-Schlüssel erhalten Sie Zugang zu Modellen wie Claude, GPT, Gemini, GLM und mehr.", - "provider.connect.kiloGateway.visit.prefix": "Besuchen Sie ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": ", um Ihren API-Schlüssel zu erhalten.", "provider.connect.kiloGateway.byok.prefix": "Für weitere Nutzungsstatistiken ", "provider.connect.kiloGateway.byok.link": "BYOK via Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": " nutzen.", @@ -24,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "Einheitlicher Zugriff auf KI-Modelle mit intelligentem Routing", // Reasoning block label - "ui.permission.run": "Ausführen", "ui.reasoning.label": "Denken", // Marketplace - "marketplace.tab.skills": "Skills", - "marketplace.tab.mcpServers": "MCP-Server", - "marketplace.category.all": "Alle", - "marketplace.placeholder": "Noch nicht implementiert", "marketplace.card.installed": "Installiert", "marketplace.card.install": "Installieren", "marketplace.card.remove": "Entfernen", @@ -75,8 +63,6 @@ export const dict = { "Sind Sie sicher, dass Sie diese {{type}} entfernen möchten? Dadurch wird sie aus Ihrer {{scope}}-Konfiguration entfernt.", "marketplace.remove.cancel": "Abbrechen", "marketplace.remove.confirm.button": "Entfernen", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "Agenten", "marketplace.search": "Suchen...", "marketplace.filter.all": "Alle Elemente", "marketplace.filter.notInstalled": "Nicht installiert", @@ -84,12 +70,10 @@ export const dict = { "marketplace.empty": "Keine Elemente gefunden", "marketplace.empty.relevant": "Keine relevanten Marketplace-Elemente für diesen Arbeitsbereich gefunden.", "marketplace.badge.mcpServer": "MCP-Server", - "marketplace.badge.mode": "Modus", "marketplace.card.by": "von {{author}}", "marketplace.install.method": "Installationsmethode", "marketplace.install.parameters": "Parameter", "marketplace.install.optional": "(optional)", - "marketplace.install.required": "{{name}} ist erforderlich", "marketplace.scope.project": "Projekt", "marketplace.scope.global": "Global", "marketplace.remove.type.mcp": "MCP-Server", @@ -131,8 +115,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "Im Diff-Viewer öffnen", - "ui.messagePart.shell.command": "Befehl", - "ui.messagePart.shell.output": "Ausgabe", "ui.messagePart.openInEditor": "Im Editor öffnen", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/en.ts b/packages/kilo-i18n/src/en.ts index 9eb679e3a0..6a36289765 100644 --- a/packages/kilo-i18n/src/en.ts +++ b/packages/kilo-i18n/src/en.ts @@ -2,13 +2,6 @@ // Keys here will override any matching keys from upstream translations export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway gives you access to a curated set of reliable optimized models for coding agents.", - "provider.connect.kiloGateway.line2": - "With a single API key you'll get access to models such as Claude, GPT, Gemini, GLM and more.", - "provider.connect.kiloGateway.visit.prefix": "Visit ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " to collect your API key.", "provider.connect.kiloGateway.byok.prefix": "For more usage stats, ", "provider.connect.kiloGateway.byok.link": "BYOK via Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": ".", @@ -26,14 +19,9 @@ export const dict = { "settings.providers.note.vercel": "Unified access to AI models with smart routing", // Reasoning block label - "ui.permission.run": "Run", "ui.reasoning.label": "Reasoning", // Marketplace - "marketplace.tab.skills": "Skills", - "marketplace.tab.mcpServers": "MCP Servers", - "marketplace.category.all": "All", - "marketplace.placeholder": "To be implemented", "marketplace.card.installed": "Installed", "marketplace.card.install": "Install", "marketplace.card.remove": "Remove", @@ -51,7 +39,8 @@ export const dict = { "marketplace.install.about.mcp": "An MCP server gives Kilo additional tools for working with external services or local programs.", "marketplace.install.about.agent": "An agent adds a reusable role with its own instructions and permissions.", - "marketplace.install.about.skill": "A skill adds task-specific instructions and resources that Kilo can load when needed.", + "marketplace.install.about.skill": + "A skill adds task-specific instructions and resources that Kilo can load when needed.", "marketplace.install.mcp.warning": "MCP servers can run local commands or connect to external services. Kilo will ask for permission before using their tools unless your permissions allow them automatically.", "marketplace.install.project.warning": @@ -73,8 +62,6 @@ export const dict = { "Are you sure you want to remove this {{type}}? This will remove it from your {{scope}} configuration.", "marketplace.remove.cancel": "Cancel", "marketplace.remove.confirm.button": "Remove", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "Agents", "marketplace.search": "Search...", "marketplace.filter.all": "All Items", "marketplace.filter.notInstalled": "Not Installed", @@ -82,12 +69,10 @@ export const dict = { "marketplace.empty": "No items found", "marketplace.empty.relevant": "No relevant marketplace items found for this workspace.", "marketplace.badge.mcpServer": "MCP Server", - "marketplace.badge.mode": "Mode", "marketplace.card.by": "by {{author}}", "marketplace.install.method": "Installation Method", "marketplace.install.parameters": "Parameters", "marketplace.install.optional": "(optional)", - "marketplace.install.required": "{{name}} is required", "marketplace.scope.project": "project", "marketplace.scope.global": "global", "marketplace.remove.type.mcp": "MCP server", @@ -134,8 +119,6 @@ export const dict = { // Edit-tool header: hover-revealed action opening the diff in a full tab. "ui.messagePart.openInDiffViewer": "Open in Diff Viewer", // Shell-tool section labels and actions. - "ui.messagePart.shell.command": "Command", - "ui.messagePart.shell.output": "Output", "ui.messagePart.openInEditor": "Open in Editor", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/es.ts b/packages/kilo-i18n/src/es.ts index a72ba48094..0e71b78c76 100644 --- a/packages/kilo-i18n/src/es.ts +++ b/packages/kilo-i18n/src/es.ts @@ -1,12 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway te da acceso a un conjunto seleccionado de modelos optimizados y confiables para agentes de codificación.", - "provider.connect.kiloGateway.line2": - "Con una sola clave API tendrás acceso a modelos como Claude, GPT, Gemini, GLM y más.", - "provider.connect.kiloGateway.visit.prefix": "Visita ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " para obtener tu clave API.", "provider.connect.kiloGateway.byok.prefix": "Para más estadísticas de uso, utiliza ", "provider.connect.kiloGateway.byok.link": "BYOK a través de Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": ".", @@ -24,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "Acceso unificado a modelos de IA con enrutamiento inteligente", // Reasoning block label - "ui.permission.run": "Ejecutar", "ui.reasoning.label": "Razonamiento", // Marketplace - "marketplace.tab.skills": "Skills", - "marketplace.tab.mcpServers": "Servidores MCP", - "marketplace.category.all": "Todos", - "marketplace.placeholder": "Por implementar", "marketplace.card.installed": "Instalado", "marketplace.card.install": "Instalar", "marketplace.card.remove": "Eliminar", @@ -49,8 +37,7 @@ export const dict = { "marketplace.install.destination": "Destino de la instalación", "marketplace.install.about.mcp": "Un servidor MCP proporciona a Kilo herramientas adicionales para trabajar con servicios externos o programas locales.", - "marketplace.install.about.agent": - "Un agente añade un rol reutilizable con sus propias instrucciones y permisos.", + "marketplace.install.about.agent": "Un agente añade un rol reutilizable con sus propias instrucciones y permisos.", "marketplace.install.about.skill": "Una habilidad añade instrucciones y recursos específicos para tareas que Kilo puede cargar cuando sea necesario.", "marketplace.install.mcp.warning": @@ -74,8 +61,6 @@ export const dict = { "¿Estás seguro de que deseas eliminar este {{type}}? Esto lo eliminará de tu configuración {{scope}}.", "marketplace.remove.cancel": "Cancelar", "marketplace.remove.confirm.button": "Eliminar", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "Agentes", "marketplace.search": "Buscar...", "marketplace.filter.all": "Todos los elementos", "marketplace.filter.notInstalled": "No instalado", @@ -83,12 +68,10 @@ export const dict = { "marketplace.empty": "No se encontraron elementos", "marketplace.empty.relevant": "No se encontraron elementos relevantes del marketplace para este espacio de trabajo.", "marketplace.badge.mcpServer": "Servidor MCP", - "marketplace.badge.mode": "Modo", "marketplace.card.by": "por {{author}}", "marketplace.install.method": "Método de instalación", "marketplace.install.parameters": "Parámetros", "marketplace.install.optional": "(opcional)", - "marketplace.install.required": "{{name}} es obligatorio", "marketplace.scope.project": "proyecto", "marketplace.scope.global": "global", "marketplace.remove.type.mcp": "servidor MCP", @@ -130,8 +113,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "Abrir en el visor de diferencias", - "ui.messagePart.shell.command": "Comando", - "ui.messagePart.shell.output": "Salida", "ui.messagePart.openInEditor": "Abrir en el editor", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/fr.ts b/packages/kilo-i18n/src/fr.ts index 6f65477082..ae5635dfaa 100644 --- a/packages/kilo-i18n/src/fr.ts +++ b/packages/kilo-i18n/src/fr.ts @@ -1,12 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway vous donne accès à une sélection de modèles fiables et optimisés pour les agents de codage.", - "provider.connect.kiloGateway.line2": - "Avec une seule clé API, vous aurez accès à des modèles tels que Claude, GPT, Gemini, GLM et plus encore.", - "provider.connect.kiloGateway.visit.prefix": "Visitez ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " pour obtenir votre clé API.", "provider.connect.kiloGateway.byok.prefix": "Pour plus de statistiques d'utilisation, utilisez ", "provider.connect.kiloGateway.byok.link": "BYOK via Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": ".", @@ -24,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "Accès unifié aux modèles IA avec routage intelligent", // Reasoning block label - "ui.permission.run": "Exécuter", "ui.reasoning.label": "Raisonnement", // Marketplace - "marketplace.tab.skills": "Skills", - "marketplace.tab.mcpServers": "Serveurs MCP", - "marketplace.category.all": "Tout", - "marketplace.placeholder": "À implémenter", "marketplace.card.installed": "Installé", "marketplace.card.install": "Installer", "marketplace.card.remove": "Supprimer", @@ -75,8 +63,6 @@ export const dict = { "Êtes-vous sûr de vouloir supprimer ce {{type}} ? Cela le supprimera de votre configuration {{scope}}.", "marketplace.remove.cancel": "Annuler", "marketplace.remove.confirm.button": "Supprimer", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "Agents", "marketplace.search": "Rechercher...", "marketplace.filter.all": "Tous les éléments", "marketplace.filter.notInstalled": "Non installé", @@ -84,12 +70,10 @@ export const dict = { "marketplace.empty": "Aucun élément trouvé", "marketplace.empty.relevant": "Aucun élément pertinent du marketplace trouvé pour cet espace de travail.", "marketplace.badge.mcpServer": "Serveur MCP", - "marketplace.badge.mode": "Mode", "marketplace.card.by": "par {{author}}", "marketplace.install.method": "Méthode d'installation", "marketplace.install.parameters": "Paramètres", "marketplace.install.optional": "(facultatif)", - "marketplace.install.required": "{{name}} est requis", "marketplace.scope.project": "projet", "marketplace.scope.global": "global", "marketplace.remove.type.mcp": "serveur MCP", @@ -131,8 +115,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "Ouvrir dans le visualiseur de différences", - "ui.messagePart.shell.command": "Commande", - "ui.messagePart.shell.output": "Sortie", "ui.messagePart.openInEditor": "Ouvrir dans l'éditeur", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/it.ts b/packages/kilo-i18n/src/it.ts index 229be83c14..d1445f057f 100644 --- a/packages/kilo-i18n/src/it.ts +++ b/packages/kilo-i18n/src/it.ts @@ -2,13 +2,6 @@ // Keys here will override any matching keys from upstream translations export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway ti offre una selezione curata di modelli affidabili e ottimizzati per agenti di coding.", - "provider.connect.kiloGateway.line2": - "Con una sola API key puoi accedere a modelli come Claude, GPT, Gemini, GLM e altri.", - "provider.connect.kiloGateway.visit.prefix": "Visita ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " per ottenere la tua API key.", "provider.connect.kiloGateway.byok.prefix": "Per ulteriori statistiche sull'utilizzo, utilizza ", "provider.connect.kiloGateway.byok.link": "BYOK tramite Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": ".", @@ -26,15 +19,9 @@ export const dict = { "settings.providers.note.vercel": "Accesso unificato ai modelli IA con routing intelligente", // Reasoning block label - "ui.permission.run": "Esegui", "ui.reasoning.label": "Ragionamento", // Marketplace - "marketplace.tab.skills": "Skill", - "marketplace.tab.mcpServers": "Server MCP", - "marketplace.tab.agents": "Agenti", - "marketplace.category.all": "Tutti", - "marketplace.placeholder": "Da implementare", "marketplace.card.installed": "Installato", "marketplace.card.install": "Installa", "marketplace.card.remove": "Rimuovi", @@ -76,7 +63,6 @@ export const dict = { "marketplace.remove.confirm": "Vuoi davvero rimuovere questo {{type}}? Verrà rimosso dalla configurazione {{scope}}.", "marketplace.remove.cancel": "Annulla", "marketplace.remove.confirm.button": "Rimuovi", - "marketplace.tab.mcp": "MCP", "marketplace.search": "Cerca...", "marketplace.filter.all": "Tutti gli elementi", "marketplace.filter.notInstalled": "Non installati", @@ -84,12 +70,10 @@ export const dict = { "marketplace.empty": "Nessun elemento trovato", "marketplace.empty.relevant": "Nessun elemento rilevante del marketplace trovato per questo spazio di lavoro.", "marketplace.badge.mcpServer": "Server MCP", - "marketplace.badge.mode": "Modalità", "marketplace.card.by": "di {{author}}", "marketplace.install.method": "Metodo di installazione", "marketplace.install.parameters": "Parametri", "marketplace.install.optional": "(opzionale)", - "marketplace.install.required": "{{name}} è obbligatorio", "marketplace.scope.project": "progetto", "marketplace.scope.global": "globale", "marketplace.remove.type.mcp": "server MCP", @@ -129,8 +113,6 @@ export const dict = { "Disattiva gli snapshot di Kilo per questo progetto. Perderai annulla/ripeti sulle modifiche ai file fatte da Kilo, ma git continuerà a tracciare tutto.", "ui.messagePart.openInDiffViewer": "Apri nel visualizzatore diff", - "ui.messagePart.shell.command": "Comando", - "ui.messagePart.shell.output": "Output", "ui.messagePart.openInEditor": "Apri nell'editor", "ui.message.feedback.helpful": "È stato utile", diff --git a/packages/kilo-i18n/src/ja.ts b/packages/kilo-i18n/src/ja.ts index c1699834b6..ed30565559 100644 --- a/packages/kilo-i18n/src/ja.ts +++ b/packages/kilo-i18n/src/ja.ts @@ -1,11 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gatewayは、コーディングエージェント向けに厳選された信頼性の高い最適化モデルへのアクセスを提供します。", - "provider.connect.kiloGateway.line2": "1つのAPIキーで、Claude、GPT、Gemini、GLMなどのモデルにアクセスできます。", - "provider.connect.kiloGateway.visit.prefix": "", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " にアクセスしてAPIキーを取得してください。", "provider.connect.kiloGateway.byok.prefix": "詳細な使用統計については、", "provider.connect.kiloGateway.byok.link": "Kilo's Gateway経由でBYOK", "provider.connect.kiloGateway.byok.suffix": "をご利用ください。", @@ -23,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "スマートルーティングによるAIモデルへの統合アクセス", // Reasoning block label - "ui.permission.run": "実行", "ui.reasoning.label": "推論", // Marketplace - "marketplace.tab.skills": "スキル", - "marketplace.tab.mcpServers": "MCPサーバー", - "marketplace.category.all": "すべて", - "marketplace.placeholder": "未実装", "marketplace.card.installed": "インストール済み", "marketplace.card.install": "インストール", "marketplace.card.remove": "削除", @@ -43,14 +32,12 @@ export const dict = { "marketplace.install.scope.global": "グローバル", "marketplace.install.scope.project.description": "このプロジェクトのみ。インストールしたファイルはバージョン管理に追加し、チームと共有できます。", - "marketplace.install.scope.global.description": - "このマシン上のすべてのプロジェクト。ユーザー設定に保存されます。", + "marketplace.install.scope.global.description": "このマシン上のすべてのプロジェクト。ユーザー設定に保存されます。", "marketplace.install.destination": "インストール先", "marketplace.install.about.mcp": "MCPサーバーは、外部サービスやローカルプログラムを操作するための追加ツールをKiloに提供します。", "marketplace.install.about.agent": "エージェントは、独自の指示と権限を持つ再利用可能な役割を追加します。", - "marketplace.install.about.skill": - "スキルは、必要に応じてKiloが読み込めるタスク固有の指示とリソースを追加します。", + "marketplace.install.about.skill": "スキルは、必要に応じてKiloが読み込めるタスク固有の指示とリソースを追加します。", "marketplace.install.mcp.warning": "MCPサーバーはローカルコマンドを実行したり、外部サービスに接続したりできます。権限で自動的に許可されていない限り、Kiloはツールを使用する前に許可を求めます。", "marketplace.install.project.warning": @@ -72,8 +59,6 @@ export const dict = { "marketplace.remove.confirm": "この{{type}}を削除してもよろしいですか? これにより、{{scope}}設定から削除されます。", "marketplace.remove.cancel": "キャンセル", "marketplace.remove.confirm.button": "削除", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "エージェント", "marketplace.search": "検索...", "marketplace.filter.all": "すべてのアイテム", "marketplace.filter.notInstalled": "未インストール", @@ -81,12 +66,10 @@ export const dict = { "marketplace.empty": "アイテムが見つかりません", "marketplace.empty.relevant": "このワークスペースに関連するマーケットプレイスのアイテムが見つかりませんでした。", "marketplace.badge.mcpServer": "MCPサーバー", - "marketplace.badge.mode": "モード", "marketplace.card.by": "作成者: {{author}}", "marketplace.install.method": "インストール方法", "marketplace.install.parameters": "パラメーター", "marketplace.install.optional": "(任意)", - "marketplace.install.required": "{{name}} は必須です", "marketplace.scope.project": "プロジェクト", "marketplace.scope.global": "グローバル", "marketplace.remove.type.mcp": "MCPサーバー", @@ -128,8 +111,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "差分ビューアーで開く", - "ui.messagePart.shell.command": "コマンド", - "ui.messagePart.shell.output": "出力", "ui.messagePart.openInEditor": "エディタで開く", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/ko.ts b/packages/kilo-i18n/src/ko.ts index a0e760dd54..cc7fa9c8f8 100644 --- a/packages/kilo-i18n/src/ko.ts +++ b/packages/kilo-i18n/src/ko.ts @@ -1,11 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway는 코딩 에이전트를 위한 신뢰할 수 있는 최적화된 모델 세트를 제공합니다.", - "provider.connect.kiloGateway.line2": "하나의 API 키로 Claude, GPT, Gemini, GLM 등의 모델을 사용할 수 있습니다.", - "provider.connect.kiloGateway.visit.prefix": "", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": "를 방문하여 API 키를 받으세요.", "provider.connect.kiloGateway.byok.prefix": "더 많은 사용 통계를 보려면 ", "provider.connect.kiloGateway.byok.link": "Kilo's Gateway를 통해 BYOK", "provider.connect.kiloGateway.byok.suffix": "를 사용하세요.", @@ -23,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "스마트 라우팅으로 AI 모델에 통합 액세스", // Reasoning block label - "ui.permission.run": "실행", "ui.reasoning.label": "추론", // Marketplace - "marketplace.tab.skills": "스킬", - "marketplace.tab.mcpServers": "MCP 서버", - "marketplace.category.all": "전체", - "marketplace.placeholder": "구현 예정", "marketplace.card.installed": "설치됨", "marketplace.card.install": "설치", "marketplace.card.remove": "제거", @@ -43,14 +32,12 @@ export const dict = { "marketplace.install.scope.global": "글로벌", "marketplace.install.scope.project.description": "이 프로젝트에만 적용됩니다. 설치된 파일을 버전 관리에 추가하고 팀과 공유할 수 있습니다.", - "marketplace.install.scope.global.description": - "이 컴퓨터의 모든 프로젝트에 적용됩니다. 사용자 구성에 저장됩니다.", + "marketplace.install.scope.global.description": "이 컴퓨터의 모든 프로젝트에 적용됩니다. 사용자 구성에 저장됩니다.", "marketplace.install.destination": "설치 위치", "marketplace.install.about.mcp": "MCP 서버는 외부 서비스나 로컬 프로그램과 작업할 수 있는 추가 도구를 Kilo에 제공합니다.", "marketplace.install.about.agent": "에이전트는 자체 지침과 권한을 가진 재사용 가능한 역할을 추가합니다.", - "marketplace.install.about.skill": - "스킬은 필요할 때 Kilo가 불러올 수 있는 작업별 지침과 리소스를 추가합니다.", + "marketplace.install.about.skill": "스킬은 필요할 때 Kilo가 불러올 수 있는 작업별 지침과 리소스를 추가합니다.", "marketplace.install.mcp.warning": "MCP 서버는 로컬 명령을 실행하거나 외부 서비스에 연결할 수 있습니다. 권한 설정에서 자동으로 허용하지 않는 한 Kilo는 도구를 사용하기 전에 권한을 요청합니다.", "marketplace.install.project.warning": @@ -58,8 +45,7 @@ export const dict = { "marketplace.install.learnMore": "Marketplace 설치 방식 알아보기", "marketplace.install.learnMcp": "MCP 자세히 알아보기", "marketplace.install.installedAt": "{{path}}에 설치됨", - "marketplace.intro": - "재사용 가능한 에이전트, 스킬 및 MCP 도구를 하나의 프로젝트 또는 모든 프로젝트에 설치하세요.", + "marketplace.intro": "재사용 가능한 에이전트, 스킬 및 MCP 도구를 하나의 프로젝트 또는 모든 프로젝트에 설치하세요.", "marketplace.intro.learnMore": "Marketplace 정보", "marketplace.install.prerequisites": "사전 요구 사항", "marketplace.install.installing": "설치 중...", @@ -72,8 +58,6 @@ export const dict = { "marketplace.remove.confirm": "이 {{type}}을(를) 제거하시겠습니까? {{scope}} 구성에서 제거됩니다.", "marketplace.remove.cancel": "취소", "marketplace.remove.confirm.button": "제거", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "에이전트", "marketplace.search": "검색...", "marketplace.filter.all": "모든 항목", "marketplace.filter.notInstalled": "설치되지 않음", @@ -81,12 +65,10 @@ export const dict = { "marketplace.empty": "항목을 찾을 수 없음", "marketplace.empty.relevant": "이 작업 영역과 관련된 마켓플레이스 항목을 찾을 수 없습니다.", "marketplace.badge.mcpServer": "MCP 서버", - "marketplace.badge.mode": "모드", "marketplace.card.by": "제작: {{author}}", "marketplace.install.method": "설치 방법", "marketplace.install.parameters": "매개변수", "marketplace.install.optional": "(선택 사항)", - "marketplace.install.required": "{{name}}이(가) 필요합니다", "marketplace.scope.project": "프로젝트", "marketplace.scope.global": "글로벌", "marketplace.remove.type.mcp": "MCP 서버", @@ -128,8 +110,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "차이점 뷰어에서 열기", - "ui.messagePart.shell.command": "명령어", - "ui.messagePart.shell.output": "출력", "ui.messagePart.openInEditor": "편집기에서 열기", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/nl.ts b/packages/kilo-i18n/src/nl.ts index bf4f116fd4..948254ee77 100644 --- a/packages/kilo-i18n/src/nl.ts +++ b/packages/kilo-i18n/src/nl.ts @@ -2,13 +2,6 @@ // Keys here will override any matching keys from upstream translations export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway geeft je toegang tot een gecureerde set van betrouwbare, geoptimaliseerde modellen voor coding agents.", - "provider.connect.kiloGateway.line2": - "Met één enkele API key krijg je toegang tot modellen zoals Claude, GPT, Gemini, GLM en meer.", - "provider.connect.kiloGateway.visit.prefix": "Bezoek ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " om je API key op te halen.", "provider.connect.kiloGateway.byok.prefix": "Voor meer gebruiksstatistieken, gebruik ", "provider.connect.kiloGateway.byok.link": "BYOK via Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": ".", @@ -26,14 +19,9 @@ export const dict = { "settings.providers.note.vercel": "Geïntegreerde toegang tot AI-modellen met slimme routering", // Reasoning block label - "ui.permission.run": "Uitvoeren", "ui.reasoning.label": "Redenering", // Marketplace - "marketplace.tab.skills": "Skills", - "marketplace.tab.mcpServers": "MCP Servers", - "marketplace.category.all": "Alle", - "marketplace.placeholder": "Nog te implementeren", "marketplace.card.installed": "Geïnstalleerd", "marketplace.card.install": "Installeren", "marketplace.card.remove": "Verwijderen", @@ -76,8 +64,6 @@ export const dict = { "Weet je zeker dat je deze {{type}} wilt verwijderen? Dit verwijdert het uit je {{scope}} configuratie.", "marketplace.remove.cancel": "Annuleren", "marketplace.remove.confirm.button": "Verwijderen", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "Agenten", "marketplace.search": "Zoeken...", "marketplace.filter.all": "Alle items", "marketplace.filter.notInstalled": "Niet geïnstalleerd", @@ -85,12 +71,10 @@ export const dict = { "marketplace.empty": "Geen items gevonden", "marketplace.empty.relevant": "Geen relevante marketplace-items gevonden voor deze werkruimte.", "marketplace.badge.mcpServer": "MCP Server", - "marketplace.badge.mode": "Modus", "marketplace.card.by": "door {{author}}", "marketplace.install.method": "Installatiemethode", "marketplace.install.parameters": "Parameters", "marketplace.install.optional": "(optioneel)", - "marketplace.install.required": "{{name}} is vereist", "marketplace.scope.project": "project", "marketplace.scope.global": "globaal", "marketplace.remove.type.mcp": "MCP server", @@ -132,8 +116,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "Openen in Diff-weergave", - "ui.messagePart.shell.command": "Opdracht", - "ui.messagePart.shell.output": "Uitvoer", "ui.messagePart.openInEditor": "Openen in editor", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/no.ts b/packages/kilo-i18n/src/no.ts index c28bfb16cd..cdee5f5a10 100644 --- a/packages/kilo-i18n/src/no.ts +++ b/packages/kilo-i18n/src/no.ts @@ -1,12 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway gir deg tilgang til et utvalgt sett med pålitelige, optimaliserte modeller for kodingsagenter.", - "provider.connect.kiloGateway.line2": - "Med én enkelt API-nøkkel får du tilgang til modeller som Claude, GPT, Gemini, GLM og flere.", - "provider.connect.kiloGateway.visit.prefix": "Besøk ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " for å hente API-nøkkelen din.", "provider.connect.kiloGateway.byok.prefix": "For mer bruksstatistikk, bruk ", "provider.connect.kiloGateway.byok.link": "BYOK via Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": ".", @@ -24,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "Samlet tilgang til AI-modeller med smart ruting", // Reasoning block label - "ui.permission.run": "Kjør", "ui.reasoning.label": "Resonnement", // Marketplace - "marketplace.tab.skills": "Skills", - "marketplace.tab.mcpServers": "MCP-servere", - "marketplace.category.all": "Alle", - "marketplace.placeholder": "Skal implementeres", "marketplace.card.installed": "Installert", "marketplace.card.install": "Installer", "marketplace.card.remove": "Fjern", @@ -73,8 +61,6 @@ export const dict = { "Er du sikker på at du vil fjerne denne {{type}}? Dette vil fjerne den fra din {{scope}}-konfigurasjon.", "marketplace.remove.cancel": "Avbryt", "marketplace.remove.confirm.button": "Fjern", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "Agenter", "marketplace.search": "Søk...", "marketplace.filter.all": "Alle elementer", "marketplace.filter.notInstalled": "Ikke installert", @@ -82,12 +68,10 @@ export const dict = { "marketplace.empty": "Ingen elementer funnet", "marketplace.empty.relevant": "Ingen relevante marketplace-elementer funnet for dette arbeidsområdet.", "marketplace.badge.mcpServer": "MCP-server", - "marketplace.badge.mode": "Modus", "marketplace.card.by": "av {{author}}", "marketplace.install.method": "Installasjonsmetode", "marketplace.install.parameters": "Parametere", "marketplace.install.optional": "(valgfritt)", - "marketplace.install.required": "{{name}} er påkrevd", "marketplace.scope.project": "prosjekt", "marketplace.scope.global": "global", "marketplace.remove.type.mcp": "MCP-server", @@ -129,8 +113,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "Åpne i diff-visning", - "ui.messagePart.shell.command": "Kommando", - "ui.messagePart.shell.output": "Utdata", "ui.messagePart.openInEditor": "Åpne i editor", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/pl.ts b/packages/kilo-i18n/src/pl.ts index 6764a61f26..ba4679a544 100644 --- a/packages/kilo-i18n/src/pl.ts +++ b/packages/kilo-i18n/src/pl.ts @@ -1,12 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway zapewnia dostęp do wyselekcjonowanego zestawu niezawodnych, zoptymalizowanych modeli dla agentów kodowania.", - "provider.connect.kiloGateway.line2": - "Za pomocą jednego klucza API uzyskasz dostęp do modeli takich jak Claude, GPT, Gemini, GLM i innych.", - "provider.connect.kiloGateway.visit.prefix": "Odwiedź ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": ", aby otrzymać swój klucz API.", "provider.connect.kiloGateway.byok.prefix": "Aby uzyskać więcej statystyk użycia, użyj ", "provider.connect.kiloGateway.byok.link": "BYOK przez Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": ".", @@ -24,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "Ujednolicony dostęp do modeli AI z inteligentnym routingiem", // Reasoning block label - "ui.permission.run": "Uruchom", "ui.reasoning.label": "Rozumowanie", // Marketplace - "marketplace.tab.skills": "Skills", - "marketplace.tab.mcpServers": "Serwery MCP", - "marketplace.category.all": "Wszystkie", - "marketplace.placeholder": "Do zaimplementowania", "marketplace.card.installed": "Zainstalowano", "marketplace.card.install": "Zainstaluj", "marketplace.card.remove": "Usuń", @@ -74,8 +62,6 @@ export const dict = { "Czy na pewno chcesz usunąć ten {{type}}? Spowoduje to usunięcie z konfiguracji: {{scope}}.", "marketplace.remove.cancel": "Anuluj", "marketplace.remove.confirm.button": "Usuń", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "Agenci", "marketplace.search": "Szukaj...", "marketplace.filter.all": "Wszystkie elementy", "marketplace.filter.notInstalled": "Nie zainstalowano", @@ -83,12 +69,10 @@ export const dict = { "marketplace.empty": "Nie znaleziono elementów", "marketplace.empty.relevant": "Nie znaleziono elementów marketplace istotnych dla tego obszaru roboczego.", "marketplace.badge.mcpServer": "Serwer MCP", - "marketplace.badge.mode": "Tryb", "marketplace.card.by": "autor: {{author}}", "marketplace.install.method": "Metoda instalacji", "marketplace.install.parameters": "Parametry", "marketplace.install.optional": "(opcjonalne)", - "marketplace.install.required": "{{name}} jest wymagane", "marketplace.scope.project": "projekt", "marketplace.scope.global": "globalny", "marketplace.remove.type.mcp": "serwer MCP", @@ -130,8 +114,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "Otwórz w podglądzie różnic", - "ui.messagePart.shell.command": "Polecenie", - "ui.messagePart.shell.output": "Wyjście", "ui.messagePart.openInEditor": "Otwórz w edytorze", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/ru.ts b/packages/kilo-i18n/src/ru.ts index d426d1c0f1..4875ff7100 100644 --- a/packages/kilo-i18n/src/ru.ts +++ b/packages/kilo-i18n/src/ru.ts @@ -1,12 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway предоставляет доступ к подборке надёжных оптимизированных моделей для агентов кодирования.", - "provider.connect.kiloGateway.line2": - "С одним API-ключом вы получите доступ к моделям Claude, GPT, Gemini, GLM и другим.", - "provider.connect.kiloGateway.visit.prefix": "Посетите ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": ", чтобы получить ваш API-ключ.", "provider.connect.kiloGateway.byok.prefix": "Для получения дополнительной статистики использования используйте ", "provider.connect.kiloGateway.byok.link": "BYOK через Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": ".", @@ -24,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "Единый доступ к AI-моделям с умной маршрутизацией", // Reasoning block label - "ui.permission.run": "Запустить", "ui.reasoning.label": "Рассуждение", // Marketplace - "marketplace.tab.skills": "Навыки", - "marketplace.tab.mcpServers": "MCP-серверы", - "marketplace.category.all": "Все", - "marketplace.placeholder": "Будет реализовано", "marketplace.card.installed": "Установлено", "marketplace.card.install": "Установить", "marketplace.card.remove": "Удалить", @@ -75,8 +63,6 @@ export const dict = { "Вы уверены, что хотите удалить этот {{type}}? Это удалит его из вашей конфигурации ({{scope}}).", "marketplace.remove.cancel": "Отмена", "marketplace.remove.confirm.button": "Удалить", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "Агенты", "marketplace.search": "Поиск...", "marketplace.filter.all": "Все элементы", "marketplace.filter.notInstalled": "Не установлено", @@ -84,12 +70,10 @@ export const dict = { "marketplace.empty": "Элементы не найдены", "marketplace.empty.relevant": "Для этого рабочего пространства не найдено подходящих элементов маркетплейса.", "marketplace.badge.mcpServer": "MCP-сервер", - "marketplace.badge.mode": "Режим", "marketplace.card.by": "от {{author}}", "marketplace.install.method": "Способ установки", "marketplace.install.parameters": "Параметры", "marketplace.install.optional": "(необязательно)", - "marketplace.install.required": "Требуется {{name}}", "marketplace.scope.project": "проект", "marketplace.scope.global": "глобально", "marketplace.remove.type.mcp": "MCP-сервер", @@ -131,8 +115,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "Открыть в просмотре различий", - "ui.messagePart.shell.command": "Команда", - "ui.messagePart.shell.output": "Вывод", "ui.messagePart.openInEditor": "Открыть в редакторе", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/th.ts b/packages/kilo-i18n/src/th.ts index 11a2f317c9..12455abf34 100644 --- a/packages/kilo-i18n/src/th.ts +++ b/packages/kilo-i18n/src/th.ts @@ -1,12 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway ให้คุณเข้าถึงชุดโมเดลที่คัดสรรมาอย่างดีและเชื่อถือได้สำหรับตัวแทนเขียนโค้ด", - "provider.connect.kiloGateway.line2": - "ด้วย API key เดียว คุณจะสามารถเข้าถึงโมเดลต่างๆ เช่น Claude, GPT, Gemini, GLM และอื่นๆ", - "provider.connect.kiloGateway.visit.prefix": "เยี่ยมชม ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " เพื่อรับ API key ของคุณ", "provider.connect.kiloGateway.byok.prefix": "สำหรับสถิติการใช้งานเพิ่มเติม โปรดใช้ ", "provider.connect.kiloGateway.byok.link": "BYOK ผ่าน Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": "", @@ -24,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "เข้าถึงโมเดล AI แบบรวมศูนย์พร้อมการกำหนดเส้นทางอัจฉริยะ", // Reasoning block label - "ui.permission.run": "เรียกใช้", "ui.reasoning.label": "การให้เหตุผล", // Marketplace - "marketplace.tab.skills": "ทักษะ", - "marketplace.tab.mcpServers": "เซิร์ฟเวอร์ MCP", - "marketplace.category.all": "ทั้งหมด", - "marketplace.placeholder": "ยังไม่ได้ดำเนินการ", "marketplace.card.installed": "ติดตั้งแล้ว", "marketplace.card.install": "ติดตั้ง", "marketplace.card.remove": "ลบ", @@ -44,14 +32,12 @@ export const dict = { "marketplace.install.scope.global": "โกลบอล", "marketplace.install.scope.project.description": "เฉพาะโปรเจกต์นี้ ไฟล์ที่ติดตั้งสามารถเพิ่มลงในระบบควบคุมเวอร์ชันและแชร์กับทีมของคุณได้", - "marketplace.install.scope.global.description": - "ทุกโปรเจกต์ในเครื่องนี้ จัดเก็บไว้ในการกำหนดค่าผู้ใช้ของคุณ", + "marketplace.install.scope.global.description": "ทุกโปรเจกต์ในเครื่องนี้ จัดเก็บไว้ในการกำหนดค่าผู้ใช้ของคุณ", "marketplace.install.destination": "ปลายทางการติดตั้ง", "marketplace.install.about.mcp": "เซิร์ฟเวอร์ MCP เพิ่มเครื่องมือให้ Kilo สำหรับทำงานกับบริการภายนอกหรือโปรแกรมในเครื่อง", "marketplace.install.about.agent": "เอเจนต์เพิ่มบทบาทที่นำกลับมาใช้ใหม่ได้พร้อมคำสั่งและสิทธิ์ของตนเอง", - "marketplace.install.about.skill": - "ทักษะเพิ่มคำสั่งและทรัพยากรเฉพาะงานที่ Kilo สามารถโหลดได้เมื่อจำเป็น", + "marketplace.install.about.skill": "ทักษะเพิ่มคำสั่งและทรัพยากรเฉพาะงานที่ Kilo สามารถโหลดได้เมื่อจำเป็น", "marketplace.install.mcp.warning": "เซิร์ฟเวอร์ MCP สามารถเรียกใช้คำสั่งในเครื่องหรือเชื่อมต่อกับบริการภายนอกได้ Kilo จะขออนุญาตก่อนใช้เครื่องมือ เว้นแต่สิทธิ์ของคุณจะอนุญาตโดยอัตโนมัติ", "marketplace.install.project.warning": @@ -73,8 +59,6 @@ export const dict = { "คุณแน่ใจหรือไม่ว่าต้องการลบ {{type}} นี้? สิ่งนี้จะลบออกจากการกำหนดค่า {{scope}} ของคุณ", "marketplace.remove.cancel": "ยกเลิก", "marketplace.remove.confirm.button": "ลบ", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "เอเจนต์", "marketplace.search": "ค้นหา...", "marketplace.filter.all": "รายการทั้งหมด", "marketplace.filter.notInstalled": "ยังไม่ได้ติดตั้ง", @@ -82,12 +66,10 @@ export const dict = { "marketplace.empty": "ไม่พบรายการ", "marketplace.empty.relevant": "ไม่พบรายการในมาร์เก็ตเพลสที่เกี่ยวข้องกับพื้นที่ทำงานนี้", "marketplace.badge.mcpServer": "เซิร์ฟเวอร์ MCP", - "marketplace.badge.mode": "โหมด", "marketplace.card.by": "โดย {{author}}", "marketplace.install.method": "วิธีการติดตั้ง", "marketplace.install.parameters": "พารามิเตอร์", "marketplace.install.optional": "(ไม่บังคับ)", - "marketplace.install.required": "จำเป็นต้องใช้ {{name}}", "marketplace.scope.project": "โปรเจ็กต์", "marketplace.scope.global": "โกลบอล", "marketplace.remove.type.mcp": "เซิร์ฟเวอร์ MCP", @@ -129,8 +111,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "เปิดในตัวดูความแตกต่าง", - "ui.messagePart.shell.command": "คำสั่ง", - "ui.messagePart.shell.output": "ผลลัพธ์", "ui.messagePart.openInEditor": "เปิดในตัวแก้ไข", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/tr.ts b/packages/kilo-i18n/src/tr.ts index a787e69f62..bf162698d8 100644 --- a/packages/kilo-i18n/src/tr.ts +++ b/packages/kilo-i18n/src/tr.ts @@ -1,12 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway, kodlama ajanları için optimize edilmiş güvenilir modellere erişim sağlar.", - "provider.connect.kiloGateway.line2": - "Tek bir API anahtarıyla Claude, GPT, Gemini, GLM ve daha fazlası gibi modellere erişebilirsiniz.", - "provider.connect.kiloGateway.visit.prefix": "API anahtarınızı almak için ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " adresini ziyaret edin.", "provider.connect.kiloGateway.byok.prefix": "Daha fazla kullanım istatistiği için ", "provider.connect.kiloGateway.byok.link": "Kilo's Gateway üzerinden BYOK", "provider.connect.kiloGateway.byok.suffix": " kullanın.", @@ -24,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "Akıllı yönlendirme ile AI modellerine birleşik erişim", // Reasoning block label - "ui.permission.run": "Çalıştır", "ui.reasoning.label": "Akıl Yürütme", // Marketplace - "marketplace.tab.skills": "Yetenekler", - "marketplace.tab.mcpServers": "MCP Sunucuları", - "marketplace.category.all": "Tümü", - "marketplace.placeholder": "Uygulanacak", "marketplace.card.installed": "Yüklendi", "marketplace.card.install": "Yükle", "marketplace.card.remove": "Kaldır", @@ -44,12 +32,12 @@ export const dict = { "marketplace.install.scope.global": "Genel", "marketplace.install.scope.project.description": "Yalnızca bu proje. Yüklenen dosyalar sürüm kontrolüne eklenebilir ve ekibinizle paylaşılabilir.", - "marketplace.install.scope.global.description": - "Bu makinedeki tüm projeler. Kullanıcı yapılandırmanızda saklanır.", + "marketplace.install.scope.global.description": "Bu makinedeki tüm projeler. Kullanıcı yapılandırmanızda saklanır.", "marketplace.install.destination": "Yükleme hedefi", "marketplace.install.about.mcp": "Bir MCP sunucusu, harici hizmetler veya yerel programlarla çalışmak için Kilo'ya ek araçlar sağlar.", - "marketplace.install.about.agent": "Bir ajan, kendi talimatları ve izinleri olan yeniden kullanılabilir bir rol ekler.", + "marketplace.install.about.agent": + "Bir ajan, kendi talimatları ve izinleri olan yeniden kullanılabilir bir rol ekler.", "marketplace.install.about.skill": "Bir yetenek, Kilo'nun gerektiğinde yükleyebileceği göreve özel talimatlar ve kaynaklar ekler.", "marketplace.install.mcp.warning": @@ -74,8 +62,6 @@ export const dict = { "Bu {{type}} öğesini kaldırmak istediğinizden emin misiniz? Bu, {{scope}} yapılandırmanızdan kaldırılacaktır.", "marketplace.remove.cancel": "İptal", "marketplace.remove.confirm.button": "Kaldır", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "Ajanlar", "marketplace.search": "Ara...", "marketplace.filter.all": "Tüm Öğeler", "marketplace.filter.notInstalled": "Yüklü Değil", @@ -83,12 +69,10 @@ export const dict = { "marketplace.empty": "Öğe bulunamadı", "marketplace.empty.relevant": "Bu çalışma alanıyla ilgili marketplace öğesi bulunamadı.", "marketplace.badge.mcpServer": "MCP Sunucusu", - "marketplace.badge.mode": "Mod", "marketplace.card.by": "{{author}} tarafından", "marketplace.install.method": "Yükleme Yöntemi", "marketplace.install.parameters": "Parametreler", "marketplace.install.optional": "(isteğe bağlı)", - "marketplace.install.required": "{{name}} gereklidir", "marketplace.scope.project": "proje", "marketplace.scope.global": "genel", "marketplace.remove.type.mcp": "MCP sunucusu", @@ -130,8 +114,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "Fark Görüntüleyicide Aç", - "ui.messagePart.shell.command": "Komut", - "ui.messagePart.shell.output": "Çıktı", "ui.messagePart.openInEditor": "Düzenleyicide Aç", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/uk.ts b/packages/kilo-i18n/src/uk.ts index b95a8240da..9c7dc984a5 100644 --- a/packages/kilo-i18n/src/uk.ts +++ b/packages/kilo-i18n/src/uk.ts @@ -1,12 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": - "Kilo Gateway надає доступ до добірки надійних оптимізованих моделей для агентів кодування.", - "provider.connect.kiloGateway.line2": - "За допомогою одного API-ключа ви отримаєте доступ до таких моделей, як Claude, GPT, Gemini, GLM та інших.", - "provider.connect.kiloGateway.visit.prefix": "Відвідайте ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " щоб отримати свій API-ключ.", "provider.connect.kiloGateway.byok.prefix": "Для отримання додаткової статистики використання використовуйте ", "provider.connect.kiloGateway.byok.link": "BYOK через Kilo's Gateway", "provider.connect.kiloGateway.byok.suffix": ".", @@ -24,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "Єдиний доступ до моделей ШІ з розумною маршрутизацією", // Reasoning block label - "ui.permission.run": "Виконати", "ui.reasoning.label": "Міркування", // Marketplace - "marketplace.tab.skills": "Навички", - "marketplace.tab.mcpServers": "MCP-сервери", - "marketplace.category.all": "Усі", - "marketplace.placeholder": "Буде реалізовано", "marketplace.card.installed": "Встановлено", "marketplace.card.install": "Встановити", "marketplace.card.remove": "Видалити", @@ -49,8 +37,7 @@ export const dict = { "marketplace.install.destination": "Місце встановлення", "marketplace.install.about.mcp": "MCP-сервер надає Kilo додаткові інструменти для роботи із зовнішніми сервісами або локальними програмами.", - "marketplace.install.about.agent": - "Агент додає багаторазову роль із власними інструкціями та дозволами.", + "marketplace.install.about.agent": "Агент додає багаторазову роль із власними інструкціями та дозволами.", "marketplace.install.about.skill": "Навичка додає інструкції та ресурси для певних завдань, які Kilo може завантажити за потреби.", "marketplace.install.mcp.warning": @@ -60,8 +47,7 @@ export const dict = { "marketplace.install.learnMore": "Дізнайтеся, як працює встановлення з Marketplace", "marketplace.install.learnMcp": "Докладніше про MCP", "marketplace.install.installedAt": "Встановлено в {{path}}", - "marketplace.intro": - "Встановлюйте багаторазових агентів, навички та інструменти MCP для одного або всіх проєктів.", + "marketplace.intro": "Встановлюйте багаторазових агентів, навички та інструменти MCP для одного або всіх проєктів.", "marketplace.intro.learnMore": "Про Marketplace", "marketplace.install.prerequisites": "Передумови", "marketplace.install.installing": "Встановлення...", @@ -75,8 +61,6 @@ export const dict = { "Ви впевнені, що хочете видалити цей {{type}}? Це видалить його з вашої конфігурації {{scope}}.", "marketplace.remove.cancel": "Скасувати", "marketplace.remove.confirm.button": "Видалити", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "Агенти", "marketplace.search": "Пошук...", "marketplace.filter.all": "Усі елементи", "marketplace.filter.notInstalled": "Не встановлено", @@ -84,12 +68,10 @@ export const dict = { "marketplace.empty": "Елементів не знайдено", "marketplace.empty.relevant": "Для цього робочого простору не знайдено відповідних елементів маркетплейсу.", "marketplace.badge.mcpServer": "MCP-сервер", - "marketplace.badge.mode": "Режим", "marketplace.card.by": "від {{author}}", "marketplace.install.method": "Метод встановлення", "marketplace.install.parameters": "Параметри", "marketplace.install.optional": "(необов'язково)", - "marketplace.install.required": "{{name}} є обов'язковим", "marketplace.scope.project": "проєкт", "marketplace.scope.global": "глобально", "marketplace.remove.type.mcp": "MCP-сервер", @@ -131,8 +113,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "Відкрити в переглядачі відмінностей", - "ui.messagePart.shell.command": "Команда", - "ui.messagePart.shell.output": "Вивід", "ui.messagePart.openInEditor": "Відкрити в редакторі", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/zh.ts b/packages/kilo-i18n/src/zh.ts index 8baddc98f3..ffc77d1ec0 100644 --- a/packages/kilo-i18n/src/zh.ts +++ b/packages/kilo-i18n/src/zh.ts @@ -1,10 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": "Kilo Gateway 为您提供一组精选的可靠优化模型,专为编码代理设计。", - "provider.connect.kiloGateway.line2": "只需一个 API 密钥,您就可以使用 Claude、GPT、Gemini、GLM 等模型。", - "provider.connect.kiloGateway.visit.prefix": "访问 ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " 获取您的 API 密钥。", "provider.connect.kiloGateway.byok.prefix": "如需更多使用统计信息,请", "provider.connect.kiloGateway.byok.link": "通过 Kilo's Gateway 进行 BYOK", "provider.connect.kiloGateway.byok.suffix": "。", @@ -22,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "通过智能路由统一访问 AI 模型", // Reasoning block label - "ui.permission.run": "运行", "ui.reasoning.label": "推理", // Marketplace - "marketplace.tab.skills": "技能", - "marketplace.tab.mcpServers": "MCP 服务器", - "marketplace.category.all": "全部", - "marketplace.placeholder": "待实现", "marketplace.card.installed": "已安装", "marketplace.card.install": "安装", "marketplace.card.remove": "移除", @@ -40,12 +30,10 @@ export const dict = { "marketplace.install.scope": "作用域", "marketplace.install.scope.project": "项目", "marketplace.install.scope.global": "全局", - "marketplace.install.scope.project.description": - "仅限此项目。安装的文件可以提交到版本控制并与你的团队共享。", + "marketplace.install.scope.project.description": "仅限此项目。安装的文件可以提交到版本控制并与你的团队共享。", "marketplace.install.scope.global.description": "此计算机上的所有项目。存储在你的用户配置中。", "marketplace.install.destination": "安装位置", - "marketplace.install.about.mcp": - "MCP 服务器为 Kilo 提供用于处理外部服务或本地程序的额外工具。", + "marketplace.install.about.mcp": "MCP 服务器为 Kilo 提供用于处理外部服务或本地程序的额外工具。", "marketplace.install.about.agent": "智能体会添加一个具有专属指令和权限的可复用角色。", "marketplace.install.about.skill": "技能会添加特定任务的指令和资源,Kilo 可在需要时加载它们。", "marketplace.install.mcp.warning": @@ -68,8 +56,6 @@ export const dict = { "marketplace.remove.confirm": "确定要移除此 {{type}} 吗?这会将其从你的 {{scope}} 配置中移除。", "marketplace.remove.cancel": "取消", "marketplace.remove.confirm.button": "移除", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "智能体", "marketplace.search": "搜索...", "marketplace.filter.all": "所有项目", "marketplace.filter.notInstalled": "未安装", @@ -77,12 +63,10 @@ export const dict = { "marketplace.empty": "未找到项目", "marketplace.empty.relevant": "未找到与此工作区相关的市场项目。", "marketplace.badge.mcpServer": "MCP 服务器", - "marketplace.badge.mode": "模式", "marketplace.card.by": "作者:{{author}}", "marketplace.install.method": "安装方法", "marketplace.install.parameters": "参数", "marketplace.install.optional": "(可选)", - "marketplace.install.required": "{{name}} 是必填项", "marketplace.scope.project": "项目", "marketplace.scope.global": "全局", "marketplace.remove.type.mcp": "MCP 服务器", @@ -122,8 +106,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "在差异查看器中打开", - "ui.messagePart.shell.command": "命令", - "ui.messagePart.shell.output": "输出", "ui.messagePart.openInEditor": "在编辑器中打开", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-i18n/src/zht.ts b/packages/kilo-i18n/src/zht.ts index fdb58ffcaf..3774a9a495 100644 --- a/packages/kilo-i18n/src/zht.ts +++ b/packages/kilo-i18n/src/zht.ts @@ -1,10 +1,5 @@ export const dict = { // Kilo Gateway provider translations - "provider.connect.kiloGateway.line1": "Kilo Gateway 為您提供一組精選的可靠優化模型,專為編碼代理設計。", - "provider.connect.kiloGateway.line2": "只需一個 API 金鑰,您就可以使用 Claude、GPT、Gemini、GLM 等模型。", - "provider.connect.kiloGateway.visit.prefix": "訪問 ", - "provider.connect.kiloGateway.visit.link": "kilo.ai", - "provider.connect.kiloGateway.visit.suffix": " 獲取您的 API 金鑰。", "provider.connect.kiloGateway.byok.prefix": "如需更多使用統計資訊,請", "provider.connect.kiloGateway.byok.link": "透過 Kilo's Gateway 進行 BYOK", "provider.connect.kiloGateway.byok.suffix": "。", @@ -22,14 +17,9 @@ export const dict = { "settings.providers.note.vercel": "透過智慧路由統一存取 AI 模型", // Reasoning block label - "ui.permission.run": "執行", "ui.reasoning.label": "推理", // Marketplace - "marketplace.tab.skills": "技能", - "marketplace.tab.mcpServers": "MCP 伺服器", - "marketplace.category.all": "全部", - "marketplace.placeholder": "待實作", "marketplace.card.installed": "已安裝", "marketplace.card.install": "安裝", "marketplace.card.remove": "移除", @@ -40,18 +30,15 @@ export const dict = { "marketplace.install.scope": "作用域", "marketplace.install.scope.project": "專案", "marketplace.install.scope.global": "全域", - "marketplace.install.scope.project.description": - "僅限此專案。安裝的檔案可以提交至版本控制並與你的團隊分享。", + "marketplace.install.scope.project.description": "僅限此專案。安裝的檔案可以提交至版本控制並與你的團隊分享。", "marketplace.install.scope.global.description": "此電腦上的所有專案。儲存在你的使用者設定中。", "marketplace.install.destination": "安裝位置", - "marketplace.install.about.mcp": - "MCP 伺服器為 Kilo 提供用於處理外部服務或本機程式的額外工具。", + "marketplace.install.about.mcp": "MCP 伺服器為 Kilo 提供用於處理外部服務或本機程式的額外工具。", "marketplace.install.about.agent": "智能體會新增一個具有專屬指示和權限的可重複使用角色。", "marketplace.install.about.skill": "技能會新增特定任務的指示和資源,Kilo 可在需要時載入它們。", "marketplace.install.mcp.warning": "MCP 伺服器可以執行本機命令或連線至外部服務。除非你的權限允許自動使用,否則 Kilo 會在使用其工具前請求許可。", - "marketplace.install.project.warning": - "專案檔案可能會提交至版本控制。除非設定引用了環境變數,否則請勿在此儲存密鑰。", + "marketplace.install.project.warning": "專案檔案可能會提交至版本控制。除非設定引用了環境變數,否則請勿在此儲存密鑰。", "marketplace.install.learnMore": "瞭解 Marketplace 安裝的運作方式", "marketplace.install.learnMcp": "深入瞭解 MCP", "marketplace.install.installedAt": "已安裝至 {{path}}", @@ -68,8 +55,6 @@ export const dict = { "marketplace.remove.confirm": "確定要移除此 {{type}} 嗎?這會將其從你的 {{scope}} 設定中移除。", "marketplace.remove.cancel": "取消", "marketplace.remove.confirm.button": "移除", - "marketplace.tab.mcp": "MCP", - "marketplace.tab.agents": "智能體", "marketplace.search": "搜尋...", "marketplace.filter.all": "所有項目", "marketplace.filter.notInstalled": "未安裝", @@ -77,12 +62,10 @@ export const dict = { "marketplace.empty": "找不到項目", "marketplace.empty.relevant": "找不到與此工作區相關的市集項目。", "marketplace.badge.mcpServer": "MCP 伺服器", - "marketplace.badge.mode": "模式", "marketplace.card.by": "作者:{{author}}", "marketplace.install.method": "安裝方法", "marketplace.install.parameters": "參數", "marketplace.install.optional": "(選填)", - "marketplace.install.required": "{{name}} 是必填項", "marketplace.scope.project": "專案", "marketplace.scope.global": "全域", "marketplace.remove.type.mcp": "MCP 伺服器", @@ -122,8 +105,6 @@ export const dict = { // Edit-tool header and shell-tool section labels "ui.messagePart.openInDiffViewer": "在差異檢視器中開啟", - "ui.messagePart.shell.command": "命令", - "ui.messagePart.shell.output": "輸出", "ui.messagePart.openInEditor": "在編輯器中開啟", // Message feedback (thumbs up/down per assistant response) diff --git a/packages/kilo-indexing/package.json b/packages/kilo-indexing/package.json index eac32eee09..fd56507373 100644 --- a/packages/kilo-indexing/package.json +++ b/packages/kilo-indexing/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-indexing", - "version": "7.4.16", + "version": "7.4.17", "type": "module", "license": "MIT", "description": "Standalone indexing engine and host helpers for Kilo Code", diff --git a/packages/kilo-jetbrains/CHANGELOG.md b/packages/kilo-jetbrains/CHANGELOG.md index 7c48f6085e..90eda933de 100644 --- a/packages/kilo-jetbrains/CHANGELOG.md +++ b/packages/kilo-jetbrains/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 7.5.0 + +### Minor Changes + +- [#12518](https://github.com/Kilo-Org/kilocode/pull/12518) [`452d0eb`](https://github.com/Kilo-Org/kilocode/commit/452d0eb55f740e951cfd906375e22cf97250144c) - Publish a signed GitHub-hosted JetBrains plugin build with the CLI bundled for offline installation. + +### Patch Changes + +- [#12571](https://github.com/Kilo-Org/kilocode/pull/12571) [`9950739`](https://github.com/Kilo-Org/kilocode/commit/9950739e36b40a682c0a25173e62f5236e60f81a) - Allow sending prompts while a session is busy and show queued prompts with a remove action. + ## 7.4.16 ### Patch Changes @@ -134,7 +144,9 @@ ### Changed - Update the JetBrains CLI pin from Kilo Core 7.4.15 to 7.4.16. + ## [7.0.10] - 2026-07-24 + ## [7.0.10] - 2026-07-24 ### Added diff --git a/packages/kilo-memory/package.json b/packages/kilo-memory/package.json index df2adc3325..d48bcabe87 100644 --- a/packages/kilo-memory/package.json +++ b/packages/kilo-memory/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-memory", - "version": "7.4.16", + "version": "7.4.17", "type": "module", "license": "MIT", "description": "Project memory storage, indexing, recall, and command helpers for Kilo Code", diff --git a/packages/kilo-sandbox/package.json b/packages/kilo-sandbox/package.json index ff29edd756..b57728afed 100644 --- a/packages/kilo-sandbox/package.json +++ b/packages/kilo-sandbox/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/sandbox", - "version": "7.4.16", + "version": "7.4.17", "type": "module", "license": "MIT", "private": true, diff --git a/packages/kilo-telemetry/package.json b/packages/kilo-telemetry/package.json index 7b9e81497a..36243e82c9 100644 --- a/packages/kilo-telemetry/package.json +++ b/packages/kilo-telemetry/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package.json", "name": "@kilocode/kilo-telemetry", - "version": "7.4.16", + "version": "7.4.17", "type": "module", "license": "MIT", "description": "Telemetry for Kilo CLI - PostHog analytics integration", diff --git a/packages/kilo-telemetry/src/__tests__/telemetry.test.ts b/packages/kilo-telemetry/src/__tests__/telemetry.test.ts index ff87bf6909..939bb5dd24 100644 --- a/packages/kilo-telemetry/src/__tests__/telemetry.test.ts +++ b/packages/kilo-telemetry/src/__tests__/telemetry.test.ts @@ -1,4 +1,6 @@ -import { describe, test, expect, beforeEach } from "bun:test" +import { arch, platform, release } from "node:os" +import { describe, test, expect, beforeEach, spyOn } from "bun:test" +import { Client } from "../client.js" import { Identity } from "../identity.js" import { TelemetryEvent } from "../events.js" import { Telemetry } from "../telemetry.js" @@ -83,6 +85,22 @@ describe("TelemetryEvent", () => { }) describe("Telemetry", () => { + test("includes host OS properties", () => { + const capture = spyOn(Client, "capture").mockImplementation(() => {}) + + Telemetry.track(TelemetryEvent.CLI_START) + + expect(capture).toHaveBeenCalledWith( + TelemetryEvent.CLI_START, + expect.objectContaining({ + os_name: platform(), + os_version: release(), + os_arch: arch(), + }), + ) + capture.mockRestore() + }) + test("indexing helpers are exposed", () => { expect(typeof Telemetry.trackIndexingStarted).toBe("function") expect(typeof Telemetry.trackIndexingCompleted).toBe("function") @@ -96,4 +114,3 @@ describe("Telemetry", () => { expect(typeof Telemetry.trackSuggestionAccepted).toBe("function") }) }) - diff --git a/packages/kilo-telemetry/src/telemetry.ts b/packages/kilo-telemetry/src/telemetry.ts index 3ac0d42e39..4d16051c2d 100644 --- a/packages/kilo-telemetry/src/telemetry.ts +++ b/packages/kilo-telemetry/src/telemetry.ts @@ -1,3 +1,4 @@ +import { release } from "node:os" import { Client } from "./client.js" import { Identity } from "./identity.js" import { TelemetryEvent } from "./events.js" @@ -6,6 +7,9 @@ export interface TelemetryProperties { appName: string appVersion: string platform: string + os_name: string + os_version: string + os_arch: string editorName?: string vscodeVersion?: string } @@ -58,6 +62,9 @@ export namespace Telemetry { appName: "kilo-cli", appVersion: "unknown", platform: process.platform, + os_name: process.platform, + os_version: release(), + os_arch: process.arch, } export async function init(options: { dataPath: string; version: string; enabled: boolean }): Promise { @@ -109,6 +116,9 @@ export namespace Telemetry { appName: props.appName, appVersion: props.appVersion, platform: props.platform, + os_name: props.os_name, + os_version: props.os_version, + os_arch: props.os_arch, }) // Link the anonymous machineId to the authenticated email diff --git a/packages/kilo-ui/package.json b/packages/kilo-ui/package.json index 450133e092..25e78d8ac0 100644 --- a/packages/kilo-ui/package.json +++ b/packages/kilo-ui/package.json @@ -1,6 +1,6 @@ { "name": "@kilocode/kilo-ui", - "version": "7.4.16", + "version": "7.4.17", "type": "module", "license": "MIT", "exports": { @@ -66,6 +66,7 @@ "./text-strikethrough": "./src/components/text-strikethrough.tsx", "./context": "./src/context/index.ts", "./context/code": "./src/context/code.tsx", + "./context/clipboard": "./src/context/clipboard.tsx", "./context/data": "./src/context/data.tsx", "./context/dialog": "./src/context/dialog.tsx", "./context/diff": "./src/context/diff.tsx", diff --git a/packages/kilo-ui/src/components/message-part.tsx b/packages/kilo-ui/src/components/message-part.tsx index 26f678681e..cbb8982989 100644 --- a/packages/kilo-ui/src/components/message-part.tsx +++ b/packages/kilo-ui/src/components/message-part.tsx @@ -30,6 +30,7 @@ import { import { useData } from "../context" import { useFileComponent } from "../context/file" import { useDialog } from "../context/dialog" +import { useClipboard } from "../context/clipboard" import { type UiI18n, useI18n } from "../context/i18n" import { BasicTool, useToolApprovalLine } from "./basic-tool" import { Accordion } from "./accordion" @@ -753,6 +754,7 @@ export function UserMessageDisplay(props: { const data = useData() const dialog = useDialog() const i18n = useI18n() + const clipboard = useClipboard() const [copied, setCopied] = createSignal(false) const textPart = createMemo( @@ -815,7 +817,7 @@ export function UserMessageDisplay(props: { const handleCopy = async () => { const content = props.copyText ?? text() if (!content) return - await navigator.clipboard.writeText(content) + await clipboard.write(content) setCopied(true) setTimeout(() => setCopied(false), 2000) } @@ -1365,6 +1367,7 @@ PART_MAPPING["compaction"] = function CompactionPartDisplay() { PART_MAPPING["text"] = function TextPartDisplay(props) { const data = useData() const i18n = useI18n() + const clipboard = useClipboard() const part = () => props.part as TextPart const displayText = () => (part().text ?? "").trim() @@ -1403,7 +1406,7 @@ PART_MAPPING["text"] = function TextPartDisplay(props) { const handleCopy = async () => { const content = displayText() if (!content) return - await navigator.clipboard.writeText(content) + await clipboard.write(content) setCopied(true) setTimeout(() => setCopied(false), 2000) } diff --git a/packages/kilo-ui/src/context/clipboard.tsx b/packages/kilo-ui/src/context/clipboard.tsx new file mode 100644 index 0000000000..3cc19dfb86 --- /dev/null +++ b/packages/kilo-ui/src/context/clipboard.tsx @@ -0,0 +1,17 @@ +import { createContext, type ParentComponent, useContext } from "solid-js" + +type ClipboardContextValue = { + write: (text: string) => void | Promise +} + +const ClipboardContext = createContext({ + write: (text) => navigator.clipboard.writeText(text), +}) + +export const ClipboardProvider: ParentComponent = (props) => ( + {props.children} +) + +export function useClipboard() { + return useContext(ClipboardContext) +} diff --git a/packages/kilo-vscode/CHANGELOG.md b/packages/kilo-vscode/CHANGELOG.md index d41e4a8c94..8e0e1d920c 100644 --- a/packages/kilo-vscode/CHANGELOG.md +++ b/packages/kilo-vscode/CHANGELOG.md @@ -1,5 +1,78 @@ # kilo-code +## 7.4.17 + +### Minor Changes + +- [#12631](https://github.com/Kilo-Org/kilocode/pull/12631) [`3321216`](https://github.com/Kilo-Org/kilocode/commit/3321216c0157e1a8a1829b0c8e0a1cae8d2f2ad2) - Show the ⌘1-9 (Ctrl+1-9 on Windows/Linux) shortcut badges on every Agent Manager sidebar card while the modifier key is held, making it easy to see which number jumps to which worktree before pressing it. + +- [#12598](https://github.com/Kilo-Org/kilocode/pull/12598) [`c6711fc`](https://github.com/Kilo-Org/kilocode/commit/c6711fcf6cea9fdbe78a04b95276d09a2faabfa7) - Let users open Agent Manager terminals in the VS Code terminal or an embedded side panel. The terminal button's dropdown picks the destination; the side panel shares the right-hand inspector with the diff view and keeps running in the background when hidden. + +- [#12633](https://github.com/Kilo-Org/kilocode/pull/12633) [`23039c0`](https://github.com/Kilo-Org/kilocode/commit/23039c0fb1e5b32704119ddde10a6a28ccd6bff3) - Support multiple Agent Manager side-panel terminals per context. The panel header is now a tab strip that reuses the main tab bar's terminal tabs: click to switch, drag to reorder, X to close a single terminal, and + to open another one. Terminal numbers fill gaps left by closed terminals, and tabs pick up the live title from the shell or running program (OSC escape codes), so a dev server or build names its own tab. + +- [#12632](https://github.com/Kilo-Org/kilocode/pull/12632) [`0d853df`](https://github.com/Kilo-Org/kilocode/commit/0d853df3ec338ac99e025939f74136dec6d9daa1) - Add a prompt navigator rail to the chat transcript. A thin rail of ticks on the left edge shows one tick per prompt you sent; hovering or focusing it expands a card listing those prompts with a short preview of the answer, and clicking jumps the transcript to that turn. It appears in the sidebar, Kilo editor tabs, the sub-agent viewer, and Agent Manager, and never changes the readable width of the chat. + +### Patch Changes + +- [#12629](https://github.com/Kilo-Org/kilocode/pull/12629) [`0a1c140`](https://github.com/Kilo-Org/kilocode/commit/0a1c14073a4bf14f8ad4e3c8295dc6ae6bfbfdaf) - Keep each Agent Manager panel's terminal destination consistent. A dropdown pick is now remembered per panel and no longer flips when another window rewrites the shared terminal destination setting, so the terminal shortcut keeps opening the terminal type that panel is actually using. The shortcut also no longer dead-ends on worktrees without an active session, and terminals left over from a reloaded webview are cleaned up instead of leaking. + +- [#12587](https://github.com/Kilo-Org/kilocode/pull/12587) [`16f8e7e`](https://github.com/Kilo-Org/kilocode/commit/16f8e7ef7fbd47755395539e7df54af3baae0c63) - Keep conversations and workspace files unchanged when a checkpoint cannot be fully restored. + +- [#12333](https://github.com/Kilo-Org/kilocode/pull/12333) [`290a5af`](https://github.com/Kilo-Org/kilocode/commit/290a5af56e6ddccd8b4a459883625e30f2ae0344) Thanks [@IamCoder18](https://github.com/IamCoder18)! - Include basic-auth credentials in the Local and Network Console URLs printed by `kilo console`, so users on headless hosts (no `DISPLAY`/`WAYLAND_DISPLAY`, SSH sessions, CI runners) can open the URL in a browser on another machine and reach the Console. + +- [#12630](https://github.com/Kilo-Org/kilocode/pull/12630) [`a7f972f`](https://github.com/Kilo-Org/kilocode/commit/a7f972f63bc948d70b73a36a5beab6d694316037) - Open Agent Manager terminals faster and avoid delayed shell prompts. + +- [#12560](https://github.com/Kilo-Org/kilocode/pull/12560) [`65c5e9d`](https://github.com/Kilo-Org/kilocode/commit/65c5e9d2c03cea152b140710228075edf9156def) - Update the visible agent mode when cycling modes in Kilo sidebars and pending session tabs. + +- [#12561](https://github.com/Kilo-Org/kilocode/pull/12561) [`44f5963`](https://github.com/Kilo-Org/kilocode/commit/44f596366931d5336f1cd4dfdd97ef54e0f2fa4c) - Fix settings changes sometimes failing to save and apply in VS Code. + +- [#12540](https://github.com/Kilo-Org/kilocode/pull/12540) [`2da8949`](https://github.com/Kilo-Org/kilocode/commit/2da89498138e49f857c354924fdecac85337e742) Thanks [@Githubguy132010](https://github.com/Githubguy132010)! - Preserve parenthesized tilde expressions as literal text in rendered chat messages. + +- [#12591](https://github.com/Kilo-Org/kilocode/pull/12591) [`625d2b9`](https://github.com/Kilo-Org/kilocode/commit/625d2b974de1381b4d475808c9215becb263d1f0) - Show prompt input toggle tooltips instantly on hover instead of after a delay. + +- [#12460](https://github.com/Kilo-Org/kilocode/pull/12460) [`51d8031`](https://github.com/Kilo-Org/kilocode/commit/51d8031c9997bd5478bcde715562169f732d04d4) - Changes from opencode v1.17.5 to v1.17.9 upstream: + - Core Bugfixes: Improved MCP server compatibility by declaring Kilo's supported client capabilities. + - Core Bugfixes: Plugin client requests now reuse the active server instead of assuming the default local port. + - Core Bugfixes: ACP shell tool calls now show the command and working directory from the start. + - Core Bugfixes: Plugin-provided shell environment variables now apply to PTY sessions. + - Core Bugfixes: OpenAI-compatible providers now accept MCP tool schemas that previously failed validation. (@jquense) + - Core Bugfixes: Cloudflare AI Gateway now receives the configured API key correctly. (@keefetang) + - Core Bugfixes: MCP tools without declared schema properties now work with providers that expect object properties. + - Core Bugfixes: Long-running MCP tools now keep their timeout alive when they report progress. (@Nomadcxx) + - Core Bugfixes: The MCP OAuth callback server now shuts down once authorization finishes or is cancelled. + - Core Bugfixes: MCP tool failures now surface the server's error text instead of a generic failure. + - Core Bugfixes: MCP OAuth error pages now escape provider error text correctly. + - Core Bugfixes: Honor configured agent step limits by forcing a final text response instead of failing mid-run. + - Core Bugfixes: Queue steering prompts before dismissing pending questions so the previous turn cannot resume first. + - Core Bugfixes: Prevent local server credentials from leaking into spawned PTY processes. + - Core Bugfixes: Fix Devstral model detection when provider IDs use different casing. (@Robin1987China) + - Core Bugfixes: Pass configured custom headers to Copilot model requests. + - Core Improvements: MCP servers can now receive the current workspace as a client root. + - Core Improvements: Session timelines load much faster and avoid flicker or scroll jumps. + - Core Improvements: Add `high` and `max` thinking variants for GLM-5.2 across supported providers. (@imranshaiedi-byte) + - Core Improvements: Stop wrapping follow-up user messages in a steering reminder so prompt caching stays effective. + - TUI Bugfixes: MCP debug now uses the SDK's latest protocol version. + - TUI Bugfixes: Only show the background subagent shortcut when the server supports it. + - UI Bugfixes: Render completed Mermaid blocks from diagram source instead of fenced Markdown. + +- [#11984](https://github.com/Kilo-Org/kilocode/pull/11984) [`26dac19`](https://github.com/Kilo-Org/kilocode/commit/26dac197fe28294c391c8d437abf06e18e2d22bd) Thanks [@sylwester-liljegren](https://github.com/sylwester-liljegren)! - Mention `@` file references in the chat input placeholder so users know they can add file mentions. Translated across all supported languages. + +- [#12593](https://github.com/Kilo-Org/kilocode/pull/12593) [`160b066`](https://github.com/Kilo-Org/kilocode/commit/160b06661acc5f04b21221ab6578c468325f64c5) - Prevent the VS Code backend from eagerly starting native file watchers for every Agent Manager worktree. + +- [#12123](https://github.com/Kilo-Org/kilocode/pull/12123) [`3075d35`](https://github.com/Kilo-Org/kilocode/commit/3075d35f13ba9738446ac28fa2eebf054097f2f5) Thanks [@mjnaderi](https://github.com/mjnaderi)! - Keep message and response copy buttons working after switching focus away from VS Code. + +- [#12583](https://github.com/Kilo-Org/kilocode/pull/12583) [`1310c12`](https://github.com/Kilo-Org/kilocode/commit/1310c1200ab613b316f27fe4fd59e23e262df02f) Thanks [@noobezlol](https://github.com/noobezlol)! - Keep Windows snapshot diffs parseable and preserve valid files when a stored patch is malformed. + +- [#12410](https://github.com/Kilo-Org/kilocode/pull/12410) [`85d65a3`](https://github.com/Kilo-Org/kilocode/commit/85d65a3137ecadfcbda8255bfb4a40daf1f155fb) - Open Kilo chats, settings, and files as tabs in the selected editor pane without creating, locking, or resizing editor panes. + +- [#12639](https://github.com/Kilo-Org/kilocode/pull/12639) [`8a47d8b`](https://github.com/Kilo-Org/kilocode/commit/8a47d8b78885fa8fd14c73b3aecdb57e1fc96c9c) - Stop flashing a "Turn interrupted" warning when a follow-up message is queued while the assistant is still working. The running turn now closes with a dedicated "superseded" reason instead of "interrupted" when it hands off to the queued prompt, so the premature-stop warning only appears for real interruptions. + +- [#12602](https://github.com/Kilo-Org/kilocode/pull/12602) [`5d87ca5`](https://github.com/Kilo-Org/kilocode/commit/5d87ca598c4c66328f0e476cb1be3fc9b26d05aa) - Remove unused translation keys from the Agent Manager, sidebar webview, shared kilo-i18n, and autocomplete dictionaries across all locales, and add a conservative lint test for unreferenced, unprotected dictionary keys. + +- [#12463](https://github.com/Kilo-Org/kilocode/pull/12463) [`1f3383c`](https://github.com/Kilo-Org/kilocode/commit/1f3383cf3de37327b02e0fc2a1c5ac176ca9134f) Thanks [@IamCoder18](https://github.com/IamCoder18)! - Show the `Ctrl+T` variant cycling shortcut in the TUI prompt hint row whenever the active model exposes reasoning variants, as the first hint before the agent and command palette hints + +- [#12634](https://github.com/Kilo-Org/kilocode/pull/12634) [`4c5c242`](https://github.com/Kilo-Org/kilocode/commit/4c5c2428927f26c4c818f23a650cfaf5723b7641) - Show the worktree directory name on the Agent Manager worktree hover card + ## 7.4.16 ### Minor Changes diff --git a/packages/kilo-vscode/package.json b/packages/kilo-vscode/package.json index c6b900f350..f1a77d9dc7 100644 --- a/packages/kilo-vscode/package.json +++ b/packages/kilo-vscode/package.json @@ -2,7 +2,7 @@ "name": "kilo-code", "displayName": "Kilo Code: AI Coding Agent, Copilot, and Autocomplete", "description": "Open Source AI coding agent that generates code from natural language, automates tasks, and runs terminal commands. Features inline autocomplete, browser automation, automated refactoring, and custom modes for planning, coding, and debugging. Supports 500+ AI models including Claude (Anthropic), Gemini, Grok, GPT, Codex and GLM.", - "version": "7.4.16", + "version": "7.4.17", "icon": "assets/icons/logo-outline-black.png", "galleryBanner": { "color": "#FFFFFF", @@ -868,7 +868,8 @@ "tr", "nl", "uk", - "it" + "it", + "fa" ], "enumDescriptions": [ "Auto (VS Code language)", @@ -891,7 +892,8 @@ "Türkçe", "Nederlands", "Українська", - "Italiano" + "Italiano", + "فارسی" ] }, "kilo-code.new.languageCommitMessage": { @@ -919,7 +921,8 @@ "tr", "nl", "uk", - "it" + "it", + "fa" ], "enumDescriptions": [ "跟随界面语言 (Sync with UI language)", @@ -942,7 +945,8 @@ "Türkçe", "Nederlands", "Українська", - "Italiano" + "Italiano", + "فارسی" ] }, "kilo-code.new.model.providerID": { @@ -1022,6 +1026,20 @@ "scope": "application", "description": "Allow Agent Manager to manage sessions and worktrees across multiple repositories as projects. The current workspace repository is always the default project; additional projects can be added and persist across restarts." }, + "kilo-code.new.agentManager.terminalButtonDestination": { + "type": "string", + "scope": "application", + "default": "vscode", + "enum": [ + "vscode", + "agentManager" + ], + "enumDescriptions": [ + "Open or focus the VS Code integrated terminal.", + "Open or focus an embedded terminal in the Agent Manager side panel." + ], + "description": "Default destination for the Agent Manager terminal button and Focus Terminal keyboard shortcut. The terminal button's dropdown remembers its own choice per Agent Manager panel; this setting only applies to panels that never picked one." + }, "kilo-code.new.indexing.showButtonWhenDisabled": { "type": "boolean", "default": true, diff --git a/packages/kilo-vscode/src/KiloProvider.ts b/packages/kilo-vscode/src/KiloProvider.ts index fb1be5343d..1af0e38824 100644 --- a/packages/kilo-vscode/src/KiloProvider.ts +++ b/packages/kilo-vscode/src/KiloProvider.ts @@ -162,6 +162,7 @@ import { AnacondaDesktopBridge } from "./anaconda-desktop/bridge" import { fetchOpenAIModels, FetchModelsError } from "./shared/fetch-models" import type { Agent } from "@kilocode/sdk/v2/client" import { configFeatures } from "./features" +import { fetchSnapshot } from "./kilo-provider/config-snapshot" import { createAutoApproveBridge } from "./kilo-provider/auto-approve" import type { KiloProviderOptions } from "./kilo-provider/options" import type { ProjectRef, SessionRef, WorktreeRef } from "./agent-manager/project-route" @@ -1018,6 +1019,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper post: (msg) => this.postMessage(msg), browserSettings: () => this.sendBrowserSettings(), exportTranscript: (sessionID) => this.handleExportSessionTranscript(sessionID), + copy: (text) => vscode.env.clipboard.writeText(text), openSessions: (ids) => this.trackOpenSessions(ids), }) ) { @@ -2629,26 +2631,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper } try { - const workspaceDir = this.settingsDirectory() - const [{ data: config }, { data: global }, { data: overlay }] = await Promise.all([ - retry(() => this.client!.config.get({ directory: workspaceDir }, { throwOnError: true })), - this.client.global.config.get({ throwOnError: true }), - this.client.config.overlay({ directory: workspaceDir, scope: "project" }, { throwOnError: true }), - ]) - this.cachedGlobalConfig = global ?? null - const bindings = this.bindingsFor(workspaceDir, overlay?.targets) - - const message = { - type: "configLoaded", - config, - globalConfig: overlay?.targets?.global.raw ?? global, - projectConfig: bindings.project ? overlay?.targets?.project.raw : undefined, - bindings, - settings: this.configSettings(), - features: configFeatures(config), - } - this.cachedConfigMessage = message - this.postMessage(message) + await this.refreshConfig("configLoaded") } catch (error) { console.error("[Kilo New] KiloProvider: Failed to fetch config:", error) } @@ -2740,32 +2723,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper private async fetchAndSendConfigUpdated(): Promise { if (!this.client || this.connectionState !== "connected") return try { - const dir = this.settingsDirectory() - const [{ data: config }, { data: global }, { data: overlay }] = await Promise.all([ - retry(() => this.client!.config.get({ directory: dir }, { throwOnError: true })), - this.client.global.config.get({ throwOnError: true }), - this.client.config.overlay({ directory: dir, scope: "project" }, { throwOnError: true }), - ]) - this.cachedGlobalConfig = global ?? null - const bindings = this.bindingsFor(dir, overlay?.targets) - this.cachedConfigMessage = { - type: "configLoaded", - config, - globalConfig: overlay?.targets?.global.raw ?? global, - projectConfig: bindings.project ? overlay?.targets?.project.raw : undefined, - bindings, - settings: this.configSettings(), - features: configFeatures(config), - } - this.postMessage({ - type: "configUpdated", - config, - globalConfig: overlay?.targets?.global.raw ?? global, - projectConfig: bindings.project ? overlay?.targets?.project.raw : undefined, - bindings, - settings: this.configSettings(), - features: configFeatures(config), - }) + await this.refreshConfig("configUpdated") } catch (error) { console.error("[Kilo New] KiloProvider: Failed to fetch config after update:", error) } @@ -3216,6 +3174,32 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper this.pending-- } } + private async refreshConfig(type: "configLoaded" | "configUpdated", dir = this.settingsDirectory()) { + const snapshot = await fetchSnapshot(this.client!, dir, () => this.configSettings()) + const bindings = this.bindingsFor(dir, snapshot.targets) + const globalConfig = (snapshot.targets?.global.raw ?? snapshot.globalConfig) as Config + const projectConfig = bindings.project ? (snapshot.targets?.project.raw as Config) : undefined + this.cachedGlobalConfig = globalConfig ?? null + this.cachedConfigMessage = { + type: "configLoaded", + config: snapshot.config, + globalConfig, + projectConfig, + bindings, + settings: snapshot.settings, + features: snapshot.features, + } + this.postMessage({ + type, + config: snapshot.config, + globalConfig, + projectConfig, + bindings, + settings: snapshot.settings, + features: snapshot.features, + }) + } + private postConfigFailure( error: unknown, completed: Array<"global" | "project"> = [], diff --git a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts index c4a5f93ca5..18f4343f1c 100644 --- a/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts +++ b/packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts @@ -56,6 +56,7 @@ import { pruneSubagents } from "./prune-subagents" import { startSession } from "./mcp-warmup" import { readTerminalFont, watchTerminalFont } from "./terminal-font" +import { readTerminalDestination, watchTerminalDestination } from "./terminal-destination" import { buildKeybindingMap } from "./format-keybinding" import { resolveVersionModels, buildInitialMessages, type CreatedVersion } from "./multi-version" import { ensureSandbox } from "./sandbox-bootstrap" @@ -99,6 +100,7 @@ export class AgentManagerProvider implements Disposable { private unsubProjects: (() => void) | undefined /** Scratch set returned when no active context exists; mutations are discarded. */ private readonly staleScratch = new Set() + private unsubDestination: (() => void) | undefined private closing: Promise | undefined private onVisibilityChange: ((visible: boolean) => void) | undefined // Tracks sessions owned by this panel until they are explicitly closed. @@ -122,6 +124,7 @@ export class AgentManagerProvider implements Disposable { ) this.terminalRouter = new TerminalRouter({ getClient: () => this.connectionService.getClient(), + getClientAsync: () => this.connectionService.getClientAsync(this.getRoot()), getServerConfig: () => this.connectionService.getServerConfig() ?? undefined, getRoot: () => this.getRoot(), getWorktreePath: (id) => this.getStateManager()?.getWorktree(id)?.path, @@ -132,6 +135,9 @@ export class AgentManagerProvider implements Disposable { this.unsubFont = watchTerminalFont((font) => { this.postToWebview({ type: "agentManager.terminal.fontChanged", font }) }) + this.unsubDestination = watchTerminalDestination((destination) => { + this.postToWebview({ type: "agentManager.terminal.destinationChanged", destination }) + }) this.run = new RunController({ root: () => this.getRoot(), state: () => this.getStateManager(), @@ -377,6 +383,7 @@ export class AgentManagerProvider implements Disposable { this.activeSessionId = undefined this.visiblePresence.clear() this.panel = undefined + void this.terminalRouter.dispose() this.onVisibilityChange?.(false) } ctx.sessions.dispose() @@ -662,6 +669,10 @@ export class AgentManagerProvider implements Disposable { this.terminalManager.showLocalTerminal() return null } + if (m.type === "agentManager.showWorktreeTerminal") { + this.terminalManager.showWorktreeTerminal(m.worktreeId, this.state) + return null + } if (m.type === "agentManager.openWorktree") { this.openWorktreeDirectory(m.worktreeId) return null @@ -740,10 +751,6 @@ export class AgentManagerProvider implements Disposable { void this.importer.branches() return null } - if (m.type === "agentManager.requestExternalWorktrees") { - void this.importer.external() - return null - } if (m.type === "agentManager.importFromBranch") { void this.importer.branch(m.branch) return null @@ -752,14 +759,6 @@ export class AgentManagerProvider implements Disposable { void this.importer.pr(m.url) return null } - if (m.type === "agentManager.importExternalWorktree") { - void this.importer.path(m.path, m.branch) - return null - } - if (m.type === "agentManager.importAllExternalWorktrees") { - void this.importer.all() - return null - } } private onDiffMessage(m: AgentManagerInMessage): Record | null | undefined { @@ -805,6 +804,13 @@ export class AgentManagerProvider implements Disposable { } private onRequestState(): void { + // requestState fires from the webview's onMount, and a freshly mounted + // webview has no terminal records — any PTYs the router still tracks + // belong to a previous webview instance (reload or crash) and are + // unreachable orphans. Kill them here rather than leaking shells until + // the panel itself is disposed. In-flight creates from the dying + // instance are reaped by the router's generation guard. + void this.terminalRouter.dispose() this.log( `onRequestState: stateReady=${this.stateReady ? "pending" : "missing"}, state=${this.state ? "ok" : "missing"}`, ) @@ -950,7 +956,7 @@ export class AgentManagerProvider implements Disposable { } } - /** Send worktreeSetup.ready + sessionMeta + pushState after worktree creation. */ + /** Send worktreeSetup.ready + pushState after worktree creation. */ private notifyWorktreeReady(sessionId: string, result: CreateWorktreeResult, worktreeId?: string): void { this.pushState() this.postToWebview({ @@ -961,14 +967,6 @@ export class AgentManagerProvider implements Disposable { branch: result.branch, worktreeId, }) - this.postToWebview({ - type: "agentManager.sessionMeta", - sessionId, - mode: "worktree", - branch: result.branch, - path: result.path, - parentBranch: result.parentBranch, - }) } private async waitForStateReady(context: string): Promise { @@ -985,6 +983,7 @@ export class AgentManagerProvider implements Disposable { } private shouldWaitForState(m: AgentManagerInMessage): boolean { + if (m.type === "agentManager.terminal.create") return m.worktreeId !== null return STATE_GATED.has(m.type) } @@ -1493,6 +1492,7 @@ export class AgentManagerProvider implements Disposable { sidebarCollapsed: state.getSidebarCollapsed(), reviewDiffStyle: state.getReviewDiffStyle(), reviewMarkdownRender: getDiffMarkdownRender(), + terminalDestination: readTerminalDestination(), isGitRepo: true, defaultBaseBranch: state.getDefaultBaseBranch(), activeTarget: state.getActiveTarget(), @@ -1518,6 +1518,7 @@ export class AgentManagerProvider implements Disposable { staleWorktreeIds: [], reviewDiffStyle: "unified", reviewMarkdownRender: getDiffMarkdownRender(), + terminalDestination: readTerminalDestination(), isGitRepo: false, runStatuses: [], runScriptConfigured: false, @@ -1867,6 +1868,7 @@ export class AgentManagerProvider implements Disposable { this.unsubSessions?.() this.unsubFont?.() this.unsubProjects?.() + this.unsubDestination?.() this.orchestration.dispose() this.visiblePresence.clear() this.diffs.stop() diff --git a/packages/kilo-vscode/src/agent-manager/SessionTerminalManager.ts b/packages/kilo-vscode/src/agent-manager/SessionTerminalManager.ts index efc8b54f42..3fc41c1170 100644 --- a/packages/kilo-vscode/src/agent-manager/SessionTerminalManager.ts +++ b/packages/kilo-vscode/src/agent-manager/SessionTerminalManager.ts @@ -34,6 +34,8 @@ export interface Disposable { */ export class SessionTerminalManager { private static readonly LOCAL_KEY = "__local__" + private static readonly SESSION_PREFIX = "session:" + private static readonly WORKTREE_PREFIX = "worktree:" private terminals = new Map() private disposables: Disposable[] = [] @@ -47,10 +49,10 @@ export class SessionTerminalManager { ) { this.disposables.push( host.onTerminalClosed((terminal) => { - for (const [sessionId, entry] of this.terminals) { + for (const [key, entry] of this.terminals) { if (entry.terminal !== terminal) continue - this.terminals.delete(sessionId) - this.log(`Removed terminal mapping for session ${sessionId} (terminal closed)`) + this.terminals.delete(key) + this.log(`Removed terminal mapping for ${key} (terminal closed)`) break } this.updateContextKey() @@ -86,7 +88,8 @@ export class SessionTerminalManager { */ showTerminal(sessionId: string, state: WorktreeStateManager | undefined): void { // If terminal already exists, just focus it - if (this.showExisting(sessionId, false)) return + const key = SessionTerminalManager.sessionKey(sessionId) + if (this.showExistingKey(key, false)) return const repoPath = this.host.repoPath() const worktreePath = state?.directoryFor(sessionId) @@ -102,7 +105,7 @@ export class SessionTerminalManager { const worktree = session?.worktreeId ? state?.getWorktree(session.worktreeId) : undefined const name = worktree ? `Agent: ${worktree.branch}` : "Agent: local" - this.showOrCreate(sessionId, cwd, name) + this.showOrCreate(key, cwd, name) } /** @@ -110,7 +113,7 @@ export class SessionTerminalManager { * Used when the user triggers a terminal in local mode without an active session. */ showLocalTerminal(): void { - if (this.showExisting(SessionTerminalManager.LOCAL_KEY, false)) return + if (this.showExistingKey(SessionTerminalManager.LOCAL_KEY, false)) return const cwd = this.host.repoPath() if (!cwd) { @@ -122,11 +125,32 @@ export class SessionTerminalManager { this.showOrCreate(SessionTerminalManager.LOCAL_KEY, cwd, "Agent: local") } + /** + * Show (or create) a terminal rooted at a worktree directory. Used when + * the worktree has no session to key the terminal off (e.g. all of its + * sessions were closed) so the shortcut never dead-ends on a sessionless + * worktree. + */ + showWorktreeTerminal(worktreeId: string, state: WorktreeStateManager | undefined): void { + const key = SessionTerminalManager.worktreeKey(worktreeId) + if (this.showExistingKey(key, false)) return + + const worktree = state?.getWorktree(worktreeId) + const cwd = worktree?.path ?? this.host.repoPath() + if (!cwd) { + this.log(`showWorktreeTerminal: no cwd resolved for worktree ${worktreeId}`) + this.host.showWarning("Open a folder that contains a git repository to use worktrees") + return + } + + this.showOrCreate(key, cwd, worktree ? `Agent: ${worktree.branch}` : "Agent: worktree") + } + /** * Show the existing local terminal if one was previously created (used on context switch). */ showExistingLocal(): boolean { - return this.showExisting(SessionTerminalManager.LOCAL_KEY) + return this.showExistingKey(SessionTerminalManager.LOCAL_KEY) } /** @@ -159,46 +183,38 @@ export class SessionTerminalManager { * Pass preserveFocus=true to keep focus on the current editor (default for session switching). */ showExisting(sessionId: string, preserveFocus = true): boolean { - const entry = this.terminals.get(sessionId) + return this.showExistingKey(SessionTerminalManager.sessionKey(sessionId), preserveFocus) + } + + private showExistingKey(key: string, preserveFocus = true): boolean { + const entry = this.terminals.get(key) if (!entry) return false if (entry.terminal.exitStatus !== undefined) { - this.terminals.delete(sessionId) - this.log(`showExisting: terminal exited for session ${sessionId}, clearing`) + this.terminals.delete(key) + this.log(`showExisting: terminal exited for ${key}, clearing`) return false } entry.terminal.show(preserveFocus) this.panelOpen = true - this.log(`showExisting: revealed terminal for session ${sessionId}`) + this.log(`showExisting: revealed terminal for ${key}`) return true } - /** - * Check if a session has an active terminal. - */ - hasTerminal(sessionId: string): boolean { - const entry = this.terminals.get(sessionId) - return entry !== undefined && entry.terminal.exitStatus === undefined - } - - hasActiveTerminal(): boolean { - return this.host.activeTerminal() !== undefined - } - - activeSession(): string | undefined { + private activeKey(): string | undefined { const active = this.host.activeTerminal() if (!active) return undefined - for (const [id, entry] of this.terminals) { - if (entry.terminal === active && entry.terminal.exitStatus === undefined) return id + for (const [key, entry] of this.terminals) { + if (entry.terminal === active && entry.terminal.exitStatus === undefined) return key } return undefined } prepareContext(sessionId: string): boolean { if (this.showExisting(sessionId)) return true - const active = this.activeSession() - return !active || active === sessionId + const active = this.activeKey() + return !active || active === SessionTerminalManager.sessionKey(sessionId) } dispose(): void { @@ -266,32 +282,40 @@ export class SessionTerminalManager { void this.host.setContext("kilo-code.agentTerminalFocus", managed) } - private showOrCreate(sessionId: string, cwd: string, name: string): void { - let entry = this.terminals.get(sessionId) + private showOrCreate(key: string, cwd: string, name: string): void { + let entry = this.terminals.get(key) // Clean up exited terminals if (entry && entry.terminal.exitStatus !== undefined) { - this.terminals.delete(sessionId) + this.terminals.delete(key) entry = undefined } // Recreate if CWD changed if (entry && entry.cwd !== cwd) { entry.terminal.dispose() - this.terminals.delete(sessionId) + this.terminals.delete(key) entry = undefined - this.log(`showTerminal: cwd changed for session ${sessionId}, recreating`) + this.log(`showTerminal: cwd changed for ${key}, recreating`) } if (!entry) { const terminal = this.host.createTerminal({ cwd, name }) entry = { terminal, cwd } - this.terminals.set(sessionId, entry) - this.log(`showTerminal: created terminal for session ${sessionId} (cwd=${cwd})`) + this.terminals.set(key, entry) + this.log(`showTerminal: created terminal for ${key} (cwd=${cwd})`) } entry.terminal.show(false) this.panelOpen = true this.updateContextKey() } + + private static sessionKey(id: string): string { + return `${SessionTerminalManager.SESSION_PREFIX}${id}` + } + + private static worktreeKey(id: string): string { + return `${SessionTerminalManager.WORKTREE_PREFIX}${id}` + } } diff --git a/packages/kilo-vscode/src/agent-manager/SetupScriptService.ts b/packages/kilo-vscode/src/agent-manager/SetupScriptService.ts index f579d0779c..16fb48d93f 100644 --- a/packages/kilo-vscode/src/agent-manager/SetupScriptService.ts +++ b/packages/kilo-vscode/src/agent-manager/SetupScriptService.ts @@ -66,18 +66,6 @@ export class SetupScriptService { return this.resolveScript(platform) !== undefined } - /** Read the setup script content. Returns null if not found or read fails. */ - async getScript(platform: NodeJS.Platform = process.platform): Promise { - const script = this.resolveScript(platform) - if (!script) return null - try { - return await fs.promises.readFile(script.path, "utf-8") - } catch (error) { - this.log(`Failed to read setup script: ${error}`) - return null - } - } - /** Create a default setup script with helpful comments for the current platform. */ async createDefaultScript(platform: NodeJS.Platform = process.platform): Promise { if (!fs.existsSync(this.dir)) { diff --git a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts index 96b3bcceb3..32f2fb1b9f 100644 --- a/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts +++ b/packages/kilo-vscode/src/agent-manager/WorktreeManager.ts @@ -67,11 +67,6 @@ export interface CreateWorktreeResult { startPointWarning?: string } -export interface ExternalWorktreeItem { - path: string - branch: string -} - /** * Backward compat: split a possibly-prefixed branch like "origin/main" into * `{ branch: "main", remote: "origin" }`. If no slash is found, returns bare branch. @@ -815,7 +810,7 @@ export class WorktreeManager { // 4. Derived fallback if (allowFallback) { - const fallbacks = await this.derivedFallbackBranches(branch) + const fallbacks = await this.derivedFallbackBranches() for (const fallback of fallbacks) { if (fallback === branch) continue // already tried try { @@ -863,7 +858,7 @@ export class WorktreeManager { } } - async derivedFallbackBranches(requested: string): Promise { + async derivedFallbackBranches(): Promise { const defaults = [] try { defaults.push(await this.defaultBranch()) @@ -1015,22 +1010,6 @@ export class WorktreeManager { } } - async listExternalWorktrees(managedPaths: Set): Promise { - try { - const raw = await this.git.raw(["worktree", "list", "--porcelain"]) - const normalizedRoot = normalizePath(this.root) - const normalizedManaged = new Set([...managedPaths].map(normalizePath)) - return parseWorktreeList(raw) - .filter( - (e) => !e.bare && normalizePath(e.path) !== normalizedRoot && !normalizedManaged.has(normalizePath(e.path)), - ) - .map((e) => ({ path: e.path, branch: e.branch })) - } catch (error) { - this.log(`Failed to list external worktrees: ${error}`) - return [] - } - } - async createFromPR(url: string): Promise { return this.withGitLock(() => this.createFromPRImpl(url)) } diff --git a/packages/kilo-vscode/src/agent-manager/__tests__/AgentManagerProvider.spec.ts b/packages/kilo-vscode/src/agent-manager/__tests__/AgentManagerProvider.spec.ts index 05352e380a..7766476185 100644 --- a/packages/kilo-vscode/src/agent-manager/__tests__/AgentManagerProvider.spec.ts +++ b/packages/kilo-vscode/src/agent-manager/__tests__/AgentManagerProvider.spec.ts @@ -39,6 +39,7 @@ vi.mock("../SessionTerminalManager", () => ({ SessionTerminalManager: class { showTerminal() {} showLocalTerminal() {} + showWorktreeTerminal() {} syncLocalOnSessionSwitch() {} syncOnSessionSwitch() { return false @@ -77,7 +78,6 @@ function createMockHost(): Host { openFolder: vi.fn(), createOutput: () => ({ appendLine: vi.fn(), dispose: vi.fn() }) as OutputHandle, extensionKeybindings: () => [], - serverPort: () => undefined, capture: vi.fn(), dispose: vi.fn(), } @@ -217,6 +217,20 @@ describe("AgentManagerProvider worktree creation", () => { expect(manager.createWorktreeOnDisk).toHaveBeenCalledTimes(1) }) + it("disposes orphaned terminals when a freshly mounted webview requests state", async () => { + const manager = createHarness() + const dispose = vi.fn().mockResolvedValue(undefined) + manager.terminalRouter = { handle: vi.fn().mockReturnValue(false), dispose } as unknown as { + handle: ReturnType + } + // Avoid the vscode-backed pushEmptyState; the disposal under test is synchronous. + ;(manager as unknown as Record).pushEmptyState = vi.fn() + + await manager.onMessage({ type: "agentManager.requestState" }) + + expect(dispose).toHaveBeenCalledTimes(1) + }) + it("routes file search through the active worktree session", async () => { const manager = createHarness() manager.activeSessionId = "session-wt" diff --git a/packages/kilo-vscode/src/agent-manager/host.ts b/packages/kilo-vscode/src/agent-manager/host.ts index 8b81951b17..d11c73d515 100644 --- a/packages/kilo-vscode/src/agent-manager/host.ts +++ b/packages/kilo-vscode/src/agent-manager/host.ts @@ -165,9 +165,6 @@ export interface Host { /** Read extension keybinding metadata. */ extensionKeybindings(): Array<{ command: string; key?: string; mac?: string }> - /** Get the CLI server port (for webview CSP). */ - serverPort(): number | undefined - /** Copy text to the system clipboard. */ copyToClipboard(text: string): void diff --git a/packages/kilo-vscode/src/agent-manager/terminal-destination.ts b/packages/kilo-vscode/src/agent-manager/terminal-destination.ts new file mode 100644 index 0000000000..f6fbceb591 --- /dev/null +++ b/packages/kilo-vscode/src/agent-manager/terminal-destination.ts @@ -0,0 +1,37 @@ +/** + * Read and watch the user's Agent Manager terminal destination setting. + * + * The terminal button and `Cmd/Ctrl+/` either open the VS Code integrated + * terminal (default, backwards compatible) or an embedded xterm in the + * Agent Manager side panel. Kept next to `terminal-font.ts`; a separate + * module so the font helpers stay untouched. + */ + +import * as vscode from "vscode" + +export type TerminalDestination = "vscode" | "agentManager" + +const KEY = "kilo-code.new.agentManager.terminalButtonDestination" + +/** Unknown values fall back to the VS Code terminal so a stale or + * hand-edited setting never strands the user without a terminal. */ +export function resolveTerminalDestination(value: unknown): TerminalDestination { + return value === "agentManager" ? value : "vscode" +} + +export function readTerminalDestination(): TerminalDestination { + const config = vscode.workspace.getConfiguration("kilo-code.new.agentManager") + return resolveTerminalDestination(config.get("terminalButtonDestination")) +} + +export function affectsTerminalDestination(e: vscode.ConfigurationChangeEvent): boolean { + return e.affectsConfiguration(KEY) +} + +/** Subscribe to destination changes. Returns a cleanup function. */ +export function watchTerminalDestination(callback: (destination: TerminalDestination) => void): () => void { + const sub = vscode.workspace.onDidChangeConfiguration((e) => { + if (affectsTerminalDestination(e)) callback(readTerminalDestination()) + }) + return () => sub.dispose() +} diff --git a/packages/kilo-vscode/src/agent-manager/terminal-manager.ts b/packages/kilo-vscode/src/agent-manager/terminal-manager.ts index c1662d92b3..9f3d030728 100644 --- a/packages/kilo-vscode/src/agent-manager/terminal-manager.ts +++ b/packages/kilo-vscode/src/agent-manager/terminal-manager.ts @@ -113,6 +113,16 @@ export class TerminalManager { } } + /** Titles of every live terminal in a context — used by the router to + * pick the lowest free "Terminal N" ordinal. */ + titles(worktreeId: string | null): string[] { + const out: string[] = [] + for (const entry of this.entries.values()) { + if (entry.worktreeId === worktreeId) out.push(entry.title) + } + return out + } + /** Kill a single terminal. Best-effort — we always drop our bookkeeping. * The SDK's `pty.remove` returns `{ data, error }` without throwing * on 4xx/5xx, so we have to check `error` ourselves; otherwise a diff --git a/packages/kilo-vscode/src/agent-manager/terminal-routing.ts b/packages/kilo-vscode/src/agent-manager/terminal-routing.ts index 7c8c7e46cc..6ca6491c7a 100644 --- a/packages/kilo-vscode/src/agent-manager/terminal-routing.ts +++ b/packages/kilo-vscode/src/agent-manager/terminal-routing.ts @@ -7,7 +7,7 @@ * Owns: * - the `TerminalManager` lifecycle (create / close / resize / dispose) * - the per-context "Terminal N" ordinal counter - * - cwd resolution (worktree path → workspace root fallback) + * - cwd resolution (selected worktree or workspace root) * - WebSocket URL construction with loopback `auth_token` auth * * Vscode-free: all VS Code access is funnelled through the `deps` @@ -15,7 +15,7 @@ */ import type { KiloClient } from "@kilocode/sdk/v2/client" -import type { AgentManagerInMessage, AgentManagerOutMessage, TerminalFont } from "./types" +import type { AgentManagerInMessage, AgentManagerOutMessage, TerminalFont, TerminalPlacement } from "./types" import { TerminalManager } from "./terminal-manager" interface ServerConfig { @@ -26,6 +26,8 @@ interface ServerConfig { export interface TerminalRoutingDeps { /** Shared SDK client. Throws when the CLI backend is not connected. */ getClient(): KiloClient + /** Shared SDK client, connecting the CLI backend when needed. */ + getClientAsync(): Promise /** Loopback URL + basic-auth password for the running `kilo serve`. */ getServerConfig(): ServerConfig | undefined /** Workspace root — used as cwd fallback when no worktree is selected (LOCAL). */ @@ -52,14 +54,21 @@ function isTerminalMessage( } export class TerminalRouter { - private readonly manager: TerminalManager - private readonly ordinals = new Map() + private manager: TerminalManager + /** Ordinals reserved by in-flight creates, per context — prevents two + * concurrent creates from grabbing the same "Terminal N" title. */ + private readonly reserved = new Map>() + private generation = 0 constructor(private readonly deps: TerminalRoutingDeps) { - this.manager = new TerminalManager({ - getClient: () => deps.getClient(), + this.manager = this.createManager() + } + + private createManager(): TerminalManager { + return new TerminalManager({ + getClient: () => this.deps.getClient(), buildWsUrl: (ptyID, cwd) => this.buildWsUrl(ptyID, cwd), - log: deps.log, + log: this.deps.log, }) } @@ -71,7 +80,7 @@ export class TerminalRouter { handle(m: AgentManagerInMessage): boolean { if (!isTerminalMessage(m)) return false if (m.type === "agentManager.terminal.create") { - void this.handleCreate(m.worktreeId) + void this.handleCreate(m.createId, m.placement, m.worktreeId) return true } if (m.type === "agentManager.terminal.close") { @@ -85,25 +94,49 @@ export class TerminalRouter { return true } - /** Tear down every live PTY. Forwards to `TerminalManager.dispose`. */ + /** + * Tear down every live PTY and invalidate in-flight create requests. + * The router stays usable afterwards: a create landing from before the + * disposal is closed immediately instead of leaking a PTY the webview + * no longer tracks. + */ dispose(): Promise { - return this.manager.dispose() + this.generation++ + const manager = this.manager + this.manager = this.createManager() + this.reserved.clear() + return manager.dispose() } - private async handleCreate(worktreeId: string | null): Promise { + private async handleCreate(createId: string, placement: TerminalPlacement, worktreeId: string | null): Promise { + const generation = this.generation + const manager = this.manager const cwd = this.resolveCwd(worktreeId) if (!cwd) { this.deps.post({ type: "agentManager.terminal.error", - message: "Open a folder before creating a terminal", + createId, + message: worktreeId + ? "The selected worktree is no longer available" + : "Open a folder before creating a terminal", }) return } - const title = `Terminal ${this.nextOrdinal(worktreeId)}` + const ordinal = this.reserveOrdinal(worktreeId) + const title = `Terminal ${ordinal}` try { - const created = await this.manager.create({ worktreeId, cwd, title }) + // Join the shared backend connection instead of racing its synchronous + // client accessor when this is the first Kilo action in the window. + await this.deps.getClientAsync() + const created = await manager.create({ worktreeId, cwd, title }) + if (generation !== this.generation) { + await manager.close(created.terminalId) + return + } this.deps.post({ type: "agentManager.terminal.created", + createId, + placement, worktreeId: created.worktreeId, terminalId: created.terminalId, title: created.title, @@ -111,33 +144,66 @@ export class TerminalRouter { font: this.deps.getTerminalFont(), }) } catch (err) { + if (generation !== this.generation) return const message = err instanceof Error ? err.message : String(err) this.deps.log(`Terminal create failed: ${message}`) - this.deps.post({ type: "agentManager.terminal.error", message }) + this.deps.post({ type: "agentManager.terminal.error", createId, message }) + } finally { + // Only a current-generation create may release: dispose() already + // cleared this create's reservation, and releasing here would + // delete a *new* panel's reservation for the same number. + if (generation === this.generation) this.releaseOrdinal(worktreeId, ordinal) } } /** * Resolve the cwd for a terminal in the given context. * - * LOCAL (null) falls back to the workspace root; a worktree id - * resolves to its on-disk path. Returns undefined when no folder is - * open — the caller surfaces this as a user-facing error. + * LOCAL (null) uses the workspace root; a worktree id resolves strictly + * to its on-disk path — silently falling back to the workspace root + * would run the shell in the wrong directory. Returns undefined when + * no folder is open or the worktree is gone; the caller surfaces this + * as a user-facing error. */ private resolveCwd(worktreeId: string | null): string | undefined { if (worktreeId === null) return this.deps.getRoot() - return this.deps.getWorktreePath(worktreeId) ?? this.deps.getRoot() + return this.deps.getWorktreePath(worktreeId) } - /** Per-context counter so default titles are "Terminal 1", "Terminal 2"… - * Not persisted; a webview reload resets counts. */ - private nextOrdinal(worktreeId: string | null): number { + /** + * Pick the lowest "Terminal N" ordinal not used by a live terminal or + * an in-flight create in this context, and reserve it until the + * create settles. Gap-filling keeps numbering consistent: closing + * "Terminal 1" of three frees 1 for the next terminal, instead of + * drifting to ever-higher numbers. Not persisted; a webview reload + * resets the live set. + */ + private reserveOrdinal(worktreeId: string | null): number { const key = worktreeId ?? "__local__" - const next = (this.ordinals.get(key) ?? 0) + 1 - this.ordinals.set(key, next) + const used = new Set() + for (const title of this.manager.titles(worktreeId)) { + const match = /^Terminal (\d+)$/.exec(title) + if (match) used.add(Number(match[1])) + } + const pending = this.reserved.get(key) + if (pending) for (const n of pending) used.add(n) + let next = 1 + while (used.has(next)) next++ + const set = pending ?? new Set() + set.add(next) + this.reserved.set(key, set) return next } + /** Return an in-flight create's reservation. */ + private releaseOrdinal(worktreeId: string | null, ordinal: number) { + const key = worktreeId ?? "__local__" + const set = this.reserved.get(key) + if (!set) return + set.delete(ordinal) + if (set.size === 0) this.reserved.delete(key) + } + /** * Build the WebSocket URL for a given PTY. * @@ -155,6 +221,9 @@ export class TerminalRouter { const token = Buffer.from(`kilo:${config.password}`).toString("base64") const dir = encodeURIComponent(cwd) const auth = encodeURIComponent(token) - return `${base}/pty/${encodeURIComponent(ptyID)}/connect?directory=${dir}&cursor=-1&auth_token=${auth}` + // A new terminal has one initial attachment. Replay its retained startup + // bytes so xterm can answer shell capability queries emitted before the + // WebSocket connected; tailing from -1 can make shells wait for a timeout. + return `${base}/pty/${encodeURIComponent(ptyID)}/connect?directory=${dir}&cursor=0&auth_token=${auth}` } } diff --git a/packages/kilo-vscode/src/agent-manager/types.ts b/packages/kilo-vscode/src/agent-manager/types.ts index db8af59b35..f32c934666 100644 --- a/packages/kilo-vscode/src/agent-manager/types.ts +++ b/packages/kilo-vscode/src/agent-manager/types.ts @@ -13,21 +13,22 @@ import type { Worktree, ManagedSession, Section } from "./WorktreeStateManager" import type { WorktreeStats, LocalStats } from "./GitStatsPoller" import type { ApplyConflict } from "./GitOps" import type { BranchListItem, WorktreeSetupErrorCode } from "./git-import" -import type { ExternalWorktreeItem } from "./WorktreeManager" import type { RunStatus } from "./run/manager" import type { TerminalFont } from "./terminal-font" import type { ProjectSnapshot } from "./project-context" import type { SidebarTarget } from "./project-route" +import type { TerminalDestination } from "./terminal-destination" export type { TerminalFont } export type { ProjectSnapshot } +/** Where a terminal lives: main tab strip or right-side inspector panel. */ +export type TerminalPlacement = "tab" | "side" + // --------------------------------------------------------------------------- // Shared payload types // --------------------------------------------------------------------------- -type SessionMode = "worktree" | "local" - export type ApplyDiffStatus = "checking" | "applying" | "success" | "conflict" | "error" export type WorktreeDiffEntry = SnapshotFileDiff & { @@ -121,15 +122,6 @@ interface WorktreeSetupMessage { errorCode?: WorktreeSetupErrorCode } -interface SessionMetaMessage { - type: "agentManager.sessionMeta" - sessionId: string - mode: SessionMode - branch?: string - path?: string - parentBranch?: string -} - interface StateMessage { type: "agentManager.state" worktrees: Worktree[] @@ -151,6 +143,7 @@ interface StateMessage { projectId?: string /** Last selected sidebar target for seamless project-switch restore. */ activeTarget?: SidebarTarget + terminalDestination?: TerminalDestination } /** Project catalog pushed to the webview after registry or context changes. */ @@ -187,6 +180,11 @@ interface ProjectSessionsMessage { interface TerminalCreatedMessage { type: "agentManager.terminal.created" + /** Correlates with the create request; lets the webview spot stale + * creates. Deliberately not named `requestId`: that field name is the + * generic webview request/response correlation channel. */ + createId: string + placement: TerminalPlacement /** null for LOCAL, worktree id otherwise */ worktreeId: string | null terminalId: string @@ -203,9 +201,16 @@ interface TerminalClosedMessage { interface TerminalErrorMessage { type: "agentManager.terminal.error" terminalId?: string + /** Set when the error answers a specific create request. */ + createId?: string message: string } +interface TerminalDestinationChangedMessage { + type: "agentManager.terminal.destinationChanged" + destination: TerminalDestination +} + interface TerminalFontChangedMessage { type: "agentManager.terminal.fontChanged" font: TerminalFont @@ -268,11 +273,6 @@ interface BranchesMessage { defaultBranch: string } -interface ExternalWorktreesMessage { - type: "agentManager.externalWorktrees" - worktrees: ExternalWorktreeItem[] -} - interface ImportResultMessage { type: "agentManager.importResult" success: boolean @@ -349,7 +349,6 @@ export type AgentManagerOutMessage = | WorktreeStatsMessage | LocalStatsMessage | WorktreeSetupMessage - | SessionMetaMessage | StateMessage | ProjectsMessage | SelectionActivatedMessage @@ -362,7 +361,6 @@ export type AgentManagerOutMessage = | SetSessionModelMessage | SendInitialMessage | BranchesMessage - | ExternalWorktreesMessage | ImportResultMessage | KeybindingsMessage | RepoInfoMessage @@ -377,6 +375,7 @@ export type AgentManagerOutMessage = | TerminalCreatedMessage | TerminalClosedMessage | TerminalErrorMessage + | TerminalDestinationChangedMessage | TerminalFontChangedMessage // --------------------------------------------------------------------------- @@ -518,6 +517,11 @@ interface ShowLocalTerminalIn { type: "agentManager.showLocalTerminal" } +interface ShowWorktreeTerminalIn { + type: "agentManager.showWorktreeTerminal" + worktreeId: string +} + interface OpenWorktreeIn { type: "agentManager.openWorktree" projectId?: string @@ -609,10 +613,6 @@ interface SetDefaultBaseBranchIn { branch?: string } -interface RequestExternalWorktreesIn { - type: "agentManager.requestExternalWorktrees" -} - interface ImportFromBranchIn { type: "agentManager.importFromBranch" projectId?: string @@ -625,16 +625,6 @@ interface ImportFromPRIn { url: string } -interface ImportExternalWorktreeIn { - type: "agentManager.importExternalWorktree" - path: string - branch: string -} - -interface ImportAllExternalWorktreesIn { - type: "agentManager.importAllExternalWorktrees" -} - interface RequestWorktreeDiffIn { type: "agentManager.requestWorktreeDiff" sessionId: string @@ -877,6 +867,9 @@ interface MoveSectionIn { interface TerminalCreateIn { type: "agentManager.terminal.create" + /** Webview-generated correlation id, echoed back in created/error. */ + createId: string + placement: TerminalPlacement /** null for LOCAL, worktree id otherwise */ worktreeId: string | null } @@ -919,6 +912,7 @@ export type AgentManagerInMessage = | StopRunScriptIn | ShowTerminalIn | ShowLocalTerminalIn + | ShowWorktreeTerminalIn | OpenWorktreeIn | CopyToClipboardIn | ShowExistingLocalTerminalIn @@ -934,11 +928,8 @@ export type AgentManagerInMessage = | SetReviewDiffStyleIn | SetReviewMarkdownRenderIn | SetDefaultBaseBranchIn - | RequestExternalWorktreesIn | ImportFromBranchIn | ImportFromPRIn - | ImportExternalWorktreeIn - | ImportAllExternalWorktreesIn | RequestWorktreeDiffIn | RequestWorktreeDiffFileIn | ApplyWorktreeDiffIn diff --git a/packages/kilo-vscode/src/agent-manager/vscode-host.ts b/packages/kilo-vscode/src/agent-manager/vscode-host.ts index 45ac42d328..5862a4d95a 100644 --- a/packages/kilo-vscode/src/agent-manager/vscode-host.ts +++ b/packages/kilo-vscode/src/agent-manager/vscode-host.ts @@ -298,10 +298,6 @@ export class VscodeHost implements Host { return ext?.packageJSON?.contributes?.keybindings ?? [] } - serverPort(): number | undefined { - return this.connectionService.getServerInfo()?.port - } - copyToClipboard(text: string): void { void vscode.env.clipboard.writeText(text) } diff --git a/packages/kilo-vscode/src/agent-manager/worktree-importer.ts b/packages/kilo-vscode/src/agent-manager/worktree-importer.ts index cfa3d1ab96..f589a7c4eb 100644 --- a/packages/kilo-vscode/src/agent-manager/worktree-importer.ts +++ b/packages/kilo-vscode/src/agent-manager/worktree-importer.ts @@ -2,9 +2,7 @@ import type { Session } from "@kilocode/sdk/v2/client" import type { AgentManagerOutMessage } from "./types" import type { WorktreeManager, CreateWorktreeResult } from "./WorktreeManager" import type { WorktreeStateManager } from "./WorktreeStateManager" -import { classifyWorktreeError, normalizePath } from "./git-import" - -type Worktree = ReturnType +import { classifyWorktreeError } from "./git-import" export interface WorktreeImporterHost { manager(): WorktreeManager | undefined @@ -57,82 +55,15 @@ export class WorktreeImporter { } } - async external(): Promise { - const manager = this.host.manager() - const state = this.host.state() - if (!manager || !state) { - this.host.post({ type: "agentManager.externalWorktrees", worktrees: [] }) - return - } - - try { - const paths = new Set(state.getWorktrees().map((worktree) => worktree.path)) - const worktrees = await manager.listExternalWorktrees(paths) - this.host.post({ type: "agentManager.externalWorktrees", worktrees }) - } catch (error) { - this.host.log(`Failed to list external worktrees: ${error}`) - this.host.post({ type: "agentManager.externalWorktrees", worktrees: [] }) - } - } - async branch(branch: string): Promise { - const manager = this.host.manager() - const state = this.host.state() - if (!manager || !state) { - this.host.post({ type: "agentManager.importResult", success: false, message: "Not a git repository" }) - return - } - if (this.busy()) return - - this.importing = true - try { - this.host.post({ - type: "agentManager.worktreeSetup", - status: "creating", - message: "Creating worktree from branch...", - }) - const result = await manager.createWorktree({ existingBranch: branch }) - const worktree = state.addWorktree({ - branch: result.branch, - path: result.path, - parentBranch: result.parentBranch, - remote: result.remote, - branchOwned: false, - }) - this.host.push() - - try { - this.host.post({ - type: "agentManager.worktreeSetup", - status: "creating", - message: "Running setup script...", - branch: result.branch, - worktreeId: worktree.id, - }) - await this.host.setup(result.path, result.branch, worktree.id) - - const session = await this.host.session(result.path, result.branch, worktree.id) - if (!session) throw new Error("Failed to create session") - - state.addSession(session.id, worktree.id) - this.host.register(session.id, result.path) - this.host.ready(session.id, result, worktree.id) - this.host.post({ type: "agentManager.importResult", success: true, message: `Opened branch ${branch}` }) - this.host.log(`Imported branch ${branch} as worktree ${worktree.id}`) - } catch (error) { - state.removeWorktree(worktree.id) - await manager.removeWorktree(result.path) - this.host.push() - throw error - } - } catch (error) { - this.importError(error, `Branch "${branch}" is already checked out in another worktree`) - } finally { - this.importing = false - } + await this.run({ branch }) } async pr(url: string): Promise { + await this.run({ url }) + } + + private async run(target: { branch: string } | { url: string }): Promise { const manager = this.host.manager() const state = this.host.state() if (!manager || !state) { @@ -140,11 +71,21 @@ export class WorktreeImporter { return } if (this.busy()) return - this.importing = true + const branch = "branch" in target + const creating = branch ? "Creating worktree from branch..." : "Resolving PR..." + const setup = branch ? "Running setup script..." : "Setting up worktree..." + const duplicate = branch + ? `Branch "${target.branch}" is already checked out in another worktree` + : "This PR's branch is already checked out in another worktree" try { - this.host.post({ type: "agentManager.worktreeSetup", status: "creating", message: "Resolving PR..." }) - const result = await manager.createFromPR(url) + const progress = { type: "agentManager.worktreeSetup", status: "creating" } as const + this.host.post({ ...progress, message: creating }) + const result = branch + ? await manager.createWorktree({ existingBranch: target.branch }) + : await manager.createFromPR(target.url) + const success = branch ? `Opened branch ${target.branch}` : `Opened PR branch ${result.branch}` + const log = branch ? `Imported branch ${target.branch}` : `Imported PR ${target.url}` const worktree = state.addWorktree({ branch: result.branch, path: result.path, @@ -153,29 +94,16 @@ export class WorktreeImporter { branchOwned: false, }) this.host.push() - try { - this.host.post({ - type: "agentManager.worktreeSetup", - status: "creating", - message: "Setting up worktree...", - branch: result.branch, - worktreeId: worktree.id, - }) + this.host.post({ ...progress, message: setup, branch: result.branch, worktreeId: worktree.id }) await this.host.setup(result.path, result.branch, worktree.id) - const session = await this.host.session(result.path, result.branch, worktree.id) if (!session) throw new Error("Failed to create session") - state.addSession(session.id, worktree.id) this.host.register(session.id, result.path) this.host.ready(session.id, result, worktree.id) - this.host.post({ - type: "agentManager.importResult", - success: true, - message: `Opened PR branch ${result.branch}`, - }) - this.host.log(`Imported PR ${url} as worktree ${worktree.id}`) + this.host.post({ type: "agentManager.importResult", success: true, message: success }) + this.host.log(`${log} as worktree ${worktree.id}`) } catch (error) { state.removeWorktree(worktree.id) await manager.removeWorktree(result.path) @@ -183,137 +111,7 @@ export class WorktreeImporter { throw error } } catch (error) { - this.importError(error, "This PR's branch is already checked out in another worktree") - } finally { - this.importing = false - } - } - - async path(path: string, branch: string): Promise { - const state = this.host.state() - const manager = this.host.manager() - if (!state || !manager) { - this.host.post({ type: "agentManager.importResult", success: false, message: "State not initialized" }) - return - } - if (this.busy()) return - - this.importing = true - let worktree: Worktree | undefined - try { - const paths = new Set(state.getWorktrees().map((worktree) => worktree.path)) - const externals = await manager.listExternalWorktrees(paths) - if (!externals.some((worktree) => normalizePath(worktree.path) === normalizePath(path))) { - this.host.post({ - type: "agentManager.importResult", - success: false, - message: "Path is not a valid worktree for this repository", - }) - return - } - - const base = await manager.resolveBaseBranch() - worktree = state.addWorktree({ branch, path, parentBranch: base.branch, remote: base.remote, branchOwned: false }) - this.host.push() - - const session = await this.host.session(path, branch, worktree.id) - if (!session) { - state.removeWorktree(worktree.id) - this.host.push() - this.host.post({ type: "agentManager.importResult", success: false, message: "Failed to create session" }) - return - } - - state.addSession(session.id, worktree.id) - this.host.register(session.id, path) - this.host.push() - this.host.post({ - type: "agentManager.worktreeSetup", - status: "ready", - message: "Worktree imported", - sessionId: session.id, - branch, - worktreeId: worktree.id, - }) - this.host.post({ - type: "agentManager.sessionMeta", - sessionId: session.id, - mode: "worktree", - branch, - path, - parentBranch: base.branch, - }) - this.host.post({ type: "agentManager.importResult", success: true, message: `Imported ${branch}` }) - this.host.log(`Imported external worktree ${path} (${branch})`) - } catch (error) { - if (worktree) { - state.removeWorktree(worktree.id) - this.host.push() - } - const message = error instanceof Error ? error.message : String(error) - this.host.post({ type: "agentManager.importResult", success: false, message }) - } finally { - this.importing = false - } - } - - async all(): Promise { - if (this.busy()) return - - const manager = this.host.manager() - const state = this.host.state() - if (!manager || !state) { - this.host.post({ type: "agentManager.importResult", success: false, message: "Not a git repository" }) - return - } - this.importing = true - - try { - const paths = new Set(state.getWorktrees().map((worktree) => worktree.path)) - const externals = await manager.listExternalWorktrees(paths) - if (externals.length === 0) { - this.host.post({ - type: "agentManager.importResult", - success: true, - message: "No external worktrees to import", - }) - return - } - - const imported: string[] = [] - const base = await manager.resolveBaseBranch() - for (const external of externals) { - try { - const worktree = state.addWorktree({ - branch: external.branch, - path: external.path, - parentBranch: base.branch, - remote: base.remote, - branchOwned: false, - }) - const session = await this.host.session(external.path, external.branch, worktree.id) - if (session) { - state.addSession(session.id, worktree.id) - this.host.register(session.id, external.path) - imported.push(worktree.id) - continue - } - state.removeWorktree(worktree.id) - } catch (error) { - this.host.log(`Failed to import external worktree ${external.path}: ${error}`) - } - } - - this.host.push() - this.host.post({ - type: "agentManager.importResult", - success: true, - message: `Imported ${imported.length} worktree${imported.length !== 1 ? "s" : ""}`, - }) - this.host.log(`Imported ${imported.length}/${externals.length} external worktrees`) - } catch (error) { - const message = error instanceof Error ? error.message : String(error) - this.host.post({ type: "agentManager.importResult", success: false, message }) + this.importError(error, duplicate) } finally { this.importing = false } diff --git a/packages/kilo-vscode/src/diff/sources/session.ts b/packages/kilo-vscode/src/diff/sources/session.ts index 58fc6d8bdd..924c232659 100644 --- a/packages/kilo-vscode/src/diff/sources/session.ts +++ b/packages/kilo-vscode/src/diff/sources/session.ts @@ -83,7 +83,15 @@ export function toSessionDiffFile(raw: SnapshotFileDiff): DiffFile { // Empty patch means binary or summarized (>256 KB) — normalize() can't // parse it, so short-circuit to empty strings. Binary snapshot images do // not retain their sides, while text-backed SVG patches can be rebuilt. - const view = raw.patch === "" || (mime && mime !== "image/svg+xml") ? null : normalize(raw) + const view = (() => { + if (raw.patch === "" || (mime && mime !== "image/svg+xml")) return null + try { + return normalize(raw) + } catch (err) { + console.warn("[Kilo New] Failed to parse session diff", { file, err }) + return null + } + })() const before = view ? text(view, "deletions") : "" const after = view ? text(view, "additions") : "" const image = (() => { @@ -108,7 +116,8 @@ export function toSessionDiffFile(raw: SnapshotFileDiff): DiffFile { generatedLike: false, // A zero-stat empty patch has no text body to fetch; nonzero stats // indicate a deferred large-file summary. - summarized: !mime && raw.patch === "" && (raw.additions !== 0 || raw.deletions !== 0), + summarized: + !mime && ((!view && raw.patch !== "") || (raw.patch === "" && (raw.additions !== 0 || raw.deletions !== 0))), kind: mime ? "image" : undefined, image, stamp: mime ? fingerprint(raw) : undefined, diff --git a/packages/kilo-vscode/src/kilo-provider-utils.ts b/packages/kilo-vscode/src/kilo-provider-utils.ts index e5dbd40e44..c9825cb771 100644 --- a/packages/kilo-vscode/src/kilo-provider-utils.ts +++ b/packages/kilo-vscode/src/kilo-provider-utils.ts @@ -408,7 +408,7 @@ export type WebviewMessage = message: Record } | { type: "sessionStatus"; sessionID: string; status: string; attempt?: number; message?: string; next?: number } - | { type: "sessionTurnClosed"; sessionID: string; reason: "completed" | "error" | "interrupted" } + | { type: "sessionTurnClosed"; sessionID: string; reason: "completed" | "error" | "interrupted" | "superseded" } | { type: "permissionRequest" permission: { diff --git a/packages/kilo-vscode/src/kilo-provider/config-snapshot.ts b/packages/kilo-vscode/src/kilo-provider/config-snapshot.ts new file mode 100644 index 0000000000..fcb568a677 --- /dev/null +++ b/packages/kilo-vscode/src/kilo-provider/config-snapshot.ts @@ -0,0 +1,21 @@ +import type { KiloClient } from "@kilocode/sdk/v2/client" +import { configFeatures } from "../features" +import { retry } from "../services/cli-backend/retry" +import type { ConfigTarget } from "./config-bindings" + +type Client = Pick +type Settings = { maxCost: number; languageCommitMessage: string; multiProject: boolean } +export async function fetchSnapshot(client: Client, dir: string, settings: () => Settings) { + const [{ data: config }, { data: global }, { data: overlay }] = await Promise.all([ + retry(() => client.config.get({ directory: dir }, { throwOnError: true })), + client.global.config.get({ throwOnError: true }), + client.config.overlay({ directory: dir, scope: "project" }, { throwOnError: true }), + ]) + return { + config, + globalConfig: global, + targets: overlay?.targets as { global: ConfigTarget; project: ConfigTarget } | undefined, + settings: settings(), + features: configFeatures(config), + } +} diff --git a/packages/kilo-vscode/src/kilo-provider/early-message.ts b/packages/kilo-vscode/src/kilo-provider/early-message.ts index 1cc94ba2b6..f67a2144fa 100644 --- a/packages/kilo-vscode/src/kilo-provider/early-message.ts +++ b/packages/kilo-vscode/src/kilo-provider/early-message.ts @@ -15,10 +15,32 @@ type Ctx = { post: (msg: unknown) => void browserSettings: () => void exportTranscript: (sessionID: string) => Promise + copy: (text: string) => PromiseLike openSessions: (ids: string[]) => void } -export async function routeEarlyMessage(message: { type: string }, ctx: Ctx): Promise { +export async function routeEarlyMessage( + message: { type: string; id?: unknown; text?: unknown }, + ctx: Ctx, +): Promise { + if (message.type === "copyToClipboard") { + if (typeof message.id !== "string") return true + if (typeof message.text !== "string") { + ctx.post({ type: "clipboardWriteResult", id: message.id, ok: false, error: "Invalid clipboard text" }) + return true + } + await ctx.copy(message.text).then( + () => ctx.post({ type: "clipboardWriteResult", id: message.id, ok: true }), + (err) => + ctx.post({ + type: "clipboardWriteResult", + id: message.id, + ok: false, + error: err instanceof Error ? err.message : String(err), + }), + ) + return true + } await routeSuggestionWebviewMessage(ctx.question, message) if (await ModelState.handleMessage(message.type, message, ctx.client, ctx.post)) return true if (message.type === "exportSessionTranscript") { diff --git a/packages/kilo-vscode/src/services/cli-backend/i18n/fa.ts b/packages/kilo-vscode/src/services/cli-backend/i18n/fa.ts new file mode 100644 index 0000000000..3aa4dc7483 --- /dev/null +++ b/packages/kilo-vscode/src/services/cli-backend/i18n/fa.ts @@ -0,0 +1,6 @@ +export const dict = { + "server.processExited": "فرآیند CLI با کد {{code}} قبل از راه‌اندازی سرور خاتمه یافت", + "server.startupTimeout": "زمان راه‌اندازی سرور پس از {{seconds}} ثانیه به پایان رسید", + "remote.connected": "Kilo Remote: متصل شد", + "remote.connecting": "Kilo Remote: در حال اتصال…", +} as const diff --git a/packages/kilo-vscode/src/services/cli-backend/i18n/index.ts b/packages/kilo-vscode/src/services/cli-backend/i18n/index.ts index 82c8c9358a..d41b11bd2c 100644 --- a/packages/kilo-vscode/src/services/cli-backend/i18n/index.ts +++ b/packages/kilo-vscode/src/services/cli-backend/i18n/index.ts @@ -6,6 +6,7 @@ import { dict as da } from "./da" import { dict as de } from "./de" import { dict as en } from "./en" import { dict as es } from "./es" +import { dict as fa } from "./fa" import { dict as fr } from "./fr" import { dict as it } from "./it" import { dict as ja } from "./ja" @@ -29,6 +30,7 @@ const bundles: Record> = { de, en, es, + fa, fr, it, ja, diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/ar.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/ar.ts index 8b31c9e4dc..f6e3eeb1ef 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/ar.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/ar.ts @@ -3,38 +3,15 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "مؤجل", "kilocode:autocomplete.statusBar.warning": "$(warning) الإكمال التلقائي", "kilocode:autocomplete.statusBar.tooltip.basic": "الإكمال التلقائي في Kilo Code", - "kilocode:autocomplete.statusBar.tooltip.disabled": "الإكمال التلقائي في Kilo Code (معطل)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**لم يتم تكوين نموذج للإكمال التلقائي**\n\nلتفعيل الإكمال التلقائي، أضف ملفا شخصيا يحتوي على أحد هؤلاء المزودين المدعومين: {{providers}}.\n\n[فتح الإعدادات]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "إجمالي تكلفة الجلسة:", - "kilocode:autocomplete.statusBar.tooltip.provider": "المزود:", - "kilocode:autocomplete.statusBar.tooltip.model": "النموذج:", - "kilocode:autocomplete.statusBar.tooltip.profile": "الملف الشخصي: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "افتراضي", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "تم تنفيذ {{count}} عمليات إكمال بين {{startTime}} و {{endTime}}، بتكلفة إجمالية قدرها {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "يتم توفير الإكمال التلقائي بواسطة {{model}} عبر {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "الإكمال التلقائي في Kilo Code {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "جار تحليل الكود...", - "kilocode:autocomplete.progress.generating": "جار إنشاء التعديلات المقترحة...", - "kilocode:autocomplete.progress.processing": "جار معالجة التعديلات المقترحة...", - "kilocode:autocomplete.progress.showing": "جار عرض التعديلات المقترحة...", - "kilocode:autocomplete.input.title": "Kilo Code: مهمة سريعة", - "kilocode:autocomplete.input.placeholder": "مثال: 'أعد هيكلة هذه الدالة لتكون أكثر كفاءة'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: إنشاء تعديلات مقترحة", - "kilocode:autocomplete.commands.displaySuggestions": "عرض التعديلات المقترحة", - "kilocode:autocomplete.commands.cancelSuggestions": "إلغاء التعديلات المقترحة", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "تطبيق التعديل المقترح الحالي", - "kilocode:autocomplete.commands.applyAllSuggestions": "تطبيق كل التعديلات المقترحة", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: تعديلات مقترحة", - "kilocode:autocomplete.chatParticipant.fullName": "وكيل Kilo Code", - "kilocode:autocomplete.chatParticipant.name": "الوكيل", - "kilocode:autocomplete.chatParticipant.description": "يمكنني مساعدتك في المهام السريعة والتعديلات المقترحة.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "يتم حظر الإكمال التلقائي في Kilo Code بسبب تعارض مع GitHub Copilot. لإصلاح ذلك، يجب تعطيل اقتراحات Copilot المضمنة.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "تعطيل Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/br.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/br.ts index c21cfc57f6..50db537de0 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/br.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/br.ts @@ -3,37 +3,14 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "adiado", "kilocode:autocomplete.statusBar.warning": "$(warning) Autocompletar", "kilocode:autocomplete.statusBar.tooltip.basic": "Autocompletar do Kilo Code", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Autocompletar do Kilo Code (desativado)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**Nenhum modelo de autocompletar configurado**\n\nPara ativar o autocompletar, adicione um perfil com um destes provedores compatíveis: {{providers}}.\n\n[Abrir Configurações]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Custo total da sessão:", - "kilocode:autocomplete.statusBar.tooltip.provider": "Provedor:", - "kilocode:autocomplete.statusBar.tooltip.model": "Modelo:", - "kilocode:autocomplete.statusBar.tooltip.profile": "Perfil: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "Padrão", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "Foram realizadas {{count}} conclusões entre {{startTime}} e {{endTime}}, com custo total de {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Autocompletar fornecido por {{model}} via {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Autocompletar do Kilo Code {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Analisando seu código...", - "kilocode:autocomplete.progress.generating": "Gerando edições sugeridas...", - "kilocode:autocomplete.progress.processing": "Processando edições sugeridas...", - "kilocode:autocomplete.progress.showing": "Exibindo edições sugeridas...", - "kilocode:autocomplete.input.title": "Kilo Code: Tarefa rápida", - "kilocode:autocomplete.input.placeholder": "ex.: 'refatore esta função para ser mais eficiente'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: Gerar edições sugeridas", - "kilocode:autocomplete.commands.displaySuggestions": "Exibir edições sugeridas", - "kilocode:autocomplete.commands.cancelSuggestions": "Cancelar edições sugeridas", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Aplicar edição sugerida atual", - "kilocode:autocomplete.commands.applyAllSuggestions": "Aplicar todas as edições sugeridas", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: Edições sugeridas", - "kilocode:autocomplete.chatParticipant.fullName": "Agente do Kilo Code", - "kilocode:autocomplete.chatParticipant.name": "Agente", - "kilocode:autocomplete.chatParticipant.description": "Posso ajudar com tarefas rápidas e edições sugeridas.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "O Autocompletar do Kilo Code está sendo bloqueado por um conflito com o GitHub Copilot. Para corrigir isso, você deve desativar as sugestões inline do Copilot.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Desativar Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/bs.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/bs.ts index 648a46c92e..0d80d18dac 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/bs.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/bs.ts @@ -3,37 +3,14 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "odgođeno", "kilocode:autocomplete.statusBar.warning": "$(warning) Automatsko dovršavanje", "kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code automatsko dovršavanje", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code automatsko dovršavanje (onemogućeno)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**Nije konfigurisan model za automatsko dovršavanje**\n\nDa omogućite automatsko dovršavanje, dodajte profil s jednim od ovih podržanih pružalaca: {{providers}}.\n\n[Otvori postavke]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Ukupni trošak sesije:", - "kilocode:autocomplete.statusBar.tooltip.provider": "Pružalac:", - "kilocode:autocomplete.statusBar.tooltip.model": "Model:", - "kilocode:autocomplete.statusBar.tooltip.profile": "Profil: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "Zadano", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "Izvršeno je {{count}} dovršavanja između {{startTime}} i {{endTime}}, uz ukupni trošak od {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Automatska dovršavanja pruža {{model}} putem {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Kilo Code automatsko dovršavanje {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Analiziram vaš kod...", - "kilocode:autocomplete.progress.generating": "Generišem predložene izmjene...", - "kilocode:autocomplete.progress.processing": "Obrađujem predložene izmjene...", - "kilocode:autocomplete.progress.showing": "Prikazujem predložene izmjene...", - "kilocode:autocomplete.input.title": "Kilo Code: Brzi zadatak", - "kilocode:autocomplete.input.placeholder": "npr. 'refaktoriši ovu funkciju da bude efikasnija'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: Generiši predložene izmjene", - "kilocode:autocomplete.commands.displaySuggestions": "Prikaži predložene izmjene", - "kilocode:autocomplete.commands.cancelSuggestions": "Otkaži predložene izmjene", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Primijeni trenutnu predloženu izmjenu", - "kilocode:autocomplete.commands.applyAllSuggestions": "Primijeni sve predložene izmjene", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: Predložene izmjene", - "kilocode:autocomplete.chatParticipant.fullName": "Kilo Code agent", - "kilocode:autocomplete.chatParticipant.name": "Agent", - "kilocode:autocomplete.chatParticipant.description": "Mogu pomoći s brzim zadacima i predloženim izmjenama.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Kilo Code automatsko dovršavanje blokira sukob s GitHub Copilotom. Da to popravite, morate onemogućiti Copilotove inline prijedloge.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Onemogući Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/da.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/da.ts index 9b613b6c1e..5aabf0c58e 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/da.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/da.ts @@ -3,38 +3,14 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "udsat", "kilocode:autocomplete.statusBar.warning": "$(warning) Autofuldførelse", "kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code autofuldførelse", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code autofuldførelse (deaktiveret)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**Ingen autocomplete-model konfigureret**\n\nFor at aktivere autocomplete skal du tilføje en profil med en af disse understøttede udbydere: {{providers}}.\n\n[Åbn indstillinger]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Samlet sessionsomkostning:", - "kilocode:autocomplete.statusBar.tooltip.provider": "Udbyder:", - "kilocode:autocomplete.statusBar.tooltip.model": "Model:", - "kilocode:autocomplete.statusBar.tooltip.profile": "Profil: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "Standard", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "Udførte {{count}} fuldførelser mellem {{startTime}} og {{endTime}}, til en samlet pris på {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Autocomplete leveres af {{model}} via {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Kilo Code autofuldførelse {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Analyserer din kode...", - "kilocode:autocomplete.progress.generating": "Genererer foreslåede redigeringer...", - "kilocode:autocomplete.progress.processing": "Behandler foreslåede redigeringer...", - "kilocode:autocomplete.progress.showing": "Viser foreslåede redigeringer...", - "kilocode:autocomplete.input.title": "Kilo Code: Hurtig opgave", - "kilocode:autocomplete.input.placeholder": "f.eks. 'refaktorer denne funktion, så den bliver mere effektiv'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: Generer foreslåede redigeringer", - "kilocode:autocomplete.commands.displaySuggestions": "Vis foreslåede redigeringer", - "kilocode:autocomplete.commands.cancelSuggestions": "Annuller foreslåede redigeringer", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Anvend aktuel foreslået redigering", - "kilocode:autocomplete.commands.applyAllSuggestions": "Anvend alle foreslåede redigeringer", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: Foreslåede redigeringer", - "kilocode:autocomplete.chatParticipant.fullName": "Kilo Code-agent", - "kilocode:autocomplete.chatParticipant.name": "Agent", - "kilocode:autocomplete.chatParticipant.description": - "Jeg kan hjælpe dig med hurtige opgaver og foreslåede redigeringer.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Kilo Code autofuldførelse blokeres af en konflikt med GitHub Copilot. For at løse dette skal du deaktivere Copilots inline-forslag.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Deaktiver Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/de.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/de.ts index 678170ae51..edd031fa2f 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/de.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/de.ts @@ -3,39 +3,15 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "pausiert", "kilocode:autocomplete.statusBar.warning": "$(warning) Autovervollständigung", "kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autovervollständigung", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autovervollständigung (deaktiviert)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**Kein Autovervollständigungsmodell konfiguriert**\n\nUm die Autovervollständigung zu aktivieren, füge ein Profil mit einem dieser unterstützten Anbieter hinzu: {{providers}}.\n\n[Einstellungen öffnen]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Sitzungskosten gesamt:", - "kilocode:autocomplete.statusBar.tooltip.provider": "Anbieter:", - "kilocode:autocomplete.statusBar.tooltip.model": "Modell:", - "kilocode:autocomplete.statusBar.tooltip.profile": "Profil: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "Standard", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "{{count}} Vervollständigungen zwischen {{startTime}} und {{endTime}} ausgeführt, für Gesamtkosten von {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Autovervollständigungen bereitgestellt von {{model}} über {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Kilo Code Autovervollständigung {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Code wird analysiert...", - "kilocode:autocomplete.progress.generating": "Vorgeschlagene Änderungen werden generiert...", - "kilocode:autocomplete.progress.processing": "Vorgeschlagene Änderungen werden verarbeitet...", - "kilocode:autocomplete.progress.showing": "Vorgeschlagene Änderungen werden angezeigt...", - "kilocode:autocomplete.input.title": "Kilo Code: Schnellaufgabe", - "kilocode:autocomplete.input.placeholder": "z. B. 'diese Funktion effizienter refaktorisieren'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: Vorgeschlagene Änderungen generieren", - "kilocode:autocomplete.commands.displaySuggestions": "Vorgeschlagene Änderungen anzeigen", - "kilocode:autocomplete.commands.cancelSuggestions": "Vorgeschlagene Änderungen abbrechen", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Aktuelle vorgeschlagene Änderung anwenden", - "kilocode:autocomplete.commands.applyAllSuggestions": "Alle vorgeschlagenen Änderungen anwenden", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: Vorgeschlagene Änderungen", - "kilocode:autocomplete.chatParticipant.fullName": "Kilo Code-Agent", - "kilocode:autocomplete.chatParticipant.name": "Assistent", - "kilocode:autocomplete.chatParticipant.description": - "Ich kann dir bei Schnellaufgaben und vorgeschlagenen Änderungen helfen.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Kilo Code Autovervollständigung wird durch einen Konflikt mit GitHub Copilot blockiert. Um dies zu beheben, musst du die Inline-Vorschläge von Copilot deaktivieren.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Copilot deaktivieren", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/en.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/en.ts index e2e2f423dd..f0e2641adc 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/en.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/en.ts @@ -6,37 +6,14 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "snoozed", "kilocode:autocomplete.statusBar.warning": "$(warning) Autocomplete", "kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code Autocomplete", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code Autocomplete (disabled)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**No autocomplete model configured**\n\nTo enable autocomplete, add a profile with one of these supported providers: {{providers}}.\n\n[Open Settings]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Session total cost:", - "kilocode:autocomplete.statusBar.tooltip.provider": "Provider:", - "kilocode:autocomplete.statusBar.tooltip.model": "Model:", - "kilocode:autocomplete.statusBar.tooltip.profile": "Profile: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "Default", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "Performed {{count}} completions between {{startTime}} and {{endTime}}, for a total cost of {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Autocompletions provided by {{model}} via {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Kilo Code Autocomplete {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Analyzing your code...", - "kilocode:autocomplete.progress.generating": "Generating suggested edits...", - "kilocode:autocomplete.progress.processing": "Processing suggested edits...", - "kilocode:autocomplete.progress.showing": "Displaying suggested edits...", - "kilocode:autocomplete.input.title": "Kilo Code: Quick Task", - "kilocode:autocomplete.input.placeholder": "e.g., 'refactor this function to be more efficient'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: Generate Suggested Edits", - "kilocode:autocomplete.commands.displaySuggestions": "Display Suggested Edits", - "kilocode:autocomplete.commands.cancelSuggestions": "Cancel Suggested Edits", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Apply Current Suggested Edit", - "kilocode:autocomplete.commands.applyAllSuggestions": "Apply All Suggested Edits", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: Suggested Edits", - "kilocode:autocomplete.chatParticipant.fullName": "Kilo Code Agent", - "kilocode:autocomplete.chatParticipant.name": "Agent", - "kilocode:autocomplete.chatParticipant.description": "I can help you with quick tasks and suggested edits.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "The Kilo Code Autocomplete is being blocked by a conflict with GitHub Copilot. To fix this, you must disable Copilot's inline suggestions.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Disable Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/es.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/es.ts index 80dcab6a1d..f0a9cd2eda 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/es.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/es.ts @@ -3,38 +3,15 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "pospuesto", "kilocode:autocomplete.statusBar.warning": "$(warning) Autocompletado", "kilocode:autocomplete.statusBar.tooltip.basic": "Autocompletado de Kilo Code", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Autocompletado de Kilo Code (deshabilitado)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**No hay ningún modelo de autocompletado configurado**\n\nPara habilitar el autocompletado, añade un perfil con uno de estos proveedores compatibles: {{providers}}.\n\n[Abrir configuración]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Coste total de la sesión:", - "kilocode:autocomplete.statusBar.tooltip.provider": "Proveedor:", - "kilocode:autocomplete.statusBar.tooltip.model": "Modelo:", - "kilocode:autocomplete.statusBar.tooltip.profile": "Perfil: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "Predeterminado", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "Se realizaron {{count}} completados entre {{startTime}} y {{endTime}}, con un coste total de {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Autocompletados proporcionados por {{model}} mediante {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Autocompletado de Kilo Code {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Analizando tu código...", - "kilocode:autocomplete.progress.generating": "Generando ediciones sugeridas...", - "kilocode:autocomplete.progress.processing": "Procesando ediciones sugeridas...", - "kilocode:autocomplete.progress.showing": "Mostrando ediciones sugeridas...", - "kilocode:autocomplete.input.title": "Kilo Code: Tarea rápida", - "kilocode:autocomplete.input.placeholder": "p. ej., 'refactoriza esta función para que sea más eficiente'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: Generar ediciones sugeridas", - "kilocode:autocomplete.commands.displaySuggestions": "Mostrar ediciones sugeridas", - "kilocode:autocomplete.commands.cancelSuggestions": "Cancelar ediciones sugeridas", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Aplicar edición sugerida actual", - "kilocode:autocomplete.commands.applyAllSuggestions": "Aplicar todas las ediciones sugeridas", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: Ediciones sugeridas", - "kilocode:autocomplete.chatParticipant.fullName": "Agente de Kilo Code", - "kilocode:autocomplete.chatParticipant.name": "Agente", - "kilocode:autocomplete.chatParticipant.description": "Puedo ayudarte con tareas rápidas y ediciones sugeridas.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "El autocompletado de Kilo Code está bloqueado por un conflicto con GitHub Copilot. Para solucionarlo, debes deshabilitar las sugerencias en línea de Copilot.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Deshabilitar Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/fa.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/fa.ts new file mode 100644 index 0000000000..593c89eb68 --- /dev/null +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/fa.ts @@ -0,0 +1,27 @@ +// English runtime translations for autocomplete (kilocode:autocomplete.* namespace) +// Source: src/i18n/locales/en/kilocode.json → "autocomplete" section + +export const dict = { + "kilocode:autocomplete.statusBar.enabled": "$(kilo-logo) تکمیل خودکار", + "kilocode:autocomplete.statusBar.snoozed": "به تعویق افتاده", + "kilocode:autocomplete.statusBar.warning": "$(warning) تکمیل خودکار", + "kilocode:autocomplete.statusBar.tooltip.basic": "تکمیل خودکار Kilo Code", + "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": + "**هیچ مدل تکمیل خودکاری پیکربندی نشده است**\n\nبرای فعال‌سازی تکمیل خودکار، یک پروفایل با یکی از ارائه‌دهندگان پشتیبانی‌شده زیر اضافه کنید: {{providers}}.\n\n[باز کردن تنظیمات]({{command}})", + "kilocode:autocomplete.statusBar.tooltip.completionSummary": + "{{count}} تکمیل بین {{startTime}} و {{endTime}} انجام شد، با هزینه کل {{cost}}.", + "kilocode:autocomplete.statusBar.tooltip.providerInfo": + "تکمیل خودکار توسط {{model}} از طریق {{provider}} ارائه می‌شود.", + "kilocode:autocomplete.statusBar.cost.zero": "۰.۰۰$", + "kilocode:autocomplete.statusBar.cost.lessThanCent": "<۰.۰۱$", + "kilocode:autocomplete.codeAction.title": "Kilo Code: ویرایش‌های پیشنهادی", + "kilocode:autocomplete.incompatibilityExtensionPopup.message": + "تکمیل خودکار Kilo Code به دلیل تعارض با GitHub Copilot مسدود شده است. برای رفع این مشکل، باید پیشنهادات درون‌خطی Copilot را غیرفعال کنید.", + "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "غیرفعال کردن Copilot", + "kilocode:autocomplete.incompatibilityExtensionPopup.disableInlineAssist": "غیرفعال کردن تکمیل خودکار", + "kilocode:autocomplete.creditsExhausted.message": + "تکمیل خودکار Kilo Code متوقف شده است. دلایل احتمالی: حساب Kilo شما اعتبار کافی ندارد، یا کلید API پیکربندی‌شده (BYOK) به سقف مجاز خود رسیده است. برای از سرگیری تکمیل خودکار، اعتبار Kilo اضافه کنید یا تنظیمات کلید API خود را بررسی کنید.", + "kilocode:autocomplete.creditsExhausted.addCredits": "افزودن اعتبار", + "kilocode:autocomplete.authError.message": + "تکمیل خودکار Kilo Code به دلیل مشکل احراز هویت متوقف شده است. دلایل احتمالی: وارد Kilo نشده‌اید، یا کلید API (BYOK) شما نامعتبر یا وارد نشده است. لطفاً دوباره وارد شوید یا تنظیمات کلید API ارائه‌دهنده خود را بررسی کنید.", +} diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/fr.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/fr.ts index 946a7a1c33..e465d9a97b 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/fr.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/fr.ts @@ -3,39 +3,15 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "mis en pause", "kilocode:autocomplete.statusBar.warning": "$(warning) Saisie automatique", "kilocode:autocomplete.statusBar.tooltip.basic": "Saisie automatique Kilo Code", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Saisie automatique Kilo Code (désactivée)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**Aucun modèle de saisie automatique configuré**\n\nPour activer la saisie automatique, ajoutez un profil avec l'un de ces fournisseurs pris en charge : {{providers}}.\n\n[Ouvrir les paramètres]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Coût total de la session :", - "kilocode:autocomplete.statusBar.tooltip.provider": "Fournisseur :", - "kilocode:autocomplete.statusBar.tooltip.model": "Modèle :", - "kilocode:autocomplete.statusBar.tooltip.profile": "Profil : ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "Par défaut", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "{{count}} complétions effectuées entre {{startTime}} et {{endTime}}, pour un coût total de {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Saisies automatiques fournies par {{model}} via {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Saisie automatique Kilo Code {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Analyse de votre code...", - "kilocode:autocomplete.progress.generating": "Génération des modifications suggérées...", - "kilocode:autocomplete.progress.processing": "Traitement des modifications suggérées...", - "kilocode:autocomplete.progress.showing": "Affichage des modifications suggérées...", - "kilocode:autocomplete.input.title": "Kilo Code : tâche rapide", - "kilocode:autocomplete.input.placeholder": "p. ex., 'refactoriser cette fonction pour la rendre plus efficace'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code : générer des modifications suggérées", - "kilocode:autocomplete.commands.displaySuggestions": "Afficher les modifications suggérées", - "kilocode:autocomplete.commands.cancelSuggestions": "Annuler les modifications suggérées", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Appliquer la modification suggérée actuelle", - "kilocode:autocomplete.commands.applyAllSuggestions": "Appliquer toutes les modifications suggérées", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code : modifications suggérées", - "kilocode:autocomplete.chatParticipant.fullName": "Agent Kilo Code", - "kilocode:autocomplete.chatParticipant.name": "Assistant", - "kilocode:autocomplete.chatParticipant.description": - "Je peux vous aider avec des tâches rapides et des modifications suggérées.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "La saisie automatique Kilo Code est bloquée par un conflit avec GitHub Copilot. Pour résoudre ce problème, vous devez désactiver les suggestions en ligne de Copilot.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Désactiver Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/it.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/it.ts index 56a2609079..2919061b8d 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/it.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/it.ts @@ -3,38 +3,15 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "posticipato", "kilocode:autocomplete.statusBar.warning": "$(warning) Completamento automatico", "kilocode:autocomplete.statusBar.tooltip.basic": "Completamento automatico Kilo Code", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Completamento automatico Kilo Code (disabilitato)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**Nessun modello di completamento automatico configurato**\n\nPer abilitare il completamento automatico, aggiungi un profilo con uno di questi provider supportati: {{providers}}.\n\n[Apri impostazioni]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Costo totale della sessione:", - "kilocode:autocomplete.statusBar.tooltip.provider": "Fornitore:", - "kilocode:autocomplete.statusBar.tooltip.model": "Modello:", - "kilocode:autocomplete.statusBar.tooltip.profile": "Profilo: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "Predefinito", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "Eseguiti {{count}} completamenti tra {{startTime}} e {{endTime}}, per un costo totale di {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Completamenti automatici forniti da {{model}} tramite {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Completamento automatico Kilo Code {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Analisi del codice in corso...", - "kilocode:autocomplete.progress.generating": "Generazione delle modifiche suggerite...", - "kilocode:autocomplete.progress.processing": "Elaborazione delle modifiche suggerite...", - "kilocode:autocomplete.progress.showing": "Visualizzazione delle modifiche suggerite...", - "kilocode:autocomplete.input.title": "Kilo Code: attività rapida", - "kilocode:autocomplete.input.placeholder": "es. 'rifattorizza questa funzione per renderla più efficiente'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: genera modifiche suggerite", - "kilocode:autocomplete.commands.displaySuggestions": "Mostra modifiche suggerite", - "kilocode:autocomplete.commands.cancelSuggestions": "Annulla modifiche suggerite", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Applica la modifica suggerita corrente", - "kilocode:autocomplete.commands.applyAllSuggestions": "Applica tutte le modifiche suggerite", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: modifiche suggerite", - "kilocode:autocomplete.chatParticipant.fullName": "Agente Kilo Code", - "kilocode:autocomplete.chatParticipant.name": "Agente", - "kilocode:autocomplete.chatParticipant.description": "Posso aiutarti con attività rapide e modifiche suggerite.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Il completamento automatico di Kilo Code è bloccato da un conflitto con GitHub Copilot. Per risolvere il problema, devi disabilitare i suggerimenti inline di Copilot.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Disabilita Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/ja.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/ja.ts index b0913d9923..e1a44858d8 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/ja.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/ja.ts @@ -3,38 +3,15 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "一時停止中", "kilocode:autocomplete.statusBar.warning": "$(warning) オートコンプリート", "kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code オートコンプリート", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code オートコンプリート (無効)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**オートコンプリートモデルが設定されていません**\n\nオートコンプリートを有効にするには、次の対応プロバイダーのいずれかを含むプロファイルを追加してください: {{providers}}。\n\n[設定を開く]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "セッション合計コスト:", - "kilocode:autocomplete.statusBar.tooltip.provider": "プロバイダー:", - "kilocode:autocomplete.statusBar.tooltip.model": "モデル:", - "kilocode:autocomplete.statusBar.tooltip.profile": "プロファイル: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "デフォルト", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "{{startTime}} から {{endTime}} までに {{count}} 件の補完を実行し、合計コストは {{cost}} でした。", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "オートコンプリートは {{provider}} 経由の {{model}} によって提供されています。", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Kilo Code オートコンプリート {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "コードを解析しています...", - "kilocode:autocomplete.progress.generating": "提案された編集を生成しています...", - "kilocode:autocomplete.progress.processing": "提案された編集を処理しています...", - "kilocode:autocomplete.progress.showing": "提案された編集を表示しています...", - "kilocode:autocomplete.input.title": "Kilo Code: クイックタスク", - "kilocode:autocomplete.input.placeholder": "例: 'この関数をより効率的にリファクタリングする'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: 提案された編集を生成", - "kilocode:autocomplete.commands.displaySuggestions": "提案された編集を表示", - "kilocode:autocomplete.commands.cancelSuggestions": "提案された編集をキャンセル", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "現在の提案された編集を適用", - "kilocode:autocomplete.commands.applyAllSuggestions": "すべての提案された編集を適用", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: 提案された編集", - "kilocode:autocomplete.chatParticipant.fullName": "Kilo Code エージェント", - "kilocode:autocomplete.chatParticipant.name": "エージェント", - "kilocode:autocomplete.chatParticipant.description": "クイックタスクや提案された編集をお手伝いできます。", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Kilo Code オートコンプリートは GitHub Copilot との競合によりブロックされています。修正するには、Copilot のインライン提案を無効にする必要があります。", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Copilot を無効化", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/ko.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/ko.ts index ed3b47c426..a0a6c8391b 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/ko.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/ko.ts @@ -3,37 +3,14 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "일시 중지됨", "kilocode:autocomplete.statusBar.warning": "$(warning) 자동 완성", "kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code 자동 완성", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code 자동 완성(비활성화됨)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**자동 완성 모델이 구성되지 않았습니다**\n\n자동 완성을 활성화하려면 다음 지원 제공자 중 하나가 포함된 프로필을 추가하세요: {{providers}}.\n\n[설정 열기]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "세션 총 비용:", - "kilocode:autocomplete.statusBar.tooltip.provider": "제공자:", - "kilocode:autocomplete.statusBar.tooltip.model": "모델:", - "kilocode:autocomplete.statusBar.tooltip.profile": "프로필: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "기본값", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "{{startTime}}부터 {{endTime}}까지 {{count}}개의 완성을 수행했으며 총 비용은 {{cost}}입니다.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "자동 완성은 {{provider}}를 통해 {{model}}에서 제공됩니다.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Kilo Code 자동 완성 {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "코드를 분석하는 중...", - "kilocode:autocomplete.progress.generating": "제안된 편집을 생성하는 중...", - "kilocode:autocomplete.progress.processing": "제안된 편집을 처리하는 중...", - "kilocode:autocomplete.progress.showing": "제안된 편집을 표시하는 중...", - "kilocode:autocomplete.input.title": "Kilo Code: 빠른 작업", - "kilocode:autocomplete.input.placeholder": "예: '이 함수를 더 효율적으로 리팩터링'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: 제안된 편집 생성", - "kilocode:autocomplete.commands.displaySuggestions": "제안된 편집 표시", - "kilocode:autocomplete.commands.cancelSuggestions": "제안된 편집 취소", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "현재 제안된 편집 적용", - "kilocode:autocomplete.commands.applyAllSuggestions": "모든 제안된 편집 적용", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: 제안된 편집", - "kilocode:autocomplete.chatParticipant.fullName": "Kilo Code 에이전트", - "kilocode:autocomplete.chatParticipant.name": "에이전트", - "kilocode:autocomplete.chatParticipant.description": "빠른 작업과 제안된 편집을 도와드릴 수 있습니다.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Kilo Code 자동 완성이 GitHub Copilot과의 충돌로 차단되고 있습니다. 이 문제를 해결하려면 Copilot의 인라인 제안을 비활성화해야 합니다.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Copilot 비활성화", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/nl.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/nl.ts index f4feddfe8a..181a0935dd 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/nl.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/nl.ts @@ -3,38 +3,15 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "gesluimerd", "kilocode:autocomplete.statusBar.warning": "$(warning) Automatisch aanvullen", "kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code automatisch aanvullen", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code automatisch aanvullen (uitgeschakeld)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**Geen model voor automatisch aanvullen geconfigureerd**\n\nVoeg een profiel toe met een van deze ondersteunde providers om automatisch aanvullen in te schakelen: {{providers}}.\n\n[Instellingen openen]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Totale sessiekosten:", - "kilocode:autocomplete.statusBar.tooltip.provider": "Aanbieder:", - "kilocode:autocomplete.statusBar.tooltip.model": "Model:", - "kilocode:autocomplete.statusBar.tooltip.profile": "Profiel: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "Standaard", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "{{count}} aanvullingen uitgevoerd tussen {{startTime}} en {{endTime}}, voor totale kosten van {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Automatische aanvullingen geleverd door {{model}} via {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Kilo Code automatisch aanvullen {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Je code wordt geanalyseerd...", - "kilocode:autocomplete.progress.generating": "Voorgestelde bewerkingen worden gegenereerd...", - "kilocode:autocomplete.progress.processing": "Voorgestelde bewerkingen worden verwerkt...", - "kilocode:autocomplete.progress.showing": "Voorgestelde bewerkingen worden weergegeven...", - "kilocode:autocomplete.input.title": "Kilo Code: Snelle taak", - "kilocode:autocomplete.input.placeholder": "bijv. 'refactor deze functie zodat deze efficiënter is'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: Voorgestelde bewerkingen genereren", - "kilocode:autocomplete.commands.displaySuggestions": "Voorgestelde bewerkingen weergeven", - "kilocode:autocomplete.commands.cancelSuggestions": "Voorgestelde bewerkingen annuleren", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Huidige voorgestelde bewerking toepassen", - "kilocode:autocomplete.commands.applyAllSuggestions": "Alle voorgestelde bewerkingen toepassen", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: Voorgestelde bewerkingen", - "kilocode:autocomplete.chatParticipant.fullName": "Kilo Code-agent", - "kilocode:autocomplete.chatParticipant.name": "Assistent", - "kilocode:autocomplete.chatParticipant.description": "Ik kan helpen met snelle taken en voorgestelde bewerkingen.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Kilo Code automatisch aanvullen wordt geblokkeerd door een conflict met GitHub Copilot. Schakel de inline suggesties van Copilot uit om dit op te lossen.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Copilot uitschakelen", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/no.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/no.ts index aa50c748f8..dcd89df7e0 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/no.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/no.ts @@ -3,38 +3,14 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "utsatt", "kilocode:autocomplete.statusBar.warning": "$(warning) Autofullføring", "kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code autofullføring", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code autofullføring (deaktivert)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**Ingen autofullføringsmodell konfigurert**\n\nFor å aktivere autofullføring, legg til en profil med en av disse støttede leverandørene: {{providers}}.\n\n[Åpne innstillinger]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Total kostnad for økten:", - "kilocode:autocomplete.statusBar.tooltip.provider": "Leverandør:", - "kilocode:autocomplete.statusBar.tooltip.model": "Modell:", - "kilocode:autocomplete.statusBar.tooltip.profile": "Profil: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "Standard", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "Utførte {{count}} fullføringer mellom {{startTime}} og {{endTime}}, til en total kostnad på {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Autofullføringer leveres av {{model}} via {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Kilo Code autofullføring {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Analyserer koden din...", - "kilocode:autocomplete.progress.generating": "Genererer foreslåtte redigeringer...", - "kilocode:autocomplete.progress.processing": "Behandler foreslåtte redigeringer...", - "kilocode:autocomplete.progress.showing": "Viser foreslåtte redigeringer...", - "kilocode:autocomplete.input.title": "Kilo Code: Hurtig oppgave", - "kilocode:autocomplete.input.placeholder": "f.eks. 'refaktorer denne funksjonen så den blir mer effektiv'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: Generer foreslåtte redigeringer", - "kilocode:autocomplete.commands.displaySuggestions": "Vis foreslåtte redigeringer", - "kilocode:autocomplete.commands.cancelSuggestions": "Avbryt foreslåtte redigeringer", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Bruk gjeldende foreslåtte redigering", - "kilocode:autocomplete.commands.applyAllSuggestions": "Bruk alle foreslåtte redigeringer", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: Foreslåtte redigeringer", - "kilocode:autocomplete.chatParticipant.fullName": "Kilo Code-agent", - "kilocode:autocomplete.chatParticipant.name": "Agent", - "kilocode:autocomplete.chatParticipant.description": - "Jeg kan hjelpe deg med hurtige oppgaver og foreslåtte redigeringer.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Kilo Code autofullføring blokkeres av en konflikt med GitHub Copilot. For å fikse dette må du deaktivere Copilots inline-forslag.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Deaktiver Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/pl.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/pl.ts index 1ade66e91d..24168a6a36 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/pl.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/pl.ts @@ -3,37 +3,14 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "wstrzymane", "kilocode:autocomplete.statusBar.warning": "$(warning) Autouzupełnianie", "kilocode:autocomplete.statusBar.tooltip.basic": "Autouzupełnianie Kilo Code", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Autouzupełnianie Kilo Code (wyłączone)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**Nie skonfigurowano modelu autouzupełniania**\n\nAby włączyć autouzupełnianie, dodaj profil z jednym z tych obsługiwanych dostawców: {{providers}}.\n\n[Otwórz ustawienia]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Łączny koszt sesji:", - "kilocode:autocomplete.statusBar.tooltip.provider": "Dostawca:", - "kilocode:autocomplete.statusBar.tooltip.model": "Model:", - "kilocode:autocomplete.statusBar.tooltip.profile": "Profil: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "Domyślny", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "Wykonano {{count}} uzupełnień między {{startTime}} a {{endTime}}, za łączny koszt {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Autouzupełnianie zapewnia {{model}} przez {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Autouzupełnianie Kilo Code {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Analizowanie kodu...", - "kilocode:autocomplete.progress.generating": "Generowanie sugerowanych edycji...", - "kilocode:autocomplete.progress.processing": "Przetwarzanie sugerowanych edycji...", - "kilocode:autocomplete.progress.showing": "Wyświetlanie sugerowanych edycji...", - "kilocode:autocomplete.input.title": "Kilo Code: Szybkie zadanie", - "kilocode:autocomplete.input.placeholder": "np. 'zrefaktoryzuj tę funkcję, aby była bardziej wydajna'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: Generuj sugerowane edycje", - "kilocode:autocomplete.commands.displaySuggestions": "Wyświetl sugerowane edycje", - "kilocode:autocomplete.commands.cancelSuggestions": "Anuluj sugerowane edycje", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Zastosuj bieżącą sugerowaną edycję", - "kilocode:autocomplete.commands.applyAllSuggestions": "Zastosuj wszystkie sugerowane edycje", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: Sugerowane edycje", - "kilocode:autocomplete.chatParticipant.fullName": "Agent Kilo Code", - "kilocode:autocomplete.chatParticipant.name": "Agent", - "kilocode:autocomplete.chatParticipant.description": "Mogę pomóc w szybkich zadaniach i sugerowanych edycjach.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Autouzupełnianie Kilo Code jest blokowane przez konflikt z GitHub Copilot. Aby to naprawić, musisz wyłączyć sugestie inline Copilota.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Wyłącz Copilota", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/ru.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/ru.ts index 9b3915f4ae..18bd1aa41b 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/ru.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/ru.ts @@ -3,38 +3,15 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "отложено", "kilocode:autocomplete.statusBar.warning": "$(warning) Автодополнение", "kilocode:autocomplete.statusBar.tooltip.basic": "Автодополнение Kilo Code", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Автодополнение Kilo Code (отключено)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**Модель автодополнения не настроена**\n\nЧтобы включить автодополнение, добавьте профиль с одним из этих поддерживаемых провайдеров: {{providers}}.\n\n[Открыть настройки]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Общая стоимость сеанса:", - "kilocode:autocomplete.statusBar.tooltip.provider": "Провайдер:", - "kilocode:autocomplete.statusBar.tooltip.model": "Модель:", - "kilocode:autocomplete.statusBar.tooltip.profile": "Профиль: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "По умолчанию", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "Выполнено {{count}} дополнений с {{startTime}} до {{endTime}}, общая стоимость {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Автодополнения предоставляются {{model}} через {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Автодополнение Kilo Code {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Анализ кода...", - "kilocode:autocomplete.progress.generating": "Создание предлагаемых правок...", - "kilocode:autocomplete.progress.processing": "Обработка предлагаемых правок...", - "kilocode:autocomplete.progress.showing": "Отображение предлагаемых правок...", - "kilocode:autocomplete.input.title": "Kilo Code: Быстрая задача", - "kilocode:autocomplete.input.placeholder": "например: 'отрефакторить эту функцию, чтобы она была эффективнее'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: Создать предлагаемые правки", - "kilocode:autocomplete.commands.displaySuggestions": "Показать предлагаемые правки", - "kilocode:autocomplete.commands.cancelSuggestions": "Отменить предлагаемые правки", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Применить текущую предлагаемую правку", - "kilocode:autocomplete.commands.applyAllSuggestions": "Применить все предлагаемые правки", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: Предлагаемые правки", - "kilocode:autocomplete.chatParticipant.fullName": "Агент Kilo Code", - "kilocode:autocomplete.chatParticipant.name": "Агент", - "kilocode:autocomplete.chatParticipant.description": "Я могу помочь с быстрыми задачами и предлагаемыми правками.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Автодополнение Kilo Code блокируется из-за конфликта с GitHub Copilot. Чтобы исправить это, необходимо отключить встроенные предложения Copilot.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Отключить Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/th.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/th.ts index 45f999ce76..77f17f0d04 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/th.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/th.ts @@ -3,38 +3,15 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "พักไว้", "kilocode:autocomplete.statusBar.warning": "$(warning) เติมข้อความอัตโนมัติ", "kilocode:autocomplete.statusBar.tooltip.basic": "การเติมข้อความอัตโนมัติของ Kilo Code", - "kilocode:autocomplete.statusBar.tooltip.disabled": "การเติมข้อความอัตโนมัติของ Kilo Code (ปิดใช้งาน)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**ยังไม่ได้กำหนดค่าโมเดลเติมข้อความอัตโนมัติ**\n\nหากต้องการเปิดใช้การเติมข้อความอัตโนมัติ ให้เพิ่มโปรไฟล์ที่มีผู้ให้บริการที่รองรับต่อไปนี้: {{providers}}\n\n[เปิดการตั้งค่า]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "ค่าใช้จ่ายรวมของเซสชัน:", - "kilocode:autocomplete.statusBar.tooltip.provider": "ผู้ให้บริการ:", - "kilocode:autocomplete.statusBar.tooltip.model": "โมเดล:", - "kilocode:autocomplete.statusBar.tooltip.profile": "โปรไฟล์: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "ค่าเริ่มต้น", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "ดำเนินการเติมข้อความ {{count}} ครั้งระหว่าง {{startTime}} ถึง {{endTime}} โดยมีค่าใช้จ่ายรวม {{cost}}", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "การเติมข้อความอัตโนมัติให้บริการโดย {{model}} ผ่าน {{provider}}", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "การเติมข้อความอัตโนมัติของ Kilo Code {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "กำลังวิเคราะห์โค้ดของคุณ...", - "kilocode:autocomplete.progress.generating": "กำลังสร้างการแก้ไขที่แนะนำ...", - "kilocode:autocomplete.progress.processing": "กำลังประมวลผลการแก้ไขที่แนะนำ...", - "kilocode:autocomplete.progress.showing": "กำลังแสดงการแก้ไขที่แนะนำ...", - "kilocode:autocomplete.input.title": "Kilo Code: งานด่วน", - "kilocode:autocomplete.input.placeholder": "เช่น 'รีแฟกเตอร์ฟังก์ชันนี้ให้มีประสิทธิภาพมากขึ้น'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: สร้างการแก้ไขที่แนะนำ", - "kilocode:autocomplete.commands.displaySuggestions": "แสดงการแก้ไขที่แนะนำ", - "kilocode:autocomplete.commands.cancelSuggestions": "ยกเลิกการแก้ไขที่แนะนำ", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "ใช้การแก้ไขที่แนะนำปัจจุบัน", - "kilocode:autocomplete.commands.applyAllSuggestions": "ใช้การแก้ไขที่แนะนำทั้งหมด", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: การแก้ไขที่แนะนำ", - "kilocode:autocomplete.chatParticipant.fullName": "ตัวแทน Kilo Code", - "kilocode:autocomplete.chatParticipant.name": "ตัวแทน", - "kilocode:autocomplete.chatParticipant.description": "ฉันช่วยคุณทำงานด่วนและการแก้ไขที่แนะนำได้", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "การเติมข้อความอัตโนมัติของ Kilo Code ถูกบล็อกเนื่องจากขัดแย้งกับ GitHub Copilot หากต้องการแก้ไข คุณต้องปิดข้อเสนอแนะแบบอินไลน์ของ Copilot", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "ปิดใช้งาน Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/tr.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/tr.ts index 30908d7257..4313216007 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/tr.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/tr.ts @@ -3,39 +3,15 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "ertelendi", "kilocode:autocomplete.statusBar.warning": "$(warning) Otomatik tamamlama", "kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code otomatik tamamlama", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code otomatik tamamlama (devre dışı)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**Otomatik tamamlama modeli yapılandırılmadı**\n\nOtomatik tamamlamayı etkinleştirmek için desteklenen şu sağlayıcılardan birini içeren bir profil ekleyin: {{providers}}.\n\n[Ayarları Aç]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Oturum toplam maliyeti:", - "kilocode:autocomplete.statusBar.tooltip.provider": "Sağlayıcı:", - "kilocode:autocomplete.statusBar.tooltip.model": "Model:", - "kilocode:autocomplete.statusBar.tooltip.profile": "Profil: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "Varsayılan", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "{{startTime}} ile {{endTime}} arasında {{count}} tamamlama yapıldı, toplam maliyet {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Otomatik tamamlamalar {{provider}} üzerinden {{model}} tarafından sağlanır.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Kilo Code otomatik tamamlama {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Kodunuz analiz ediliyor...", - "kilocode:autocomplete.progress.generating": "Önerilen düzenlemeler oluşturuluyor...", - "kilocode:autocomplete.progress.processing": "Önerilen düzenlemeler işleniyor...", - "kilocode:autocomplete.progress.showing": "Önerilen düzenlemeler gösteriliyor...", - "kilocode:autocomplete.input.title": "Kilo Code: Hızlı görev", - "kilocode:autocomplete.input.placeholder": "örn. 'bu fonksiyonu daha verimli olacak şekilde yeniden düzenle'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: Önerilen düzenlemeler oluştur", - "kilocode:autocomplete.commands.displaySuggestions": "Önerilen düzenlemeleri göster", - "kilocode:autocomplete.commands.cancelSuggestions": "Önerilen düzenlemeleri iptal et", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Geçerli önerilen düzenlemeyi uygula", - "kilocode:autocomplete.commands.applyAllSuggestions": "Tüm önerilen düzenlemeleri uygula", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: Önerilen düzenlemeler", - "kilocode:autocomplete.chatParticipant.fullName": "Kilo Code aracı", - "kilocode:autocomplete.chatParticipant.name": "Aracı", - "kilocode:autocomplete.chatParticipant.description": - "Hızlı görevler ve önerilen düzenlemeler konusunda yardımcı olabilirim.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Kilo Code otomatik tamamlama, GitHub Copilot ile çakışma nedeniyle engelleniyor. Bunu düzeltmek için Copilot'un satır içi önerilerini devre dışı bırakmalısınız.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Copilot'u devre dışı bırak", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/uk.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/uk.ts index fe70edbd80..fb70d04b77 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/uk.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/uk.ts @@ -3,38 +3,14 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "відкладено", "kilocode:autocomplete.statusBar.warning": "$(warning) Автодоповнення", "kilocode:autocomplete.statusBar.tooltip.basic": "Автодоповнення Kilo Code", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Автодоповнення Kilo Code (вимкнено)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**Модель автодоповнення не налаштована**\n\nЩоб увімкнути автодоповнення, додайте профіль з одним із цих підтримуваних постачальників: {{providers}}.\n\n[Відкрити налаштування]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "Загальна вартість сеансу:", - "kilocode:autocomplete.statusBar.tooltip.provider": "Постачальник:", - "kilocode:autocomplete.statusBar.tooltip.model": "Модель:", - "kilocode:autocomplete.statusBar.tooltip.profile": "Профіль: ", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "За замовчуванням", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "Виконано {{count}} доповнень між {{startTime}} і {{endTime}}, із загальною вартістю {{cost}}.", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "Автодоповнення надає {{model}} через {{provider}}.", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Автодоповнення Kilo Code {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "Аналіз вашого коду...", - "kilocode:autocomplete.progress.generating": "Створення запропонованих змін...", - "kilocode:autocomplete.progress.processing": "Обробка запропонованих змін...", - "kilocode:autocomplete.progress.showing": "Показ запропонованих змін...", - "kilocode:autocomplete.input.title": "Kilo Code: Швидке завдання", - "kilocode:autocomplete.input.placeholder": "наприклад, 'рефакторинг цієї функції, щоб вона була ефективнішою'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code: Створити запропоновані зміни", - "kilocode:autocomplete.commands.displaySuggestions": "Показати запропоновані зміни", - "kilocode:autocomplete.commands.cancelSuggestions": "Скасувати запропоновані зміни", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "Застосувати поточну запропоновану зміну", - "kilocode:autocomplete.commands.applyAllSuggestions": "Застосувати всі запропоновані зміни", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code: Запропоновані зміни", - "kilocode:autocomplete.chatParticipant.fullName": "Агент Kilo Code", - "kilocode:autocomplete.chatParticipant.name": "Агент", - "kilocode:autocomplete.chatParticipant.description": - "Я можу допомогти зі швидкими завданнями та запропонованими змінами.", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Автодоповнення Kilo Code блокується через конфлікт із GitHub Copilot. Щоб виправити це, потрібно вимкнути вбудовані пропозиції Copilot.", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Вимкнути Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/zh.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/zh.ts index 1ca452b68e..3a87451e4d 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/zh.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/zh.ts @@ -3,37 +3,14 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "已暂停", "kilocode:autocomplete.statusBar.warning": "$(warning) 自动补全", "kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code 自动补全", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code 自动补全(已禁用)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**未配置自动补全模型**\n\n要启用自动补全,请添加一个包含以下受支持提供商之一的配置文件:{{providers}}。\n\n[打开设置]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "会话总成本:", - "kilocode:autocomplete.statusBar.tooltip.provider": "提供商:", - "kilocode:autocomplete.statusBar.tooltip.model": "模型:", - "kilocode:autocomplete.statusBar.tooltip.profile": "配置文件:", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "默认", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "在 {{startTime}} 到 {{endTime}} 之间执行了 {{count}} 次补全,总成本为 {{cost}}。", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "自动补全由 {{provider}} 通过 {{model}} 提供。", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Kilo Code 自动补全 {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "正在分析你的代码...", - "kilocode:autocomplete.progress.generating": "正在生成建议的编辑...", - "kilocode:autocomplete.progress.processing": "正在处理建议的编辑...", - "kilocode:autocomplete.progress.showing": "正在显示建议的编辑...", - "kilocode:autocomplete.input.title": "Kilo Code:快速任务", - "kilocode:autocomplete.input.placeholder": "例如:'重构此函数以提高效率'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code:生成建议的编辑", - "kilocode:autocomplete.commands.displaySuggestions": "显示建议的编辑", - "kilocode:autocomplete.commands.cancelSuggestions": "取消建议的编辑", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "应用当前建议的编辑", - "kilocode:autocomplete.commands.applyAllSuggestions": "应用所有建议的编辑", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code:建议的编辑", - "kilocode:autocomplete.chatParticipant.fullName": "Kilo Code 代理", - "kilocode:autocomplete.chatParticipant.name": "代理", - "kilocode:autocomplete.chatParticipant.description": "我可以帮助你处理快速任务和建议的编辑。", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Kilo Code 自动补全因与 GitHub Copilot 冲突而被阻止。要修复此问题,必须禁用 Copilot 的内联建议。", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "禁用 Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/autocomplete/zht.ts b/packages/kilo-vscode/src/services/i18n/autocomplete/zht.ts index ab8f3177c6..38900fcf58 100644 --- a/packages/kilo-vscode/src/services/i18n/autocomplete/zht.ts +++ b/packages/kilo-vscode/src/services/i18n/autocomplete/zht.ts @@ -3,37 +3,14 @@ export const dict = { "kilocode:autocomplete.statusBar.snoozed": "已暫停", "kilocode:autocomplete.statusBar.warning": "$(warning) 自動完成", "kilocode:autocomplete.statusBar.tooltip.basic": "Kilo Code 自動完成", - "kilocode:autocomplete.statusBar.tooltip.disabled": "Kilo Code 自動完成(已停用)", "kilocode:autocomplete.statusBar.tooltip.noUsableProvider": "**尚未設定自動完成模型**\n\n若要啟用自動完成,請新增包含下列其中一個支援提供者的設定檔:{{providers}}。\n\n[開啟設定]({{command}})", - "kilocode:autocomplete.statusBar.tooltip.sessionTotal": "工作階段總成本:", - "kilocode:autocomplete.statusBar.tooltip.provider": "提供者:", - "kilocode:autocomplete.statusBar.tooltip.model": "模型:", - "kilocode:autocomplete.statusBar.tooltip.profile": "設定檔:", - "kilocode:autocomplete.statusBar.tooltip.defaultProfile": "預設", "kilocode:autocomplete.statusBar.tooltip.completionSummary": "在 {{startTime}} 到 {{endTime}} 之間執行了 {{count}} 次完成,總成本為 {{cost}}。", "kilocode:autocomplete.statusBar.tooltip.providerInfo": "自動完成由 {{provider}} 透過 {{model}} 提供。", "kilocode:autocomplete.statusBar.cost.zero": "$0.00", "kilocode:autocomplete.statusBar.cost.lessThanCent": "<$0.01", - "kilocode:autocomplete.toggleMessage": "Kilo Code 自動完成 {{status}}", - "kilocode:autocomplete.progress.title": "Kilo Code", - "kilocode:autocomplete.progress.analyzing": "正在分析你的程式碼...", - "kilocode:autocomplete.progress.generating": "正在產生建議的編輯...", - "kilocode:autocomplete.progress.processing": "正在處理建議的編輯...", - "kilocode:autocomplete.progress.showing": "正在顯示建議的編輯...", - "kilocode:autocomplete.input.title": "Kilo Code:快速工作", - "kilocode:autocomplete.input.placeholder": "例如:'重構此函式以提高效率'", - "kilocode:autocomplete.commands.generateSuggestions": "Kilo Code:產生建議的編輯", - "kilocode:autocomplete.commands.displaySuggestions": "顯示建議的編輯", - "kilocode:autocomplete.commands.cancelSuggestions": "取消建議的編輯", - "kilocode:autocomplete.commands.applyCurrentSuggestion": "套用目前建議的編輯", - "kilocode:autocomplete.commands.applyAllSuggestions": "套用所有建議的編輯", - "kilocode:autocomplete.commands.category": "Kilo Code", "kilocode:autocomplete.codeAction.title": "Kilo Code:建議的編輯", - "kilocode:autocomplete.chatParticipant.fullName": "Kilo Code 代理程式", - "kilocode:autocomplete.chatParticipant.name": "代理程式", - "kilocode:autocomplete.chatParticipant.description": "我可以協助你處理快速工作和建議的編輯。", "kilocode:autocomplete.incompatibilityExtensionPopup.message": "Kilo Code 自動完成因與 GitHub Copilot 衝突而被封鎖。若要修正此問題,必須停用 Copilot 的內嵌建議。", "kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "停用 Copilot", diff --git a/packages/kilo-vscode/src/services/i18n/fa.ts b/packages/kilo-vscode/src/services/i18n/fa.ts new file mode 100644 index 0000000000..e8daa62e10 --- /dev/null +++ b/packages/kilo-vscode/src/services/i18n/fa.ts @@ -0,0 +1,7 @@ +import { dict as autocompleteDict } from "./autocomplete/fa" + +export { autocompleteDict } + +export const dict = { + ...autocompleteDict, +} as const diff --git a/packages/kilo-vscode/src/services/i18n/index.ts b/packages/kilo-vscode/src/services/i18n/index.ts index 4da8dda292..1090b84b39 100644 --- a/packages/kilo-vscode/src/services/i18n/index.ts +++ b/packages/kilo-vscode/src/services/i18n/index.ts @@ -6,6 +6,7 @@ import { dict as de } from "./de" import { dict as en } from "./en" import { type dict as enDict } from "./en" import { dict as es } from "./es" +import { dict as fa } from "./fa" import { dict as fr } from "./fr" import { dict as it } from "./it" import { dict as ja } from "./ja" @@ -28,6 +29,7 @@ const bundles: Record> = { de, en, es, + fa, fr, it, ja, diff --git a/packages/kilo-vscode/tests/accessibility.spec.ts b/packages/kilo-vscode/tests/accessibility.spec.ts index 151c71efb0..4df3806db5 100644 --- a/packages/kilo-vscode/tests/accessibility.spec.ts +++ b/packages/kilo-vscode/tests/accessibility.spec.ts @@ -13,6 +13,7 @@ const STORIES = [ { id: "settings--providers-configure", name: "Settings / providers empty state" }, { id: "marketplace--empty-list", name: "Marketplace / empty state" }, { id: "agentmanager--sidebar-search-open", name: "Agent Manager / sidebar search" }, + { id: "agentmanager--side-terminal-panel-empty", name: "Agent Manager / side terminal" }, { id: "session-tabs--switcher-open", name: "Session tabs / switcher" }, ] diff --git a/packages/kilo-vscode/tests/package.json b/packages/kilo-vscode/tests/package.json index 38105c8a8f..52e607fbd4 100644 --- a/packages/kilo-vscode/tests/package.json +++ b/packages/kilo-vscode/tests/package.json @@ -1,6 +1,6 @@ { "type": "module", - "version": "7.4.16", + "version": "7.4.17", "dependencies": {}, "devDependencies": {}, "peerDependencies": {} diff --git a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts index f0301e4abb..6012fd4128 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-arch.test.ts @@ -11,6 +11,7 @@ import { describe, it, expect } from "bun:test" import fs from "node:fs" import path from "node:path" import { Project, SyntaxKind } from "ts-morph" +import { WorktreeImporter } from "../../src/agent-manager/worktree-importer" const ROOT = path.resolve(import.meta.dir, "../..") const KILO_PROVIDER_FILE = path.join(ROOT, "src/KiloProvider.ts") @@ -49,6 +50,8 @@ const TSX_FILES = [ path.join(ROOT, "webview-ui/agent-manager/ProjectBranchDialog.tsx"), path.join(ROOT, "webview-ui/agent-manager/tab-rendering.tsx"), path.join(ROOT, "webview-ui/agent-manager/terminal/TerminalTab.tsx"), + path.join(ROOT, "webview-ui/agent-manager/terminal/SideTerminalPanel.tsx"), + path.join(ROOT, "webview-ui/agent-manager/terminal/TerminalDestinationButton.tsx"), path.join(ROOT, "webview-ui/agent-manager/terminal/SortableTerminalTab.tsx"), path.join(ROOT, "webview-ui/agent-manager/terminal/render.tsx"), path.join(ROOT, "webview-ui/diff-virtual/DiffVirtualApp.tsx"), @@ -231,8 +234,6 @@ describe("Agent Manager Provider Messages", () => { "agentManager.forgetSession", "agentManager.importFromBranch", "agentManager.importFromPR", - "agentManager.importExternalWorktree", - "agentManager.importAllExternalWorktrees", "agentManager.createSection", "agentManager.moveToSection", ] @@ -466,6 +467,7 @@ describe("Agent Manager Provider — onMessage routing", () => { "agentManager.stopRunScript", "agentManager.showTerminal", "agentManager.showLocalTerminal", + "agentManager.showWorktreeTerminal", "agentManager.showExistingLocalTerminal", "agentManager.requestRepoInfo", "agentManager.requestState", @@ -490,7 +492,6 @@ describe("Agent Manager Provider — onMessage routing", () => { const text = body("onSessionMessage") const show = text.indexOf("this.terminalManager.prepareContext(m.sessionID)") expect(show).toBeGreaterThan(-1) - expect(text).not.toContain("!this.terminalManager.hasActiveTerminal()") expect(text).toContain('type: "terminalContextError"') }) @@ -599,8 +600,6 @@ describe("Agent Manager Provider — onMessage routing", () => { const pushIdx = text.indexOf("this.pushState()") const readyIdx = text.indexOf("agentManager.worktreeSetup") expect(pushIdx, "pushState must come before worktreeSetup").toBeLessThan(readyIdx) - // Must also send sessionMeta so the webview knows the branch/path - expect(text).toContain("agentManager.sessionMeta") }) // -- agentManager.requestState in non-git workspace ------------------------- @@ -635,12 +634,61 @@ describe("Agent Manager Provider — onMessage routing", () => { it("worktree import behavior lives in the cohesive importer", () => { const text = importer() - const providerText = body("onImportMessage") - expect(text).toContain("class WorktreeImporter") - expect(text).toContain("createFromPR") - expect(text).toContain("listExternalWorktrees") - expect(text).toContain("createWorktree") - expect(providerText).toContain("this.importer") + for (const value of ["createFromPR", "createWorktree", "this.busy()"]) expect(text).toContain(value) + expect(body("onImportMessage")).toContain("this.importer") + }) + + it("preserves branch and PR import ordering and rollback", async () => { + const run = async (kind: "branch" | "pr", fail?: "setup" | "duplicate") => { + const events: string[] = [] + const create = async () => { + events.push("create") + if (fail === "duplicate") throw new Error("already checked out") + return { branch: "topic", path: "/repo/topic", parentBranch: "main" } + } + const importer = new WorktreeImporter({ + manager: () => + ({ createWorktree: create, createFromPR: create, removeWorktree: async () => events.push("disk") }) as never, + state: () => + ({ + addWorktree: (input: { branchOwned: boolean }) => ({ + id: events.push(`add:${input.branchOwned}`) ? "worktree" : "", + }), + addSession: () => events.push("state-session"), + removeWorktree: () => events.push("state-remove"), + }) as never, + post: (msg) => events.push("message" in msg ? String(msg.message) : msg.type), + push: () => events.push("push"), + setup: async () => { + events.push("setup") + if (fail === "setup") throw new Error("setup failed") + }, + session: async () => (events.push("session"), { id: "session" }) as never, + register: () => events.push("register"), + ready: () => events.push("ready"), + log: () => events.push("log"), + }) + const action = () => (kind === "branch" ? importer.branch("topic") : importer.pr("https://example.test/pull/1")) + await action() + if (fail === "setup") await action() + return events.join("|") + } + for (const kind of ["branch", "pr"] as const) { + const branch = kind === "branch" + const creating = branch ? "Creating worktree from branch..." : "Resolving PR..." + const setup = branch ? "Running setup script..." : "Setting up worktree..." + const success = branch ? "Opened branch topic" : "Opened PR branch topic" + expect(await run(kind)).toBe( + `${creating}|create|add:false|push|${setup}|setup|session|state-session|register|ready|${success}|log`, + ) + expect(await run(kind, "setup")).toBe( + `${creating}|create|add:false|push|${setup}|setup|state-remove|disk|push|setup failed|setup failed|${creating}|create|add:false|push|${setup}|setup|state-remove|disk|push|setup failed|setup failed`, + ) + const duplicate = branch + ? 'Branch "topic" is already checked out in another worktree' + : "This PR's branch is already checked out in another worktree" + expect(await run(kind, "duplicate")).toBe(`${creating}|create|${duplicate}|${duplicate}`) + } }) }) @@ -856,6 +904,10 @@ const VSCODE_ALLOWED: Record = { "terminal-font.ts": { note: "vscode config reader for integrated terminal font settings", }, + // Reads + watches the terminal button destination setting + "terminal-destination.ts": { + note: "vscode config reader for the terminal destination setting", + }, } /** @@ -946,70 +998,73 @@ describe("Agent Manager — VS Code import boundary", () => { }) }) -// --------------------------------------------------------------------------- -// Provider chain parity — sidebar App.tsx vs AgentManagerApp.tsx -// -// The agent manager reuses ChatView (and therefore MessageList, etc.) from the -// sidebar. Any context provider that ChatView's tree may call useXxx() on must -// also be present in the agent manager's provider chain. A missing provider -// crashes the entire SolidJS component tree silently. -// -// Regression: PR #7473 moved KiloNotifications into MessageList. It calls -// useNotifications(), but NotificationsProvider was only in App.tsx — the agent -// manager rendered a blank screen. -// --------------------------------------------------------------------------- - const APP_FILE = path.join(ROOT, "webview-ui/src/App.tsx") const AGENT_MANAGER_APP_FILE = path.join(ROOT, "webview-ui/agent-manager/AgentManagerApp.tsx") +const PROVIDER_SHELL_FILE = path.join(ROOT, "webview-ui/src/context/provider-shell.tsx") -describe("Agent Manager — provider chain parity with sidebar", () => { - /** - * Extract provider component names used as JSX elements in a file. - * Matches `` patterns, returning the names. - */ - function extractProviders(content: string): string[] { - const matches = [...content.matchAll(/<(\w+Provider)\b/g)] - return [...new Set(matches.map((m) => m[1]!))] +describe("Shared webview provider shell", () => { + function ordered(source: string, names: string[]) { + const positions = names.map((name) => source.indexOf(`<${name}`)) + expect( + positions.every((position) => position >= 0), + `Missing provider from ${names.join(" -> ")}`, + ).toBe(true) + expect(positions).toEqual([...positions].sort((a, b) => a - b)) } - /** - * Providers that the agent manager intentionally omits because it does not - * use the components that depend on them. If a shared component (ChatView, - * MessageList, etc.) starts using one of these, the test will fail and - * force the developer to add the provider to AgentManagerApp.tsx. - */ - const KNOWN_EXCLUSIONS: string[] = [ - // These are wrapped by LanguageBridge and DataBridge respectively, - // which the agent manager already includes in its provider chain. - "LanguageProvider", - "DataProvider", - // Agent Manager owns its local session tabs and ChatView only reads this - // optional context in the standard sidebar/editor webview. - "LocalTabsProvider", - // Work-style onboarding is injected only into the sidebar empty state. - "WorkStyleProvider", - ] + it("owns the common provider order and bridges", () => { + const source = fs.readFileSync(PROVIDER_SHELL_FILE, "utf-8") + ordered(source, [ + "ThemeProvider", + "DialogProvider", + "VSCodeProvider", + "MermaidDownloadBridge", + "ServerProvider", + "LanguageBridge", + "MarkedProvider", + "DiffComponentProvider", + "CodeComponentProvider", + "FileComponentProvider", + "ProviderProvider", + "ConfigProvider", + "SpeechToTextPrewarm", + "DisplayProvider", + "IndexingProvider", + "KiloEmbeddingModelsProvider", + "ImageModelsProvider", + "NotificationsProvider", + "SessionProvider", + "AgentRequirementsProvider", + "MemoryProvider", + "FeedbackProvider", + ]) + expect(source.indexOf("")) + }) - it("agent manager includes all context providers from sidebar App.tsx", () => { - const sidebar = fs.readFileSync(APP_FILE, "utf-8") - const agent = fs.readFileSync(AGENT_MANAGER_APP_FILE, "utf-8") + it("keeps sidebar-only providers in the sidebar root", () => { + const source = fs.readFileSync(APP_FILE, "utf-8") + ordered(source, [ + "ProviderShell.Root", + "WorkStyleProvider", + "ProviderShell.Session", + "LocalTabsProvider", + "ProviderShell.Chat", + "DataBridge", + "AppContent", + ]) + expect(fs.readFileSync(PROVIDER_SHELL_FILE, "utf-8")).not.toMatch(/WorkStyleProvider|LocalTabsProvider/) + }) - const sidebarProviders = extractProviders(sidebar) - const agentProviders = extractProviders(agent) - const agentSet = new Set(agentProviders) - const excluded = new Set(KNOWN_EXCLUSIONS) - - const missing = sidebarProviders.filter((p) => !agentSet.has(p) && !excluded.has(p)) - - expect( - missing, - `These providers are in App.tsx but missing from AgentManagerApp.tsx.\n` + - `The agent manager reuses ChatView — any provider that ChatView's component\n` + - `tree depends on must be present in both provider chains.\n\n` + - `Missing providers:\n` + - missing.map((p) => ` - ${p}`).join("\n") + - `\n\nFix: add the missing <${missing[0]}> to AgentManagerApp.tsx's provider chain,\n` + - `or add it to KNOWN_EXCLUSIONS with a justification if it's truly unused.`, - ).toEqual([]) + it("keeps worktree mode in the Agent Manager root", () => { + const source = fs.readFileSync(AGENT_MANAGER_APP_FILE, "utf-8") + ordered(source, [ + "ProviderShell.Root", + "ProviderShell.Session", + "ProviderShell.Chat", + "WorktreeModeProvider", + "DataBridge", + "AgentManagerContent", + ]) + expect(fs.readFileSync(PROVIDER_SHELL_FILE, "utf-8")).not.toContain("WorktreeModeProvider") }) }) diff --git a/packages/kilo-vscode/tests/unit/agent-manager-i18n-split.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-i18n-split.test.ts index ab0965fa7e..3b7477aafc 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-i18n-split.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-i18n-split.test.ts @@ -39,6 +39,7 @@ import { dict as amTr } from "../../webview-ui/agent-manager/i18n/tr" import { dict as amNl } from "../../webview-ui/agent-manager/i18n/nl" import { dict as amUk } from "../../webview-ui/agent-manager/i18n/uk" import { dict as amIt } from "../../webview-ui/agent-manager/i18n/it" +import { dict as amFa } from "../../webview-ui/agent-manager/i18n/fa" const PREFIX = "agentManager." @@ -63,6 +64,7 @@ const locales = { nl: amNl, uk: amUk, it: amIt, + fa: amFa, } const appLocales = { diff --git a/packages/kilo-vscode/tests/unit/agent-manager-i18n.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-i18n.test.ts index 691b8a7ab6..8142d2c20c 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-i18n.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-i18n.test.ts @@ -26,6 +26,8 @@ const ROOT = path.resolve(import.meta.dir, "../..") const TSX_FILES = [ path.join(ROOT, "webview-ui/agent-manager/AgentManagerApp.tsx"), path.join(ROOT, "webview-ui/agent-manager/sortable-tab.tsx"), + path.join(ROOT, "webview-ui/agent-manager/terminal/SideTerminalPanel.tsx"), + path.join(ROOT, "webview-ui/agent-manager/terminal/TerminalDestinationButton.tsx"), ] /** diff --git a/packages/kilo-vscode/tests/unit/agent-manager-terminal-destination.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-terminal-destination.test.ts new file mode 100644 index 0000000000..40330aa29c --- /dev/null +++ b/packages/kilo-vscode/tests/unit/agent-manager-terminal-destination.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "bun:test" +import { affectsTerminalDestination, resolveTerminalDestination } from "../../src/agent-manager/terminal-destination" + +function event(key: string) { + return { + affectsConfiguration: (target: string) => target === key, + } as Parameters[0] +} + +describe("Agent Manager terminal destination", () => { + it("defaults unknown settings to the VS Code terminal", () => { + expect(resolveTerminalDestination(undefined)).toBe("vscode") + expect(resolveTerminalDestination("invalid")).toBe("vscode") + expect(resolveTerminalDestination("vscode")).toBe("vscode") + expect(resolveTerminalDestination("agentManager")).toBe("agentManager") + }) + + it("watches only the terminal button destination setting", () => { + expect(affectsTerminalDestination(event("kilo-code.new.agentManager.terminalButtonDestination"))).toBe(true) + expect(affectsTerminalDestination(event("terminal.integrated.fontFamily"))).toBe(false) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/agent-manager-terminal-font.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-terminal-font.test.ts index 9bf257a8c3..02b7fffe27 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-terminal-font.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-terminal-font.test.ts @@ -53,6 +53,7 @@ describe("Agent Manager terminal font", () => { const message = new Promise((resolve) => { const router = new TerminalRouter({ getClient: () => client, + getClientAsync: async () => client, getServerConfig: () => ({ baseUrl: "http://127.0.0.1:4096", password: "secret" }), getRoot: () => "/workspace", getWorktreePath: () => undefined, @@ -61,7 +62,14 @@ describe("Agent Manager terminal font", () => { getTerminalFont: () => font, }) - expect(router.handle({ type: "agentManager.terminal.create", worktreeId: null })).toBe(true) + expect( + router.handle({ + type: "agentManager.terminal.create", + createId: "font-1", + placement: "tab", + worktreeId: null, + }), + ).toBe(true) }) const created = await message @@ -70,6 +78,7 @@ describe("Agent Manager terminal font", () => { expect(created.font).toEqual(font) expect(created.worktreeId).toBeNull() expect(created.wsUrl).toContain("/pty/pty-1/connect") + expect(created.wsUrl).toContain("cursor=0") }) it("keeps the created font in terminal state", () => { @@ -82,9 +91,12 @@ describe("Agent Manager terminal font", () => { saveTabMemory: () => undefined, setSelection: () => undefined, showError: () => undefined, + postMessage: () => undefined, }) const message = { type: "agentManager.terminal.created", + createId: "font-2", + placement: "tab", worktreeId: null, terminalId: "terminal-1", title: "Terminal 1", diff --git a/packages/kilo-vscode/tests/unit/agent-manager-terminal-routing.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-terminal-routing.test.ts new file mode 100644 index 0000000000..ef9bfcd6c4 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/agent-manager-terminal-routing.test.ts @@ -0,0 +1,283 @@ +import { describe, expect, it } from "bun:test" +import type { KiloClient } from "@kilocode/sdk/v2/client" +import { TerminalRouter } from "../../src/agent-manager/terminal-routing" +import type { AgentManagerOutMessage } from "../../src/agent-manager/types" + +const font = { fontFamily: "Menlo", fontSize: 12 } + +function wait() { + return new Promise((resolve) => setTimeout(resolve, 0)) +} + +describe("Agent Manager terminal routing", () => { + it("round-trips side placement and rejects missing worktrees", async () => { + const messages: AgentManagerOutMessage[] = [] + const client = { + pty: { + create: async () => ({ data: { id: "pty-1", title: "Terminal 1" } }), + remove: async () => ({ data: true }), + update: async () => ({ data: true }), + }, + } as unknown as KiloClient + const router = new TerminalRouter({ + getClient: () => client, + getClientAsync: async () => client, + getServerConfig: () => ({ baseUrl: "http://127.0.0.1:4096", password: "secret" }), + getRoot: () => "/workspace", + getWorktreePath: (id) => (id === "wt-1" ? "/workspace/wt-1" : undefined), + log: () => undefined, + post: (message) => messages.push(message), + getTerminalFont: () => font, + }) + + router.handle({ + type: "agentManager.terminal.create", + createId: "side-1", + placement: "side", + worktreeId: "wt-1", + }) + await wait() + expect(messages[0]).toMatchObject({ + type: "agentManager.terminal.created", + createId: "side-1", + placement: "side", + worktreeId: "wt-1", + }) + + router.handle({ + type: "agentManager.terminal.create", + createId: "side-missing", + placement: "side", + worktreeId: "missing", + }) + expect(messages[1]).toMatchObject({ + type: "agentManager.terminal.error", + createId: "side-missing", + }) + await router.dispose() + }) + + it("isolates a reopened panel from an in-flight disposal", async () => { + const messages: AgentManagerOutMessage[] = [] + const removed: string[] = [] + const resolvers: Array<(value: { data: { id: string; title: string } }) => void> = [] + let creates = 0 + const client = { + pty: { + create: () => + new Promise<{ data: { id: string; title: string } }>((resolve) => { + creates++ + if (creates === 1) resolvers.push(resolve) + else resolve({ data: { id: "pty-new", title: "Terminal 1" } }) + }), + remove: async ({ ptyID }: { ptyID: string }) => { + removed.push(ptyID) + return { data: true } + }, + update: async () => ({ data: true }), + }, + } as unknown as KiloClient + const router = new TerminalRouter({ + getClient: () => client, + getClientAsync: async () => client, + getServerConfig: () => ({ baseUrl: "http://127.0.0.1:4096", password: "secret" }), + getRoot: () => "/workspace", + getWorktreePath: () => undefined, + log: () => undefined, + post: (message) => messages.push(message), + getTerminalFont: () => font, + }) + + router.handle({ + type: "agentManager.terminal.create", + createId: "old", + placement: "side", + worktreeId: null, + }) + await router.dispose() + router.handle({ + type: "agentManager.terminal.create", + createId: "new", + placement: "side", + worktreeId: null, + }) + resolvers[0]?.({ data: { id: "pty-old", title: "Terminal 1" } }) + await wait() + + expect(messages).toHaveLength(1) + expect(messages[0]).toMatchObject({ type: "agentManager.terminal.created", createId: "new" }) + expect(removed).toContain("pty-old") + await router.dispose() + expect(removed).toContain("pty-new") + }) + + it("fills numbering gaps left by closed terminals", async () => { + const messages: AgentManagerOutMessage[] = [] + const titles: string[] = [] + let seq = 0 + const client = { + pty: { + create: async ({ title }: { title: string }) => { + titles.push(title) + seq++ + return { data: { id: `pty-${seq}`, title } } + }, + remove: async () => ({ data: true }), + update: async () => ({ data: true }), + }, + } as unknown as KiloClient + const router = new TerminalRouter({ + getClient: () => client, + getClientAsync: async () => client, + getServerConfig: () => ({ baseUrl: "http://127.0.0.1:4096", password: "secret" }), + getRoot: () => "/workspace", + getWorktreePath: () => undefined, + log: () => undefined, + post: (message) => messages.push(message), + getTerminalFont: () => font, + }) + const create = (createId: string) => + router.handle({ type: "agentManager.terminal.create", createId, placement: "side", worktreeId: null }) + + create("one") + await wait() + create("two") + await wait() + expect(titles).toEqual(["Terminal 1", "Terminal 2"]) + + // Close "Terminal 1"; the next create reuses the freed number. + const first = messages.find((m) => m.type === "agentManager.terminal.created" && m.createId === "one") + if (first?.type !== "agentManager.terminal.created") throw new Error("missing created message") + router.handle({ type: "agentManager.terminal.close", terminalId: first.terminalId }) + await wait() + + create("three") + await wait() + expect(titles).toEqual(["Terminal 1", "Terminal 2", "Terminal 1"]) + await router.dispose() + }) + + it("hands out distinct numbers to concurrent creates", async () => { + const messages: AgentManagerOutMessage[] = [] + const titles: string[] = [] + const resolvers: Array<(value: { data: { id: string; title: string } }) => void> = [] + const client = { + pty: { + create: ({ title }: { title: string }) => + new Promise<{ data: { id: string; title: string } }>((resolve) => { + titles.push(title) + resolvers.push(resolve) + }), + remove: async () => ({ data: true }), + update: async () => ({ data: true }), + }, + } as unknown as KiloClient + const router = new TerminalRouter({ + getClient: () => client, + getClientAsync: async () => client, + getServerConfig: () => ({ baseUrl: "http://127.0.0.1:4096", password: "secret" }), + getRoot: () => "/workspace", + getWorktreePath: () => undefined, + log: () => undefined, + post: (message) => messages.push(message), + getTerminalFont: () => font, + }) + + // Two creates before either settles must not share an ordinal. The + // backend-connection await defers the PTY creates to a microtask. + router.handle({ type: "agentManager.terminal.create", createId: "a", placement: "side", worktreeId: null }) + router.handle({ type: "agentManager.terminal.create", createId: "b", placement: "side", worktreeId: null }) + await wait() + expect(titles).toEqual(["Terminal 1", "Terminal 2"]) + resolvers[0]?.({ data: { id: "pty-a", title: titles[0]! } }) + resolvers[1]?.({ data: { id: "pty-b", title: titles[1]! } }) + await wait() + + // A failed create releases its reservation for the next attempt. + await router.dispose() + }) + + it("does not let a stale create release a new generation's reservation", async () => { + const titles: string[] = [] + const resolvers: Array<(value: { data: { id: string; title: string } }) => void> = [] + const client = { + pty: { + create: ({ title }: { title: string }) => + new Promise<{ data: { id: string; title: string } }>((resolve) => { + titles.push(title) + resolvers.push(resolve) + }), + remove: async () => ({ data: true }), + update: async () => ({ data: true }), + }, + } as unknown as KiloClient + const router = new TerminalRouter({ + getClient: () => client, + getClientAsync: async () => client, + getServerConfig: () => ({ baseUrl: "http://127.0.0.1:4096", password: "secret" }), + getRoot: () => "/workspace", + getWorktreePath: () => undefined, + log: () => undefined, + post: () => undefined, + getTerminalFont: () => font, + }) + + // Create A starts before the panel is recreated; its reservation dies + // with dispose(). Create B of the new generation reserves the same + // free number. When A's late completion settles, its release must not + // wipe B's reservation — otherwise create C would duplicate B's title. + router.handle({ type: "agentManager.terminal.create", createId: "a", placement: "side", worktreeId: null }) + await router.dispose() + router.handle({ type: "agentManager.terminal.create", createId: "b", placement: "side", worktreeId: null }) + await wait() + expect(titles).toEqual(["Terminal 1", "Terminal 1"]) + resolvers[0]?.({ data: { id: "pty-a", title: titles[0]! } }) + await wait() + router.handle({ type: "agentManager.terminal.create", createId: "c", placement: "side", worktreeId: null }) + await wait() + expect(titles).toEqual(["Terminal 1", "Terminal 1", "Terminal 2"]) + await router.dispose() + }) + + it("awaits the shared backend connection before creating a terminal", async () => { + let connected = false + const client = { + pty: { + create: async () => ({ data: { id: "pty-1", title: "Terminal 1" } }), + remove: async () => ({ data: true }), + update: async () => ({ data: true }), + }, + } as unknown as KiloClient + const messages: AgentManagerOutMessage[] = [] + const router = new TerminalRouter({ + getClient: () => { + if (!connected) throw new Error("Not connected") + return client + }, + getClientAsync: async () => { + await wait() + connected = true + return client + }, + getServerConfig: () => ({ baseUrl: "http://127.0.0.1:4096", password: "secret" }), + getRoot: () => "/workspace", + getWorktreePath: () => undefined, + log: () => undefined, + post: (message) => messages.push(message), + getTerminalFont: () => font, + }) + + router.handle({ + type: "agentManager.terminal.create", + createId: "real", + placement: "tab", + worktreeId: null, + }) + expect(messages).toHaveLength(0) + await wait() + await wait() + + expect(messages[0]).toMatchObject({ type: "agentManager.terminal.created", createId: "real" }) + await router.dispose() + }) +}) diff --git a/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts new file mode 100644 index 0000000000..9a071e5c15 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts @@ -0,0 +1,188 @@ +import { describe, expect, it } from "bun:test" +import { + createSideTerminal, + readSavedDestination, + resolveVscodeTerminalRequest, +} from "../../webview-ui/agent-manager/terminal/side" + +function scene( + opts: { + destination?: "vscode" | "agentManager" + saved?: "vscode" | "agentManager" + visible?: boolean + focusedId?: string + } = {}, +) { + const calls = { + requestSide: 0, + closed: [] as string[], + hide: 0, + refocus: 0, + openVscode: 0, + persisted: [] as string[], + posted: [] as Array>, + tracked: [] as string[], + } + let visible = opts.visible ?? false + let focusedId = opts.focusedId as string | undefined + const ctl = createSideTerminal({ + handlers: { + requestSide: () => { + calls.requestSide++ + visible = true + }, + closeSide: (terminalId) => { + calls.closed.push(terminalId) + focusedId = undefined + return true + }, + }, + visible: () => visible, + focusedId: () => focusedId, + hide: () => { + calls.hide++ + visible = false + }, + refocus: () => calls.refocus++, + postMessage: (msg) => calls.posted.push(msg as Record), + track: (button) => calls.tracked.push(button), + openVscode: () => calls.openVscode++, + saved: opts.saved, + save: (destination) => calls.persisted.push(destination), + }) + if (opts.destination) ctl.syncDefault(opts.destination) + return { ctl, calls } +} + +describe("Agent Manager side terminal controller", () => { + it("toggles the panel and hands focus to the chat only when the terminal had it", () => { + const focused = scene({ destination: "agentManager", visible: true, focusedId: "terminal:side" }) + focused.ctl.toggle() + expect(focused.calls.hide).toBe(1) + expect(focused.calls.refocus).toBe(1) + + const elsewhere = scene({ destination: "agentManager", visible: true }) + elsewhere.ctl.toggle() + expect(elsewhere.calls.hide).toBe(1) + expect(elsewhere.calls.refocus).toBe(0) + + const hidden = scene({ destination: "agentManager", visible: false }) + hidden.ctl.toggle() + expect(hidden.calls.requestSide).toBe(1) + expect(hidden.calls.hide).toBe(0) + }) + + it("kills the focused terminal and refocuses the chat", () => { + const focused = scene({ focusedId: "terminal:two" }) + expect(focused.ctl.close()).toBe(true) + expect(focused.calls.closed).toEqual(["terminal:two"]) + expect(focused.calls.refocus).toBe(1) + }) + + it("does nothing on close without a focused terminal", () => { + const item = scene() + expect(item.ctl.close()).toBe(false) + expect(item.calls.closed).toEqual([]) + expect(item.calls.refocus).toBe(0) + }) + + it("routes the primary action by destination", () => { + const vscodeFirst = scene({ destination: "vscode" }) + vscodeFirst.ctl.openPreferred("tab_toolbar") + expect(vscodeFirst.calls.openVscode).toBe(1) + expect(vscodeFirst.calls.requestSide).toBe(0) + + const panelFirst = scene({ destination: "agentManager" }) + panelFirst.ctl.openPreferred("keyboard_shortcut") + expect(panelFirst.calls.requestSide).toBe(1) + expect(panelFirst.calls.openVscode).toBe(0) + }) + + it("persists the picked destination with a section-relative settings key", () => { + const item = scene() + item.ctl.choose("agentManager") + expect(item.ctl.destination()).toBe("agentManager") + expect(item.calls.posted).toEqual([ + { type: "updateSetting", key: "agentManager.terminalButtonDestination", value: "agentManager" }, + ]) + expect(item.calls.persisted).toEqual(["agentManager"]) + }) + + it("follows the remote default while the panel has no explicit choice", () => { + const item = scene() + item.ctl.syncDefault("agentManager") + expect(item.ctl.destination()).toBe("agentManager") + item.ctl.syncDefault("vscode") + expect(item.ctl.destination()).toBe("vscode") + }) + + it("keeps the panel's explicit choice when another window rewrites the shared setting", () => { + const item = scene() + item.ctl.choose("agentManager") + // Echo of the application-scoped setting being rewritten elsewhere: + // worktree window B picked the VS Code terminal, which must not flip + // this panel's routing. + item.ctl.syncDefault("vscode") + expect(item.ctl.destination()).toBe("agentManager") + item.ctl.openPreferred("keyboard_shortcut") + expect(item.calls.requestSide).toBe(1) + expect(item.calls.openVscode).toBe(0) + }) + + it("restores a saved panel choice and ignores remote defaults", () => { + const item = scene({ saved: "agentManager" }) + expect(item.ctl.destination()).toBe("agentManager") + item.ctl.syncDefault("vscode") + expect(item.ctl.destination()).toBe("agentManager") + }) +}) + +describe("readSavedDestination", () => { + it("reads a valid choice and rejects anything else", () => { + expect(readSavedDestination({ terminalDestination: "agentManager" })).toBe("agentManager") + expect(readSavedDestination({ terminalDestination: "vscode" })).toBe("vscode") + expect(readSavedDestination({ terminalDestination: "bogus" })).toBeUndefined() + expect(readSavedDestination({})).toBeUndefined() + expect(readSavedDestination(undefined)).toBeUndefined() + }) +}) + +describe("resolveVscodeTerminalRequest", () => { + const sessions = new Map([ + ["wt-1", "session-a"], + ["wt-2", "session-b"], + ]) + const forWorktree = (id: string) => sessions.get(id) + + it("prefers the current session", () => { + expect(resolveVscodeTerminalRequest("wt-1", "session-current", forWorktree)).toEqual({ + type: "agentManager.showTerminal", + sessionId: "session-current", + }) + }) + + it("falls back to a session of the selected worktree when the current session is cleared", () => { + // Terminal tab activation clears the current session; the shortcut + // must still open a terminal for the worktree, not dead-end. + expect(resolveVscodeTerminalRequest("wt-2", undefined, forWorktree)).toEqual({ + type: "agentManager.showTerminal", + sessionId: "session-b", + }) + }) + + it("opens a worktree-rooted terminal for sessionless worktrees", () => { + expect(resolveVscodeTerminalRequest("wt-3", undefined, forWorktree)).toEqual({ + type: "agentManager.showWorktreeTerminal", + worktreeId: "wt-3", + }) + }) + + it("opens the local terminal for the local context and unassigned selections", () => { + expect(resolveVscodeTerminalRequest("local", undefined, forWorktree)).toEqual({ + type: "agentManager.showLocalTerminal", + }) + expect(resolveVscodeTerminalRequest(null, undefined, forWorktree)).toEqual({ + type: "agentManager.showLocalTerminal", + }) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/agent-manager-terminal-state.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-terminal-state.test.ts new file mode 100644 index 0000000000..db0acb769f --- /dev/null +++ b/packages/kilo-vscode/tests/unit/agent-manager-terminal-state.test.ts @@ -0,0 +1,286 @@ +import { describe, expect, it } from "bun:test" +import { createRoot, createSignal } from "solid-js" +import { LOCAL } from "../../webview-ui/agent-manager/navigate" +import { + createTerminalHandlers, + createTerminalMessageHandler, + createTerminalState, +} from "../../webview-ui/agent-manager/terminal/state" +import type { ExtensionMessage } from "../../webview-ui/src/types/messages/extension-messages" + +const font = { fontFamily: "Menlo", fontSize: 12 } + +function scene(initial: string | null = LOCAL) { + const [selection, setSelection] = createSignal(initial) + const state = createTerminalState(selection) + const posted: Array> = [] + const events = { activated: [] as string[], selected: [] as string[], saved: 0, shown: [] as string[], errors: 0 } + const tabs = () => state.current().map((term) => term.id) + const handlers = createTerminalHandlers({ + state, + tabIds: tabs, + selectReview: () => undefined, + selectSessionTab: () => undefined, + clearSession: () => undefined, + resetOthers: () => undefined, + isPendingId: () => false, + findTab: () => undefined, + postMessage: (message) => posted.push(message as Record), + onShowSide: (key) => events.shown.push(key), + getSelection: selection, + LOCAL, + REVIEW_TAB_ID: "review", + }) + const dispatch = createTerminalMessageHandler({ + state, + activate: (id) => events.activated.push(id), + saveTabMemory: () => events.saved++, + setSelection: (value) => { + events.selected.push(value) + setSelection(value) + }, + showError: () => events.errors++, + postMessage: (message) => posted.push(message as Record), + }) + return { state, selection, setSelection, posted, events, handlers, dispatch } +} + +function createdSide(createId: string, terminalId: string, title = "Terminal 1") { + return { + type: "agentManager.terminal.created", + createId, + placement: "side", + worktreeId: null, + terminalId, + title, + wsUrl: `ws://${terminalId}`, + font, + } satisfies ExtensionMessage +} + +describe("Agent Manager terminal state", () => { + it("keeps side terminals out of the tab state and shares root context with unassigned sessions", () => { + createRoot((dispose) => { + const item = scene() + item.state.add(null, { + id: "terminal:tab", + title: "Terminal 1", + wsUrl: "ws://tab", + font, + placement: "tab", + }) + item.state.add(null, { + id: "terminal:side", + title: "Terminal 2", + wsUrl: "ws://side", + font, + placement: "side", + }) + item.state.setSideActive(LOCAL, "terminal:side") + + expect(item.state.current().map((term) => term.id)).toEqual(["terminal:tab"]) + expect(item.state.all().map((term) => term.id)).toEqual(["terminal:tab"]) + expect(item.state.sides().map((term) => term.id)).toEqual(["terminal:side"]) + expect(item.state.sideActiveFor(LOCAL)).toBe("terminal:side") + + item.setSelection(null) + expect(item.state.current()).toEqual([]) + expect(item.state.sideKey()).toBe(LOCAL) + expect(item.state.sideActiveFor(LOCAL)).toBe("terminal:side") + dispose() + }) + }) + + it("deduplicates an in-flight reveal and focuses the active terminal on repeat", () => { + createRoot((dispose) => { + const item = scene() + item.handlers.requestSide() + item.handlers.requestSide() + + expect(item.posted).toHaveLength(1) + const request = item.posted[0]! + expect(request).toMatchObject({ type: "agentManager.terminal.create", placement: "side", worktreeId: null }) + const createId = String(request.createId) + expect(item.dispatch(createdSide(createId, "terminal:side"))).toBe(true) + expect(item.state.sideActiveFor(LOCAL)).toBe("terminal:side") + expect(item.events.activated).toEqual([]) + expect(item.events.selected).toEqual([]) + expect(item.events.saved).toBe(0) + + item.handlers.requestSide() + expect(item.posted).toHaveLength(1) + expect(item.state.focusRequest()?.id).toBe("terminal:side") + dispose() + }) + }) + + it("supports several side terminals per context with newest active", () => { + createRoot((dispose) => { + const item = scene() + item.handlers.addSide() + item.handlers.addSide() + expect(item.posted).toHaveLength(2) + const first = String(item.posted[0]!.createId) + const second = String(item.posted[1]!.createId) + + item.dispatch(createdSide(first, "terminal:one", "Terminal 1")) + expect(item.state.sidesForContext(LOCAL).map((term) => term.id)).toEqual(["terminal:one"]) + expect(item.state.sideActiveFor(LOCAL)).toBe("terminal:one") + + item.dispatch(createdSide(second, "terminal:two", "Terminal 2")) + expect(item.state.sidesForContext(LOCAL).map((term) => term.id)).toEqual(["terminal:one", "terminal:two"]) + expect(item.state.sideActiveFor(LOCAL)).toBe("terminal:two") + dispose() + }) + }) + + it("switches the active side terminal on select", () => { + createRoot((dispose) => { + const item = scene() + item.state.add(null, { id: "terminal:one", title: "Terminal 1", wsUrl: "ws://one", font, placement: "side" }) + item.state.add(null, { id: "terminal:two", title: "Terminal 2", wsUrl: "ws://two", font, placement: "side" }) + item.state.setSideActive(LOCAL, "terminal:two") + + item.handlers.selectSide("terminal:one") + expect(item.state.sideActiveFor(LOCAL)).toBe("terminal:one") + expect(item.state.focusRequest()?.id).toBe("terminal:one") + dispose() + }) + }) + + it("moves activation to the last remaining side terminal on close", () => { + createRoot((dispose) => { + const item = scene() + item.state.add(null, { id: "terminal:one", title: "Terminal 1", wsUrl: "ws://one", font, placement: "side" }) + item.state.add(null, { id: "terminal:two", title: "Terminal 2", wsUrl: "ws://two", font, placement: "side" }) + item.state.setSideActive(LOCAL, "terminal:two") + + expect(item.handlers.closeSide("terminal:two")).toBe(true) + expect(item.state.sideActiveFor(LOCAL)).toBe("terminal:one") + expect(item.posted).toEqual([{ type: "agentManager.terminal.close", terminalId: "terminal:two" }]) + + expect(item.handlers.closeSide("terminal:one")).toBe(true) + expect(item.state.sideActiveFor(LOCAL)).toBeUndefined() + expect(item.state.sidesForContext(LOCAL)).toEqual([]) + + // Closing an unknown or non-side id is a no-op. + expect(item.handlers.closeSide("terminal:gone")).toBe(false) + expect(item.posted).toHaveLength(2) + dispose() + }) + }) + + it("closes a stale side answer whose create request is unknown", () => { + createRoot((dispose) => { + const item = scene() + // A created message for a createId the webview never sent (e.g. it + // reloaded while the PTY was starting) must not leak the PTY. + item.dispatch(createdSide("stale-id", "terminal:stale")) + expect(item.state.sidesForContext(LOCAL)).toEqual([]) + expect(item.posted).toEqual([{ type: "agentManager.terminal.close", terminalId: "terminal:stale" }]) + dispose() + }) + }) + + it("creates explicit terminal tabs independently of the side destination", () => { + createRoot((dispose) => { + const item = scene("wt-1") + item.handlers.requestNew() + expect(item.posted[0]).toMatchObject({ + type: "agentManager.terminal.create", + placement: "tab", + worktreeId: "wt-1", + }) + dispose() + }) + }) + + it("routes side creates of a worktree context to that worktree", () => { + createRoot((dispose) => { + const item = scene("wt-1") + item.handlers.addSide() + expect(item.posted[0]).toMatchObject({ + type: "agentManager.terminal.create", + placement: "side", + worktreeId: "wt-1", + }) + dispose() + }) + }) + + it("tracks OSC titles per terminal without touching the terminal records", () => { + createRoot((dispose) => { + const item = scene() + item.state.add(null, { id: "terminal:one", title: "Terminal 1", wsUrl: "ws://one", font, placement: "side" }) + const before = item.state.sidesForContext(LOCAL)[0]! + + item.state.setTitle("terminal:one", "npm run dev") + expect(item.state.title("terminal:one")).toBe("npm run dev") + // Reference stability: the stored record is untouched so does + // not remount the xterm instance on a title change. + expect(item.state.sidesForContext(LOCAL)[0]).toBe(before) + + // Empty titles are ignored; removal drops the override. + item.state.setTitle("terminal:one", " ") + expect(item.state.title("terminal:one")).toBe("npm run dev") + item.state.remove("terminal:one") + expect(item.state.title("terminal:one")).toBeUndefined() + dispose() + }) + }) + + it("reorders side terminals within their context via drag", () => { + createRoot((dispose) => { + const item = scene() + item.state.add(null, { id: "terminal:one", title: "Terminal 1", wsUrl: "ws://one", font, placement: "side" }) + item.state.add(null, { id: "terminal:two", title: "Terminal 2", wsUrl: "ws://two", font, placement: "side" }) + item.state.add(null, { id: "terminal:three", title: "Terminal 3", wsUrl: "ws://three", font, placement: "side" }) + item.state.add(null, { id: "terminal:tab", title: "Terminal 4", wsUrl: "ws://tab", font, placement: "tab" }) + + // Drag the first side terminal onto the third position. + expect(item.state.reorderSideDrag(LOCAL, "terminal:one", "terminal:three")).toBe(true) + expect(item.state.sidesForContext(LOCAL).map((term) => term.id)).toEqual([ + "terminal:two", + "terminal:three", + "terminal:one", + ]) + // Tab terminals are untouched. + expect(item.state.current().map((term) => term.id)).toEqual(["terminal:tab"]) + + // The order survives switching to another context and back. + item.setSelection("wt-1") + expect(item.state.sidesForContext(LOCAL).map((term) => term.id)).toEqual([ + "terminal:two", + "terminal:three", + "terminal:one", + ]) + item.setSelection(LOCAL) + expect(item.state.sidesForContext(LOCAL).map((term) => term.id)).toEqual([ + "terminal:two", + "terminal:three", + "terminal:one", + ]) + + // Unknown ids, tab-placement ids, and foreign contexts are rejected. + expect(item.state.reorderSideDrag(LOCAL, "terminal:gone", "terminal:two")).toBe(false) + expect(item.state.reorderSideDrag(LOCAL, "terminal:tab", "terminal:two")).toBe(false) + expect(item.state.reorderSideDrag("wt-1", "terminal:two", "terminal:three")).toBe(false) + dispose() + }) + }) + + it("reports the focused side terminal only for the current context", () => { + createRoot((dispose) => { + const item = scene() + item.state.add(null, { id: "terminal:side", title: "Terminal 1", wsUrl: "ws://side", font, placement: "side" }) + item.state.add(null, { id: "terminal:tab", title: "Terminal 2", wsUrl: "ws://tab", font, placement: "tab" }) + + expect(item.state.sideFocusedId()).toBeUndefined() + item.state.setFocusedId("terminal:tab") + expect(item.state.sideFocusedId()).toBeUndefined() + item.state.setFocusedId("terminal:side") + expect(item.state.sideFocusedId()).toBe("terminal:side") + dispose() + }) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/agent-manager-worktree-diffs.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-worktree-diffs.test.ts new file mode 100644 index 0000000000..5d2e5bc3e1 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/agent-manager-worktree-diffs.test.ts @@ -0,0 +1,103 @@ +import { describe, expect, it } from "bun:test" +import { createRoot } from "solid-js" +import { createWorktreeDiffs } from "../../webview-ui/agent-manager/worktree-diffs" +import type { WorktreeFileDiff } from "../../webview-ui/src/types/messages" + +const diff = (file: string, additions = 1): WorktreeFileDiff => ({ + file, + before: "", + after: "", + additions, + deletions: 0, +}) + +interface Sent { + type: string + sessionId?: string + file?: string +} + +// Only `postMessage` is exercised by the diff workflow, so a recording stub is +// enough — the signals and merge/pending logic under test are the real thing. +const vscode = (sent: Sent[]) => + ({ postMessage: (msg: Sent) => sent.push(msg) }) as unknown as Parameters[0] + +const withDiffs = (fn: (diffs: ReturnType, sent: Sent[]) => void) => { + createRoot((dispose) => { + const sent: Sent[] = [] + fn(createWorktreeDiffs(vscode(sent)), sent) + dispose() + }) +} + +describe("createWorktreeDiffs", () => { + it("stores full diffs per session", () => { + withDiffs((diffs) => { + diffs.onWorktreeDiff({ type: "agentManager.worktreeDiff", sessionId: "s1", diffs: [diff("a.ts")] }) + expect(diffs.diffDatas()["s1"]).toHaveLength(1) + }) + }) + + it("does not replace state when an update produces an identical diff list", () => { + withDiffs((diffs) => { + diffs.onWorktreeDiff({ type: "agentManager.worktreeDiff", sessionId: "s1", diffs: [diff("a.ts")] }) + const before = diffs.diffDatas() + diffs.onWorktreeDiff({ type: "agentManager.worktreeDiff", sessionId: "s1", diffs: [diff("a.ts")] }) + expect(diffs.diffDatas()).toBe(before) + }) + }) + + it("replaces a single file on a diffFile message and clears its pending flag", () => { + withDiffs((diffs) => { + diffs.onWorktreeDiff({ type: "agentManager.worktreeDiff", sessionId: "s1", diffs: [diff("a.ts", 1)] }) + diffs.onWorktreeDiffFile({ + type: "agentManager.worktreeDiffFile", + sessionId: "s1", + file: "a.ts", + diff: diff("a.ts", 9), + }) + expect(diffs.diffDatas()["s1"]![0]!.additions).toBe(9) + expect(diffs.diffFileLoadingFor(() => "s1").size).toBe(0) + }) + }) + + it("tracks panel loading via diffLoading", () => { + withDiffs((diffs) => { + diffs.onWorktreeDiffLoading({ type: "agentManager.worktreeDiffLoading", sessionId: "s1", loading: true }) + expect(diffs.diffLoading()).toBe(true) + diffs.onWorktreeDiffLoading({ type: "agentManager.worktreeDiffLoading", sessionId: "s1", loading: false }) + expect(diffs.diffLoading()).toBe(false) + }) + }) + + it("requestDiffFile marks a file pending, posts once, and ignores repeats", () => { + withDiffs((diffs, sent) => { + diffs.requestDiffFile("s1", "a.ts") + diffs.requestDiffFile("s1", "a.ts") + expect(sent.filter((m) => m.type === "agentManager.requestWorktreeDiffFile")).toHaveLength(1) + expect(diffs.diffFileLoadingFor(() => "s1").has("a.ts")).toBe(true) + }) + }) + + it("refreshStaleDiffs requests only files not already loading", () => { + withDiffs((diffs, sent) => { + diffs.requestDiffFile("s1", "a.ts") + diffs.refreshStaleDiffs("s1", new Set(["a.ts", "b.ts"])) + const files = sent.filter((m) => m.type === "agentManager.requestWorktreeDiffFile").map((m) => m.file) + expect(files).toEqual(["a.ts", "b.ts"]) + }) + }) + + it("clears the session key once its last pending file resolves", () => { + withDiffs((diffs) => { + diffs.requestDiffFile("s1", "a.ts") + diffs.onWorktreeDiffFile({ + type: "agentManager.worktreeDiffFile", + sessionId: "s1", + file: "a.ts", + diff: diff("a.ts"), + }) + expect(diffs.diffFileLoadingFor(() => "s1").size).toBe(0) + }) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/diff-session-source.test.ts b/packages/kilo-vscode/tests/unit/diff-session-source.test.ts index ce870f0abb..081fec45ca 100644 --- a/packages/kilo-vscode/tests/unit/diff-session-source.test.ts +++ b/packages/kilo-vscode/tests/unit/diff-session-source.test.ts @@ -91,6 +91,39 @@ describe("createSessionDiffSource.fetch", () => { expect(result.diffs[2]?.summarized).toBe(true) }) + it("keeps valid files visible when one persisted patch is malformed", async () => { + const malformed = [ + "diff --git a/broken.ts b/broken.ts", + "--- a/broken.ts", + "+++ b/broken.ts", + "@@ -1,2 +1,2 @@", + "-old", + "+new", + "", + ].join("\n") + const { fetch } = recording([ + { file: "broken.ts", patch: malformed, additions: 1, deletions: 1, status: "modified" }, + { file: "foo.ts", patch: modifiedPatch, additions: 1, deletions: 1, status: "modified" }, + ]) + + const result = await createSessionDiffSource("s-malformed", fetch, "/repo").fetch() + + expect(result.diffs).toHaveLength(2) + expect(result.diffs[0]).toMatchObject({ + file: "broken.ts", + before: "", + after: "", + patch: malformed, + summarized: true, + }) + expect(result.diffs[1]).toMatchObject({ + file: "foo.ts", + before: "keep\nold\n", + after: "keep\nnew\n", + summarized: false, + }) + }) + it("rebuilds text-backed SVG snapshot sides without sending them to Pierre", async () => { const before = '' const after = '' diff --git a/packages/kilo-vscode/tests/unit/early-message.test.ts b/packages/kilo-vscode/tests/unit/early-message.test.ts new file mode 100644 index 0000000000..6075faff48 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/early-message.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from "bun:test" +import { routeEarlyMessage } from "../../src/kilo-provider/early-message" + +type Ctx = Parameters[1] + +function context(copied: string[], posted: unknown[], fail = false) { + return { + copy: async (text: string) => { + if (fail) throw new Error("clipboard unavailable") + copied.push(text) + }, + post: (message: unknown) => posted.push(message), + } as Ctx +} + +describe("routeEarlyMessage clipboard handling", () => { + it("routes clipboard text to the host", async () => { + const copied: string[] = [] + const posted: unknown[] = [] + + const handled = await routeEarlyMessage( + { type: "copyToClipboard", id: "copy-1", text: "message text" }, + context(copied, posted), + ) + + expect(handled).toBe(true) + expect(copied).toEqual(["message text"]) + expect(posted).toEqual([{ type: "clipboardWriteResult", id: "copy-1", ok: true }]) + }) + + it("reports host clipboard failures", async () => { + const copied: string[] = [] + const posted: unknown[] = [] + + const handled = await routeEarlyMessage( + { type: "copyToClipboard", id: "copy-2", text: "message text" }, + context(copied, posted, true), + ) + + expect(handled).toBe(true) + expect(copied).toEqual([]) + expect(posted).toEqual([{ type: "clipboardWriteResult", id: "copy-2", ok: false, error: "clipboard unavailable" }]) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/extension-arch.test.ts b/packages/kilo-vscode/tests/unit/extension-arch.test.ts index 1485db0e78..3e8e4d3067 100644 --- a/packages/kilo-vscode/tests/unit/extension-arch.test.ts +++ b/packages/kilo-vscode/tests/unit/extension-arch.test.ts @@ -117,6 +117,36 @@ describe("Extension — package.json command sync", () => { }) }) + it("keeps Agent Manager terminal shortcuts distinct", () => { + const terminal = pkg.contributes?.keybindings?.find( + (item: { command: string }) => item.command === "kilo-code.new.agentManager.showTerminal", + ) + const create = pkg.contributes?.keybindings?.find( + (item: { command: string }) => item.command === "kilo-code.new.agentManager.newTerminal", + ) + expect(terminal).toMatchObject({ + key: "ctrl+/", + mac: "cmd+/", + when: "activeWebviewPanelId == 'kilo-code.new.AgentManagerPanel'", + }) + expect(create).toMatchObject({ + key: "ctrl+shift+t", + mac: "cmd+shift+t", + when: "activeWebviewPanelId == 'kilo-code.new.AgentManagerPanel'", + }) + }) + + it("declares the Agent Manager terminal destination setting", () => { + const setting = pkg.contributes?.configuration?.properties?.["kilo-code.new.agentManager.terminalButtonDestination"] + expect(setting).toMatchObject({ + type: "string", + scope: "application", + default: "vscode", + enum: ["vscode", "agentManager"], + }) + expect(setting.enumDescriptions).toHaveLength(setting.enum.length) + }) + it("scopes the open PR shortcut to Agent Manager", () => { const binding = pkg.contributes?.keybindings?.find( (item: { command: string }) => item.command === "kilo-code.new.agentManager.openPR", diff --git a/packages/kilo-vscode/tests/unit/i18n-keys.test.ts b/packages/kilo-vscode/tests/unit/i18n-keys.test.ts index 11c8c277e3..1aa3db8111 100644 --- a/packages/kilo-vscode/tests/unit/i18n-keys.test.ts +++ b/packages/kilo-vscode/tests/unit/i18n-keys.test.ts @@ -41,6 +41,7 @@ import { dict as appTr } from "../../webview-ui/src/i18n/tr" import { dict as appNl } from "../../webview-ui/src/i18n/nl" import { dict as appUk } from "../../webview-ui/src/i18n/uk" import { dict as appIt } from "../../webview-ui/src/i18n/it" +import { dict as appFa } from "../../webview-ui/src/i18n/fa" // Layer 2: upstream UI (@opencode-ai/ui re-exported via @kilocode/kilo-ui) import { dict as uiEn } from "../../../ui/src/i18n/en" @@ -114,6 +115,7 @@ import { dict as cliTr } from "../../src/services/cli-backend/i18n/tr" import { dict as cliNl } from "../../src/services/cli-backend/i18n/nl" import { dict as cliUk } from "../../src/services/cli-backend/i18n/uk" import { dict as cliIt } from "../../src/services/cli-backend/i18n/it" +import { dict as cliFa } from "../../src/services/cli-backend/i18n/fa" import { dict as hostEn } from "../../src/services/i18n/en" import { dict as hostZh } from "../../src/services/i18n/zh" @@ -135,6 +137,7 @@ import { dict as hostTr } from "../../src/services/i18n/tr" import { dict as hostNl } from "../../src/services/i18n/nl" import { dict as hostUk } from "../../src/services/i18n/uk" import { dict as hostIt } from "../../src/services/i18n/it" +import { dict as hostFa } from "../../src/services/i18n/fa" // ── Locale maps ───────────────────────────────────────────────────────────── @@ -161,6 +164,7 @@ const appLocales: Record> = { nl: appNl, uk: appUk, it: appIt, + fa: appFa, } const kiloLocales: Record> = { @@ -230,6 +234,7 @@ const cliLocales: Record> = { nl: cliNl, uk: cliUk, it: cliIt, + fa: cliFa, } const hostLocales: Record> = { @@ -253,6 +258,7 @@ const hostLocales: Record> = { nl: hostNl, uk: hostUk, it: hostIt, + fa: hostFa, } // Merge webview dictionaries in the same priority order as language.tsx diff --git a/packages/kilo-vscode/tests/unit/i18n-unused-keys.test.ts b/packages/kilo-vscode/tests/unit/i18n-unused-keys.test.ts new file mode 100644 index 0000000000..ec10e384eb --- /dev/null +++ b/packages/kilo-vscode/tests/unit/i18n-unused-keys.test.ts @@ -0,0 +1,153 @@ +/** + * Localization lint: unused translation keys (all Kilo-owned pools) + * + * Ensures every key in every Kilo-owned english dictionary is either + * referenced from repository source or explicitly protected as a runtime + * protocol key. Keys that lose their last usage during refactors tend to + * linger in all 20 locale files forever; this test fails for review instead. + * + * Complements i18n-keys.test.ts, which checks the opposite direction + * (t() calls referencing keys missing from the dictionaries). + * + * Covered pools: + * app webview sidebar dict (webview-ui/src/i18n) + * kilo-i18n shared webview overrides (packages/kilo-i18n) + * agent-manager agent manager webview dict + * cli-backend extension server/remote dict + * host extension host dict (autocomplete) + * + * The upstream @opencode-ai/ui dict (packages/ui/src/i18n) is intentionally + * NOT covered: it is upstream-owned code where removals create merge + * conflicts, and its keys may be consumed by upstream surfaces outside + * this repo. + * + * This is intentionally conservative: false "used" results are acceptable, + * but a live key must never be classified as unused. A key counts as used + * when either: + * - a quoted string literal equal to the key appears in scanned code + * (covers t("key") calls, ternaries producing key strings, key strings + * embedded in the CLI backend that travel to the webview as data, e.g. + * provider metadata noteKeys in opencode/src/kilocode/provider), or + * - a template literal with a static dotted head ending right before ${} + * covers the key by prefix (e.g. t(`agentManager.setup.error.${code}`), + * t(`workStyle.choice.${choice}.title`), const k = `a.b.${x}`). + * - a quoted dotted prefix appears in production source (conservatively + * covering concatenation and indirect prefix reuse), or + * - the pool explicitly protects a runtime namespace used by backend + * payloads. Add indirect/external keys there rather than deleting them. + */ + +import { describe, it, expect } from "bun:test" +import { Glob } from "bun" +import path from "node:path" + +import { dict as appEn } from "../../webview-ui/src/i18n/en" +import { dict as kiloEn } from "../../../kilo-i18n/src/en" +import { dict as amEn } from "../../webview-ui/agent-manager/i18n/en" +import { dict as cliEn } from "../../src/services/cli-backend/i18n/en" +import { dict as hostEn } from "../../src/services/i18n/en" + +const REPO = path.resolve(import.meta.dir, "../../../..") +const VSCODE = path.join(REPO, "packages/kilo-vscode") + +// Webview key strings are consumed not only in the webview itself but also +// embedded as data in the CLI backend / extension host (provider metadata, +// question flows), so those sources are scanned for webview pools too. +const WEBVIEW_ROOTS = [ + VSCODE, + path.join(REPO, "packages/kilo-ui"), + path.join(REPO, "packages/opencode"), + path.join(REPO, "packages/kilo-gateway"), +] + +const pools = [ + { name: "app", dict: appEn, roots: WEBVIEW_ROOTS, runtime: ["settings.providers.note."] }, + // kilo-i18n also overrides upstream ui.* keys consumed by components in + // packages/ui/src (and the TUI), so those count as usage too. + { + name: "kilo-i18n", + dict: kiloEn, + roots: [...WEBVIEW_ROOTS, path.join(REPO, "packages/ui"), path.join(REPO, "packages/tui")], + runtime: ["plan.followup.", "snapshot.slowRepo.", "settings.providers.note."], + }, + { name: "agent-manager", dict: amEn, roots: WEBVIEW_ROOTS, runtime: ["agentManager.setup.error."] }, + { name: "cli-backend", dict: cliEn, roots: [VSCODE], runtime: [] }, + { name: "host", dict: hostEn, roots: [VSCODE], runtime: [] }, +] as const + +const EXACT_RE = /["'`]([a-zA-Z][a-zA-Z0-9_]*(?:[.:][a-zA-Z0-9_]+)+)["'`]/g +const DYNAMIC_RE = /`([a-zA-Z][a-zA-Z0-9_]*(?:[.:][a-zA-Z0-9_]+)*[.:])\$\{/g +const PREFIX_RE = /["'`]([a-zA-Z][a-zA-Z0-9_]*(?:[.:][a-zA-Z0-9_]+)*[.:])["'`]/g +const SOURCE_GLOB = "**/*.{ts,tsx,js,jsx,mjs,cjs,json,jsonc,json5,yaml,yml,toml,md,mdx,html,xml,properties,kt,kts,java}" +const SKIP = ["/i18n/", "/tests/", "/test/", "/node_modules/", "/dist/", "/out/", "/build/", "/coverage/", "/.turbo/"] + +interface Usage { + exact: Set + prefixes: Set +} + +const cache = new Map>() + +function scan(root: string): Promise { + const hit = cache.get(root) + if (hit) return hit + const job = (async (): Promise => { + const glob = new Glob(SOURCE_GLOB) + const exact = new Set() + const prefixes = new Set() + for await (const file of glob.scan({ cwd: root, absolute: true })) { + // skip the locale dictionaries themselves — they contain every key + if (SKIP.some((part) => file.includes(part)) || file.includes(".test.") || file.includes(".spec.")) continue + const text = await Bun.file(file).text() + for (const match of text.matchAll(EXACT_RE)) { + const key = match[1] + if (!key) continue + exact.add(key) + } + for (const match of text.matchAll(DYNAMIC_RE)) { + if (match[1]) prefixes.add(match[1]) + } + for (const match of text.matchAll(PREFIX_RE)) { + if (match[1]) prefixes.add(match[1]) + } + } + return { exact, prefixes } + })() + cache.set(root, job) + return job +} + +async function unused( + dict: Record, + roots: readonly string[], + runtime: readonly string[], +): Promise { + const exact = new Set() + const prefixes = new Set() + for (const root of roots) { + const usage = await scan(root) + for (const key of usage.exact) exact.add(key) + for (const key of usage.prefixes) prefixes.add(key) + } + return Object.keys(dict).filter((key) => { + if (exact.has(key)) return false + if (runtime.some((prefix) => key.startsWith(prefix))) return false + for (const prefix of prefixes) { + if (key.startsWith(prefix)) return false + } + return true + }) +} + +describe("i18n — no unused keys", () => { + for (const pool of pools) { + it(`references every ${pool.name} dictionary key from code`, async () => { + const dead = await unused(pool.dict, pool.roots, pool.runtime) + const report = dead.map((key) => ` "${key}": "${pool.dict[key as keyof typeof pool.dict]}"`).join("\n") + expect( + dead, + `Found ${dead.length} apparently unused ${pool.name} i18n key(s). Do not delete automatically: verify runtime/external consumers, then either add a source reference, protect the runtime namespace in this test, or remove the key from every locale:\n${report}`, + ).toEqual([]) + }) + } +}) diff --git a/packages/kilo-vscode/tests/unit/kilo-provider-indexing-refresh.test.ts b/packages/kilo-vscode/tests/unit/kilo-provider-indexing-refresh.test.ts index bbe7591840..82621a7e46 100644 --- a/packages/kilo-vscode/tests/unit/kilo-provider-indexing-refresh.test.ts +++ b/packages/kilo-vscode/tests/unit/kilo-provider-indexing-refresh.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "bun:test" import type { Config } from "@kilocode/sdk/v2/client" +import { fetchSnapshot } from "../../src/kilo-provider/config-snapshot" // vscode mock is provided by the shared preload (tests/setup/vscode-mock.ts) const { KiloProvider } = await import("../../src/KiloProvider") @@ -17,6 +18,7 @@ type Internals = { projectUnset?: string[][], ) => Promise fetchAndSendConfig: () => Promise + fetchAndSendConfigUpdated: () => Promise fetchAndSendProviders: () => Promise fetchAndSendAgents: () => Promise fetchAndSendSkills: () => Promise @@ -88,6 +90,7 @@ function createConnection() { } return { + client, drains: () => drains, patches: () => patches, service: { @@ -100,6 +103,43 @@ function createConnection() { } describe("KiloProvider indexing refresh", () => { + it("shares snapshot payloads across load, SSE refresh, and post-save refresh", async () => { + const conn = createConnection() + const settings = () => ({ maxCost: 0, languageCommitMessage: "sync", multiProject: false }) + const snapshot = await fetchSnapshot(conn.client as never, "/repo", settings) + const provider = new KiloProvider({} as never, conn.service as never) + const internal = provider as unknown as Internals + const sent: Array> = [] + provider.postMessage = (message) => void sent.push(message as Record) + Object.assign(internal, { connectionState: "connected", commitMessageLanguageSetting: () => "sync" }) + await internal.fetchAndSendConfig() + await internal.fetchAndSendConfigUpdated() + // Save against the binding the latest config load issued, like the webview + // does: each load supersedes older bindings for the same scope+directory. + const issued = (sent[sent.length - 1]!.bindings as { global: { id: string } }).global.id + await internal.handleUpdateConfig({ model: "test/global" }, {}, [], [], issued) + + // bindings carry fresh per-load revision state, so compare the payload + // without them; each message still must carry a bindings object. + const strip = (m: Record) => { + const { bindings, ...rest } = m + expect(bindings).toMatchObject({ global: expect.anything() }) + return rest + } + const payload = { + config: snapshot.config, + globalConfig: snapshot.targets!.global.raw, + projectConfig: snapshot.targets!.project.raw, + settings: snapshot.settings, + features: snapshot.features, + } + expect(sent.map(strip)).toEqual([ + { type: "configLoaded", ...payload }, + { type: "configUpdated", ...payload }, + { type: "configUpdated", ...payload }, + ]) + }) + it("reloadAfterAuthChange fetches config first, then indexing status", async () => { const provider = new KiloProvider({} as never, {} as never) const internal = provider as unknown as Internals diff --git a/packages/kilo-vscode/tests/unit/language-utils.test.ts b/packages/kilo-vscode/tests/unit/language-utils.test.ts index cc1a8c286b..b4dc8e0aee 100644 --- a/packages/kilo-vscode/tests/unit/language-utils.test.ts +++ b/packages/kilo-vscode/tests/unit/language-utils.test.ts @@ -33,6 +33,11 @@ describe("normalizeLocale", () => { expect(normalizeLocale("ko-KR")).toBe("ko") }) + it("returns 'fa' for Persian", () => { + expect(normalizeLocale("fa")).toBe("fa") + expect(normalizeLocale("fa-IR")).toBe("fa") + }) + it("returns 'no' for Norwegian Bokmål", () => { expect(normalizeLocale("nb")).toBe("no") expect(normalizeLocale("nb-NO")).toBe("no") diff --git a/packages/kilo-vscode/tests/unit/prompt-rail.test.ts b/packages/kilo-vscode/tests/unit/prompt-rail.test.ts new file mode 100644 index 0000000000..d9854b5226 --- /dev/null +++ b/packages/kilo-vscode/tests/unit/prompt-rail.test.ts @@ -0,0 +1,192 @@ +import { describe, expect, it } from "bun:test" +import { messageTurns } from "../../webview-ui/src/context/session-queue" +import { transcriptRows } from "../../webview-ui/src/context/transcript-rows" +import type { Message, Part, TextPart } from "../../webview-ui/src/types/messages" +import { capacity, previewText, promptItems, railItems } from "../../webview-ui/src/components/chat/prompt-rail" + +const base = { + sessionID: "session", + createdAt: "2026-01-01T00:00:00.000Z", + time: { created: 1 }, +} + +const user = (id: string, opts: Partial = {}): Message => ({ ...base, id, role: "user", ...opts }) +const assistant = (id: string, parentID: string, opts: Partial = {}): Message => ({ + ...base, + id, + parentID, + role: "assistant", + ...opts, +}) +const text = (id: string, messageID: string, value: string, opts: Partial = {}): Part => ({ + id, + messageID, + type: "text", + text: value, + ...opts, +}) +const tool = (id: string, messageID: string, title: string): Part => ({ + id, + messageID, + type: "tool", + tool: "bash", + state: { status: "completed", input: {}, output: "", title }, +}) +const lookup = (values: Record) => (id: string) => values[id] ?? [] + +describe("previewText", () => { + it("collapses whitespace and keeps plain text", () => { + expect(previewText(" hello\n\nworld ")).toBe("hello world") + }) + + it("drops fenced code blocks and inline code", () => { + expect(previewText("before `const x = 1` after\n```\nconst y = 2\n```\nend")).toBe("before after end") + }) + + it("keeps link labels but drops URLs and images", () => { + expect(previewText("see [the docs](https://example.com) and ![shot](img.png) now")).toBe("see the docs and now") + }) + + it("keeps bracket text inside inline code literal", () => { + expect(previewText("echo `[label](url)` verbatim")).toBe("echo verbatim") + }) + + it("strips heading, list, and quote markers", () => { + expect(previewText("# Title\n- one\n> two\nthree")).toBe("Title one two three") + }) +}) + +describe("promptItems", () => { + it("emits one item per non-partial user turn with prompt and answer", () => { + const u1 = user("u1") + const a1 = assistant("a1", "u1") + const u2 = user("u2") + const a2 = assistant("a2", "u2") + const parts = { + u1: [text("up1", "u1", "add authentication")], + a1: [text("ap1", "a1", "Done, auth is wired up.")], + u2: [text("up2", "u2", "now fix the bug")], + a2: [text("ap2", "a2", "Fixed it.")], + } + const rows = transcriptRows(messageTurns([u1, a1, u2, a2]), lookup(parts)) + + const items = promptItems(rows) + + expect(items).toEqual([ + { key: "u1:user", turn: "u1", queued: false, prompt: "add authentication", answer: "Done, auth is wired up." }, + { key: "u2:user", turn: "u2", queued: false, prompt: "now fix the bug", answer: "Fixed it." }, + ]) + }) + + it("keeps assistant chunks of one turn joined", () => { + const u1 = user("u1") + const a1 = assistant("a1", "u1") + const parts = { + u1: [text("up1", "u1", "go")], + a1: [text("ap1", "a1", "First."), text("ap2", "a1", "Second.")], + } + const rows = transcriptRows(messageTurns([u1, a1]), lookup(parts)) + + expect(promptItems(rows)[0]?.answer).toBe("First. Second.") + }) + + it("skips synthetic parts and leaves an empty answer for tool-only turns", () => { + const u1 = user("u1") + const a1 = assistant("a1", "u1") + const parts = { + u1: [text("up1", "u1", "run it"), text("up2", "u1", "internal", { synthetic: true })], + a1: [tool("at1", "a1", "Run tests"), text("at2", "a1", "hidden", { synthetic: true })], + } + const rows = transcriptRows(messageTurns([u1, a1]), lookup(parts)) + + expect(promptItems(rows)[0]).toMatchObject({ prompt: "run it", answer: "" }) + }) + + it("marks queued rows", () => { + const u1 = user("u1") + const u2 = user("u2") + const rows = transcriptRows(messageTurns([u1, u2]), lookup({}), { queued: new Set(["u2"]) }) + + expect(promptItems(rows).map((item) => item.queued)).toEqual([false, true]) + }) + + it("truncates long prompts and answers", () => { + const u1 = user("u1") + const a1 = assistant("a1", "u1") + const parts = { + u1: [text("up1", "u1", "x".repeat(400))], + a1: [text("ap1", "a1", "y".repeat(400))], + } + const rows = transcriptRows(messageTurns([u1, a1]), lookup(parts)) + + const [item] = promptItems(rows) + expect(item!.prompt.length).toBe(160) + expect(item!.prompt.endsWith("…")).toBe(true) + expect(item!.answer.length).toBe(220) + expect(item!.answer.endsWith("…")).toBe(true) + }) + + it("omits partial turns (assistant-only leads)", () => { + const a1 = assistant("a1", "u1") + const rows = transcriptRows(messageTurns([a1]), lookup({ a1: [text("p1", "a1", "hello")] })) + + expect(promptItems(rows)).toEqual([]) + }) + + it("promotes the answer when the prompt carries no text (image-only message)", () => { + const u1 = user("u1") + const a1 = assistant("a1", "u1") + const parts = { + u1: [{ id: "up1", messageID: "u1", type: "file", mime: "image/png", url: "data:," } as Part], + a1: [text("ap1", "a1", "That screenshot shows the rail overlapping the gutter.")], + } + const rows = transcriptRows(messageTurns([u1, a1]), lookup(parts)) + + expect(promptItems(rows)[0]).toMatchObject({ + prompt: "That screenshot shows the rail overlapping the gutter.", + answer: "", + }) + }) + + it("leaves both empty when neither prompt nor answer has text", () => { + const u1 = user("u1") + const rows = transcriptRows(messageTurns([u1]), lookup({})) + + expect(promptItems(rows)[0]).toMatchObject({ prompt: "", answer: "" }) + }) +}) + +describe("capacity", () => { + it("counts how many worst-case rows fit the transcript height", () => { + expect(capacity(24 + 76 * 5)).toBe(5) + expect(capacity(100)).toBe(1) + }) + + it("returns nothing usable for unmeasured or tiny transcripts", () => { + expect(capacity(0)).toBeLessThan(1) + expect(capacity(99)).toBeLessThan(1) + }) +}) + +describe("railItems", () => { + const items = Array.from({ length: 5 }, (_, i) => ({ + key: `k${i}`, + turn: `t${i}`, + queued: false, + prompt: `p${i}`, + answer: `a${i}`, + })) + + it("passes through when everything fits", () => { + expect(railItems(items, 5)).toEqual(items) + expect(railItems(items, 10)).toEqual(items) + }) + + it("keeps the newest items when capacity is smaller", () => { + expect(railItems(items, 2)).toEqual(items.slice(-2)) + }) + + it("returns nothing at zero capacity", () => { + expect(railItems(items, 0)).toEqual([]) + }) +}) diff --git a/packages/kilo-vscode/tests/unit/session-outcome.test.ts b/packages/kilo-vscode/tests/unit/session-outcome.test.ts index 8dd06b6dd6..93b8475e5f 100644 --- a/packages/kilo-vscode/tests/unit/session-outcome.test.ts +++ b/packages/kilo-vscode/tests/unit/session-outcome.test.ts @@ -138,6 +138,13 @@ describe("terminal", () => { ).toBe("error") }) + it("hides superseded turns that handed off to a queued follow-up", () => { + expect( + terminal({ reason: "superseded", messages: [message("tool-calls")], todos: [todo("pending")] }), + ).toBeUndefined() + expect(terminal({ reason: "superseded", messages: [message("unknown")], todos: [] })).toBeUndefined() + }) + it("reports only the latest assistant finish reason", () => { const user: Message = { id: "u1", sessionID: "s1", role: "user", createdAt: new Date(1).toISOString() } expect(terminal({ reason: "completed", messages: [message("length"), user], todos: [] })?.finish).toBeUndefined() diff --git a/packages/kilo-vscode/tests/unit/session-terminal-manager.test.ts b/packages/kilo-vscode/tests/unit/session-terminal-manager.test.ts index da340afc0b..5669acaeac 100644 --- a/packages/kilo-vscode/tests/unit/session-terminal-manager.test.ts +++ b/packages/kilo-vscode/tests/unit/session-terminal-manager.test.ts @@ -9,6 +9,7 @@ import { describe, it, expect } from "bun:test" import path from "node:path" import { Project, SyntaxKind } from "ts-morph" import { SessionTerminalManager, type TerminalHost } from "../../src/agent-manager/SessionTerminalManager" +import type { WorktreeStateManager } from "../../src/agent-manager/WorktreeStateManager" const ROOT = path.resolve(import.meta.dir, "../..") const FILE = path.join(ROOT, "src/agent-manager/SessionTerminalManager.ts") @@ -130,13 +131,8 @@ describe("SessionTerminalManager structure", () => { expect(text).toContain("panel command registration skipped") }) - it("exposes active terminal state for terminal context routing", () => { - const text = body("hasActiveTerminal") - expect(text).toContain("this.host.activeTerminal()") - }) - - it("resolves the session that owns the active managed terminal", () => { - const text = body("activeSession") + it("resolves the key that owns the active managed terminal", () => { + const text = body("activeKey") expect(text).toContain("this.host.activeTerminal()") expect(text).toContain("entry.terminal === active") }) @@ -144,7 +140,8 @@ describe("SessionTerminalManager structure", () => { it("rejects context capture from another managed session", () => { const text = body("prepareContext") expect(text).toContain("this.showExisting(sessionId)") - expect(text).toContain("this.activeSession()") + expect(text).toContain("this.activeKey()") + expect(text).toContain("SessionTerminalManager.sessionKey(sessionId)") }) }) @@ -167,3 +164,74 @@ describe("SessionTerminalManager command restoration", () => { state.manager.dispose() }) }) + +describe("SessionTerminalManager worktree terminals", () => { + function scene(opts: { worktreePath?: string; repoPath?: string } = {}) { + const created: Array<{ cwd: string; name: string }> = [] + const warnings: string[] = [] + let shown = 0 + const host: TerminalHost = { + createTerminal(o) { + created.push(o) + return { + show: () => shown++, + dispose() {}, + exitStatus: undefined, + } + }, + activeTerminal: () => undefined, + repoPath: () => opts.repoPath, + showWarning: (msg) => warnings.push(msg), + setContext() {}, + onTerminalClosed: () => ({ dispose() {} }), + onActiveTerminalChanged: () => ({ dispose() {} }), + registerCommand: () => ({ dispose() {} }), + executeCommand: () => Promise.resolve(), + } + const state = { + getWorktree: (id: string) => + opts.worktreePath ? { id, path: opts.worktreePath, branch: "feature/x" } : undefined, + } as unknown as WorktreeStateManager + const manager = new SessionTerminalManager(() => {}, host) + return { manager, state, created, warnings, shown: () => shown } + } + + it("creates a terminal rooted at the worktree path", () => { + const s = scene({ worktreePath: "/repo/.kilo/worktrees/wt-1", repoPath: "/repo" }) + s.manager.showWorktreeTerminal("wt-1", s.state) + expect(s.created).toEqual([{ cwd: "/repo/.kilo/worktrees/wt-1", name: "Agent: feature/x" }]) + expect(s.shown()).toBe(1) + }) + + it("reuses the live terminal on repeat calls", () => { + const s = scene({ worktreePath: "/repo/.kilo/worktrees/wt-1" }) + s.manager.showWorktreeTerminal("wt-1", s.state) + s.manager.showWorktreeTerminal("wt-1", s.state) + expect(s.created).toHaveLength(1) + expect(s.shown()).toBe(2) + }) + + it("keeps session and worktree terminal keys in separate namespaces", () => { + const s = scene({ worktreePath: "/repo/.kilo/worktrees/wt-1", repoPath: "/repo" }) + s.manager.showTerminal("worktree:wt-1", undefined) + s.manager.showWorktreeTerminal("wt-1", s.state) + expect(s.created).toEqual([ + { cwd: "/repo", name: "Agent: local" }, + { cwd: "/repo/.kilo/worktrees/wt-1", name: "Agent: feature/x" }, + ]) + expect(s.shown()).toBe(2) + }) + + it("falls back to the repo root when the worktree is unknown", () => { + const s = scene({ repoPath: "/repo" }) + s.manager.showWorktreeTerminal("gone", s.state) + expect(s.created).toEqual([{ cwd: "/repo", name: "Agent: worktree" }]) + }) + + it("warns and creates nothing when no cwd resolves", () => { + const s = scene({}) + s.manager.showWorktreeTerminal("gone", s.state) + expect(s.created).toHaveLength(0) + expect(s.warnings).toHaveLength(1) + }) +}) diff --git a/packages/kilo-vscode/tests/visual-regression.spec.mts b/packages/kilo-vscode/tests/visual-regression.spec.mts index 3f210bb7cb..437ac4bd85 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.mts +++ b/packages/kilo-vscode/tests/visual-regression.spec.mts @@ -75,9 +75,12 @@ async function settle(page: Page) { // Spinner animation captures at an indeterminate frame, causing flaky diffs. // Permission dock config-preloaded has non-deterministic toggle rendering. // Sandboxing rows can settle at different scroll heights after settings context updates. +// Side terminal tabs mount live xterm instances whose websocket error text +// lands at indeterminate times. const SKIP = new Set([ "agentmanager--worktree-item-busy", "agentmanager--full-screen-diff-agent-edit-scroll", + "agentmanager--side-terminal-panel-tabs", "composite-webview--permission-dock-config-preloaded", "settings--sandboxing-allowlist", "settings--sandboxing-panel", diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts b/packages/kilo-vscode/tests/visual-regression.spec.ts index 3f210bb7cb..437ac4bd85 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts @@ -75,9 +75,12 @@ async function settle(page: Page) { // Spinner animation captures at an indeterminate frame, causing flaky diffs. // Permission dock config-preloaded has non-deterministic toggle rendering. // Sandboxing rows can settle at different scroll heights after settings context updates. +// Side terminal tabs mount live xterm instances whose websocket error text +// lands at indeterminate times. const SKIP = new Set([ "agentmanager--worktree-item-busy", "agentmanager--full-screen-diff-agent-edit-scroll", + "agentmanager--side-terminal-panel-tabs", "composite-webview--permission-dock-config-preloaded", "settings--sandboxing-allowlist", "settings--sandboxing-panel", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index 1fd4c45216..a7aa97e736 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -14,10 +14,10 @@ import { type Setter, } from "solid-js" import type { - ExtensionMessage, AgentManagerRepoInfoMessage, AgentManagerWorktreeSetupMessage, AgentManagerStateMessage, + ExtensionMessage, AgentManagerKeybindingsMessage, AgentManagerMultiVersionProgressMessage, AgentManagerSendInitialMessage, @@ -26,8 +26,6 @@ import type { AgentManagerWorktreeDiffFileMessage, AgentManagerWorktreeDiffLoadingMessage, AgentManagerApplyWorktreeDiffResultMessage, - AgentManagerApplyWorktreeDiffStatus, - AgentManagerApplyWorktreeDiffConflict, AgentManagerWorktreeStatsMessage, AgentManagerLocalStatsMessage, WorktreeFileDiff, @@ -48,24 +46,15 @@ import type { import { IndexingProvider } from "../src/context/indexing" import {} from "@thisbeyond/solid-dnd" import type { DragEvent } from "@thisbeyond/solid-dnd" -import { ThemeProvider } from "@kilocode/kilo-ui/theme" -import { DialogProvider, useDialog } from "@kilocode/kilo-ui/context/dialog" +import { useDialog } from "@kilocode/kilo-ui/context/dialog" import { Dialog } from "@kilocode/kilo-ui/dialog" -import {} from "@kilocode/kilo-ui/dropdown-menu" -import { MarkedProvider } from "@kilocode/kilo-ui/context/marked" -import { CodeComponentProvider } from "@kilocode/kilo-ui/context/code" -import { DiffComponentProvider } from "@kilocode/kilo-ui/context/diff" -import { FileComponentProvider } from "@kilocode/kilo-ui/context/file" -import { Code } from "@kilocode/kilo-ui/code" -import { Diff } from "@kilocode/kilo-ui/diff" -import { File } from "@kilocode/kilo-ui/file" -import { Toast, showToast } from "@kilocode/kilo-ui/toast" +import { showToast } from "@kilocode/kilo-ui/toast" import { ResizeHandle } from "@kilocode/kilo-ui/resize-handle" import { Icon } from "@kilocode/kilo-ui/icon" import { Button } from "@kilocode/kilo-ui/button" import { IconButton } from "@kilocode/kilo-ui/icon-button" import { Spinner } from "@kilocode/kilo-ui/spinner" -import { Tooltip } from "@kilocode/kilo-ui/tooltip" +import { Tooltip, TooltipKeybind } from "@kilocode/kilo-ui/tooltip" import { Popover } from "@kilocode/kilo-ui/popover" import { VSCodeProvider, useVSCode } from "../src/context/vscode" import { ServerProvider } from "../src/context/server" @@ -80,8 +69,8 @@ import { MemoryProvider } from "../src/context/memory" import { SessionProvider, useSession } from "../src/context/session" import { AgentRequirementsProvider } from "../src/context/agent-requirements" import { WorktreeModeProvider } from "../src/context/worktree-mode" +import { ProviderShell } from "../src/context/provider-shell" import { ChatView } from "../src/components/chat" -import { SpeechToTextPrewarm } from "../src/components/speech-to-text/SpeechToTextPrewarm" import HistoryView from "../src/components/history/HistoryView" import { NewWorktreeDialog } from "./NewWorktreeDialog" import { ProjectList } from "./ProjectList" @@ -92,11 +81,11 @@ import { createProjectSessionsLive } from "./project/sessions-live" import { applyProjectSelection, createTargetRememberer } from "./project/selection" import { createLocalSessions, persistLocalTabs } from "./project/local-tabs" import { createProjectRegistry, type PersistedProjectTabs } from "./project/registry" -import type { ApplyState, WorktreeBusyState } from "./project/store" +import type { WorktreeBusyState } from "./project/store" import { rememberTarget, restoreProjectTarget } from "./project/restore" import { createProjectStateRouter } from "./project/state" import { selectLocalAction, selectWorktreeAction } from "./selection-actions" -import { DataBridge, MermaidDownloadBridge } from "../src/App" +import { DataBridge } from "../src/App" import { LanguageBridge } from "../src/context/language-bridge" import { useLanguage } from "../src/context/language" import { createTabFocus } from "../src/utils/tab-navigation" @@ -128,15 +117,25 @@ import { import { reorderTabs, applyTabOrder, firstOrderedTitle } from "./tab-order" import { createTabOrderSync } from "./tab-order-sync" import { reportRemoteSessions, reportVisibleSession, visible } from "./remote-sessions" -import {} from "../src/components/chat/TabDnd" -import { isTerminalTabId, createTerminalState, createTerminalHandlers, createTerminalMessageHandler } from "./terminal" +import { ConstrainDragYAxis } from "../src/components/chat/TabDnd" +import { + SideTerminalPanel, + TerminalDestinationButton, + isTerminalTabId, + createTerminalState, + createTerminalHandlers, + createTerminalMessageHandler, + createSideTerminal, + readSavedDestination, + resolveVscodeTerminalRequest, +} from "./terminal" import { focusCurrentTab, renderTab, renderTerminalLayer, renderNewTabButton } from "./tab-rendering" import { useTabScroll } from "./tab-scroll" import { DiffPanel } from "./DiffPanel" import { createRevertFile } from "./revert-file" import { FullScreenDiffView } from "../diff-viewer/FullScreenDiffView" -import { ApplyDialog } from "./ApplyDialog" -import { groupApplyConflicts } from "./apply-conflicts" +import { createApplyToLocal } from "./apply-to-local" +import { createWorktreeDiffs } from "./worktree-diffs" import type { ReviewComment } from "../diff-viewer/review-comments" import { clearReviewComposer, createReviewComposer } from "../diff-viewer/review-annotations" import type { SidebarSearchMenuRef } from "./SidebarSearchMenu" @@ -179,7 +178,7 @@ interface SetupState { /** Sidebar selection: LOCAL for local repo, worktree ID for a worktree, or null for an unassigned session. */ type SidebarSelection = typeof LOCAL | string | null -type SidePanel = "diff" | "pr" | null +type SidePanel = "diff" | "pr" | "terminal" | null const isMac = typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent) // Fallback keybindings before extension sends resolved ones const MAX_JUMP_INDEX = 9 @@ -234,6 +233,8 @@ const AgentManagerContent: Component = () => { const setBusyWorktrees: Setter> = (v) => registry.active().setBusy(v) const staleWorktreeIds = () => registry.active().staleWorktreeIds() const setStaleWorktreeIds: Setter> = (v) => registry.active().setStaleWorktreeIds(v) + /** True while the ⌘/Ctrl jump modifier is held — reveals the ⌘1-9 badges on all sidebar items. */ + const [held, setHeld] = createSignal(false) const [worktreesLoaded, setWorktreesLoaded] = createSignal(false) const [sessionsLoaded, setSessionsLoaded] = createSignal(false) const [isGitRepo, setIsGitRepo] = createSignal(true) @@ -286,16 +287,43 @@ const AgentManagerContent: Component = () => { // rAF coalescing for resize handlers — at most one signal write per frame let sidebarRaf: number | undefined let pendingSidebarWidth: number | undefined - let diffRaf: number | undefined - let pendingDiffWidth: number | undefined + let sideRaf: number | undefined + let pendingSideWidth: number | undefined const [history, setHistory] = createSignal(false) const [sidePanel, setSidePanel] = createSignal(null) const diffOpen = () => sidePanel() === "diff" - const [diffDatas, setDiffDatas] = createSignal>({}) - const [diffLoading, setDiffLoading] = createSignal(false) - const [diffFileLoading, setDiffFileLoading] = createSignal>>({}) + const diffs = createWorktreeDiffs(vscode) + const diffDatas = diffs.diffDatas + const diffLoading = diffs.diffLoading + const setDiffLoading = diffs.setDiffLoading + // The diff and terminal panels each remember their own width: a diff + // benefits from half the window, a terminal only needs about a third. + const TERMINAL_MIN_WIDTH = 360 + const TERMINAL_MAX_WIDTH = 640 const [diffWidth, setDiffWidth] = createSignal(Math.round(window.innerWidth * 0.5)) + const [terminalWidth, setTerminalWidth] = createSignal( + Math.min(TERMINAL_MAX_WIDTH, Math.max(TERMINAL_MIN_WIDTH, Math.round(window.innerWidth / 3))), + ) + // The hidden-but-mounted host still fits the terminal, so pick the + // terminal's width whenever one is alive and no other mode is showing. + const widthMode = () => sidePanel() ?? (terms.sides().length > 0 ? "terminal" : null) + const hostWidth = () => (widthMode() === "terminal" ? terminalWidth() : diffWidth()) + const sideMin = () => (widthMode() === "terminal" ? TERMINAL_MIN_WIDTH : 200) + const resizeSide = (width: number) => { + pendingSideWidth = Math.max(sideMin(), Math.min(width, window.innerWidth * 0.8)) + if (sideRaf !== undefined) return + sideRaf = requestAnimationFrame(() => { + sideRaf = undefined + if (widthMode() === "terminal") setTerminalWidth(pendingSideWidth!) + else setDiffWidth(pendingSideWidth!) + }) + } + const showSideTerminal = () => { + setHistory(false) + setReviewActive(false) + setSidePanel("terminal") + } const [reviewOpenByContext, setReviewOpenByContext] = createSignal>({}) const [reviewCommentsByContext, setReviewCommentsByContext] = createSignal>({}) @@ -323,13 +351,6 @@ const AgentManagerContent: Component = () => { branch: (branch) => setRepoBranch(branch), }) - // Per-worktree apply-to-local status - const applyStates = () => registry.active().applyStates() - const setApplyStates: Setter> = (v) => registry.active().setApplyStates(v) - const [applyTarget, setApplyTarget] = createSignal() - const [applySelectedFiles, setApplySelectedFiles] = createSignal([]) - const [applySelectionTouched, setApplySelectionTouched] = createSignal(false) - const PENDING_PREFIX = "pending:" const closedDrafts = new Set() const [activePendingId, setActivePendingId] = createSignal() @@ -391,12 +412,6 @@ const AgentManagerContent: Component = () => { setReviewCommentsByContext((prev) => ({ ...prev, [sel]: comments })) } - const applyStateForSelection = createMemo(() => { - const sel = selection() - if (!sel || sel === LOCAL) return undefined - return applyStates()[sel] - }) - const resolveWorktreeSessionId = (worktreeId: string) => { const id = session.currentSessionID() if (id) { @@ -406,157 +421,19 @@ const AgentManagerContent: Component = () => { return managedSessions().find((entry) => entry.worktreeId === worktreeId)?.id } - const applyTargetSessionId = createMemo(() => { - const target = applyTarget() - if (!target) return undefined - return resolveWorktreeSessionId(target) + const apply = createApplyToLocal({ + vscode, + dialog, + t, + selection, + local: LOCAL, + worktrees, + diffDatas, + diffLoading, + resolveWorktreeSessionId, + track: metrics.track, }) - - const applyDiffs = createMemo(() => { - const target = applyTarget() - if (!target) return [] as WorktreeFileDiff[] - const data = diffDatas() - const current = applyTargetSessionId() - if (current && data[current]) return data[current]! - const ids = managedSessions() - .filter((entry) => entry.worktreeId === target) - .map((entry) => entry.id) - for (const id of ids) { - if (data[id]) return data[id]! - } - return [] as WorktreeFileDiff[] - }) - - const applyStateForTarget = createMemo(() => { - const target = applyTarget() - if (!target) return undefined - return applyStates()[target] - }) - - const applyBusyForTarget = createMemo(() => { - const state = applyStateForTarget() - if (!state) return false - return state.status === "checking" || state.status === "applying" - }) - - const applySelectedSet = createMemo(() => new Set(applySelectedFiles())) - - const applySelectionStats = createMemo(() => { - const set = applySelectedSet() - const selected = applyDiffs().filter((diff) => set.has(diff.file)) - const additions = selected.reduce((sum, diff) => sum + diff.additions, 0) - const deletions = selected.reduce((sum, diff) => sum + diff.deletions, 0) - return { - total: applyDiffs().length, - selected: selected.length, - additions, - deletions, - } - }) - - const applyHasSelection = createMemo(() => applySelectionStats().selected > 0) - - const applyConflictRows = createMemo(() => groupApplyConflicts(applyStateForTarget()?.conflicts ?? [])) - - const applyToLocal = (worktreeId: string, selectedFiles: string[]) => { - setApplyStates((prev) => ({ - ...prev, - [worktreeId]: { - status: "checking", - message: t("agentManager.apply.checking"), - conflicts: [], - }, - })) - vscode.postMessage({ type: "agentManager.applyWorktreeDiff", worktreeId, selectedFiles }) - } - - const resetApplyDialog = () => { - setApplyTarget(undefined) - setApplySelectedFiles([]) - setApplySelectionTouched(false) - } - - const closeApplyDialog = () => { - resetApplyDialog() - dialog.close() - } - - const applySelectAll = () => { - setApplySelectionTouched(true) - setApplySelectedFiles(applyDiffs().map((diff) => diff.file)) - } - - const applySelectNone = () => { - setApplySelectionTouched(true) - setApplySelectedFiles([]) - } - - const applyToggleFile = (file: string, checked: boolean) => { - setApplySelectionTouched(true) - setApplySelectedFiles((prev) => { - if (checked) { - if (prev.includes(file)) return prev - const set = new Set(prev) - set.add(file) - return applyDiffs() - .map((diff) => diff.file) - .filter((path) => set.has(path)) - } - if (!prev.includes(file)) return prev - return prev.filter((path) => path !== file) - }) - } - - const triggerApply = () => { - const target = applyTarget() - if (!target) return - if (!applyHasSelection()) return - if (applyBusyForTarget()) return - metrics.track("apply_to_local", "apply_dialog", { fileCount: applySelectedFiles().length }) - applyToLocal(target, applySelectedFiles()) - } - - const openApplyDialog = () => { - const sel = selection() - if (!sel || sel === LOCAL) return - setApplyStates((prev) => { - if (!prev[sel]) return prev - const next = { ...prev } - delete next[sel] - return next - }) - setApplyTarget(sel) - setApplySelectionTouched(false) - setApplySelectedFiles([]) - const sid = resolveWorktreeSessionId(sel) - if (sid) vscode.postMessage({ type: "agentManager.requestWorktreeDiff", sessionId: sid }) - - setApplySelectedFiles(applyDiffs().map((diff) => diff.file)) - - dialog.show( - () => ( - - ), - resetApplyDialog, - ) - } + const openApplyDialog = apply.openApplyDialog const openWorktreeDirectory = () => { const sel = selection() @@ -588,31 +465,6 @@ const AgentManagerContent: Component = () => { if (sel) runWorktree(sel) } - createEffect( - on( - () => [applyTarget(), applyDiffs(), applySelectionTouched()] as const, - ([target, diffs, touched]) => { - if (!target) return - const files = diffs.map((diff) => diff.file) - if (files.length === 0) { - if (!touched) setApplySelectedFiles([]) - return - } - - if (!touched) { - setApplySelectedFiles(files) - return - } - - const current = applySelectedFiles() - const set = new Set(current) - const next = files.filter((file) => set.has(file)) - const same = next.length === current.length && next.every((file, index) => file === current[index]) - if (!same) setApplySelectedFiles(next) - }, - ), - ) - const isPending = (id: string) => id.startsWith(PENDING_PREFIX) reportRemoteSessions(vscode, localSessionIDs, managedSessions, isPending) @@ -723,14 +575,6 @@ const AgentManagerContent: Component = () => { if (Object.keys(next).length === Object.keys(prev).length) return prev return next }) - setApplyStates((prev) => { - const next = Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))) - if (Object.keys(next).length === Object.keys(prev).length) return prev - return next - }) - - const target = applyTarget() - if (target && !ids.has(target)) closeApplyDialog() }) const worktreeSessionIds = createMemo( @@ -1218,12 +1062,7 @@ const AgentManagerContent: Component = () => { requestAnimationFrame(() => sidebarSearchMenu?.open()) } } else if (msg.action === "showTerminal") { - // Cmd+/ opens the legacy VS Code integrated terminal for the - // active session (or local). The new xterm tab affordance has - // its own keybind (Cmd+Shift+T) so both coexist. - const id = session.currentSessionID() - if (id) vscode.postMessage({ type: "agentManager.showTerminal", sessionId: id }) - else if (selection() === LOCAL) vscode.postMessage({ type: "agentManager.showLocalTerminal" }) + sideCtl.openPreferred("keyboard_shortcut") } else if (msg.action === "toggleDiff") { if (reviewActive()) { closeReviewTab() @@ -1294,6 +1133,18 @@ const AgentManagerContent: Component = () => { } window.addEventListener("keydown", deleteKeyHandler) + // Reveal the ⌘/Ctrl+1-9 jump badges on all sidebar items while the modifier is held. + // Capture phase so the terminal's key handlers can't swallow them; blur resets state + // when the keyup is lost (e.g. Cmd+Tab away). + const modifier = isMac ? "Meta" : "Control" + const modTrack = (e: KeyboardEvent) => { + if (e.key === modifier) setHeld(e.type === "keydown") + } + const modReset = () => setHeld(false) + window.addEventListener("keydown", modTrack, true) + window.addEventListener("keyup", modTrack, true) + window.addEventListener("blur", modReset) + // When the panel regains focus (e.g. returning from terminal), focus the prompt // and clear any stale body styles left by Kobalte modal overlays (dropdowns/dialogs // set pointer-events:none and overflow:hidden on body, but cleanup never runs if @@ -1364,7 +1215,14 @@ const AgentManagerContent: Component = () => { setSelection, showError: (message) => showToast({ variant: "error", title: t("agentManager.terminal.errorTitle"), description: message }), + postMessage: (message) => vscode.postMessage(message as never), onCreated: (contextKey, terminalId) => appendToTabOrder(contextKey, terminalId), + onSideCreated: (contextKey, terminalId) => { + // Focus only when the user is still looking at this panel — + // a slow create landing after a mode switch must not steal it. + if (sidePanel() === "terminal" && terms.sideKey() === contextKey) terms.requestFocus(terminalId) + }, + onDestinationChanged: (destination) => sideCtl.syncDefault(destination), }) const unsubTerminals = vscode.onMessage((msg) => { terminalDispatch(msg) @@ -1519,65 +1377,19 @@ const AgentManagerContent: Component = () => { } if (msg.type === "agentManager.worktreeDiff") { - const ev = msg as AgentManagerWorktreeDiffMessage - let staleFiles: Set | undefined - setDiffDatas((prev) => { - const existing = prev[ev.sessionId] - const merged = existing - ? mergeWorktreeDiffs(existing, ev.diffs) - : { diffs: ev.diffs, stale: new Set() } - staleFiles = merged.stale - const next = merged.diffs - if (existing && existing.length === next.length && existing.every((old, i) => old === next[i])) return prev - return { ...prev, [ev.sessionId]: next } - }) - if (staleFiles) refreshStaleDiffs(ev.sessionId, staleFiles) + diffs.onWorktreeDiff(msg as AgentManagerWorktreeDiffMessage) } if (msg.type === "agentManager.worktreeDiffFile") { - const ev = msg as AgentManagerWorktreeDiffFileMessage - if (ev.diff) { - setDiffDatas((prev) => { - const existing = prev[ev.sessionId] ?? [] - const next = existing.map((item) => (item.file === ev.diff!.file ? ev.diff! : item)) - return { ...prev, [ev.sessionId]: next } - }) - setDiffFilePending(ev.sessionId, ev.diff.file, false) - return - } - setDiffFilePending(ev.sessionId, ev.file, false) + diffs.onWorktreeDiffFile(msg as AgentManagerWorktreeDiffFileMessage) } if (msg.type === "agentManager.worktreeDiffLoading") { - const ev = msg as AgentManagerWorktreeDiffLoadingMessage - setDiffLoading(ev.loading) + diffs.onWorktreeDiffLoading(msg as AgentManagerWorktreeDiffLoadingMessage) } if (msg.type === "agentManager.applyWorktreeDiffResult") { - const ev = msg as AgentManagerApplyWorktreeDiffResultMessage - const files = new Set((ev.conflicts ?? []).map((entry) => entry.file).filter(Boolean)).size - const count = ev.conflicts?.length ?? 0 - setApplyStates((prev) => ({ - ...prev, - [ev.worktreeId]: { - status: ev.status, - message: ev.message, - conflicts: ev.conflicts ?? [], - }, - })) - - if (ev.status === "success") { - showToast({ variant: "success", title: t("agentManager.apply.success"), description: ev.message }) - if (applyTarget() === ev.worktreeId) closeApplyDialog() - } - if (ev.status === "conflict") { - const summary = - count > 0 ? t("agentManager.apply.conflictToast", { count, files: Math.max(files, 1) }) : ev.message - showToast({ variant: "error", title: t("agentManager.apply.conflict"), description: summary }) - } - if (ev.status === "error") { - showToast({ variant: "error", title: t("agentManager.apply.error"), description: ev.message }) - } + apply.onApplyResult(msg as AgentManagerApplyWorktreeDiffResultMessage) } if (msg.type === "agentManager.revertWorktreeFileResult") revertCtl.onResult(msg as never) @@ -1604,6 +1416,9 @@ const AgentManagerContent: Component = () => { window.removeEventListener("message", handler) window.removeEventListener("keydown", preventDefaults, true) window.removeEventListener("keydown", deleteKeyHandler) + window.removeEventListener("keydown", modTrack, true) + window.removeEventListener("keyup", modTrack, true) + window.removeEventListener("blur", modReset) window.removeEventListener("focus", onWindowFocus) window.removeEventListener("newTaskRequest", newTaskHandler, true) drafts.cleanup() @@ -1733,54 +1548,13 @@ const AgentManagerContent: Component = () => { vscode.postMessage({ type: "agentManager.setReviewDiffStyle", style }) } - const setDiffFilePending = (sessionId: string, file: string, value: boolean) => { - setDiffFileLoading((prev) => { - const session = prev[sessionId] ?? {} - if (value) { - if (session[file]) return prev - return { - ...prev, - [sessionId]: { ...session, [file]: true }, - } - } - - if (!session[file]) return prev - const next = { ...session } - delete next[file] - if (Object.keys(next).length === 0) { - const result = { ...prev } - delete result[sessionId] - return result - } - return { - ...prev, - [sessionId]: next, - } - }) - } - const requestDiffFile = (file: string) => { const sessionId = currentDiffSessionId() if (!sessionId) return - if (diffFileLoading()[sessionId]?.[file]) return - setDiffFilePending(sessionId, file, true) - vscode.postMessage({ type: "agentManager.requestWorktreeDiffFile", sessionId, file }) + diffs.requestDiffFile(sessionId, file) } - const refreshStaleDiffs = (sessionId: string, files: Set) => { - const loading = diffFileLoading()[sessionId] ?? {} - for (const file of files) { - if (loading[file]) continue - setDiffFilePending(sessionId, file, true) - vscode.postMessage({ type: "agentManager.requestWorktreeDiffFile", sessionId, file }) - } - } - - const diffFileLoadingForCurrent = createMemo(() => { - const sessionId = currentDiffSessionId() - if (!sessionId) return new Set() - return new Set(Object.keys(diffFileLoading()[sessionId] ?? {})) - }) + const diffFileLoadingForCurrent = createMemo(() => diffs.diffFileLoadingFor(currentDiffSessionId)) const revertCtl = createRevertFile(currentDiffSessionId, vscode, showToast, t) @@ -2120,11 +1894,33 @@ const AgentManagerContent: Component = () => { findTab: (id) => tabLookup().get(id), postMessage: (msg) => vscode.postMessage(msg as never), onRemove: freezeTabs, + onShowSide: showSideTerminal, getSelection: selection, LOCAL, REVIEW_TAB_ID, }) + const sideCtl = createSideTerminal({ + handlers: termHandlers, + visible: () => sidePanel() === "terminal", + focusedId: () => terms.sideFocusedId(), + hide: () => setSidePanel(null), + refocus: () => window.dispatchEvent(new Event("focusPrompt")), + postMessage: (msg) => vscode.postMessage(msg as never), + track: (button, surface, properties) => metrics.track(button, surface, properties), + // Panel-local pick, immune to cross-window setting echoes (see side.ts). + saved: readSavedDestination(vscode.getState>()), + save: (d) => vscode.setState({ ...vscode.getState>(), terminalDestination: d }), + openVscode: () => + vscode.postMessage( + resolveVscodeTerminalRequest( + selection(), + session.currentSessionID(), + (wt) => managedSessions().find((ms) => ms.worktreeId === wt)?.id, + ) as never, + ), + }) + const handleReviewTabMouseDown = (e: MouseEvent) => { if (e.button !== 1) return e.preventDefault() @@ -2196,8 +1992,8 @@ const AgentManagerContent: Component = () => { if (!id) return undefined if (id === REVIEW_TAB_ID) return { id, title: t("session.tab.review") } if (isTerminalTabId(id)) { - const term = terms.lookup().get(id) - return term ? { id, title: term.title } : undefined + const title = terms.title(id) + return title ? { id, title } : undefined } return activeTabs().find((s) => s.id === id) }) @@ -2223,6 +2019,12 @@ const AgentManagerContent: Component = () => { // Close the currently active tab via keyboard shortcut. // If no tabs remain, fall through to close the selected worktree. const closeActiveTab = () => { + // A focused side terminal owns Cmd+W while its panel is visible — + // closing a chat tab out from under the user's cursor would be + // surprising. Only that terminal dies; the panel keeps the rest. + if (sidePanel() === "terminal" && terms.sideFocusedId()) { + if (sideCtl.close()) return + } if (termHandlers.closeActive()) { tabFocus.restore() return @@ -2278,16 +2080,6 @@ const AgentManagerContent: Component = () => { setSidePanel((prev) => (prev === "diff" ? null : "diff")) } - const showActiveTerminal = () => { - metrics.track("vscode_terminal", "tab_toolbar") - const id = session.currentSessionID() - if (id) { - vscode.postMessage({ type: "agentManager.showTerminal", sessionId: id }) - return - } - if (selection() === LOCAL) vscode.postMessage({ type: "agentManager.showLocalTerminal" }) - } - const renderTabById = (id: string) => renderTab(id, { terms, @@ -2338,7 +2130,7 @@ const AgentManagerContent: Component = () => { >
@@ -2461,7 +2253,7 @@ const AgentManagerContent: Component = () => { overlay={draggedTab} localStats={localStats} worktreeStats={worktreeStats} - applyState={applyStateForSelection} + applyState={apply.applyStateForSelection} onOpen={openWindow} onApply={openApplyDialog} runStatuses={runStatuses} @@ -2472,7 +2264,11 @@ const AgentManagerContent: Component = () => { reviewActive={reviewActive} onToggleDiff={toggleDiffPanel} onToggleReview={metrics.click("fullscreen_review", "tab_toolbar", toggleReviewTab)} - onShowTerminal={showActiveTerminal} + terminalDestination={sideCtl.destination} + terminalDestinationActive={() => sidePanel() === "terminal"} + terminalKeybind={() => kb().showTerminal ?? ""} + onTerminalDestinationOpen={() => sideCtl.openPreferred("tab_toolbar")} + onTerminalDestinationChoose={sideCtl.choose} track={metrics.click} /> @@ -2611,24 +2407,26 @@ const AgentManagerContent: Component = () => {
- -
- { - pendingDiffWidth = Math.max(200, Math.min(w, window.innerWidth * 0.8)) - if (diffRaf === undefined) { - diffRaf = requestAnimationFrame(() => { - diffRaf = undefined - setDiffWidth(pendingDiffWidth!) - }) - } - }} - /> + {/* One inspector host for all right-side modes. It stays + mounted while a side terminal is alive — hidden via + .am-side-host-hidden (absolute + opacity), never + unmounted, so xterm render loops keep streaming. */} + 0}> +
+ + +
{ activeTerminalId={terms.activeId()} /> + sidePanel() === "terminal"} + onSelect={(id) => termHandlers.selectSide(id)} + onClose={(id) => termHandlers.closeSide(id)} + onStart={() => termHandlers.addSide()} + />
@@ -2707,53 +2513,16 @@ const AgentManagerContent: Component = () => { export const AgentManagerApp: Component = () => { return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + ) } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx index a0b61dae68..5d297a1e7b 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/TabBar.tsx @@ -19,6 +19,8 @@ import { LOCAL } from "./navigate" import { ConstrainDragYAxis } from "../src/components/chat/TabDnd" import type { tracker } from "./telemetry" import { SidebarToggleButton } from "./SidebarToggleButton" +import { TerminalDestinationButton } from "./terminal/TerminalDestinationButton" +import type { TerminalDestination } from "../src/types/messages/agent-manager" /** Everything the tab bar reads from the app. */ export interface TabBarProps { @@ -50,7 +52,11 @@ export interface TabBarProps { reviewActive: () => boolean onToggleDiff: () => void onToggleReview: () => void - onShowTerminal: () => void + terminalDestination: () => TerminalDestination + terminalDestinationActive: () => boolean + terminalKeybind: () => string + onTerminalDestinationOpen: () => void + onTerminalDestinationChoose: (destination: TerminalDestination) => void track: ReturnType["click"] } @@ -236,23 +242,17 @@ export const TabBar: Component = (props) => ( /> - {/* Legacy VS Code integrated terminal shortcut. Coexists with the - xterm terminal tabs (accessed via the `+` split-button or - Cmd+Shift+T): Cmd+/ still opens the integrated terminal for the - active session. */} - - - + {/* Terminal destination split button: the primary action + follows the user's setting (VS Code integrated terminal + or the embedded side panel), the dropdown picks which. + Cmd+Shift+T still creates an xterm tab via the `+` menu. */} +
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx index 0e2da9a257..7088537db7 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/WorktreeItem.tsx @@ -169,6 +169,13 @@ export const WorktreeItem: Component = (props) => { props.onOpenPR?.() } + /** Worktree directory basename shown in the hover card (e.g. "decorous-taker"). */ + const name = () => { + const p = props.worktree.path.replace(/[\\/]+$/, "") + const i = Math.max(p.lastIndexOf("/"), p.lastIndexOf("\\")) + return i >= 0 ? p.slice(i + 1) : p + } + return ( <> @@ -389,6 +396,11 @@ export const WorktreeItem: Component = (props) => { {props.navHint} +
+
+ {t("agentManager.hoverCard.worktree")} + {name()} +
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 320c19d95a..1c25e1451f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css +++ b/packages/kilo-vscode/webview-ui/agent-manager/agent-manager.css @@ -575,6 +575,25 @@ button.am-section-toggle:hover .am-section-label { color: var(--text-weak); } +/* Jump modifier held (⌘/Ctrl): reveal shortcut badges on every item, not just the + hovered one. Scoped via :has so items without a badge (beyond ⌘9) keep their stats. + The close button stays hidden so holding the modifier never exposes the delete action. */ +.am-show-shortcuts .am-wt-hover-actions:has(.am-shortcut-badge) { + opacity: 1; + visibility: visible; +} +.am-show-shortcuts .am-wt-hover-actions .am-worktree-close { + display: none; +} +.am-show-shortcuts .am-wt-actions-cell:has(.am-shortcut-badge) > .am-worktree-stats, +.am-show-shortcuts .am-wt-actions-cell:has(.am-shortcut-badge) > .am-worktree-stats-skeleton { + opacity: 0; + visibility: hidden; +} +.am-show-shortcuts .am-local-item .am-shortcut-badge { + opacity: 1; +} + .am-worktree-item:has(.am-worktree-rename-input) .am-wt-row2 { display: none; } @@ -2664,6 +2683,13 @@ body.am-wt-dragging-active * { min-width: 250px; } +/* Fixed-width slot so menu items with and without a check mark align. */ +.am-menu-check { + display: flex; + flex-shrink: 0; + width: 16px; +} + .am-split-menu [data-slot="dropdown-menu-item"] { padding: 6px 10px; } @@ -4684,6 +4710,128 @@ body.vscode-high-contrast-light { z-index: 1; } +/* Side terminal panel — lives inside .am-diff-panel-wrapper next to the + diff and PR panels. Header reuses .am-diff-header metrics so the + chrome does not shift when switching inspector modes. Visibility is + opacity-only: the xterm render loop dies if the subtree leaves the + paint tree. */ +.am-side-terminal { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + min-width: 0; + min-height: 0; + opacity: 0; + pointer-events: none; + z-index: 1; + background: var(--vscode-terminal-background, #1e1e1e); + /* Force a dedicated compositor layer so opacity flips do not + re-lay-out the xterm canvases underneath. */ + will-change: opacity; +} + +.am-side-terminal-visible { + opacity: 1; + pointer-events: auto; +} + +.am-side-terminal-layer { + position: relative; + flex: 1; + min-width: 0; + min-height: 0; + opacity: 0; + pointer-events: none; +} + +.am-side-terminal-layer-active { + opacity: 1; + pointer-events: auto; +} + +.am-side-terminal-state { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + color: var(--text-weak); + font-size: var(--font-size-small); + background: var(--vscode-terminal-background, #1e1e1e); +} + +.am-side-terminal-empty { + color: var(--text-weak); +} + +/* Side terminal tab strip — one row of tabs reusing the top bar's + .am-tab chrome, plus the "+" action. Height matches .am-diff-header + (32px: 4px padding + 24px content) so switching inspector modes does + not shift the panel chrome. The strip itself never scrolls; the tab + list does, so a narrow panel never pushes the "+" action out of view + (same split as .am-tab-list-wrap / .am-tab-add-wrap). */ +.am-side-terminal-tabs { + display: flex; + align-items: stretch; + height: 32px; + padding: 4px 4px 0; + gap: 2px; + flex-shrink: 0; + border-bottom: 1px solid var(--border-weak-base); + background: var(--surface-base); + position: relative; + z-index: 20; +} + +.am-side-terminal-tablist { + display: flex; + align-items: stretch; + gap: 2px; + flex: 1; + min-width: 0; + height: 100%; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: none; +} + +.am-side-terminal-tablist::-webkit-scrollbar { + display: none; +} + +/* Each tab shares the available width and shrinks with ellipsis. + touch-action unlocks pointer-based drag reordering (same as + .am-tab-sortable). */ +.am-side-terminal-tab { + display: flex; + flex: 0 1 140px; + min-width: 64px; + height: 100%; + touch-action: none; +} + +.am-side-terminal-add { + display: flex; + align-items: center; + flex-shrink: 0; + padding: 0 2px; +} + +/* Hidden-but-alive side panel host: taken out of the flow so the chat + reclaims the width, but kept painted so hidden side terminals keep + streaming. Anchored to .am-detail-stack (position: relative). */ +.am-side-host-hidden { + position: absolute; + top: 0; + right: 0; + bottom: 0; + opacity: 0; + pointer-events: none; +} + .am-terminal-host { flex: 1; min-height: 0; diff --git a/packages/kilo-vscode/webview-ui/agent-manager/apply-to-local.tsx b/packages/kilo-vscode/webview-ui/agent-manager/apply-to-local.tsx new file mode 100644 index 0000000000..36063bb64d --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/apply-to-local.tsx @@ -0,0 +1,286 @@ +/** @jsxImportSource solid-js */ + +/** + * Apply-to-local workflow for Agent Manager. + * + * Owns everything about applying a selected worktree's diff into the local + * repo: the per-worktree apply status, the dialog's file selection state, the + * derived memos that drive `ApplyDialog`, and the result/toast handling for + * the backend's `applyWorktreeDiffResult` message. Extracted from + * `AgentManagerApp.tsx` so the app component only wires the workflow in. + */ + +import { createEffect, createMemo, createSignal, on, type Accessor } from "solid-js" +import { showToast } from "@kilocode/kilo-ui/toast" +import { groupApplyConflicts } from "./apply-conflicts" +import { ApplyDialog } from "./ApplyDialog" +import type { tracker } from "./telemetry" +import type { useDialog } from "@kilocode/kilo-ui/context/dialog" +import type { useLanguage } from "../src/context/language" +import type { useVSCode } from "../src/context/vscode" +import type { AgentManagerApplyWorktreeDiffResultMessage, WorktreeFileDiff } from "../src/types/messages" + +interface ApplyState { + status: AgentManagerApplyWorktreeDiffResultMessage["status"] + message: string + conflicts: NonNullable +} + +interface ApplyToLocalOptions { + vscode: ReturnType + dialog: ReturnType + t: ReturnType["t"] + /** Current sidebar selection (LOCAL, a worktree id, or null). */ + selection: Accessor + /** Sentinel id for the local repo selection. */ + local: string + worktrees: Accessor<{ id: string }[]> + diffDatas: Accessor> + diffLoading: Accessor + resolveWorktreeSessionId: (worktreeId: string) => string | undefined + /** Telemetry: metrics.track(name, surface, data). */ + track: ReturnType["track"] +} + +export function createApplyToLocal(opts: ApplyToLocalOptions) { + const { vscode, dialog, t, selection, local, worktrees, diffDatas, diffLoading } = opts + + const [applyStates, setApplyStates] = createSignal>({}) + const [applyTarget, setApplyTarget] = createSignal() + const [applySelectedFiles, setApplySelectedFiles] = createSignal([]) + const [applySelectionTouched, setApplySelectionTouched] = createSignal(false) + + const applyStateForSelection = createMemo(() => { + const sel = selection() + if (!sel || sel === local) return undefined + return applyStates()[sel] + }) + + const applyBusyForSelection = createMemo(() => { + const state = applyStateForSelection() + if (!state) return false + return state.status === "checking" || state.status === "applying" + }) + + const applyTargetSessionId = createMemo(() => { + const target = applyTarget() + if (!target) return undefined + return opts.resolveWorktreeSessionId(target) + }) + + const applyDiffs = createMemo(() => { + const target = applyTarget() + if (!target) return [] as WorktreeFileDiff[] + const data = diffDatas() + const current = applyTargetSessionId() + if (current && data[current]) return data[current]! + return [] as WorktreeFileDiff[] + }) + + const applyStateForTarget = createMemo(() => { + const target = applyTarget() + if (!target) return undefined + return applyStates()[target] + }) + + const applyBusyForTarget = createMemo(() => { + const state = applyStateForTarget() + if (!state) return false + return state.status === "checking" || state.status === "applying" + }) + + const applySelectedSet = createMemo(() => new Set(applySelectedFiles())) + + const applySelectionStats = createMemo(() => { + const set = applySelectedSet() + const selected = applyDiffs().filter((diff) => set.has(diff.file)) + const additions = selected.reduce((sum, diff) => sum + diff.additions, 0) + const deletions = selected.reduce((sum, diff) => sum + diff.deletions, 0) + return { + total: applyDiffs().length, + selected: selected.length, + additions, + deletions, + } + }) + + const applyHasSelection = createMemo(() => applySelectionStats().selected > 0) + + const applyConflictRows = createMemo(() => groupApplyConflicts(applyStateForTarget()?.conflicts ?? [])) + + const applyToLocal = (worktreeId: string, selectedFiles: string[]) => { + setApplyStates((prev) => ({ + ...prev, + [worktreeId]: { + status: "checking", + message: t("agentManager.apply.checking"), + conflicts: [], + }, + })) + vscode.postMessage({ type: "agentManager.applyWorktreeDiff", worktreeId, selectedFiles }) + } + + const resetApplyDialog = () => { + setApplyTarget(undefined) + setApplySelectedFiles([]) + setApplySelectionTouched(false) + } + + const closeApplyDialog = () => { + resetApplyDialog() + dialog.close() + } + + const applySelectAll = () => { + setApplySelectionTouched(true) + setApplySelectedFiles(applyDiffs().map((diff) => diff.file)) + } + + const applySelectNone = () => { + setApplySelectionTouched(true) + setApplySelectedFiles([]) + } + + const applyToggleFile = (file: string, checked: boolean) => { + setApplySelectionTouched(true) + setApplySelectedFiles((prev) => { + if (checked) { + if (prev.includes(file)) return prev + const set = new Set(prev) + set.add(file) + return applyDiffs() + .map((diff) => diff.file) + .filter((path) => set.has(path)) + } + if (!prev.includes(file)) return prev + return prev.filter((path) => path !== file) + }) + } + + const triggerApply = () => { + const target = applyTarget() + if (!target) return + if (!applyHasSelection()) return + if (applyBusyForTarget()) return + opts.track("apply_to_local", "apply_dialog", { fileCount: applySelectedFiles().length }) + applyToLocal(target, applySelectedFiles()) + } + + const openApplyDialog = () => { + const sel = selection() + if (!sel || sel === local) return + setApplyStates((prev) => { + if (!prev[sel]) return prev + const next = { ...prev } + delete next[sel] + return next + }) + setApplyTarget(sel) + setApplySelectionTouched(false) + setApplySelectedFiles([]) + const sid = opts.resolveWorktreeSessionId(sel) + if (sid) vscode.postMessage({ type: "agentManager.requestWorktreeDiff", sessionId: sid }) + + setApplySelectedFiles(applyDiffs().map((diff) => diff.file)) + + dialog.show( + () => ( + + ), + resetApplyDialog, + ) + } + + // Keep the dialog selection in step with the diff set: select everything on + // first load, then drop files that disappear from the diff without clobbering + // a selection the user has already made. + createEffect( + on( + () => [applyTarget(), applyDiffs(), applySelectionTouched()] as const, + ([target, diffs, touched]) => { + if (!target) return + const files = diffs.map((diff) => diff.file) + if (files.length === 0) { + if (!touched) setApplySelectedFiles([]) + return + } + + if (!touched) { + setApplySelectedFiles(files) + return + } + + const current = applySelectedFiles() + const set = new Set(current) + const next = files.filter((file) => set.has(file)) + const same = next.length === current.length && next.every((file, index) => file === current[index]) + if (!same) setApplySelectedFiles(next) + }, + ), + ) + + // Drop apply state for worktrees that no longer exist, and close a dialog + // whose target disappeared. + createEffect(() => { + const ids = new Set(worktrees().map((wt) => wt.id)) + setApplyStates((prev) => { + const next = Object.fromEntries(Object.entries(prev).filter(([id]) => ids.has(id))) + if (Object.keys(next).length === Object.keys(prev).length) return prev + return next + }) + + const target = applyTarget() + if (target && !ids.has(target)) closeApplyDialog() + }) + + // Backend `applyWorktreeDiffResult` message: record the new status and toast. + const onApplyResult = (ev: AgentManagerApplyWorktreeDiffResultMessage) => { + const files = new Set((ev.conflicts ?? []).map((entry) => entry.file).filter(Boolean)).size + const count = ev.conflicts?.length ?? 0 + setApplyStates((prev) => ({ + ...prev, + [ev.worktreeId]: { + status: ev.status, + message: ev.message, + conflicts: ev.conflicts ?? [], + }, + })) + + if (ev.status === "success") { + showToast({ variant: "success", title: t("agentManager.apply.success"), description: ev.message }) + if (applyTarget() === ev.worktreeId) closeApplyDialog() + } + if (ev.status === "conflict") { + const summary = + count > 0 ? t("agentManager.apply.conflictToast", { count, files: Math.max(files, 1) }) : ev.message + showToast({ variant: "error", title: t("agentManager.apply.conflict"), description: summary }) + } + if (ev.status === "error") { + showToast({ variant: "error", title: t("agentManager.apply.error"), description: ev.message }) + } + } + + return { + applyStateForSelection, + applyBusyForSelection, + openApplyDialog, + onApplyResult, + } +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts index e6f1419c59..26a715d9ec 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ar.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "نسخ المسار", "agentManager.worktree.openInVscode": "فتح في VS Code", "agentManager.worktree.rename": "إعادة تسمية", - "agentManager.worktree.moveToSection": "نقل إلى القسم", "agentManager.worktree.newSection": "قسم جديد", "agentManager.worktree.ungrouped": "غير مجمع", "agentManager.section.rename": "إعادة تسمية القسم", "agentManager.section.setColor": "تعيين اللون", "agentManager.section.delete": "حذف القسم", - "agentManager.section.defaultColor": "افتراضي", "agentManager.section.defaultName": "قسم جديد", "agentManager.section.moveUp": "تحريك لأعلى", "agentManager.section.moveDown": "تحريك لأسفل", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "الفرع", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "الأساس", "agentManager.hoverCard.sessions": "الجلسات", "agentManager.hoverCard.files": "الملفات", @@ -66,6 +64,12 @@ export const dict = { "agentManager.terminal.new": "علامة تبويب جديدة للمحطة الطرفية", "agentManager.terminal.ended": "انتهت المحطة الطرفية — أغلق علامة التبويب للإخفاء", "agentManager.terminal.connectionError": "خطأ في اتصال المحطة الطرفية", + "agentManager.terminal.add": "محطة طرفية جديدة", + "agentManager.terminal.empty": "لا توجد محطة طرفية هنا بعد", + "agentManager.terminal.start": "بدء المحطة الطرفية", + "agentManager.terminal.destination": "اختر ما الذي يفتحه زر المحطة الطرفية", + "agentManager.terminal.openInVscode": "المحطة الطرفية في VS Code", + "agentManager.terminal.openInPanel": "لوحة Agent Manager", "agentManager.terminal.errorTitle": "خطأ في المحطة الطرفية", "agentManager.setup.failed": "فشل إعداد مساحة العمل", "agentManager.setup.settingUp": "جارٍ إعداد مساحة العمل", @@ -131,7 +135,6 @@ export const dict = { "agentManager.dialog.creating": "جارٍ الإنشاء...", "agentManager.dialog.createWorktree": "إنشاء شجرة العمل", "agentManager.dialog.removeImage": "إزالة الصورة", - "agentManager.dialog.advanced": "متقدم...", "agentManager.diff.toggle": "تبديل الفرق", "agentManager.diff.openFile": "فتح الملف", @@ -140,19 +143,13 @@ export const dict = { "agentManager.diff.revertError": "فشل الاستعادة", "agentManager.open.button": "فتح", "agentManager.open.tooltip": "فتح Worktree هذا في VS Code", - "agentManager.apply.button": "تطبيق محليًا", "agentManager.apply.globalButton": "تطبيق", "agentManager.apply.tooltip": "تطبيق تغييرات شجرة العمل المحددة على الفرع المحلي", "agentManager.apply.dialogTitle": "تطبيق التغييرات على الفرع المحلي", "agentManager.apply.confirm": "تطبيق الآن", - "agentManager.apply.retry": "إعادة محاولة التطبيق", - "agentManager.apply.previewTitle": "تطبيق هذه التغييرات على الفرع المحلي؟", - "agentManager.apply.previewSummary": "{{files}} ملفات، +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} من {{total}} ملفات محددة", "agentManager.apply.selectAll": "تحديد الكل", "agentManager.apply.selectNone": "إلغاء تحديد الكل", - "agentManager.apply.moreFiles": "+{{count}} المزيد", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} المزيد)", "agentManager.apply.checking": "جارٍ التحقق...", "agentManager.apply.applying": "جارٍ التطبيق...", "agentManager.apply.success": "تم تطبيق التغييرات", @@ -165,9 +162,6 @@ export const dict = { "agentManager.apply.reason.patch": "لا يمكن تطبيق التصحيح بشكل نظيف", "agentManager.apply.reason.contents": "لا يمكن قراءة محتويات الملف المحلي الحالي", "agentManager.apply.reason.unknown": "تم اكتشاف تعارض", - "agentManager.apply.inline.success": "تم التطبيق", - "agentManager.apply.inline.conflict": "تعارضات", - "agentManager.apply.inline.error": "فشل", "agentManager.shortcuts.toggleDiff": "تبديل لوحة الفرق", "agentManager.shortcuts.category.quickSwitch": "التبديل السريع", "agentManager.shortcuts.jumpToItem": "الانتقال إلى العنصر 1\u20139", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts index 34bd4af721..8588016918 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/br.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "Copiar Caminho", "agentManager.worktree.openInVscode": "Abrir no VS Code", "agentManager.worktree.rename": "Renomear", - "agentManager.worktree.moveToSection": "Mover para a Seção", "agentManager.worktree.newSection": "Nova Seção", "agentManager.worktree.ungrouped": "Não Agrupado", "agentManager.section.rename": "Renomear Seção", "agentManager.section.setColor": "Definir Cor", "agentManager.section.delete": "Excluir Seção", - "agentManager.section.defaultColor": "Padrão", "agentManager.section.defaultName": "Nova Seção", "agentManager.section.moveUp": "Mover para Cima", "agentManager.section.moveDown": "Mover para Baixo", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "BRANCH", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Base", "agentManager.hoverCard.sessions": "Sessões", "agentManager.hoverCard.files": "Arquivos", @@ -67,6 +65,12 @@ export const dict = { "agentManager.terminal.new": "Nova aba de terminal", "agentManager.terminal.ended": "terminal encerrado — feche a aba para dispensar", "agentManager.terminal.connectionError": "erro de conexão do terminal", + "agentManager.terminal.add": "Novo terminal", + "agentManager.terminal.empty": "Ainda não há terminal aqui", + "agentManager.terminal.start": "Iniciar terminal", + "agentManager.terminal.destination": "Escolha o que o botão do terminal abre", + "agentManager.terminal.openInVscode": "Terminal do VS Code", + "agentManager.terminal.openInPanel": "Painel do Agent Manager", "agentManager.terminal.errorTitle": "Erro no terminal", "agentManager.setup.failed": "Falha na configuração do worktree", "agentManager.setup.settingUp": "Configurando worktree", @@ -134,7 +138,6 @@ export const dict = { "agentManager.dialog.creating": "Criando...", "agentManager.dialog.createWorktree": "Criar Worktree", "agentManager.dialog.removeImage": "Remover imagem", - "agentManager.dialog.advanced": "Avançado...", "agentManager.diff.toggle": "Alternar diff", "agentManager.diff.openFile": "Abrir arquivo", @@ -143,19 +146,13 @@ export const dict = { "agentManager.diff.revertError": "Falha ao reverter", "agentManager.open.button": "Abrir", "agentManager.open.tooltip": "Abrir este Worktree no VS Code", - "agentManager.apply.button": "Aplicar localmente", "agentManager.apply.globalButton": "Aplicar", "agentManager.apply.tooltip": "Aplicar alterações da worktree selecionada na branch local", "agentManager.apply.dialogTitle": "Aplicar alterações na branch local", "agentManager.apply.confirm": "Aplicar agora", - "agentManager.apply.retry": "Tentar aplicar novamente", - "agentManager.apply.previewTitle": "Aplicar essas alterações na branch local?", - "agentManager.apply.previewSummary": "{{files}} arquivos, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} de {{total}} arquivos selecionados", "agentManager.apply.selectAll": "Selecionar todos", "agentManager.apply.selectNone": "Desmarcar todos", - "agentManager.apply.moreFiles": "+{{count}} mais", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} mais)", "agentManager.apply.checking": "Verificando...", "agentManager.apply.applying": "Aplicando...", "agentManager.apply.success": "Alterações aplicadas", @@ -168,9 +165,6 @@ export const dict = { "agentManager.apply.reason.patch": "O patch não pode ser aplicado de forma limpa", "agentManager.apply.reason.contents": "Não é possível ler o conteúdo do arquivo local atual", "agentManager.apply.reason.unknown": "Conflito detectado", - "agentManager.apply.inline.success": "Aplicado", - "agentManager.apply.inline.conflict": "Conflitos", - "agentManager.apply.inline.error": "Falhou", "agentManager.shortcuts.toggleDiff": "Alternar painel de diff", "agentManager.shortcuts.category.quickSwitch": "Troca rápida", "agentManager.shortcuts.jumpToItem": "Ir para o item 1\u20139", 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 8b17c5e014..4a186f818a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/bs.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "Kopiraj putanju", "agentManager.worktree.openInVscode": "Otvori u VS Code", "agentManager.worktree.rename": "Preimenuj", - "agentManager.worktree.moveToSection": "Premjesti u sekciju", "agentManager.worktree.newSection": "Nova sekcija", "agentManager.worktree.ungrouped": "Bez grupe", "agentManager.section.rename": "Preimenuj sekciju", "agentManager.section.setColor": "Postavi boju", "agentManager.section.delete": "Izbriši sekciju", - "agentManager.section.defaultColor": "Zadano", "agentManager.section.defaultName": "Nova sekcija", "agentManager.section.moveUp": "Pomjeri gore", "agentManager.section.moveDown": "Pomjeri dolje", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "BRANCH", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Baza", "agentManager.hoverCard.sessions": "Sesije", "agentManager.hoverCard.files": "Datoteke", @@ -66,6 +64,12 @@ export const dict = { "agentManager.terminal.new": "Nova kartica terminala", "agentManager.terminal.ended": "terminal je završen — zatvorite karticu da biste odbacili", "agentManager.terminal.connectionError": "greška u vezi terminala", + "agentManager.terminal.add": "Novi terminal", + "agentManager.terminal.empty": "Ovdje još nema terminala", + "agentManager.terminal.start": "Pokreni terminal", + "agentManager.terminal.destination": "Odaberite šta otvara dugme terminala", + "agentManager.terminal.openInVscode": "VS Code terminal", + "agentManager.terminal.openInPanel": "Panel Agent Managera", "agentManager.terminal.errorTitle": "Greška terminala", "agentManager.setup.failed": "Postavljanje radnog prostora neuspješno", "agentManager.setup.settingUp": "Postavljanje radnog prostora", @@ -134,7 +138,6 @@ export const dict = { "agentManager.dialog.creating": "Kreiranje...", "agentManager.dialog.createWorktree": "Kreiraj worktree", "agentManager.dialog.removeImage": "Ukloni sliku", - "agentManager.dialog.advanced": "Napredno...", "agentManager.diff.toggle": "Prebaci diff", "agentManager.diff.openFile": "Otvori datoteku", @@ -143,19 +146,13 @@ export const dict = { "agentManager.diff.revertError": "Vraćanje neuspješno", "agentManager.open.button": "Otvori", "agentManager.open.tooltip": "Otvori ovaj worktree u VS Code-u", - "agentManager.apply.button": "Primijeni lokalno", "agentManager.apply.globalButton": "Primijeni", "agentManager.apply.tooltip": "Primijeni promjene odabranog radnog stabla na lokalnu granu", "agentManager.apply.dialogTitle": "Primijeni promjene na lokalnu granu", "agentManager.apply.confirm": "Primijeni sada", - "agentManager.apply.retry": "Ponovo pokušaj primjenu", - "agentManager.apply.previewTitle": "Primijeniti ove promjene na lokalnu granu?", - "agentManager.apply.previewSummary": "{{files}} datoteka, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} od {{total}} datoteka odabrano", "agentManager.apply.selectAll": "Odaberi sve", "agentManager.apply.selectNone": "Poništi odabir", - "agentManager.apply.moreFiles": "+{{count}} više", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} više)", "agentManager.apply.checking": "Provjera...", "agentManager.apply.applying": "Primjenjivanje...", "agentManager.apply.success": "Promjene primijenjene", @@ -168,9 +165,6 @@ export const dict = { "agentManager.apply.reason.patch": "Zakrpa se ne može čisto primijeniti", "agentManager.apply.reason.contents": "Nije moguće pročitati sadržaj trenutne lokalne datoteke", "agentManager.apply.reason.unknown": "Otkriven konflikt", - "agentManager.apply.inline.success": "Primijenjeno", - "agentManager.apply.inline.conflict": "Konflikti", - "agentManager.apply.inline.error": "Neuspjelo", "agentManager.shortcuts.toggleDiff": "Prebaci panel za diff", "agentManager.shortcuts.category.quickSwitch": "Brzo prebacivanje", "agentManager.shortcuts.jumpToItem": "Idi na stavku 1\u20139", 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 129d560f82..ca7575378c 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/da.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "Kopier sti", "agentManager.worktree.openInVscode": "Åbn i VS Code", "agentManager.worktree.rename": "Omdøb", - "agentManager.worktree.moveToSection": "Flyt til sektion", "agentManager.worktree.newSection": "Ny sektion", "agentManager.worktree.ungrouped": "Ugrupperet", "agentManager.section.rename": "Omdøb sektion", "agentManager.section.setColor": "Indstil farve", "agentManager.section.delete": "Slet sektion", - "agentManager.section.defaultColor": "Standard", "agentManager.section.defaultName": "Ny sektion", "agentManager.section.moveUp": "Flyt op", "agentManager.section.moveDown": "Flyt ned", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "BRANCH", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Base", "agentManager.hoverCard.sessions": "Sessioner", "agentManager.hoverCard.files": "Filer", @@ -68,6 +66,12 @@ export const dict = { "agentManager.terminal.new": "Ny terminalfane", "agentManager.terminal.ended": "terminal afsluttet — luk fanen for at fjerne", "agentManager.terminal.connectionError": "forbindelsesfejl til terminal", + "agentManager.terminal.add": "Ny terminal", + "agentManager.terminal.empty": "Ingen terminal her endnu", + "agentManager.terminal.start": "Start terminal", + "agentManager.terminal.destination": "Vælg, hvad terminalknappen åbner", + "agentManager.terminal.openInVscode": "VS Code-terminal", + "agentManager.terminal.openInPanel": "Agent Manager-panel", "agentManager.terminal.errorTitle": "Terminalfejl", "agentManager.setup.failed": "Opsætning af worktree mislykkedes", "agentManager.setup.settingUp": "Opsætter worktree", @@ -135,7 +139,6 @@ export const dict = { "agentManager.dialog.creating": "Opretter...", "agentManager.dialog.createWorktree": "Opret Worktree", "agentManager.dialog.removeImage": "Fjern billede", - "agentManager.dialog.advanced": "Avanceret...", "agentManager.diff.toggle": "Skift diff", "agentManager.diff.openFile": "Åbn fil", @@ -144,19 +147,13 @@ export const dict = { "agentManager.diff.revertError": "Gendannelse fejlede", "agentManager.open.button": "Åbn", "agentManager.open.tooltip": "Åbn dette Worktree i VS Code", - "agentManager.apply.button": "Anvend lokalt", "agentManager.apply.globalButton": "Anvend", "agentManager.apply.tooltip": "Anvend valgte worktree-ændringer på den lokale gren", "agentManager.apply.dialogTitle": "Anvend ændringer på den lokale gren", "agentManager.apply.confirm": "Anvend nu", - "agentManager.apply.retry": "Prøv at anvende igen", - "agentManager.apply.previewTitle": "Anvend disse ændringer på den lokale gren?", - "agentManager.apply.previewSummary": "{{files}} filer, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} af {{total}} filer valgt", "agentManager.apply.selectAll": "Vælg alle", "agentManager.apply.selectNone": "Fravælg alle", - "agentManager.apply.moreFiles": "+{{count}} flere", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} flere)", "agentManager.apply.checking": "Kontrollerer...", "agentManager.apply.applying": "Anvender...", "agentManager.apply.success": "Ændringer anvendt", @@ -169,9 +166,6 @@ export const dict = { "agentManager.apply.reason.patch": "Patch kan ikke anvendes rent", "agentManager.apply.reason.contents": "Kan ikke læse det aktuelle lokale filindhold", "agentManager.apply.reason.unknown": "Konflikt opdaget", - "agentManager.apply.inline.success": "Anvendt", - "agentManager.apply.inline.conflict": "Konflikter", - "agentManager.apply.inline.error": "Mislykkedes", "agentManager.shortcuts.toggleDiff": "Skift diff-panel", "agentManager.shortcuts.category.quickSwitch": "Hurtigskift", "agentManager.shortcuts.jumpToItem": "Hop til element 1\u20139", 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 1260bb8bc0..e157522ebb 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/de.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "Pfad kopieren", "agentManager.worktree.openInVscode": "In VS Code öffnen", "agentManager.worktree.rename": "Umbenennen", - "agentManager.worktree.moveToSection": "In Bereich verschieben", "agentManager.worktree.newSection": "Neuer Bereich", "agentManager.worktree.ungrouped": "Nicht gruppiert", "agentManager.section.rename": "Bereich umbenennen", "agentManager.section.setColor": "Farbe festlegen", "agentManager.section.delete": "Bereich löschen", - "agentManager.section.defaultColor": "Standard", "agentManager.section.defaultName": "Neuer Bereich", "agentManager.section.moveUp": "Nach oben verschieben", "agentManager.section.moveDown": "Nach unten verschieben", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "BRANCH", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Basis", "agentManager.hoverCard.sessions": "Sitzungen", "agentManager.hoverCard.files": "Dateien", @@ -67,6 +65,12 @@ export const dict = { "agentManager.terminal.new": "Neuer Terminal-Tab", "agentManager.terminal.ended": "Terminal beendet — Tab schließen zum Verwerfen", "agentManager.terminal.connectionError": "Verbindungsfehler im Terminal", + "agentManager.terminal.add": "Neues Terminal", + "agentManager.terminal.empty": "Hier ist noch kein Terminal", + "agentManager.terminal.start": "Terminal starten", + "agentManager.terminal.destination": "Auswählen, was die Terminal-Schaltfläche öffnet", + "agentManager.terminal.openInVscode": "VS Code-Terminal", + "agentManager.terminal.openInPanel": "Agent Manager-Panel", "agentManager.terminal.errorTitle": "Terminal-Fehler", "agentManager.setup.failed": "Einrichtung des Arbeitsbereichs fehlgeschlagen", "agentManager.setup.settingUp": "Arbeitsbereich wird eingerichtet", @@ -135,7 +139,6 @@ export const dict = { "agentManager.dialog.creating": "Wird erstellt...", "agentManager.dialog.createWorktree": "Worktree erstellen", "agentManager.dialog.removeImage": "Bild entfernen", - "agentManager.dialog.advanced": "Erweitert...", "agentManager.diff.toggle": "Diff umschalten", "agentManager.diff.openFile": "Datei öffnen", @@ -144,19 +147,13 @@ export const dict = { "agentManager.diff.revertError": "Zurücksetzen fehlgeschlagen", "agentManager.open.button": "Öffnen", "agentManager.open.tooltip": "Dieses Worktree in VS Code öffnen", - "agentManager.apply.button": "Lokal anwenden", "agentManager.apply.globalButton": "Anwenden", "agentManager.apply.tooltip": "Ausgewählte Worktree-Änderungen auf den lokalen Branch anwenden", "agentManager.apply.dialogTitle": "Änderungen auf den lokalen Branch anwenden", "agentManager.apply.confirm": "Jetzt anwenden", - "agentManager.apply.retry": "Anwenden erneut versuchen", - "agentManager.apply.previewTitle": "Diese Änderungen auf den lokalen Branch anwenden?", - "agentManager.apply.previewSummary": "{{files}} Dateien, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} von {{total}} Dateien ausgewählt", "agentManager.apply.selectAll": "Alle auswählen", "agentManager.apply.selectNone": "Keine auswählen", - "agentManager.apply.moreFiles": "+{{count}} weitere", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} weitere)", "agentManager.apply.checking": "Prüfe...", "agentManager.apply.applying": "Wird angewendet...", "agentManager.apply.success": "Änderungen angewendet", @@ -169,9 +166,6 @@ export const dict = { "agentManager.apply.reason.patch": "Patch kann nicht sauber angewendet werden", "agentManager.apply.reason.contents": "Inhalt der aktuellen lokalen Datei kann nicht gelesen werden", "agentManager.apply.reason.unknown": "Konflikt erkannt", - "agentManager.apply.inline.success": "Angewendet", - "agentManager.apply.inline.conflict": "Konflikte", - "agentManager.apply.inline.error": "Fehlgeschlagen", "agentManager.shortcuts.toggleDiff": "Diff-Panel umschalten", "agentManager.shortcuts.category.quickSwitch": "Schnellwechsel", "agentManager.shortcuts.jumpToItem": "Zu Element 1\u20139 springen", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts index 58860e5827..4a92fba9c0 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/en.ts @@ -27,19 +27,17 @@ export const dict = { "agentManager.worktree.copyPath": "Copy Path", "agentManager.worktree.openInVscode": "Open in VS Code", "agentManager.worktree.rename": "Rename", - "agentManager.worktree.moveToSection": "Move to Section", "agentManager.worktree.newSection": "New Section", "agentManager.worktree.ungrouped": "Ungrouped", "agentManager.section.rename": "Rename Section", "agentManager.section.setColor": "Set Color", "agentManager.section.delete": "Delete Section", - "agentManager.section.defaultColor": "Default", "agentManager.section.defaultName": "New Section", "agentManager.section.moveUp": "Move Up", "agentManager.section.moveDown": "Move Down", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "BRANCH", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Base", "agentManager.hoverCard.sessions": "Sessions", "agentManager.hoverCard.files": "Files", @@ -68,8 +66,14 @@ export const dict = { "agentManager.sidebarSearch.contexts": "LOCAL & WORKTREES", "agentManager.terminal.new": "New Terminal Tab", + "agentManager.terminal.add": "New terminal", "agentManager.terminal.ended": "terminal ended — close tab to dismiss", "agentManager.terminal.connectionError": "terminal connection error", + "agentManager.terminal.empty": "No terminal here yet", + "agentManager.terminal.start": "Start terminal", + "agentManager.terminal.destination": "Choose what the terminal button opens", + "agentManager.terminal.openInVscode": "VS Code terminal", + "agentManager.terminal.openInPanel": "Agent Manager panel", "agentManager.terminal.errorTitle": "Terminal error", "agentManager.setup.failed": "Worktree setup failed", @@ -137,7 +141,6 @@ export const dict = { "agentManager.dialog.creating": "Creating...", "agentManager.dialog.createWorktree": "Create Worktree", "agentManager.dialog.removeImage": "Remove image", - "agentManager.dialog.advanced": "Advanced...", "agentManager.dialog.configureWorktree": "Configure New Worktree...", "agentManager.diff.toggle": "Toggle diff", @@ -147,19 +150,13 @@ export const dict = { "agentManager.diff.revertError": "Revert failed", "agentManager.open.button": "Open", "agentManager.open.tooltip": "Open this worktree in VS Code", - "agentManager.apply.button": "Apply to local", "agentManager.apply.globalButton": "Apply", "agentManager.apply.tooltip": "Apply selected worktree changes to local branch", "agentManager.apply.dialogTitle": "Apply changes to local branch", "agentManager.apply.confirm": "Apply now", - "agentManager.apply.retry": "Retry apply", - "agentManager.apply.previewTitle": "Apply these changes to local branch?", - "agentManager.apply.previewSummary": "{{files}} files, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} of {{total}} files selected", "agentManager.apply.selectAll": "Select all", "agentManager.apply.selectNone": "Select none", - "agentManager.apply.moreFiles": "+{{count}} more", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} more)", "agentManager.apply.checking": "Checking...", "agentManager.apply.applying": "Applying...", "agentManager.apply.success": "Changes applied", @@ -172,9 +169,6 @@ export const dict = { "agentManager.apply.reason.patch": "Patch cannot be applied cleanly", "agentManager.apply.reason.contents": "Cannot read current local file contents", "agentManager.apply.reason.unknown": "Conflict detected", - "agentManager.apply.inline.success": "Applied", - "agentManager.apply.inline.conflict": "Conflicts", - "agentManager.apply.inline.error": "Failed", "agentManager.shortcuts.toggleDiff": "Toggle diff panel", "agentManager.shortcuts.category.quickSwitch": "Quick Switch", "agentManager.shortcuts.jumpToItem": "Jump to item 1\u20139", 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 741862bb01..c6347b2d76 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/es.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "Copiar ruta", "agentManager.worktree.openInVscode": "Abrir en VS Code", "agentManager.worktree.rename": "Renombrar", - "agentManager.worktree.moveToSection": "Mover a la sección", "agentManager.worktree.newSection": "Nueva sección", "agentManager.worktree.ungrouped": "Desagrupado", "agentManager.section.rename": "Renombrar sección", "agentManager.section.setColor": "Establecer color", "agentManager.section.delete": "Eliminar sección", - "agentManager.section.defaultColor": "Predeterminado", "agentManager.section.defaultName": "Nueva sección", "agentManager.section.moveUp": "Mover hacia arriba", "agentManager.section.moveDown": "Mover hacia abajo", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "BRANCH", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Base", "agentManager.hoverCard.sessions": "Sesiones", "agentManager.hoverCard.files": "Archivos", @@ -67,6 +65,12 @@ export const dict = { "agentManager.terminal.new": "Nueva pestaña de terminal", "agentManager.terminal.ended": "terminal finalizado — cierra la pestaña para descartar", "agentManager.terminal.connectionError": "error de conexión del terminal", + "agentManager.terminal.add": "Nuevo terminal", + "agentManager.terminal.empty": "Aún no hay ningún terminal aquí", + "agentManager.terminal.start": "Iniciar terminal", + "agentManager.terminal.destination": "Elegir qué abre el botón del terminal", + "agentManager.terminal.openInVscode": "Terminal de VS Code", + "agentManager.terminal.openInPanel": "Panel de Agent Manager", "agentManager.terminal.errorTitle": "Error de terminal", "agentManager.setup.failed": "Error en la configuración del worktree", "agentManager.setup.settingUp": "Configurando worktree", @@ -134,7 +138,6 @@ export const dict = { "agentManager.dialog.creating": "Creando...", "agentManager.dialog.createWorktree": "Crear Worktree", "agentManager.dialog.removeImage": "Eliminar imagen", - "agentManager.dialog.advanced": "Avanzado...", "agentManager.diff.toggle": "Alternar diff", "agentManager.diff.openFile": "Abrir archivo", @@ -143,19 +146,13 @@ export const dict = { "agentManager.diff.revertError": "Error al revertir", "agentManager.open.button": "Abrir", "agentManager.open.tooltip": "Abrir este Worktree en VS Code", - "agentManager.apply.button": "Aplicar en local", "agentManager.apply.globalButton": "Aplicar", "agentManager.apply.tooltip": "Aplicar los cambios del worktree seleccionado a la rama local", "agentManager.apply.dialogTitle": "Aplicar cambios a la rama local", "agentManager.apply.confirm": "Aplicar ahora", - "agentManager.apply.retry": "Reintentar aplicación", - "agentManager.apply.previewTitle": "¿Aplicar estos cambios a la rama local?", - "agentManager.apply.previewSummary": "{{files}} archivos, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} de {{total}} archivos seleccionados", "agentManager.apply.selectAll": "Seleccionar todos", "agentManager.apply.selectNone": "Deseleccionar todos", - "agentManager.apply.moreFiles": "+{{count}} más", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} más)", "agentManager.apply.checking": "Verificando...", "agentManager.apply.applying": "Aplicando...", "agentManager.apply.success": "Cambios aplicados", @@ -168,9 +165,6 @@ export const dict = { "agentManager.apply.reason.patch": "El parche no se puede aplicar limpiamente", "agentManager.apply.reason.contents": "No se puede leer el contenido del archivo local actual", "agentManager.apply.reason.unknown": "Conflicto detectado", - "agentManager.apply.inline.success": "Aplicado", - "agentManager.apply.inline.conflict": "Conflictos", - "agentManager.apply.inline.error": "Falló", "agentManager.shortcuts.toggleDiff": "Alternar panel de diff", "agentManager.shortcuts.category.quickSwitch": "Cambio rápido", "agentManager.shortcuts.jumpToItem": "Ir al elemento 1\u20139", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts new file mode 100644 index 0000000000..4adcfda7d2 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fa.ts @@ -0,0 +1,212 @@ +export const dict = { + "agentManager.local": "محلی", + "agentManager.sidebar.collapse": "بستن نوار کناری", + "agentManager.sidebar.expand": "نمایش نوار کناری", + "agentManager.section.worktrees": "WORKTREES", + "agentManager.section.sessions": "SESSIONS", + "agentManager.projects": "PROJECTS", + "agentManager.project.add": "افزودن پروژه", + "agentManager.project.remove": "حذف از Agent Manager", + "agentManager.project.trust": "اعتماد", + "agentManager.project.missing": "مخزن یافت نشد", + "agentManager.notGitRepo": "این یک مخزن git نیست", + + "agentManager.worktree.settings": "تنظیمات Worktree", + "agentManager.worktree.new": "Worktree جدید", + "agentManager.worktree.setupScript": "اسکریپت راه‌اندازی Worktree", + "agentManager.worktree.delete": "حذف Worktree", + "agentManager.worktree.confirmDelete": "حذف شود؟", + "agentManager.worktree.stale": "منسوخ", + "agentManager.worktree.staleTooltip": "روی دیسک وجود ندارد یا دیگر توسط git worktree ردیابی نمی‌شود", + "agentManager.worktree.removeStale": "حذف worktree منسوخ", + "agentManager.worktree.doubleClickRename": "برای تغییر نام دوبار کلیک کنید", + "agentManager.worktree.versions": "{{count}} نسخه", + "agentManager.worktree.advancedOptions": "گزینه‌های پیشرفته worktree", + "agentManager.worktree.defaultBaseBranch": "شاخه پایه پیش‌فرض", + "agentManager.worktree.defaultBaseBranchAuto": "تشخیص خودکار", + "agentManager.worktree.copyPath": "کپی مسیر", + "agentManager.worktree.openInVscode": "باز کردن در VS Code", + "agentManager.worktree.rename": "تغییر نام", + "agentManager.worktree.newSection": "بخش جدید", + "agentManager.worktree.ungrouped": "بدون گروه", + "agentManager.section.rename": "تغییر نام بخش", + "agentManager.section.setColor": "تنظیم رنگ", + "agentManager.section.delete": "حذف بخش", + "agentManager.section.defaultName": "بخش جدید", + "agentManager.section.moveUp": "انتقال به بالا", + "agentManager.section.moveDown": "انتقال به پایین", + + "agentManager.hoverCard.branch": "شاخه", + "agentManager.hoverCard.worktree": "Worktree", + "agentManager.hoverCard.base": "پایه", + "agentManager.hoverCard.sessions": "جلسات", + "agentManager.hoverCard.files": "فایل‌ها", + "agentManager.hoverCard.changes": "تغییرات", + "agentManager.hoverCard.commits": "کامیت‌ها", + + "agentManager.session.new": "جلسه جدید", + "agentManager.session.untitled": "بدون عنوان", + "agentManager.session.newSession": "جلسه جدید", + "agentManager.session.openInWorktree": "باز کردن در worktree", + "agentManager.session.openLocally": "باز کردن به صورت محلی", + "agentManager.session.readonly": "جلسه فقط‌خواندنی", + "agentManager.session.noSessions": "هیچ جلسه‌ای باز نیست", + + "agentManager.tab.close": "بستن", + "agentManager.tab.closeOthers": "بستن بقیه", + "agentManager.tab.closeTab": "بستن برگه", + "agentManager.tab.forkSession": "انشعاب جلسه", + "agentManager.tab.terminal": "ترمینال", + "agentManager.tab.openTerminal": "باز کردن ترمینال", + "agentManager.tab.newOptions": "گزینه‌های بیشتر برای تب جدید", + "agentManager.tabsMenu.status.waiting": "انتظار", + "agentManager.tabsMenu.status.retry": "تلاش مجدد", + "agentManager.sidebarSearch.label": "جستجوی worktree‌ها و جلسات", + "agentManager.sidebarSearch.scope": "جستجو در فضای کاری محلی، جلسات محلی، worktree‌ها و جلسات آن‌ها", + "agentManager.sidebarSearch.contexts": "محلی و WORKTREE‌ها", + + "agentManager.terminal.new": "تب ترمینال جدید", + "agentManager.terminal.add": "ترمینال جدید", + "agentManager.terminal.ended": "ترمینال پایان یافت — برای بستن، تب را ببندید", + "agentManager.terminal.connectionError": "خطای اتصال ترمینال", + "agentManager.terminal.empty": "هنوز ترمینالی اینجا وجود ندارد", + "agentManager.terminal.start": "شروع ترمینال", + "agentManager.terminal.destination": "انتخاب کنید دکمه ترمینال چه چیزی را باز کند", + "agentManager.terminal.openInVscode": "ترمینال VS Code", + "agentManager.terminal.openInPanel": "پنل Agent Manager", + "agentManager.terminal.errorTitle": "خطای ترمینال", + + "agentManager.setup.failed": "راه‌اندازی Worktree ناموفق بود", + "agentManager.setup.settingUp": "در حال راه‌اندازی Worktree", + "agentManager.setup.error.git_not_found": + "Git نصب نشده یا در PATH یافت نشد. لطفاً Git را نصب کرده و VS Code را مجدداً راه‌اندازی کنید.", + "agentManager.setup.error.not_git_repo": "برای استفاده از Worktree، پوشه‌ای که حاوی یک مخزن git است را باز کنید.", + "agentManager.setup.error.lfs_missing": + "این مخزن از Git LFS استفاده می‌کند، اما git-lfs یافت نشد. لطفاً Git LFS را نصب کنید.", + "agentManager.setup.error.no_commits": + "این مخزن هنوز هیچ کامیتی ندارد. قبل از استفاده از Worktree، یک کامیت اولیه ایجاد کنید.", + "agentManager.shortcuts.title": "میانبرهای صفحه‌کلید", + "agentManager.shortcuts.category.sidebar": "نوار کناری", + "agentManager.shortcuts.category.tabs": "تب‌ها", + "agentManager.shortcuts.category.terminal": "ترمینال", + "agentManager.shortcuts.category.global": "سراسری", + "agentManager.shortcuts.previousItem": "مورد قبلی", + "agentManager.shortcuts.nextItem": "مورد بعدی", + "agentManager.shortcuts.newWorktree": "Worktree جدید", + "agentManager.shortcuts.advancedWorktree": "پیکربندی Worktree جدید", + "agentManager.shortcuts.openWorktree": "باز کردن Worktree", + "agentManager.shortcuts.openPR": "باز کردن pull request", + "agentManager.shortcuts.deleteWorktree": "حذف Worktree", + "agentManager.shortcuts.previousTab": "تب قبلی", + "agentManager.shortcuts.nextTab": "تب بعدی", + "agentManager.shortcuts.newTab": "تب جدید", + "agentManager.shortcuts.closeTab": "بستن تب", + "agentManager.shortcuts.toggleTerminal": "نمایش/پنهان کردن ترمینال", + "agentManager.shortcuts.runScript": "اجرای اسکریپت", + "agentManager.run.options": "گزینه‌های اجرا", + "agentManager.run.configure": "پیکربندی اسکریپت اجرا", + "agentManager.shortcuts.openAgentManager": "باز کردن Agent Manager", + "agentManager.shortcuts.cycleAgentMode": "حالت عامل بعدی", + "agentManager.shortcuts.cyclePreviousAgentMode": "حالت عامل قبلی", + "agentManager.shortcuts.showShortcuts": "نمایش میانبرهای صفحه‌کلید", + + "agentManager.dialog.removeStaleWorktree.title": "حذف Worktree قدیمی", + "agentManager.dialog.removeStaleWorktree.messagePre": "حذف Worktree قدیمی ", + "agentManager.dialog.removeStaleWorktree.messagePost": + "؟ این عملیات فقط نگاشت Agent Manager را حذف می‌کند و فایل‌های روی دیسک دست‌نخورده باقی می‌مانند.", + "agentManager.dialog.removeStaleWorktree.cancel": "لغو", + "agentManager.dialog.removeStaleWorktree.confirm": "حذف Worktree قدیمی", + + "agentManager.dialog.openWorktree": "Worktree جدید", + "agentManager.dialog.tab.new": "جدید", + "agentManager.dialog.tab.import": "وارد کردن", + "agentManager.dialog.namePlaceholder": "نام Worktree (اختیاری)", + "agentManager.dialog.promptPlaceholder.mac": "پیامی بنویسید (⌘Enter برای ارسال)", + "agentManager.dialog.promptPlaceholder.other": "پیامی بنویسید (Ctrl+Enter برای ارسال)", + "agentManager.dialog.advancedOptions": "گزینه‌های پیشرفته", + "agentManager.dialog.branchName": "نام شاخه", + "agentManager.dialog.branchNamePlaceholder": "تولید خودکار", + "agentManager.dialog.baseBranch": "شاخه پایه", + "agentManager.dialog.searchBranches": "جستجوی شاخه‌ها...", + "agentManager.dialog.branchBadge.default": "پیش‌فرض", + "agentManager.dialog.branchBadge.remote": "راه دور", + "agentManager.dialog.versions": "نسخه‌ها", + "agentManager.dialog.versionHint": "{{count}} worktree به صورت موازی اجرا خواهند شد", + "agentManager.dialog.compareModels": "مقایسه مدل‌ها", + "agentManager.dialog.compareModels.tooltip": "اجرای عامل‌ها روی مدل‌های مختلف به صورت موازی برای مقایسه نتایج", + "agentManager.dialog.compareModels.searchModels": "جستجوی مدل‌ها...", + "agentManager.dialog.compareModels.selectModels": "انتخاب مدل‌ها...", + "agentManager.dialog.compareModels.effort": "میزان استدلال", + "agentManager.dialog.compareModels.effortDefault": "پیش‌فرض", + "agentManager.dialog.creating": "در حال ایجاد...", + "agentManager.dialog.createWorktree": "ایجاد Worktree", + "agentManager.dialog.removeImage": "حذف تصویر", + "agentManager.dialog.configureWorktree": "پیکربندی Worktree جدید...", + + "agentManager.diff.toggle": "تغییر وضعیت diff", + "agentManager.diff.openFile": "باز کردن فایل", + "agentManager.diff.revertFile": "بازگردانی فایل", + "agentManager.diff.revertSuccess": "فایل بازگردانی شد", + "agentManager.diff.revertError": "بازگردانی ناموفق بود", + "agentManager.open.button": "باز کردن", + "agentManager.open.tooltip": "باز کردن این Worktree در VS Code", + "agentManager.apply.globalButton": "اعمال", + "agentManager.apply.tooltip": "اعمال تغییرات worktree انتخاب‌شده به شاخه محلی", + "agentManager.apply.dialogTitle": "اعمال تغییرات به شاخه محلی", + "agentManager.apply.confirm": "همین الان اعمال کن", + "agentManager.apply.selectionSummary": "{{selected}} از {{total}} فایل انتخاب‌شده", + "agentManager.apply.selectAll": "انتخاب همه", + "agentManager.apply.selectNone": "هیچ‌کدام را انتخاب نکن", + "agentManager.apply.checking": "در حال بررسی...", + "agentManager.apply.applying": "در حال اعمال...", + "agentManager.apply.success": "تغییرات اعمال شد", + "agentManager.apply.conflict": "اعمال دارای تعارض است", + "agentManager.apply.conflictToast": "{{count}} تعارض در {{files}} فایل. جزئیات را در Apply بررسی کنید.", + "agentManager.apply.error": "اعمال ناموفق بود", + "agentManager.apply.conflictsTitle": "تعارض‌ها", + "agentManager.apply.unknownFile": "فایل نامشخص", + "agentManager.apply.reason.index": "فایل محلی با نسخه مورد انتظار تفاوت دارد", + "agentManager.apply.reason.patch": "پچ به‌درستی قابل اعمال نیست", + "agentManager.apply.reason.contents": "خواندن محتوای فایل محلی فعلی ممکن نیست", + "agentManager.apply.reason.unknown": "تعارض شناسایی شد", + "agentManager.shortcuts.toggleDiff": "نمایش/پنهان کردن پنل diff", + "agentManager.shortcuts.category.quickSwitch": "تغییر سریع", + "agentManager.shortcuts.jumpToItem": "پرش به مورد ۱–۹", + "agentManager.review.sendAllToChat": "ارسال همه به چت", + "agentManager.review.sendAllToChatWithCount": "ارسال همه به چت ({{count}})", + "agentManager.review.sendAllShortcut.mac": "⌘Enter", + "agentManager.review.sendAllShortcut.other": "Ctrl+Enter", + "agentManager.review.modalTitle": "بررسی نظر", + "agentManager.review.inlineCount": "نظرات بررسی ({{count}})", + "agentManager.review.clearAll": "پاک کردن همه", + "agentManager.review.commentOnLine": "نظر در خط {{line}}", + "agentManager.review.editCommentOnLine": "ویرایش نظر در خط {{line}}", + "agentManager.review.commentPlaceholder": "یک نظر بگذارید...", + "agentManager.review.commentAction": "نظر", + "agentManager.review.sendToChat": "ارسال به چت", + "agentManager.review.metaFile": "فایل", + "agentManager.review.metaLine": "خط", + "agentManager.review.metaComment": "نظر کاربر", + "agentManager.review.collapsedOnly": "{{count}} جمع‌شده", + "agentManager.review.collapsedWithLarge": "{{collapsed}} جمع‌شده، {{large}} بزرگ", + "agentManager.review.largeFileCollapsed": "فایل بزرگ (جمع‌شده)", + "agentManager.review.image": "تصویر", + "agentManager.review.imageBefore": "قبل", + "agentManager.review.imageAfter": "بعد", + "agentManager.review.imageTooLarge": "تصویر برای پیش‌نمایش خیلی بزرگ است ({{size}}).", + "agentManager.review.imageUnreadable": "این تصویر قابل نمایش نیست.", + "agentManager.review.imageUnavailable": "پیش‌نمایش تصویر برای این نمونه جلسه در دسترس نیست.", + "agentManager.review.endOfLongDiff": "به انتها رسیدید!", + + "agentManager.import.pullRequest": "Pull Request", + "agentManager.import.pastePrUrl": "URL درخواست PR را وارد کنید...", + "agentManager.import.open": "باز کردن", + "agentManager.import.branches": "شاخه‌ها", + "agentManager.import.selectBranch": "انتخاب شاخه...", + "agentManager.import.loading": "در حال بارگذاری...", + "agentManager.import.loadingBranches": "در حال بارگذاری شاخه‌ها...", + "agentManager.import.noMatchingBranches": "شاخه‌ای مطابق یافت نشد", + "agentManager.import.noBranchesFound": "هیچ شاخه‌ای یافت نشد.", + "agentManager.import.noBranchesHint": "یک URL درخواست ادغام را در بالا جای‌گذاری کنید یا یک worktree جدید بسازید.", + "agentManager.import.failed": "وارد کردن ناموفق بود", +} 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 ce67c87f69..c95e23e64f 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/fr.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "Copier le chemin", "agentManager.worktree.openInVscode": "Ouvrir dans VS Code", "agentManager.worktree.rename": "Renommer", - "agentManager.worktree.moveToSection": "Déplacer vers la section", "agentManager.worktree.newSection": "Nouvelle section", "agentManager.worktree.ungrouped": "Non groupé", "agentManager.section.rename": "Renommer la section", "agentManager.section.setColor": "Définir la couleur", "agentManager.section.delete": "Supprimer la section", - "agentManager.section.defaultColor": "Par défaut", "agentManager.section.defaultName": "Nouvelle section", "agentManager.section.moveUp": "Déplacer vers le haut", "agentManager.section.moveDown": "Déplacer vers le bas", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "BRANCHE", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Base", "agentManager.hoverCard.sessions": "Sessions", "agentManager.hoverCard.files": "Fichiers", @@ -67,6 +65,12 @@ export const dict = { "agentManager.terminal.new": "Nouvel onglet de terminal", "agentManager.terminal.ended": "terminal terminé — fermez l'onglet pour ignorer", "agentManager.terminal.connectionError": "erreur de connexion du terminal", + "agentManager.terminal.add": "Nouveau terminal", + "agentManager.terminal.empty": "Aucun terminal ici pour l'instant", + "agentManager.terminal.start": "Démarrer le terminal", + "agentManager.terminal.destination": "Choisir ce que le bouton Terminal ouvre", + "agentManager.terminal.openInVscode": "Terminal VS Code", + "agentManager.terminal.openInPanel": "Panneau Agent Manager", "agentManager.terminal.errorTitle": "Erreur du terminal", "agentManager.setup.failed": "Échec de la configuration de l'espace de travail", "agentManager.setup.settingUp": "Configuration de l'espace de travail", @@ -134,7 +138,6 @@ export const dict = { "agentManager.dialog.creating": "Création...", "agentManager.dialog.createWorktree": "Créer un worktree", "agentManager.dialog.removeImage": "Supprimer l'image", - "agentManager.dialog.advanced": "Avancé...", "agentManager.diff.toggle": "Basculer le diff", "agentManager.diff.openFile": "Ouvrir le fichier", @@ -143,19 +146,13 @@ export const dict = { "agentManager.diff.revertError": "Échec du rétablissement", "agentManager.open.button": "Ouvrir", "agentManager.open.tooltip": "Ouvrir ce worktree dans VS Code", - "agentManager.apply.button": "Appliquer en local", "agentManager.apply.globalButton": "Appliquer", "agentManager.apply.tooltip": "Appliquer les modifications du worktree sélectionné à la branche locale", "agentManager.apply.dialogTitle": "Appliquer les modifications à la branche locale", "agentManager.apply.confirm": "Appliquer maintenant", - "agentManager.apply.retry": "Réessayer l'application", - "agentManager.apply.previewTitle": "Appliquer ces modifications à la branche locale ?", - "agentManager.apply.previewSummary": "{{files}} fichiers, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} sur {{total}} fichiers sélectionnés", "agentManager.apply.selectAll": "Tout sélectionner", "agentManager.apply.selectNone": "Tout désélectionner", - "agentManager.apply.moreFiles": "+{{count}} de plus", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} de plus)", "agentManager.apply.checking": "Vérification...", "agentManager.apply.applying": "Application...", "agentManager.apply.success": "Modifications appliquées", @@ -169,9 +166,6 @@ export const dict = { "agentManager.apply.reason.patch": "Le patch ne peut pas être appliqué proprement", "agentManager.apply.reason.contents": "Impossible de lire le contenu du fichier local actuel", "agentManager.apply.reason.unknown": "Conflit détecté", - "agentManager.apply.inline.success": "Appliqué", - "agentManager.apply.inline.conflict": "Conflits", - "agentManager.apply.inline.error": "Échoué", "agentManager.shortcuts.toggleDiff": "Basculer le panneau de diff", "agentManager.shortcuts.category.quickSwitch": "Changement rapide", "agentManager.shortcuts.jumpToItem": "Aller à l'élément 1\u20139", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts index 2584d750b0..ef46e80dfa 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/it.ts @@ -27,19 +27,17 @@ export const dict = { "agentManager.worktree.copyPath": "Copia percorso", "agentManager.worktree.openInVscode": "Apri in VS Code", "agentManager.worktree.rename": "Rinomina", - "agentManager.worktree.moveToSection": "Sposta nella sezione", "agentManager.worktree.newSection": "Nuova sezione", "agentManager.worktree.ungrouped": "Non raggruppati", "agentManager.section.rename": "Rinomina sezione", "agentManager.section.setColor": "Imposta colore", "agentManager.section.delete": "Elimina sezione", - "agentManager.section.defaultColor": "Predefinito", "agentManager.section.defaultName": "Nuova sezione", "agentManager.section.moveUp": "Sposta su", "agentManager.section.moveDown": "Sposta giù", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "BRANCH", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Base", "agentManager.hoverCard.sessions": "Sessioni", "agentManager.hoverCard.files": "File", @@ -71,6 +69,12 @@ export const dict = { "agentManager.terminal.new": "Nuova scheda terminale", "agentManager.terminal.ended": "terminale terminato - chiudi la scheda per nasconderlo", "agentManager.terminal.connectionError": "errore di connessione del terminale", + "agentManager.terminal.add": "Nuovo terminale", + "agentManager.terminal.empty": "Qui non c'è ancora un terminale", + "agentManager.terminal.start": "Avvia terminale", + "agentManager.terminal.destination": "Scegli cosa apre il pulsante del terminale", + "agentManager.terminal.openInVscode": "Terminale di VS Code", + "agentManager.terminal.openInPanel": "Pannello Agent Manager", "agentManager.terminal.errorTitle": "Errore terminale", "agentManager.setup.failed": "Setup del worktree non riuscito", @@ -139,7 +143,6 @@ export const dict = { "agentManager.dialog.creating": "Creazione...", "agentManager.dialog.createWorktree": "Crea worktree", "agentManager.dialog.removeImage": "Rimuovi immagine", - "agentManager.dialog.advanced": "Avanzate...", "agentManager.dialog.configureWorktree": "Configura nuovo worktree...", "agentManager.diff.toggle": "Mostra/nascondi diff", @@ -149,19 +152,13 @@ export const dict = { "agentManager.diff.revertError": "Ripristino non riuscito", "agentManager.open.button": "Apri", "agentManager.open.tooltip": "Apri questo worktree in VS Code", - "agentManager.apply.button": "Applica in locale", "agentManager.apply.globalButton": "Applica", "agentManager.apply.tooltip": "Applica le modifiche selezionate del worktree al branch locale", "agentManager.apply.dialogTitle": "Applica modifiche al branch locale", "agentManager.apply.confirm": "Applica ora", - "agentManager.apply.retry": "Riprova applicazione", - "agentManager.apply.previewTitle": "Applicare queste modifiche al branch locale?", - "agentManager.apply.previewSummary": "{{files}} file, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} di {{total}} file selezionati", "agentManager.apply.selectAll": "Seleziona tutto", "agentManager.apply.selectNone": "Deseleziona tutto", - "agentManager.apply.moreFiles": "+{{count}} altri", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} altri)", "agentManager.apply.checking": "Controllo...", "agentManager.apply.applying": "Applicazione...", "agentManager.apply.success": "Modifiche applicate", @@ -174,9 +171,6 @@ export const dict = { "agentManager.apply.reason.patch": "La patch non può essere applicata in modo pulito", "agentManager.apply.reason.contents": "Impossibile leggere il contenuto corrente del file locale", "agentManager.apply.reason.unknown": "Conflitto rilevato", - "agentManager.apply.inline.success": "Applicato", - "agentManager.apply.inline.conflict": "Conflitti", - "agentManager.apply.inline.error": "Non riuscito", "agentManager.shortcuts.toggleDiff": "Mostra/nascondi pannello diff", "agentManager.shortcuts.category.quickSwitch": "Cambio rapido", "agentManager.shortcuts.jumpToItem": "Vai all'elemento 1-9", 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 53f08ced3d..823a95ae82 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ja.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "パスをコピー", "agentManager.worktree.openInVscode": "VS Codeで開く", "agentManager.worktree.rename": "名前を変更", - "agentManager.worktree.moveToSection": "セクションに移動", "agentManager.worktree.newSection": "新しいセクション", "agentManager.worktree.ungrouped": "グループなし", "agentManager.section.rename": "セクションの名前変更", "agentManager.section.setColor": "色の設定", "agentManager.section.delete": "セクションの削除", - "agentManager.section.defaultColor": "デフォルト", "agentManager.section.defaultName": "新しいセクション", "agentManager.section.moveUp": "上に移動", "agentManager.section.moveDown": "下に移動", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "ブランチ", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "ベース", "agentManager.hoverCard.sessions": "セッション", "agentManager.hoverCard.files": "ファイル", @@ -67,6 +65,12 @@ export const dict = { "agentManager.terminal.new": "新しいターミナルタブ", "agentManager.terminal.ended": "ターミナルが終了しました — タブを閉じて破棄", "agentManager.terminal.connectionError": "ターミナル接続エラー", + "agentManager.terminal.add": "新しいターミナル", + "agentManager.terminal.empty": "ここにはまだターミナルがありません", + "agentManager.terminal.start": "ターミナルを開始", + "agentManager.terminal.destination": "ターミナルボタンで開く場所を選択", + "agentManager.terminal.openInVscode": "VS Codeのターミナル", + "agentManager.terminal.openInPanel": "Agent Managerパネル", "agentManager.terminal.errorTitle": "ターミナルエラー", "agentManager.setup.failed": "ワークスペースのセットアップに失敗しました", "agentManager.setup.settingUp": "ワークスペースをセットアップ中", @@ -133,7 +137,6 @@ export const dict = { "agentManager.dialog.creating": "作成中...", "agentManager.dialog.createWorktree": "ワークツリーを作成", "agentManager.dialog.removeImage": "画像を削除", - "agentManager.dialog.advanced": "詳細...", "agentManager.diff.toggle": "差分を切り替え", "agentManager.diff.openFile": "ファイルを開く", @@ -142,19 +145,13 @@ export const dict = { "agentManager.diff.revertError": "元に戻せませんでした", "agentManager.open.button": "開く", "agentManager.open.tooltip": "このWorktreeをVS Codeで開く", - "agentManager.apply.button": "ローカルに適用", "agentManager.apply.globalButton": "適用", "agentManager.apply.tooltip": "選択したワークツリーの変更をローカルブランチに適用", "agentManager.apply.dialogTitle": "ローカルブランチに変更を適用", "agentManager.apply.confirm": "今すぐ適用", - "agentManager.apply.retry": "適用を再試行", - "agentManager.apply.previewTitle": "これらの変更をローカルブランチに適用しますか?", - "agentManager.apply.previewSummary": "{{files}} ファイル、+{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{total}} ファイル中 {{selected}} 件選択", "agentManager.apply.selectAll": "すべて選択", "agentManager.apply.selectNone": "選択解除", - "agentManager.apply.moreFiles": "+{{count}} 件", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} 件)", "agentManager.apply.checking": "確認中...", "agentManager.apply.applying": "適用中...", "agentManager.apply.success": "変更が適用されました", @@ -168,9 +165,6 @@ export const dict = { "agentManager.apply.reason.patch": "パッチをクリーンに適用できません", "agentManager.apply.reason.contents": "現在のローカルファイルの内容を読み取れません", "agentManager.apply.reason.unknown": "競合が検出されました", - "agentManager.apply.inline.success": "適用済み", - "agentManager.apply.inline.conflict": "競合", - "agentManager.apply.inline.error": "失敗", "agentManager.shortcuts.toggleDiff": "差分パネルを切り替え", "agentManager.shortcuts.category.quickSwitch": "クイック切り替え", "agentManager.shortcuts.jumpToItem": "項目 1\u20139 に移動", 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 32439495f6..0c084f29d6 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ko.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "경로 복사", "agentManager.worktree.openInVscode": "VS Code에서 열기", "agentManager.worktree.rename": "이름 바꾸기", - "agentManager.worktree.moveToSection": "섹션으로 이동", "agentManager.worktree.newSection": "새 섹션", "agentManager.worktree.ungrouped": "그룹 해제됨", "agentManager.section.rename": "섹션 이름 바꾸기", "agentManager.section.setColor": "색상 설정", "agentManager.section.delete": "섹션 삭제", - "agentManager.section.defaultColor": "기본값", "agentManager.section.defaultName": "새 섹션", "agentManager.section.moveUp": "위로 이동", "agentManager.section.moveDown": "아래로 이동", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "브랜치", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "베이스", "agentManager.hoverCard.sessions": "세션", "agentManager.hoverCard.files": "파일", @@ -66,6 +64,12 @@ export const dict = { "agentManager.terminal.new": "새 터미널 탭", "agentManager.terminal.ended": "터미널 종료됨 — 탭을 닫아 해제", "agentManager.terminal.connectionError": "터미널 연결 오류", + "agentManager.terminal.add": "새 터미널", + "agentManager.terminal.empty": "아직 여기에 터미널이 없습니다", + "agentManager.terminal.start": "터미널 시작", + "agentManager.terminal.destination": "터미널 버튼으로 열 위치 선택", + "agentManager.terminal.openInVscode": "VS Code 터미널", + "agentManager.terminal.openInPanel": "Agent Manager 패널", "agentManager.terminal.errorTitle": "터미널 오류", "agentManager.setup.failed": "워크스페이스 설정 실패", "agentManager.setup.settingUp": "워크스페이스 설정 중", @@ -132,7 +136,6 @@ export const dict = { "agentManager.dialog.creating": "생성 중...", "agentManager.dialog.createWorktree": "워크트리 생성", "agentManager.dialog.removeImage": "이미지 제거", - "agentManager.dialog.advanced": "고급...", "agentManager.diff.toggle": "차이점 전환", "agentManager.diff.openFile": "파일 열기", @@ -141,19 +144,13 @@ export const dict = { "agentManager.diff.revertError": "되돌리기 실패", "agentManager.open.button": "열기", "agentManager.open.tooltip": "이 Worktree를 VS Code에서 열기", - "agentManager.apply.button": "로컬에 적용", "agentManager.apply.globalButton": "적용", "agentManager.apply.tooltip": "선택한 워크트리 변경사항을 로컬 브랜치에 적용", "agentManager.apply.dialogTitle": "로컬 브랜치에 변경사항 적용", "agentManager.apply.confirm": "지금 적용", - "agentManager.apply.retry": "적용 재시도", - "agentManager.apply.previewTitle": "이 변경사항을 로컬 브랜치에 적용하시겠습니까?", - "agentManager.apply.previewSummary": "{{files}}개 파일, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{total}}개 파일 중 {{selected}}개 선택됨", "agentManager.apply.selectAll": "모두 선택", "agentManager.apply.selectNone": "선택 해제", - "agentManager.apply.moreFiles": "+{{count}}개 더", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}}개 더)", "agentManager.apply.checking": "확인 중...", "agentManager.apply.applying": "적용 중...", "agentManager.apply.success": "변경사항이 적용되었습니다", @@ -167,9 +164,6 @@ export const dict = { "agentManager.apply.reason.patch": "패치를 깨끗하게 적용할 수 없습니다", "agentManager.apply.reason.contents": "현재 로컬 파일 내용을 읽을 수 없습니다", "agentManager.apply.reason.unknown": "충돌이 감지되었습니다", - "agentManager.apply.inline.success": "적용됨", - "agentManager.apply.inline.conflict": "충돌", - "agentManager.apply.inline.error": "실패", "agentManager.shortcuts.toggleDiff": "차이점 패널 전환", "agentManager.shortcuts.category.quickSwitch": "빠른 전환", "agentManager.shortcuts.jumpToItem": "항목 1\u20139(으)로 이동", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts index 234cc77c69..8c835e1aa2 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/nl.ts @@ -27,19 +27,17 @@ export const dict = { "agentManager.worktree.copyPath": "Pad kopiëren", "agentManager.worktree.openInVscode": "Openen in VS Code", "agentManager.worktree.rename": "Hernoemen", - "agentManager.worktree.moveToSection": "Verplaatsen naar sectie", "agentManager.worktree.newSection": "Nieuwe sectie", "agentManager.worktree.ungrouped": "Niet gegroepeerd", "agentManager.section.rename": "Sectie hernoemen", "agentManager.section.setColor": "Kleur instellen", "agentManager.section.delete": "Sectie verwijderen", - "agentManager.section.defaultColor": "Standaard", "agentManager.section.defaultName": "Nieuwe sectie", "agentManager.section.moveUp": "Omhoog verplaatsen", "agentManager.section.moveDown": "Omlaag verplaatsen", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "BRANCH", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Basis", "agentManager.hoverCard.sessions": "Sessies", "agentManager.hoverCard.files": "Bestanden", @@ -70,6 +68,12 @@ export const dict = { "agentManager.terminal.new": "Nieuw terminaltabblad", "agentManager.terminal.ended": "terminal beëindigd — sluit tabblad om te negeren", "agentManager.terminal.connectionError": "terminalverbindingsfout", + "agentManager.terminal.add": "Nieuwe terminal", + "agentManager.terminal.empty": "Hier is nog geen terminal", + "agentManager.terminal.start": "Terminal starten", + "agentManager.terminal.destination": "Kies wat de terminalknop opent", + "agentManager.terminal.openInVscode": "VS Code-terminal", + "agentManager.terminal.openInPanel": "Agent Manager-paneel", "agentManager.terminal.errorTitle": "Terminalfout", "agentManager.setup.failed": "Worktree setup mislukt", @@ -139,7 +143,6 @@ export const dict = { "agentManager.dialog.creating": "Maken...", "agentManager.dialog.createWorktree": "Worktree maken", "agentManager.dialog.removeImage": "Afbeelding verwijderen", - "agentManager.dialog.advanced": "Geavanceerd...", "agentManager.diff.toggle": "Diff in-/uitschakelen", "agentManager.diff.openFile": "Bestand openen", @@ -148,19 +151,13 @@ export const dict = { "agentManager.diff.revertError": "Terugzetten mislukt", "agentManager.open.button": "Openen", "agentManager.open.tooltip": "Open deze worktree in VS Code", - "agentManager.apply.button": "Toepassen op lokaal", "agentManager.apply.globalButton": "Toepassen", "agentManager.apply.tooltip": "Geselecteerde worktree wijzigingen toepassen op lokale branch", "agentManager.apply.dialogTitle": "Wijzigingen toepassen op lokale branch", "agentManager.apply.confirm": "Nu toepassen", - "agentManager.apply.retry": "Toepassen opnieuw proberen", - "agentManager.apply.previewTitle": "Deze wijzigingen toepassen op lokale branch?", - "agentManager.apply.previewSummary": "{{files}} bestanden, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} van {{total}} bestanden geselecteerd", "agentManager.apply.selectAll": "Alles selecteren", "agentManager.apply.selectNone": "Niets selecteren", - "agentManager.apply.moreFiles": "nog +{{count}}", - "agentManager.apply.moreReasons": "{{reason}} (nog +{{count}})", "agentManager.apply.checking": "Controleren...", "agentManager.apply.applying": "Toepassen...", "agentManager.apply.success": "Wijzigingen toegepast", @@ -173,9 +170,6 @@ export const dict = { "agentManager.apply.reason.patch": "Patch kan niet netjes worden toegepast", "agentManager.apply.reason.contents": "Kan huidige lokale bestandsinhoud niet lezen", "agentManager.apply.reason.unknown": "Conflict gedetecteerd", - "agentManager.apply.inline.success": "Toegepast", - "agentManager.apply.inline.conflict": "Conflicten", - "agentManager.apply.inline.error": "Mislukt", "agentManager.shortcuts.toggleDiff": "Diff-paneel in-/uitschakelen", "agentManager.shortcuts.category.quickSwitch": "Snel wisselen", "agentManager.shortcuts.jumpToItem": "Spring naar item 1\u20139", 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 2f09368f09..20128c4f8a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/no.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "Kopier sti", "agentManager.worktree.openInVscode": "Åpne i VS Code", "agentManager.worktree.rename": "Gi nytt navn", - "agentManager.worktree.moveToSection": "Flytt til seksjon", "agentManager.worktree.newSection": "Ny seksjon", "agentManager.worktree.ungrouped": "Ugruppert", "agentManager.section.rename": "Gi seksjon nytt navn", "agentManager.section.setColor": "Angi farge", "agentManager.section.delete": "Slett seksjon", - "agentManager.section.defaultColor": "Standard", "agentManager.section.defaultName": "Ny seksjon", "agentManager.section.moveUp": "Flytt opp", "agentManager.section.moveDown": "Flytt ned", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "BRANCH", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Base", "agentManager.hoverCard.sessions": "Økter", "agentManager.hoverCard.files": "Filer", @@ -66,6 +64,12 @@ export const dict = { "agentManager.terminal.new": "Ny terminalfane", "agentManager.terminal.ended": "terminal avsluttet — lukk fanen for å avvise", "agentManager.terminal.connectionError": "tilkoblingsfeil for terminal", + "agentManager.terminal.add": "Ny terminal", + "agentManager.terminal.empty": "Ingen terminal her ennå", + "agentManager.terminal.start": "Start terminal", + "agentManager.terminal.destination": "Velg hva terminalknappen åpner", + "agentManager.terminal.openInVscode": "VS Code-terminal", + "agentManager.terminal.openInPanel": "Agent Manager-panel", "agentManager.terminal.errorTitle": "Terminalfeil", "agentManager.setup.failed": "Oppsett av arbeidsområde mislyktes", "agentManager.setup.settingUp": "Setter opp arbeidsområde", @@ -133,7 +137,6 @@ export const dict = { "agentManager.dialog.creating": "Oppretter...", "agentManager.dialog.createWorktree": "Opprett worktree", "agentManager.dialog.removeImage": "Fjern bilde", - "agentManager.dialog.advanced": "Avansert...", "agentManager.diff.toggle": "Veksle diff", "agentManager.diff.openFile": "Åpne fil", @@ -142,19 +145,13 @@ export const dict = { "agentManager.diff.revertError": "Tilbakestilling feilet", "agentManager.open.button": "Åpne", "agentManager.open.tooltip": "Åpne dette Worktree-et i VS Code", - "agentManager.apply.button": "Bruk lokalt", "agentManager.apply.globalButton": "Bruk", "agentManager.apply.tooltip": "Bruk valgte worktree-endringer på den lokale grenen", "agentManager.apply.dialogTitle": "Bruk endringer på den lokale grenen", "agentManager.apply.confirm": "Bruk nå", - "agentManager.apply.retry": "Prøv å bruke på nytt", - "agentManager.apply.previewTitle": "Bruke disse endringene på den lokale grenen?", - "agentManager.apply.previewSummary": "{{files}} filer, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} av {{total}} filer valgt", "agentManager.apply.selectAll": "Velg alle", "agentManager.apply.selectNone": "Velg ingen", - "agentManager.apply.moreFiles": "+{{count}} flere", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} flere)", "agentManager.apply.checking": "Sjekker...", "agentManager.apply.applying": "Bruker...", "agentManager.apply.success": "Endringer brukt", @@ -167,9 +164,6 @@ export const dict = { "agentManager.apply.reason.patch": "Patch kan ikke brukes rent", "agentManager.apply.reason.contents": "Kan ikke lese innholdet i gjeldende lokal fil", "agentManager.apply.reason.unknown": "Konflikt oppdaget", - "agentManager.apply.inline.success": "Brukt", - "agentManager.apply.inline.conflict": "Konflikter", - "agentManager.apply.inline.error": "Mislyktes", "agentManager.shortcuts.toggleDiff": "Veksle diff-panel", "agentManager.shortcuts.category.quickSwitch": "Hurtigbytte", "agentManager.shortcuts.jumpToItem": "Hopp til element 1\u20139", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts index 7e688b9495..eff9b28c71 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/pl.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "Kopiuj ścieżkę", "agentManager.worktree.openInVscode": "Otwórz w VS Code", "agentManager.worktree.rename": "Zmień nazwę", - "agentManager.worktree.moveToSection": "Przenieś do sekcji", "agentManager.worktree.newSection": "Nowa sekcja", "agentManager.worktree.ungrouped": "Niezgrupowane", "agentManager.section.rename": "Zmień nazwę sekcji", "agentManager.section.setColor": "Ustaw kolor", "agentManager.section.delete": "Usuń sekcję", - "agentManager.section.defaultColor": "Domyślny", "agentManager.section.defaultName": "Nowa sekcja", "agentManager.section.moveUp": "Przenieś w górę", "agentManager.section.moveDown": "Przenieś w dół", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "BRANCH", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Baza", "agentManager.hoverCard.sessions": "Sesje", "agentManager.hoverCard.files": "Pliki", @@ -67,6 +65,12 @@ export const dict = { "agentManager.terminal.new": "Nowa karta terminala", "agentManager.terminal.ended": "terminal zakończony — zamknij kartę, aby zamknąć", "agentManager.terminal.connectionError": "błąd połączenia terminala", + "agentManager.terminal.add": "Nowy terminal", + "agentManager.terminal.empty": "Nie ma tu jeszcze terminala", + "agentManager.terminal.start": "Uruchom terminal", + "agentManager.terminal.destination": "Wybierz, co otwiera przycisk terminala", + "agentManager.terminal.openInVscode": "Terminal VS Code", + "agentManager.terminal.openInPanel": "Panel Agent Manager", "agentManager.terminal.errorTitle": "Błąd terminala", "agentManager.setup.failed": "Konfiguracja worktree nie powiodła się", "agentManager.setup.settingUp": "Konfigurowanie worktree", @@ -133,7 +137,6 @@ export const dict = { "agentManager.dialog.creating": "Tworzenie...", "agentManager.dialog.createWorktree": "Utwórz Worktree", "agentManager.dialog.removeImage": "Usuń obraz", - "agentManager.dialog.advanced": "Zaawansowane...", "agentManager.diff.toggle": "Przełącz diff", "agentManager.diff.openFile": "Otwórz plik", @@ -142,19 +145,13 @@ export const dict = { "agentManager.diff.revertError": "Cofanie nie powiodło się", "agentManager.open.button": "Otwórz", "agentManager.open.tooltip": "Otwórz ten Worktree w VS Code", - "agentManager.apply.button": "Zastosuj lokalnie", "agentManager.apply.globalButton": "Zastosuj", "agentManager.apply.tooltip": "Zastosuj wybrane zmiany worktree na lokalnej gałęzi", "agentManager.apply.dialogTitle": "Zastosuj zmiany na lokalnej gałęzi", "agentManager.apply.confirm": "Zastosuj teraz", - "agentManager.apply.retry": "Ponów zastosowanie", - "agentManager.apply.previewTitle": "Zastosować te zmiany na lokalnej gałęzi?", - "agentManager.apply.previewSummary": "{{files}} plików, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} z {{total}} plików wybranych", "agentManager.apply.selectAll": "Zaznacz wszystkie", "agentManager.apply.selectNone": "Odznacz wszystkie", - "agentManager.apply.moreFiles": "+{{count}} więcej", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} więcej)", "agentManager.apply.checking": "Sprawdzanie...", "agentManager.apply.applying": "Stosowanie...", "agentManager.apply.success": "Zmiany zastosowane", @@ -167,9 +164,6 @@ export const dict = { "agentManager.apply.reason.patch": "Łatka nie może być zastosowana czysto", "agentManager.apply.reason.contents": "Nie można odczytać zawartości bieżącego pliku lokalnego", "agentManager.apply.reason.unknown": "Wykryto konflikt", - "agentManager.apply.inline.success": "Zastosowano", - "agentManager.apply.inline.conflict": "Konflikty", - "agentManager.apply.inline.error": "Błąd", "agentManager.shortcuts.toggleDiff": "Przełącz panel diff", "agentManager.shortcuts.category.quickSwitch": "Szybkie przełączanie", "agentManager.shortcuts.jumpToItem": "Przejdź do elementu 1\u20139", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts index 3925dda327..4a44db6021 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/ru.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "Копировать путь", "agentManager.worktree.openInVscode": "Открыть в VS Code", "agentManager.worktree.rename": "Переименовать", - "agentManager.worktree.moveToSection": "Переместить в раздел", "agentManager.worktree.newSection": "Новый раздел", "agentManager.worktree.ungrouped": "Без группы", "agentManager.section.rename": "Переименовать раздел", "agentManager.section.setColor": "Задать цвет", "agentManager.section.delete": "Удалить раздел", - "agentManager.section.defaultColor": "По умолчанию", "agentManager.section.defaultName": "Новый раздел", "agentManager.section.moveUp": "Переместить вверх", "agentManager.section.moveDown": "Переместить вниз", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "ВЕТКА", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Основа", "agentManager.hoverCard.sessions": "Сессии", "agentManager.hoverCard.files": "Файлы", @@ -67,6 +65,12 @@ export const dict = { "agentManager.terminal.new": "Новая вкладка терминала", "agentManager.terminal.ended": "терминал завершен — закройте вкладку, чтобы скрыть", "agentManager.terminal.connectionError": "ошибка подключения к терминалу", + "agentManager.terminal.add": "Новый терминал", + "agentManager.terminal.empty": "Здесь пока нет терминала", + "agentManager.terminal.start": "Запустить терминал", + "agentManager.terminal.destination": "Выберите, где будет открываться терминал", + "agentManager.terminal.openInVscode": "Терминал VS Code", + "agentManager.terminal.openInPanel": "Панель Agent Manager", "agentManager.terminal.errorTitle": "Ошибка терминала", "agentManager.setup.failed": "Не удалось настроить рабочее пространство", "agentManager.setup.settingUp": "Настройка рабочего пространства", @@ -134,7 +138,6 @@ export const dict = { "agentManager.dialog.creating": "Создание...", "agentManager.dialog.createWorktree": "Создать worktree", "agentManager.dialog.removeImage": "Удалить изображение", - "agentManager.dialog.advanced": "Дополнительно...", "agentManager.diff.toggle": "Переключить diff", "agentManager.diff.openFile": "Открыть файл", @@ -143,19 +146,13 @@ export const dict = { "agentManager.diff.revertError": "Ошибка отката", "agentManager.open.button": "Открыть", "agentManager.open.tooltip": "Открыть этот Worktree в VS Code", - "agentManager.apply.button": "Применить локально", "agentManager.apply.globalButton": "Применить", "agentManager.apply.tooltip": "Применить изменения выбранного рабочего дерева к локальной ветке", "agentManager.apply.dialogTitle": "Применить изменения к локальной ветке", "agentManager.apply.confirm": "Применить сейчас", - "agentManager.apply.retry": "Повторить применение", - "agentManager.apply.previewTitle": "Применить эти изменения к локальной ветке?", - "agentManager.apply.previewSummary": "{{files}} файлов, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} из {{total}} файлов выбрано", "agentManager.apply.selectAll": "Выбрать все", "agentManager.apply.selectNone": "Снять выделение", - "agentManager.apply.moreFiles": "+{{count}} ещё", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} ещё)", "agentManager.apply.checking": "Проверка...", "agentManager.apply.applying": "Применение...", "agentManager.apply.success": "Изменения применены", @@ -168,9 +165,6 @@ export const dict = { "agentManager.apply.reason.patch": "Патч не может быть применён чисто", "agentManager.apply.reason.contents": "Не удаётся прочитать содержимое текущего локального файла", "agentManager.apply.reason.unknown": "Обнаружен конфликт", - "agentManager.apply.inline.success": "Применено", - "agentManager.apply.inline.conflict": "Конфликты", - "agentManager.apply.inline.error": "Ошибка", "agentManager.shortcuts.toggleDiff": "Переключить панель diff", "agentManager.shortcuts.category.quickSwitch": "Быстрое переключение", "agentManager.shortcuts.jumpToItem": "Перейти к элементу 1\u20139", 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 883848a300..11245a7632 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/th.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "คัดลอกเส้นทาง", "agentManager.worktree.openInVscode": "เปิดใน VS Code", "agentManager.worktree.rename": "เปลี่ยนชื่อ", - "agentManager.worktree.moveToSection": "ย้ายไปที่ส่วน", "agentManager.worktree.newSection": "ส่วนใหม่", "agentManager.worktree.ungrouped": "ไม่ได้จัดกลุ่ม", "agentManager.section.rename": "เปลี่ยนชื่อส่วน", "agentManager.section.setColor": "ตั้งค่าสี", "agentManager.section.delete": "ลบส่วน", - "agentManager.section.defaultColor": "ค่าเริ่มต้น", "agentManager.section.defaultName": "ส่วนใหม่", "agentManager.section.moveUp": "เลื่อนขึ้น", "agentManager.section.moveDown": "เลื่อนลง", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "BRANCH", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "ฐาน", "agentManager.hoverCard.sessions": "เซสชัน", "agentManager.hoverCard.files": "ไฟล์", @@ -66,6 +64,12 @@ export const dict = { "agentManager.terminal.new": "แท็บเทอร์มินัลใหม่", "agentManager.terminal.ended": "เทอร์มินัลสิ้นสุด — ปิดแท็บเพื่อยกเลิก", "agentManager.terminal.connectionError": "ข้อผิดพลาดการเชื่อมต่อเทอร์มินัล", + "agentManager.terminal.add": "เทอร์มินัลใหม่", + "agentManager.terminal.empty": "ยังไม่มีเทอร์มินัลที่นี่", + "agentManager.terminal.start": "เริ่มเทอร์มินัล", + "agentManager.terminal.destination": "เลือกว่าปุ่มเทอร์มินัลจะเปิดอะไร", + "agentManager.terminal.openInVscode": "เทอร์มินัล VS Code", + "agentManager.terminal.openInPanel": "แผง Agent Manager", "agentManager.terminal.errorTitle": "ข้อผิดพลาดเทอร์มินัล", "agentManager.setup.failed": "ตั้งค่า Worktree ล้มเหลว", "agentManager.setup.settingUp": "กำลังตั้งค่า Worktree", @@ -129,7 +133,6 @@ export const dict = { "agentManager.dialog.creating": "กำลังสร้าง...", "agentManager.dialog.createWorktree": "สร้าง Worktree", "agentManager.dialog.removeImage": "ลบรูปภาพ", - "agentManager.dialog.advanced": "ขั้นสูง...", "agentManager.diff.toggle": "สลับ diff", "agentManager.diff.openFile": "เปิดไฟล์", @@ -138,19 +141,13 @@ export const dict = { "agentManager.diff.revertError": "ย้อนกลับล้มเหลว", "agentManager.open.button": "เปิด", "agentManager.open.tooltip": "เปิด Worktree นี้ใน VS Code", - "agentManager.apply.button": "นำไปใช้ในเครื่อง", "agentManager.apply.globalButton": "นำไปใช้", "agentManager.apply.tooltip": "นำการเปลี่ยนแปลงของ worktree ที่เลือกไปใช้กับสาขาในเครื่อง", "agentManager.apply.dialogTitle": "นำการเปลี่ยนแปลงไปใช้กับสาขาในเครื่อง", "agentManager.apply.confirm": "นำไปใช้ตอนนี้", - "agentManager.apply.retry": "ลองนำไปใช้อีกครั้ง", - "agentManager.apply.previewTitle": "นำการเปลี่ยนแปลงเหล่านี้ไปใช้กับสาขาในเครื่อง?", - "agentManager.apply.previewSummary": "{{files}} ไฟล์, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "เลือก {{selected}} จาก {{total}} ไฟล์", "agentManager.apply.selectAll": "เลือกทั้งหมด", "agentManager.apply.selectNone": "ยกเลิกการเลือกทั้งหมด", - "agentManager.apply.moreFiles": "+{{count}} เพิ่มเติม", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} เพิ่มเติม)", "agentManager.apply.checking": "กำลังตรวจสอบ...", "agentManager.apply.applying": "กำลังนำไปใช้...", "agentManager.apply.success": "นำการเปลี่ยนแปลงไปใช้แล้ว", @@ -163,9 +160,6 @@ export const dict = { "agentManager.apply.reason.patch": "ไม่สามารถนำ patch ไปใช้ได้อย่างสะอาด", "agentManager.apply.reason.contents": "ไม่สามารถอ่านเนื้อหาของไฟล์ในเครื่องปัจจุบัน", "agentManager.apply.reason.unknown": "ตรวจพบข้อขัดแย้ง", - "agentManager.apply.inline.success": "นำไปใช้แล้ว", - "agentManager.apply.inline.conflict": "ข้อขัดแย้ง", - "agentManager.apply.inline.error": "ล้มเหลว", "agentManager.shortcuts.toggleDiff": "สลับแผง diff", "agentManager.shortcuts.category.quickSwitch": "สลับด่วน", "agentManager.shortcuts.jumpToItem": "ข้ามไปยังรายการ 1\u20139", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts index f0822bea1b..911f4ba40a 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/tr.ts @@ -27,19 +27,17 @@ export const dict = { "agentManager.worktree.copyPath": "Yolu Kopyala", "agentManager.worktree.openInVscode": "VS Code'da Aç", "agentManager.worktree.rename": "Yeniden Adlandır", - "agentManager.worktree.moveToSection": "Bölüme Taşı", "agentManager.worktree.newSection": "Yeni Bölüm", "agentManager.worktree.ungrouped": "Gruplandırılmamış", "agentManager.section.rename": "Bölümü Yeniden Adlandır", "agentManager.section.setColor": "Renk Ayarla", "agentManager.section.delete": "Bölümü Sil", - "agentManager.section.defaultColor": "Varsayılan", "agentManager.section.defaultName": "Yeni Bölüm", "agentManager.section.moveUp": "Yukarı Taşı", "agentManager.section.moveDown": "Aşağı Taşı", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "DAL", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "Temel", "agentManager.hoverCard.sessions": "Oturumlar", "agentManager.hoverCard.files": "Dosyalar", @@ -71,6 +69,12 @@ export const dict = { "agentManager.terminal.new": "Yeni Terminal Sekmesi", "agentManager.terminal.ended": "terminal sona erdi — kapatmak için sekmeyi kapatın", "agentManager.terminal.connectionError": "terminal bağlantı hatası", + "agentManager.terminal.add": "Yeni terminal", + "agentManager.terminal.empty": "Burada henüz terminal yok", + "agentManager.terminal.start": "Terminali başlat", + "agentManager.terminal.destination": "Terminal düğmesinin ne açacağını seçin", + "agentManager.terminal.openInVscode": "VS Code terminali", + "agentManager.terminal.openInPanel": "Agent Manager paneli", "agentManager.terminal.errorTitle": "Terminal hatası", "agentManager.setup.failed": "Worktree kurulumu başarısız", @@ -140,7 +144,6 @@ export const dict = { "agentManager.dialog.creating": "Oluşturuluyor...", "agentManager.dialog.createWorktree": "Worktree Oluştur", "agentManager.dialog.removeImage": "Görüntüyü kaldır", - "agentManager.dialog.advanced": "Gelişmiş...", "agentManager.diff.toggle": "diff'i aç/kapat", "agentManager.diff.openFile": "Dosyayı aç", @@ -149,19 +152,13 @@ export const dict = { "agentManager.diff.revertError": "Geri alma başarısız", "agentManager.open.button": "Aç", "agentManager.open.tooltip": "Bu worktree'yi VS Code'da aç", - "agentManager.apply.button": "Yerele uygula", "agentManager.apply.globalButton": "Uygula", "agentManager.apply.tooltip": "Seçilen worktree değişikliklerini yerel dala uygula", "agentManager.apply.dialogTitle": "Değişiklikleri yerel dala uygula", "agentManager.apply.confirm": "Şimdi uygula", - "agentManager.apply.retry": "Yeniden uygula", - "agentManager.apply.previewTitle": "Bu değişiklikler yerel dala uygulansın mı?", - "agentManager.apply.previewSummary": "{{files}} dosya, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{total}} dosyadan {{selected}} tanesi seçildi", "agentManager.apply.selectAll": "Tümünü seç", "agentManager.apply.selectNone": "Hiçbirini seçme", - "agentManager.apply.moreFiles": "+{{count}} tane daha", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} tane daha)", "agentManager.apply.checking": "Kontrol ediliyor...", "agentManager.apply.applying": "Uygulanıyor...", "agentManager.apply.success": "Değişiklikler uygulandı", @@ -175,9 +172,6 @@ export const dict = { "agentManager.apply.reason.patch": "Yama (patch) sorunsuz uygulanamıyor", "agentManager.apply.reason.contents": "Geçerli yerel dosya içerikleri okunamıyor", "agentManager.apply.reason.unknown": "Çakışma algılandı", - "agentManager.apply.inline.success": "Uygulandı", - "agentManager.apply.inline.conflict": "Çakışmalar", - "agentManager.apply.inline.error": "Başarısız oldu", "agentManager.shortcuts.toggleDiff": "diff panelini aç/kapat", "agentManager.shortcuts.category.quickSwitch": "Hızlı Geçiş", "agentManager.shortcuts.jumpToItem": "1\u20139 öğesine atla", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts b/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts index 202e090213..1d75387770 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/uk.ts @@ -27,19 +27,17 @@ export const dict = { "agentManager.worktree.copyPath": "Копіювати шлях", "agentManager.worktree.openInVscode": "Відкрити у VS Code", "agentManager.worktree.rename": "Перейменувати", - "agentManager.worktree.moveToSection": "Перемістити до розділу", "agentManager.worktree.newSection": "Новий розділь", "agentManager.worktree.ungrouped": "Без групи", "agentManager.section.rename": "Перейменувати розділ", "agentManager.section.setColor": "Встановити колір", "agentManager.section.delete": "Видалити розділ", - "agentManager.section.defaultColor": "За замовчуванням", "agentManager.section.defaultName": "Новий розділ", "agentManager.section.moveUp": "Перемістити вгору", "agentManager.section.moveDown": "Перемістити вниз", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "ГІЛКА", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "База", "agentManager.hoverCard.sessions": "Сесії", "agentManager.hoverCard.files": "Файли", @@ -71,6 +69,12 @@ export const dict = { "agentManager.terminal.new": "Нова вкладка термінала", "agentManager.terminal.ended": "термінал завершено — закрийте вкладку, щоб відхилити", "agentManager.terminal.connectionError": "помилка з'єднання термінала", + "agentManager.terminal.add": "Новий термінал", + "agentManager.terminal.empty": "Тут ще немає термінала", + "agentManager.terminal.start": "Запустити термінал", + "agentManager.terminal.destination": "Виберіть, що відкриватиме кнопка термінала", + "agentManager.terminal.openInVscode": "Термінал VS Code", + "agentManager.terminal.openInPanel": "Панель Agent Manager", "agentManager.terminal.errorTitle": "Помилка термінала", "agentManager.setup.failed": "Налаштування робочого дерева не вдалося", @@ -141,7 +145,6 @@ export const dict = { "agentManager.dialog.creating": "Створення...", "agentManager.dialog.createWorktree": "Створити робоче дерево", "agentManager.dialog.removeImage": "Видалити зображення", - "agentManager.dialog.advanced": "Розширено...", "agentManager.diff.toggle": "Показати або приховати diff", "agentManager.diff.openFile": "Відкрити файл", @@ -150,19 +153,13 @@ export const dict = { "agentManager.diff.revertError": "Не вдалося відновити", "agentManager.open.button": "Відкрити", "agentManager.open.tooltip": "Відкрити це робоче дерево у VS Code", - "agentManager.apply.button": "Застосувати локально", "agentManager.apply.globalButton": "Застосувати", "agentManager.apply.tooltip": "Застосувати вибрані зміни робочого дерева до локальної гілки", "agentManager.apply.dialogTitle": "Застосувати зміни до локальної гілки", "agentManager.apply.confirm": "Застосувати зараз", - "agentManager.apply.retry": "Повторити застосування", - "agentManager.apply.previewTitle": "Застосувати ці зміни до локальної гілки?", - "agentManager.apply.previewSummary": "{{files}} файлів, +{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "{{selected}} з {{total}} файлів вибрано", "agentManager.apply.selectAll": "Вибрати все", "agentManager.apply.selectNone": "Зняти вибір", - "agentManager.apply.moreFiles": "+{{count}} ще", - "agentManager.apply.moreReasons": "{{reason}} (+{{count}} ще)", "agentManager.apply.checking": "Перевірка...", "agentManager.apply.applying": "Застосування...", "agentManager.apply.success": "Зміни застосовано", @@ -176,9 +173,6 @@ export const dict = { "agentManager.apply.reason.patch": "Патч неможливо застосувати без конфліктів", "agentManager.apply.reason.contents": "Неможливо прочитати вміст поточного локального файлу", "agentManager.apply.reason.unknown": "Виявлено конфлікт", - "agentManager.apply.inline.success": "Застосовано", - "agentManager.apply.inline.conflict": "Конфлікти", - "agentManager.apply.inline.error": "Не вдалося", "agentManager.shortcuts.toggleDiff": "Показати або приховати панель diff", "agentManager.shortcuts.category.quickSwitch": "Швидке перемикання", "agentManager.shortcuts.jumpToItem": "Перейти до елемента 1\u20139", 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 b1bb9f31ea..80d0d31c22 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zh.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "复制路径", "agentManager.worktree.openInVscode": "在 VS Code 中打开", "agentManager.worktree.rename": "重命名", - "agentManager.worktree.moveToSection": "移动到分区", "agentManager.worktree.newSection": "新分区", "agentManager.worktree.ungrouped": "未分组", "agentManager.section.rename": "重命名分区", "agentManager.section.setColor": "设置颜色", "agentManager.section.delete": "删除分区", - "agentManager.section.defaultColor": "默认", "agentManager.section.defaultName": "新分区", "agentManager.section.moveUp": "上移", "agentManager.section.moveDown": "下移", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "分支", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "基础", "agentManager.hoverCard.sessions": "会话", "agentManager.hoverCard.files": "文件", @@ -66,6 +64,12 @@ export const dict = { "agentManager.terminal.new": "新建终端标签页", "agentManager.terminal.ended": "终端已结束 — 关闭标签页以消除", "agentManager.terminal.connectionError": "终端连接错误", + "agentManager.terminal.add": "新建终端", + "agentManager.terminal.empty": "此处尚无终端", + "agentManager.terminal.start": "启动终端", + "agentManager.terminal.destination": "选择终端按钮的打开目标", + "agentManager.terminal.openInVscode": "VS Code 终端", + "agentManager.terminal.openInPanel": "Agent Manager 面板", "agentManager.terminal.errorTitle": "终端错误", "agentManager.setup.failed": "工作区设置失败", "agentManager.setup.settingUp": "正在设置工作区", @@ -128,7 +132,6 @@ export const dict = { "agentManager.dialog.creating": "创建中...", "agentManager.dialog.createWorktree": "创建工作树", "agentManager.dialog.removeImage": "移除图片", - "agentManager.dialog.advanced": "高级...", "agentManager.diff.toggle": "切换差异", "agentManager.diff.openFile": "打开文件", @@ -137,19 +140,13 @@ export const dict = { "agentManager.diff.revertError": "还原失败", "agentManager.open.button": "打开", "agentManager.open.tooltip": "在 VS Code 中打开此 Worktree", - "agentManager.apply.button": "应用到本地", "agentManager.apply.globalButton": "应用", "agentManager.apply.tooltip": "将所选工作树的更改应用到本地分支", "agentManager.apply.dialogTitle": "将更改应用到本地分支", "agentManager.apply.confirm": "立即应用", - "agentManager.apply.retry": "重试应用", - "agentManager.apply.previewTitle": "将这些更改应用到本地分支?", - "agentManager.apply.previewSummary": "{{files}} 个文件,+{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "已选择 {{selected}}/{{total}} 个文件", "agentManager.apply.selectAll": "全选", "agentManager.apply.selectNone": "取消全选", - "agentManager.apply.moreFiles": "+{{count}} 更多", - "agentManager.apply.moreReasons": "{{reason}}(+{{count}} 更多)", "agentManager.apply.checking": "检查中...", "agentManager.apply.applying": "应用中...", "agentManager.apply.success": "更改已应用", @@ -162,9 +159,6 @@ export const dict = { "agentManager.apply.reason.patch": "补丁无法干净地应用", "agentManager.apply.reason.contents": "无法读取当前本地文件内容", "agentManager.apply.reason.unknown": "检测到冲突", - "agentManager.apply.inline.success": "已应用", - "agentManager.apply.inline.conflict": "冲突", - "agentManager.apply.inline.error": "失败", "agentManager.shortcuts.toggleDiff": "切换差异面板", "agentManager.shortcuts.category.quickSwitch": "快速切换", "agentManager.shortcuts.jumpToItem": "跳转到项目 1\u20139", 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 16196e6348..5371586ce6 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/i18n/zht.ts @@ -26,18 +26,16 @@ export const dict = { "agentManager.worktree.copyPath": "複製路徑", "agentManager.worktree.openInVscode": "在 VS Code 中打開", "agentManager.worktree.rename": "重新命名", - "agentManager.worktree.moveToSection": "移動到分區", "agentManager.worktree.newSection": "新分區", "agentManager.worktree.ungrouped": "未分組", "agentManager.section.rename": "重新命名分區", "agentManager.section.setColor": "設定顏色", "agentManager.section.delete": "刪除分區", - "agentManager.section.defaultColor": "預設", "agentManager.section.defaultName": "新分區", "agentManager.section.moveUp": "上移", "agentManager.section.moveDown": "下移", - "agentManager.section.count": "{{count}}", "agentManager.hoverCard.branch": "分支", + "agentManager.hoverCard.worktree": "Worktree", "agentManager.hoverCard.base": "基底", "agentManager.hoverCard.sessions": "工作階段", "agentManager.hoverCard.files": "檔案", @@ -66,6 +64,12 @@ export const dict = { "agentManager.terminal.new": "新增終端分頁", "agentManager.terminal.ended": "終端已結束 — 關閉分頁以消除", "agentManager.terminal.connectionError": "終端連線錯誤", + "agentManager.terminal.add": "新增終端機", + "agentManager.terminal.empty": "此處尚無終端機", + "agentManager.terminal.start": "啟動終端機", + "agentManager.terminal.destination": "選擇終端機按鈕的開啟目標", + "agentManager.terminal.openInVscode": "VS Code 終端機", + "agentManager.terminal.openInPanel": "Agent Manager 面板", "agentManager.terminal.errorTitle": "終端錯誤", "agentManager.setup.failed": "工作區設定失敗", "agentManager.setup.settingUp": "正在設定工作區", @@ -128,7 +132,6 @@ export const dict = { "agentManager.dialog.creating": "建立中...", "agentManager.dialog.createWorktree": "建立工作樹", "agentManager.dialog.removeImage": "移除圖片", - "agentManager.dialog.advanced": "進階...", "agentManager.diff.toggle": "切換差異", "agentManager.diff.openFile": "開啟檔案", @@ -137,19 +140,13 @@ export const dict = { "agentManager.diff.revertError": "還原失敗", "agentManager.open.button": "開啟", "agentManager.open.tooltip": "在 VS Code 中開啟此 Worktree", - "agentManager.apply.button": "套用到本地", "agentManager.apply.globalButton": "套用", "agentManager.apply.tooltip": "將所選工作樹的變更套用到本地分支", "agentManager.apply.dialogTitle": "將變更套用到本地分支", "agentManager.apply.confirm": "立即套用", - "agentManager.apply.retry": "重試套用", - "agentManager.apply.previewTitle": "將這些變更套用到本地分支?", - "agentManager.apply.previewSummary": "{{files}} 個檔案,+{{additions}} -{{deletions}}", "agentManager.apply.selectionSummary": "已選取 {{selected}}/{{total}} 個檔案", "agentManager.apply.selectAll": "全選", "agentManager.apply.selectNone": "取消全選", - "agentManager.apply.moreFiles": "+{{count}} 更多", - "agentManager.apply.moreReasons": "{{reason}}(+{{count}} 更多)", "agentManager.apply.checking": "檢查中...", "agentManager.apply.applying": "套用中...", "agentManager.apply.success": "變更已套用", @@ -162,9 +159,6 @@ export const dict = { "agentManager.apply.reason.patch": "修補程式無法乾淨地套用", "agentManager.apply.reason.contents": "無法讀取目前本地檔案內容", "agentManager.apply.reason.unknown": "偵測到衝突", - "agentManager.apply.inline.success": "已套用", - "agentManager.apply.inline.conflict": "衝突", - "agentManager.apply.inline.error": "失敗", "agentManager.shortcuts.toggleDiff": "切換差異面板", "agentManager.shortcuts.category.quickSwitch": "快速切換", "agentManager.shortcuts.jumpToItem": "跳至項目 1\u20139", diff --git a/packages/kilo-vscode/webview-ui/agent-manager/project/store.ts b/packages/kilo-vscode/webview-ui/agent-manager/project/store.ts index 8559b26865..1cc1ca3e80 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/project/store.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/project/store.ts @@ -1,7 +1,5 @@ import { createSignal, type Accessor, type Setter } from "solid-js" import type { - AgentManagerApplyWorktreeDiffConflict, - AgentManagerApplyWorktreeDiffStatus, AgentManagerStateMessage, LocalGitStats, ManagedSessionState, @@ -18,12 +16,6 @@ export interface WorktreeBusyState { branch?: string } -export interface ApplyState { - status: AgentManagerApplyWorktreeDiffStatus - message: string - conflicts: AgentManagerApplyWorktreeDiffConflict[] -} - /** Local session tab ids owned by one project. */ export function createStoreTabs(initial: string[] = []) { const [ids, setIds] = createSignal(initial) @@ -73,7 +65,6 @@ export function createProjectStore(id: string, opts: { tabs?: string[] } = {}) { const [runStatuses, setRunStatuses] = field>({}) const [worktreeStats, setWorktreeStats] = field>({}) const [localStats, setLocalStats] = field(undefined) - const [applyStates, setApplyStates] = field>({}) const [busy, setBusy] = field>(new Map()) /** Write a project state payload into this store (data fields only). */ @@ -129,8 +120,6 @@ export function createProjectStore(id: string, opts: { tabs?: string[] } = {}) { setWorktreeStats, localStats, setLocalStats, - applyStates, - setApplyStates, busy, setBusy, } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/SideTerminalPanel.tsx b/packages/kilo-vscode/webview-ui/agent-manager/terminal/SideTerminalPanel.tsx new file mode 100644 index 0000000000..517965d88d --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/SideTerminalPanel.tsx @@ -0,0 +1,171 @@ +/** + * Right-side terminal panel for the Agent Manager inspector. + * + * Lives inside the shared `.am-diff-panel-wrapper` host next to the diff + * and PR panels, so all three inspector modes share one resize handle + * and one width. + * + * A context can own several side terminals. The header is a tab strip + * that reuses the top tab bar's `TerminalTabChrome` (same `am-tab*` + * structure, same X close button) plus a `+` action to add terminals. + * Tabs are drag-sortable via the same `@thisbeyond/solid-dnd` stack as + * the top tab bar; the order lives in the terminal state, so it is + * preserved across sidebar context switches for the webview's lifetime. + * The strip stays visible even when empty so the `+` action is always + * reachable. + * + * Visibility is opacity-based, never unmount: the xterm render loop + * dies when its subtree leaves the paint tree (see `render.tsx`). + */ + +import type { Accessor, Component } from "solid-js" +import { For, Show, createEffect, createSignal } from "solid-js" +import { DragDropProvider, DragDropSensors, DragOverlay, SortableProvider, closestCenter } from "@thisbeyond/solid-dnd" +import type { DragEvent } from "@thisbeyond/solid-dnd" +import { IconButton } from "@kilocode/kilo-ui/icon-button" +import { Button } from "@kilocode/kilo-ui/button" +import { Spinner } from "@kilocode/kilo-ui/spinner" +import { Tooltip } from "@kilocode/kilo-ui/tooltip" +import { useLanguage } from "../../src/context/language" +import { ConstrainDragYAxis, SortableTabContainer } from "../../src/components/chat/TabDnd" +import { renderSideTerminalLayer } from "./render" +import { TerminalTabChrome } from "./SortableTerminalTab" +import type { TerminalStateControls } from "./state" + +interface Props { + state: TerminalStateControls + /** Context the panel currently shows (`state.sideKey`). */ + contextKey: Accessor + /** True while the inspector is in terminal mode. */ + visible: Accessor + /** Make a terminal the visible one in the strip. */ + onSelect: (terminalId: string) => void + /** Kill one terminal. */ + onClose: (terminalId: string) => void + /** Create a new side terminal for this context. */ + onStart: () => void +} + +export const SideTerminalPanel: Component = (props) => { + const { t } = useLanguage() + let panel!: HTMLElement + createEffect(() => { + panel.inert = !props.visible() + }) + const [dragging, setDragging] = createSignal<{ id: string; width: number } | undefined>() + const sides = () => props.state.sidesForContext(props.contextKey()) + const ids = () => sides().map((term) => term.id) + const pending = () => props.state.pendingSide(props.contextKey()) + const onDragStart = (event: DragEvent) => { + const id = event.draggable?.id + if (typeof id !== "string") return + // Pin the overlay to the tab's width: the overlay container uses + // min-width, so a long OSC title would otherwise overflow it and + // shift the visual center off the cursor (the "drag offset" bug). + const width = event.draggable?.layout.width ?? event.draggable?.node.getBoundingClientRect().width + setDragging({ id, width }) + } + const onDragEnd = () => setDragging(undefined) + const onDragOver = (event: DragEvent) => { + const from = event.draggable?.id + const to = event.droppable?.id + if (typeof from !== "string" || typeof to !== "string") return + props.state.reorderSideDrag(props.contextKey(), from, to) + } + return ( +
+
+ + + + {/* Scrollable tab list — mirrors the top bar's .am-tab-list split + so the "+" action never scrolls away. role="tablist" only + when tabs exist: axe aria-required-children rejects an empty + tablist (and non-tab children like the add button). */} +
0 ? "tablist" : undefined} + aria-label={sides().length > 0 ? t("agentManager.tab.terminal") : undefined} + > + + + {(term) => ( + + props.onSelect(term.id)} + onMiddleClick={(e: MouseEvent) => { + if (e.button !== 1) return + e.preventDefault() + e.stopPropagation() + props.onClose(term.id) + }} + onClose={(e: MouseEvent) => { + e.stopPropagation() + props.onClose(term.id) + }} + /> + + )} + + +
+ {/* Cursor-following clone of the dragged tab (same pattern as + the top tab bar). The overlay is what makes the in-list + original use solid-dnd's slot-compensated transform, so the + dragged tab tracks the cursor without a jump/offset. The + original stays dimmed in its slot via .am-tab-dragging. */} + + + {(tab) => ( +
+ {props.state.title(tab().id) ?? t("agentManager.tab.terminal")} +
+ )} +
+
+
+
+ + + +
+
+ {renderSideTerminalLayer({ state: props.state, contextKey: props.contextKey, visible: props.visible })} + +
+ + {t("common.loading")} +
+
+ +
+ {t("agentManager.terminal.empty")} + +
+
+
+ ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/SortableTerminalTab.tsx b/packages/kilo-vscode/webview-ui/agent-manager/terminal/SortableTerminalTab.tsx index 9211b7850a..f4ce0d3f23 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/terminal/SortableTerminalTab.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/SortableTerminalTab.tsx @@ -1,9 +1,12 @@ /** - * Draggable tab chrome for an xterm terminal tab. + * Tab chrome for xterm terminals. * - * Shares the same hover/tooltip/close/right-click mechanism as the - * session and review tab variants so users get consistent navigation - * hints and context actions regardless of tab kind. + * `TerminalTabChrome` is the shared visual tab: console icon, title, + * tooltip/keybinding hints, and the X close button — the same + * `am-tab*` structure the session tabs use. `SortableTerminalTab` + * wraps it with drag-and-drop and a right-click context menu for the + * top tab bar; the side terminal panel uses the chrome directly so + * both surfaces render identical terminal tabs. */ import { Component, Show, type JSX } from "solid-js" @@ -15,6 +18,70 @@ import { useLanguage } from "../../src/context/language" import { SortableTabContainer } from "../../src/components/chat/TabDnd" import { parseBindingTokens } from "../keybind-tokens" +export const TerminalTabChrome: Component<{ + label: string + tooltip: string + keybind?: string + closeKeybind?: string + active: boolean + role?: "tab" + selected?: boolean + tabIndex?: number + onKeyDown?: JSX.EventHandlerUnion + onSelect: () => void + onMiddleClick?: (e: MouseEvent) => void + onClose: (e: MouseEvent) => void +}> = (props) => { + const { t } = useLanguage() + return ( +
+
+ + + + + + {props.label} + + +
+ + + +
+ ) +} + export const SortableTerminalTab: Component<{ id: string label: string @@ -36,51 +103,20 @@ export const SortableTerminalTab: Component<{ -
-
- - - - - - {props.label} - - -
- - - -
+
diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalDestinationButton.tsx b/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalDestinationButton.tsx new file mode 100644 index 0000000000..a0ae02bee2 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalDestinationButton.tsx @@ -0,0 +1,65 @@ +/** + * Terminal destination split button for the Agent Manager tab toolbar. + * + * The primary action opens whatever the user picked (VS Code integrated + * terminal or the embedded side panel); the dropdown switches between + * them. Markup mirrors the `+` new-tab split button in + * `tab-rendering.tsx` so both share the same split-button styling. + */ + +import type { Accessor, Component } from "solid-js" +import { Show } from "solid-js" +import { DropdownMenu } from "@kilocode/kilo-ui/dropdown-menu" +import { Icon } from "@kilocode/kilo-ui/icon" +import { IconButton } from "@kilocode/kilo-ui/icon-button" +import { TooltipKeybind } from "@kilocode/kilo-ui/tooltip" +import { useLanguage } from "../../src/context/language" +import type { TerminalDestination } from "../../src/types/messages/agent-manager" + +interface Props { + destination: Accessor + /** True while the embedded terminal panel is showing. */ + active: Accessor + keybind: Accessor + onOpen: () => void + onChoose: (destination: TerminalDestination) => void +} + +export const TerminalDestinationButton: Component = (props) => { + const { t } = useLanguage() + const item = (destination: TerminalDestination, label: string) => ( + props.onChoose(destination)}> + + + + + + {label} + + ) + return ( +
+ + + + + + + + + + {item("vscode", t("agentManager.terminal.openInVscode"))} + {item("agentManager", t("agentManager.terminal.openInPanel"))} + + + +
+ ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx b/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx index 8266925535..c4aaa5af11 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/TerminalTab.tsx @@ -38,6 +38,19 @@ interface Props { * an xterm re-paint when the slot transitions back to visible after * sitting behind an occluding layer. */ active: boolean + /** Serial of the latest explicit focus request for this terminal + * (`state.focusRequest()`), consumed so re-requesting focus on an + * already-visible terminal still re-focuses it. */ + focusSerial?: number + /** Reports DOM focus entering or leaving the xterm host. The state + * layer tracks this as `focusedId` so `Cmd+W` can target the + * terminal that actually has the cursor. */ + onFocusChange?: (focused: boolean) => void + /** Reports OSC window-title escape codes (`ESC ] 0/1/2 ; title BEL`) + * sent by the shell or running programs — fish sets it to the active + * command, oh-my-zsh to user@host:cwd, vim to the file name. The + * state layer mirrors it into the tab label. */ + onTitleChange?: (title: string) => void } /** How long the ResizeObserver waits after the last size change before @@ -188,6 +201,21 @@ export const TerminalTab: Component = (props) => { // ⌘T / ⌘W / ⌘⌥← etc. still work while the terminal is focused. term.attachCustomKeyEventHandler((event) => !isAgentManagerShortcut(event)) + // Track DOM focus so the state layer knows which terminal holds the + // cursor (drives Cmd+W targeting). focusout is ignored when focus + // moves within the same host (xterm shuffles inner nodes). + const onFocusIn = () => props.onFocusChange?.(true) + const onFocusOut = (event: FocusEvent) => { + if (event.relatedTarget instanceof Node && host.contains(event.relatedTarget)) return + props.onFocusChange?.(false) + } + host.addEventListener("focusin", onFocusIn) + host.addEventListener("focusout", onFocusOut) + + // OSC 0/1/2 window-title sequences → tab label. xterm parses and + // strips these itself, so this only fires for real title codes. + const disposeTitle = term.onTitleChange((title) => props.onTitleChange?.(title)) + const ws = new WebSocket(props.wsUrl) ws.binaryType = "arraybuffer" let closed = false @@ -260,7 +288,12 @@ export const TerminalTab: Component = (props) => { // — historically "press Enter to wake it up". Forcing a // `fit + refresh(0, rows-1)` once per activation reclaims the paint // priority; from then on the browser keeps the canvas live. + // + // Focus is opt-in per repaint (`shouldFocus`): repaints triggered by + // resizes or font changes must not yank the cursor out of the chat + // input, only explicit activation / focus requests may. let pendingFrame: number | null = null + let shouldFocus = false const isRenderable = () => { if (!host.isConnected) return false const rect = host.getBoundingClientRect() @@ -278,9 +311,11 @@ export const TerminalTab: Component = (props) => { log("repaint fit() threw", err) } term.refresh(0, Math.max(0, term.rows - 1)) - if (document.hasFocus()) term.focus() + if (shouldFocus && document.hasFocus()) term.focus() + shouldFocus = false } - const scheduleRepaint = () => { + const scheduleRepaint = (focus = false) => { + shouldFocus ||= focus if (pendingFrame !== null) return pendingFrame = requestAnimationFrame(runRepaint) } @@ -309,26 +344,37 @@ export const TerminalTab: Component = (props) => { scheduleRepaint() }) - let wasActive = props.active + // Activation and explicit focus requests focus the terminal; + // deactivation blurs it so keystrokes never land in a hidden xterm. + // `wasActive` starts false so a terminal mounted already-active (the + // create-and-activate flow) still gets its initial focus repaint. + let wasActive = false + let focusSerial = -1 createEffect(() => { const now = props.active - if (now && !wasActive) scheduleRepaint() + const serial = props.focusSerial ?? 0 + if (now && (!wasActive || serial !== focusSerial)) scheduleRepaint(true) + if (!now && wasActive) term.blur() wasActive = now + focusSerial = serial }) // Also recover when the user returns from an external window or the // OS-level window manager (alt-tab, browser → VS Code, etc.) — the // browser often suspends canvas paint while the window is in the // background, and the Solid `active` prop alone doesn't see that. - // Gated on `props.active` so inactive tabs don't do needless work. + // Gated on `props.active` so inactive tabs don't do needless work, + // and on the terminal already owning focus so returning to the + // window never steals the cursor back from the chat input. + const ownsFocus = () => host.contains(document.activeElement) const onVisibilityChange = () => { if (document.hidden) return - if (!props.active) return - scheduleRepaint() + if (!props.active || !ownsFocus()) return + scheduleRepaint(true) } const onWindowFocus = () => { - if (!props.active) return - scheduleRepaint() + if (!props.active || !ownsFocus()) return + scheduleRepaint(true) } document.addEventListener("visibilitychange", onVisibilityChange) window.addEventListener("focus", onWindowFocus) @@ -350,11 +396,14 @@ export const TerminalTab: Component = (props) => { if (pendingFrame !== null) cancelAnimationFrame(pendingFrame) document.removeEventListener("visibilitychange", onVisibilityChange) window.removeEventListener("focus", onWindowFocus) + host.removeEventListener("focusin", onFocusIn) + host.removeEventListener("focusout", onFocusOut) fontSub() themeObserver.disconnect() clearTimeout(resizeTimer) ro.disconnect() disposeData.dispose() + disposeTitle.dispose() try { ws.close() } catch (err) { diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/index.ts b/packages/kilo-vscode/webview-ui/agent-manager/terminal/index.ts index 22dd8038a1..2d76c0f1a5 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/terminal/index.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/index.ts @@ -20,6 +20,9 @@ export { createTerminalMessageHandler, } from "./state" export type { TerminalTabState, TerminalStateControls, TerminalHandlerDeps } from "./state" -export { renderTerminalTab, renderTerminalLayer } from "./render" +export { renderTerminalTab, renderTerminalLayer, renderSideTerminalLayer } from "./render" +export { SideTerminalPanel } from "./SideTerminalPanel" +export { TerminalDestinationButton } from "./TerminalDestinationButton" +export { createSideTerminal, readSavedDestination, resolveVscodeTerminalRequest } from "./side" export { TerminalTab } from "./TerminalTab" export { SortableTerminalTab } from "./SortableTerminalTab" diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/render.tsx b/packages/kilo-vscode/webview-ui/agent-manager/terminal/render.tsx index ad6fced8aa..28aaddbb17 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/terminal/render.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/render.tsx @@ -7,11 +7,18 @@ */ import { For, Show } from "solid-js" -import type { JSX } from "solid-js" +import type { Accessor, JSX } from "solid-js" import { SortableTerminalTab } from "./SortableTerminalTab" import { TerminalTab } from "./TerminalTab" import type { TerminalStateControls } from "./state" +/** Serial of the latest focus request addressed to `id`, or 0. Read + * inside JSX so the effect re-runs when a request lands. */ +function focusSerial(state: TerminalStateControls, id: string): number { + const request = state.focusRequest() + return request?.id === id ? request.serial : 0 +} + export interface TerminalTabRenderDeps { id: string terms: TerminalStateControls @@ -34,11 +41,13 @@ export function renderTerminalTab(deps: TerminalTabRenderDeps): JSX.Element { const term = deps.terms.lookup().get(deps.id) if (!term) return null const isActive = () => deps.terms.activeId() === deps.id + // Label and tooltip read through `terms.title` so OSC title changes + // (shell/program escape codes) rename the tab live. return ( { const visible = () => slotVisible(term.id, term.contextKey) return ( -
- +
+ props.state.setFocusedId(focused ? term.id : undefined)} + onTitleChange={(title) => props.state.setTitle(term.id, title)} + />
) }} @@ -104,3 +121,45 @@ export function renderTerminalLayer(props: { state: TerminalStateControls }): JS ) } + +/** + * Render the side-panel terminal layer inside the right-hand inspector. + * + * Same paint-tree invariant as `renderTerminalLayer`: every side + * terminal stays mounted, visibility is toggled via `opacity` / + * `pointer-events` / `inert` only. The layer is scoped to + * `contextKey` — side terminals from other contexts stay composed in + * the background and never refit — and within a context only the + * active strip tab's terminal is shown. + */ +export function renderSideTerminalLayer(props: { + state: TerminalStateControls + contextKey: Accessor + visible: Accessor +}): JSX.Element { + return ( +
+ + {(term) => { + const active = () => + props.visible() && + term.contextKey === props.contextKey() && + props.state.sideActiveFor(term.contextKey) === term.id + return ( +
+ props.state.setFocusedId(focused ? term.id : undefined)} + onTitleChange={(title) => props.state.setTitle(term.id, title)} + /> +
+ ) + }} +
+
+ ) +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/side.ts b/packages/kilo-vscode/webview-ui/agent-manager/terminal/side.ts new file mode 100644 index 0000000000..d0420b9916 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/side.ts @@ -0,0 +1,158 @@ +/** + * Right-side terminal wiring for the Agent Manager webview. + * + * Extracted from AgentManagerApp.tsx to keep that file under the + * `max-lines` lint cap. Owns the destination preference plus the toggle + * semantics of the toolbar button / `Cmd/Ctrl+/` shortcut, so the + * embedded terminal behaves like the diff panel: press once to reveal, + * press again to hide. Hiding never kills the terminal — only the + * explicit close action (or `Cmd+W` while it holds focus) does. + * + * ## Destination state ownership + * + * The VS Code setting is application-scoped, so one value is shared by + * every window and echoed back via `terminal.destinationChanged` + * whenever ANY window rewrites it. Two windows can therefore fight: + * picking "VS Code terminal" in worktree window B would silently flip + * the routing of the panel in window A. To keep each panel consistent, + * an explicit dropdown pick is stored per panel (webview state) and + * wins over remote echoes; the setting only drives panels that never + * picked a destination themselves (it stays the default for new ones). + */ + +import { createSignal } from "solid-js" +import type { Accessor } from "solid-js" +import type { TerminalDestination } from "../../src/types/messages/agent-manager" +import { LOCAL } from "../navigate" + +/** Read the panel-local destination choice from raw webview state. */ +export function readSavedDestination(state: Record | undefined): TerminalDestination | undefined { + const value = state?.terminalDestination + return value === "agentManager" || value === "vscode" ? value : undefined +} + +export type VscodeTerminalRequest = + | { type: "agentManager.showTerminal"; sessionId: string } + | { type: "agentManager.showWorktreeTerminal"; worktreeId: string } + | { type: "agentManager.showLocalTerminal" } + +/** + * Pick the message the terminal button / Focus Terminal shortcut sends + * when the destination is the VS Code integrated terminal. The fallback + * chain exists so the shortcut never dead-ends: activating a terminal + * tab clears the current session, and a worktree may have no sessions + * at all. Extracted from AgentManagerApp.tsx (max-lines cap). + */ +export function resolveVscodeTerminalRequest( + selection: string | null, + currentSessionID: string | undefined, + sessionForWorktree: (worktreeId: string) => string | undefined, +): VscodeTerminalRequest { + const id = currentSessionID ?? (selection && selection !== LOCAL ? sessionForWorktree(selection) : undefined) + if (id) return { type: "agentManager.showTerminal", sessionId: id } + if (selection && selection !== LOCAL) return { type: "agentManager.showWorktreeTerminal", worktreeId: selection } + return { type: "agentManager.showLocalTerminal" } +} + +interface Handlers { + requestSide(): void + closeSide(terminalId: string): boolean +} + +export interface SideTerminalDeps { + handlers: Handlers + /** True while the right-side inspector shows the terminal. */ + visible: Accessor + /** Id of the side terminal holding DOM focus, if any. */ + focusedId: Accessor + /** Leave terminal mode; the terminal stays alive in the background. */ + hide: () => void + /** Move focus back to the chat composer. */ + refocus: () => void + postMessage: (msg: unknown) => void + track: (button: string, surface: string, properties: Record) => void + /** Open or focus the VS Code integrated terminal for the active context. */ + openVscode: () => void + /** Panel-local choice restored from webview state, if the user ever + * picked one in this panel. */ + saved: TerminalDestination | undefined + /** Persist the panel-local choice so it survives webview reloads. */ + save: (destination: TerminalDestination) => void +} + +export function createSideTerminal(deps: SideTerminalDeps) { + const [local, setLocal] = createSignal(deps.saved) + const [destination, setDestination] = createSignal(deps.saved ?? "vscode") + + /** + * Hiding while the terminal holds focus would strand the cursor on + * , so hand it to the chat composer — the common flow is + * type → Cmd+/ → run command → Cmd+/ → keep typing. When the user + * was anywhere else (chat, diff, another tab), focus stays put. + */ + const handoff = (wasFocused: boolean) => { + if (wasFocused) deps.refocus() + } + + const toggle = () => { + if (deps.visible()) { + const was = deps.focusedId() !== undefined + deps.hide() + handoff(was) + return + } + deps.handlers.requestSide() + } + + /** Kill the focused side terminal (Cmd/Ctrl+W). The panel stays open + * on the remaining terminals, or on the empty state when this was + * the last one. */ + const close = (): boolean => { + const id = deps.focusedId() + if (!id) return false + const done = deps.handlers.closeSide(id) + if (done) handoff(true) + return done + } + + /** Toolbar button and `Cmd/Ctrl+/`: follow the user's destination. */ + const openPreferred = (trigger: "keyboard_shortcut" | "tab_toolbar") => { + const target = destination() + deps.track("terminal", trigger, { destination: target }) + if (target === "agentManager") { + toggle() + return + } + deps.openVscode() + } + + /** + * Dropdown pick. The choice is panel-local and sticky: it is kept in + * webview state and beats later `terminal.destinationChanged` echoes + * caused by other windows rewriting the shared application-scoped + * setting. The setting is still written so it stays the default for + * panels that never picked a destination (and new panels). + * The key is relative to the `kilo-code.new` section, matching every + * other `updateSetting` sender. + */ + const choose = (target: TerminalDestination) => { + deps.track("terminal_destination", "tab_toolbar", { destination: target }) + setLocal(target) + setDestination(target) + deps.save(target) + deps.postMessage({ type: "updateSetting", key: "agentManager.terminalButtonDestination", value: target }) + } + + /** + * Apply a remote default (initial `agentManager.state` payload or a + * live `terminal.destinationChanged` echo). Ignored once the user + * picked a destination in this panel — their choice wins over every + * echo, including ones triggered by this panel's own `choose` write. + */ + const syncDefault = (target: TerminalDestination) => { + if (local()) return + setDestination(target) + } + + return { destination, syncDefault, toggle, close, openPreferred, choose } +} diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/state.ts b/packages/kilo-vscode/webview-ui/agent-manager/terminal/state.ts index f472623b28..4dff179435 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/terminal/state.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/state.ts @@ -5,13 +5,18 @@ * `max-lines` lint cap. Owns the per-context terminal list, the * `activeTerminalId` focus signal, and a small set of imperative * helpers the main component composes with its existing tab logic. + * + * Main terminal tabs and right-side terminals share the same PTY + * transport, but their UI state is intentionally separate: tab + * activation replaces the chat, while a side terminal lives in the + * right-hand inspector and keeps the current session visible. */ -import { createMemo, createSignal } from "solid-js" +import { createSignal } from "solid-js" import type { Accessor } from "solid-js" import { LOCAL } from "../navigate" import type { ExtensionMessage } from "../../src/types/messages/extension-messages" -import type { TerminalFont } from "../../src/types/messages/agent-manager" +import type { TerminalDestination, TerminalFont, TerminalPlacement } from "../../src/types/messages/agent-manager" export type { TerminalFont } @@ -26,6 +31,7 @@ export interface TerminalTabState { title: string wsUrl: string font: TerminalFont + placement: TerminalPlacement } /** Terminal row enriched with the sidebar context it belongs to. Used by @@ -35,28 +41,70 @@ export interface TerminalTabStateWithContext extends TerminalTabState { contextKey: string } +/** Explicit focus demand, consumed by `TerminalTab` via the render layer. + * The serial lets repeated requests for the same terminal retrigger the + * focus effect. */ +export interface TerminalFocusRequest { + id: string + serial: number +} + +/** A create request for a side terminal that has not been answered yet. + * Multiple creates can be in flight for the same context at once. */ +interface SideRequest { + contextKey: string +} + export interface TerminalStateControls { /** Record received from `terminal.created`. */ add(worktreeId: string | null, term: TerminalTabState): void - /** Drop a terminal from its context (location resolved automatically). */ - remove(terminalId: string): string | undefined + /** Drop a terminal from its context (location resolved automatically). + * Returns the removed record so callers can react to placement. */ + remove(terminalId: string): TerminalTabStateWithContext | undefined /** Resolve the context key a terminal lives in, if any. */ contextFor(terminalId: string): string | undefined - /** All terminals for the given sidebar selection. */ + /** All tab terminals for the given sidebar selection. */ forSelection(selection: string | null): TerminalTabStateWithContext[] /** Map of { id -> tab state } for O(1) lookup. */ lookup: Accessor> - /** All terminals for the currently selected context. */ + /** All tab terminals for the currently selected context. */ current: Accessor - /** Every terminal across every context (for the persistent render layer). */ + /** Every tab terminal across every context (for the persistent render layer). */ all: Accessor + /** Every side terminal across every context (for the side-panel layer). */ + sides: Accessor + /** Every side terminal of the given context, in creation order. */ + sidesForContext(contextKey: string): TerminalTabStateWithContext[] + /** Id of the active side terminal for a context. */ + sideActiveFor(contextKey: string): string | undefined + /** Mark a side terminal as the visible one for its context. */ + setSideActive(contextKey: string, terminalId: string): void + /** Id of the side terminal holding DOM focus in the current context, if any. */ + sideFocusedId(): string | undefined /** Context key for the current sidebar selection, or `undefined` when nothing is selected. */ currentKey: Accessor + /** Context key for the side panel: like `currentKey` but unassigned + * sessions (null selection) share the LOCAL workspace-root terminal. */ + sideKey: Accessor /** Active terminal id signal + setter. */ activeId: Accessor setActiveId: (id: string | undefined) => void + /** The terminal that currently holds DOM focus, if any. Set by + * `TerminalTab` focus listeners; drives `Cmd+W` targeting. */ + focusedId: Accessor + setFocusedId: (id: string | undefined) => void + /** Latest explicit focus demand. */ + focusRequest: Accessor + requestFocus(id: string): void /** True when the given remembered tab id points to a live terminal for the given selection. */ hasRemembered(selection: string | null, remembered: string | undefined): boolean + /** Live display title for a terminal: the OSC-provided title when the + * shell/program set one, otherwise the create-time title. */ + title(terminalId: string): string | undefined + /** Record an OSC title change for a terminal. Kept outside the + * terminal records so `` reference stability (and therefore the + * mounted xterm instances) is preserved. */ + setTitle(terminalId: string, title: string): void /** * Persist a new order for a context's terminals (webview-memory only — * terminals are ephemeral and never round-trip through the extension @@ -70,49 +118,130 @@ export interface TerminalStateControls { * was applied, false otherwise so the caller can fall through. */ reorderDrag(from: string, to: string): boolean + /** + * Apply a drag-over reorder within a context's side terminals (the + * side-panel strip). Returns true when both ends are side terminals + * of that context. + */ + reorderSideDrag(contextKey: string, from: string, to: string): boolean + /** Request ids of the in-flight side-terminal creates for a context. */ + pendingSide(contextKey: string): boolean + /** Mark a side-terminal create as in flight for a context. */ + beginSide(contextKey: string, createId: string): void + /** Settle a create request; returns it so the caller can validate. */ + completeSide(createId: string): SideRequest | undefined } /** Wire up reactive state for terminal tabs. The caller passes the current - * `selection()` accessor so memos can key by the right context. + * `selection()` accessor so the accessors below key by the right context. * * ## Reference stability * * Terminals are stored as `TerminalTabStateWithContext` (contextKey - * baked in) so the reactive accessors below can return them *by - * reference* without ever allocating a new object per terminal. That - * matters because Solid's `` uses element reference equality to - * decide whether a child is "the same" across renders. If `all()` - * created `{...t, contextKey}` each time (the original bug), adding - * a new terminal to context A would rewrite every object in every - * context — `` would then unmount + remount every live xterm - * across the whole app, destroying instances and losing canvas state. + * baked in) so the accessors below can return them *by reference* + * without ever allocating a new object per terminal. That matters + * because Solid's `` uses element reference equality to decide + * whether a child is "the same" across renders. If `all()` created + * `{...t, contextKey}` each time (the original bug), adding a new + * terminal to context A would rewrite every object in every context — + * `` would then unmount + remount every live xterm across the + * whole app, destroying instances and losing canvas state. + * + * ## Plain accessors, not memos + * + * The derived accessors are plain functions rather than `createMemo`. + * Signal reads inside them are still tracked by whatever computation + * calls them, and `` identity comes from the stored records above + * (not from the array), so behavior is identical — while the module + * stays unit-testable: bun resolves `solid-js` to its server build, + * where a memo never recomputes after a signal write. Re-filtering a + * handful of terminals per read is far cheaper than an xterm frame. */ export function createTerminalState(selection: Accessor): TerminalStateControls { const [terminalsByContext, setTerminalsByContext] = createSignal>({}) const [activeId, setActiveId] = createSignal() + const [focusedId, setFocusedId] = createSignal() + const [focusRequest, setFocusRequest] = createSignal() + // OSC-provided titles, keyed by terminal id. Separate from the terminal + // records on purpose: replacing a record would remount its xterm via + // reference inequality (see the module comment above). + const [titles, setTitles] = createSignal>({}) + // Active side terminal per context. + const [actives, setActives] = createSignal>({}) + let focusSerial = 0 + // In-flight side-terminal creates, keyed both ways: per context (what + // the panel shows) and per request id (what the answer carries). A + // context can have several creates in flight at once. + const [pending, setPending] = createSignal>({}) + const requests = new Map() - const currentKey = createMemo((): string | undefined => { + const currentKey = (): string | undefined => { const sel = selection() if (sel === null) return undefined return sel === LOCAL ? LOCAL : sel - }) + } - const current = createMemo((): TerminalTabStateWithContext[] => { + const sideKey = (): string => { + const sel = selection() + if (sel === null || sel === LOCAL) return LOCAL + return sel + } + + const current = (): TerminalTabStateWithContext[] => { const key = currentKey() if (!key) return [] - return terminalsByContext()[key] ?? [] - }) + return (terminalsByContext()[key] ?? []).filter((t) => t.placement === "tab") + } - const all = createMemo((): TerminalTabStateWithContext[] => { + const all = (): TerminalTabStateWithContext[] => { const map = terminalsByContext() // Concat existing per-context arrays without spreading their // elements, so the same record references flow through to . const out: TerminalTabStateWithContext[] = [] - for (const list of Object.values(map)) out.push(...list) + for (const list of Object.values(map)) { + for (const t of list) if (t.placement === "tab") out.push(t) + } return out - }) + } - const lookup = createMemo(() => new Map(current().map((t) => [t.id, t]))) + const sides = (): TerminalTabStateWithContext[] => { + const map = terminalsByContext() + // Same reference-stability rule as `all` — the side render layer is + // a over live xterm instances too. + const out: TerminalTabStateWithContext[] = [] + for (const list of Object.values(map)) { + for (const t of list) if (t.placement === "side") out.push(t) + } + return out + } + + const sidesForContext = (key: string) => (terminalsByContext()[key] ?? []).filter((t) => t.placement === "side") + const sideActiveFor = (key: string) => actives()[key] + const sideFocusedId = () => { + const id = focusedId() + if (!id) return undefined + return sidesForContext(sideKey()).some((t) => t.id === id) ? id : undefined + } + + const setSideActive = (key: string, terminalId: string) => { + setActives((prev) => (prev[key] === terminalId ? prev : { ...prev, [key]: terminalId })) + } + + const title = (terminalId: string): string | undefined => { + const live = titles()[terminalId] + if (live) return live + const key = contextFor(terminalId) + if (!key) return undefined + return terminalsByContext()[key]?.find((t) => t.id === terminalId)?.title + } + + const setTitle = (terminalId: string, next: string) => { + const trimmed = next.trim() + if (!trimmed) return + setTitles((prev) => (prev[terminalId] === trimmed ? prev : { ...prev, [terminalId]: trimmed })) + } + + const lookup = () => new Map(current().map((t) => [t.id, t])) const contextFor = (terminalId: string): string | undefined => { for (const [key, terms] of Object.entries(terminalsByContext())) { @@ -124,7 +253,7 @@ export function createTerminalState(selection: Accessor): Termina const forSelection = (sel: string | null): TerminalTabStateWithContext[] => { if (sel === null) return [] const key = sel === LOCAL ? LOCAL : sel - return terminalsByContext()[key] ?? [] + return (terminalsByContext()[key] ?? []).filter((t) => t.placement === "tab") } const add = (worktreeId: string | null, term: TerminalTabState) => { @@ -137,9 +266,10 @@ export function createTerminalState(selection: Accessor): Termina }) } - const remove = (terminalId: string): string | undefined => { + const remove = (terminalId: string): TerminalTabStateWithContext | undefined => { const key = contextFor(terminalId) if (!key) return undefined + const removed = terminalsByContext()[key]?.find((t) => t.id === terminalId) setTerminalsByContext((prev) => { const list = (prev[key] ?? []).filter((t) => t.id !== terminalId) const next = { ...prev } @@ -147,7 +277,31 @@ export function createTerminalState(selection: Accessor): Termina else next[key] = list return next }) - return key + if (focusedId() === terminalId) setFocusedId(undefined) + // A removed active side terminal hands activation to the last + // remaining one of its context, so the panel never shows a dead slot. + if (removed?.placement === "side" && actives()[key] === terminalId) { + const rest = sidesForContext(key) + setActives((prev) => { + const next = { ...prev } + if (rest.length === 0) delete next[key] + else next[key] = rest[rest.length - 1]!.id + return next + }) + } + if (titles()[terminalId] !== undefined) { + setTitles((prev) => { + const next = { ...prev } + delete next[terminalId] + return next + }) + } + return removed + } + + const requestFocus = (id: string) => { + focusSerial++ + setFocusRequest({ id, serial: focusSerial }) } const hasRemembered = (sel: string | null, remembered: string | undefined): boolean => { @@ -159,7 +313,11 @@ export function createTerminalState(selection: Accessor): Termina setTerminalsByContext((prev) => { const list = prev[key] if (!list || list.length === 0) return prev - const byId = new Map(list.map((t) => [t.id, t])) + // Tab order only covers tab terminals; side terminals never join + // the tab strip and keep their position at the end of the list. + const tabs = list.filter((t) => t.placement === "tab") + const side = list.filter((t) => t.placement === "side") + const byId = new Map(tabs.map((t) => [t.id, t])) const next: TerminalTabStateWithContext[] = [] for (const id of orderedIds) { const t = byId.get(id) @@ -172,9 +330,10 @@ export function createTerminalState(selection: Accessor): Termina // appeared between drag start and commit) at their original tail // position — simpler than merging and matches the existing // `applyTabOrder` semantics used elsewhere in the app. - for (const t of list) if (byId.has(t.id)) next.push(t) - if (next.length === list.length && next.every((t, i) => t.id === list[i]!.id)) return prev - return { ...prev, [key]: next } + for (const t of tabs) if (byId.has(t.id)) next.push(t) + const ordered = [...next, ...side] + if (ordered.length === list.length && ordered.every((t, i) => t.id === list[i]!.id)) return prev + return { ...prev, [key]: ordered } }) } @@ -187,7 +346,7 @@ export function createTerminalState(selection: Accessor): Termina const reorderDrag = (from: string, to: string): boolean => { const key = currentKey() if (!key) return false - const order = (terminalsByContext()[key] ?? []).map((t) => t.id) + const order = current().map((t) => t.id) const fi = order.indexOf(from) const ti = order.indexOf(to) if (fi === -1 || ti === -1 || fi === ti) return false @@ -198,6 +357,63 @@ export function createTerminalState(selection: Accessor): Termina return true } + /** + * Reorder the side terminals of a context by moving `from` to `to`'s + * position (side-panel strip drag-and-drop). Tab terminals keep their + * leading positions; only the side subset is reshuffled. The order + * lives in the same `terminalsByContext` list, so it survives sidebar + * context switches for the lifetime of the webview. + */ + const reorderSideDrag = (key: string, from: string, to: string): boolean => { + const order = sidesForContext(key).map((t) => t.id) + const fi = order.indexOf(from) + const ti = order.indexOf(to) + if (fi === -1 || ti === -1 || fi === ti) return false + const next = [...order] + next.splice(fi, 1) + next.splice(ti, 0, from) + setTerminalsByContext((prev) => { + const list = prev[key] + if (!list || list.length === 0) return prev + const tabs = list.filter((t) => t.placement === "tab") + const sides = list.filter((t) => t.placement === "side") + const byId = new Map(sides.map((t) => [t.id, t])) + const moved: TerminalTabStateWithContext[] = [] + for (const id of next) { + const t = byId.get(id) + if (t) moved.push(t) + } + // Fresh terminals that appeared mid-drag keep their tail position. + for (const t of sides) if (!moved.includes(t)) moved.push(t) + const ordered = [...tabs, ...moved] + if (ordered.length === list.length && ordered.every((t, i) => t.id === list[i]!.id)) return prev + return { ...prev, [key]: ordered } + }) + return true + } + + const pendingSide = (key: string) => (pending()[key]?.length ?? 0) > 0 + + const beginSide = (key: string, createId: string) => { + requests.set(createId, { contextKey: key }) + setPending((prev) => ({ ...prev, [key]: [...(prev[key] ?? []), createId] })) + } + + const completeSide = (createId: string): SideRequest | undefined => { + const request = requests.get(createId) + if (!request) return undefined + requests.delete(createId) + setPending((prev) => { + const list = (prev[request.contextKey] ?? []).filter((id) => id !== createId) + if (list.length === (prev[request.contextKey]?.length ?? 0)) return prev + const next = { ...prev } + if (list.length === 0) delete next[request.contextKey] + else next[request.contextKey] = list + return next + }) + return request + } + return { add, remove, @@ -206,12 +422,28 @@ export function createTerminalState(selection: Accessor): Termina lookup, current, all, + sides, + sidesForContext, + sideActiveFor, + setSideActive, + sideFocusedId, currentKey, + sideKey, activeId, setActiveId, + focusedId, + setFocusedId, + focusRequest, + requestFocus, hasRemembered, + title, + setTitle, reorder, reorderDrag, + reorderSideDrag, + pendingSide, + beginSide, + completeSide, } } @@ -228,6 +460,8 @@ export interface TerminalHandlerDeps { findTab: (id: string) => { id: string } | undefined postMessage: (msg: unknown) => void onRemove?: () => void + /** Reveal the right-side inspector in terminal mode. */ + onShowSide: (contextKey: string) => void /** Resolve the current sidebar selection for the new-terminal helper. */ getSelection: () => string | null /** Sentinel value for the LOCAL sidebar selection. */ @@ -235,6 +469,12 @@ export interface TerminalHandlerDeps { REVIEW_TAB_ID: string } +/** Correlation ids for terminal create requests. */ +function newId(): string { + if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID() + return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}` +} + /** * Build the close-terminal handler the main component wires to the * close button. Picks the next visible tab before dropping the entry @@ -253,7 +493,49 @@ export function createTerminalHandlers(deps: TerminalHandlerDeps) { const requestNew = () => { const sel = deps.getSelection() if (sel === null) return - deps.postMessage({ type: "agentManager.terminal.create", worktreeId: sel === deps.LOCAL ? null : sel }) + deps.postMessage({ + type: "agentManager.terminal.create", + createId: newId(), + placement: "tab", + worktreeId: sel === deps.LOCAL ? null : sel, + }) + } + + /** + * Always create a fresh side terminal for the current context (the + * panel's `+` action and empty state). Multiple creates may be in + * flight at once; each lands as its own tab in the panel strip. + */ + const addSide = () => { + const key = deps.state.sideKey() + deps.onShowSide(key) + const id = newId() + deps.state.beginSide(key, id) + deps.postMessage({ + type: "agentManager.terminal.create", + createId: id, + placement: "side", + worktreeId: key === deps.LOCAL ? null : key, + }) + } + + /** + * Reveal the side panel and focus the context's active side terminal, + * creating one when the context has none. Never touches the tab strip + * or the chat session. + */ + const requestSide = () => { + const key = deps.state.sideKey() + deps.onShowSide(key) + const existing = deps.state.sidesForContext(key) + if (existing.length > 0) { + const active = deps.state.sideActiveFor(key) ?? existing[existing.length - 1]!.id + deps.state.setSideActive(key, active) + deps.state.requestFocus(active) + return + } + if (deps.state.pendingSide(key)) return + addSide() } const closeTerminal = (terminalId: string) => { @@ -289,6 +571,30 @@ export function createTerminalHandlers(deps: TerminalHandlerDeps) { deps.postMessage({ type: "agentManager.terminal.close", terminalId }) } + /** + * Kill one side terminal. The panel stays open on the remaining + * terminals (or the empty state when this was the last one) — hiding + * is the toggle's job, not the close button's. Active-tab fallback + * is handled by the state layer. + */ + const closeSide = (terminalId: string): boolean => { + // Validate before mutating: dropping a non-side record here would + // unmount its xterm while the backend PTY leaks (no close sent). + const term = deps.state.sides().find((t) => t.id === terminalId) + if (!term) return false + deps.state.remove(terminalId) + deps.postMessage({ type: "agentManager.terminal.close", terminalId }) + return true + } + + /** Make a side terminal the visible one in its panel and focus it. */ + const selectSide = (terminalId: string) => { + const key = deps.state.contextFor(terminalId) + if (!key) return + deps.state.setSideActive(key, terminalId) + deps.state.requestFocus(terminalId) + } + const middleClick = (terminalId: string, e: MouseEvent) => { if (e.button !== 1) return e.preventDefault() @@ -303,7 +609,18 @@ export function createTerminalHandlers(deps: TerminalHandlerDeps) { return true } - return { closeTerminal, middleClick, activate, deactivate, requestNew, closeActive } + return { + closeTerminal, + closeSide, + selectSide, + middleClick, + activate, + deactivate, + requestNew, + requestSide, + addSide, + closeActive, + } } export interface TerminalMessageHandlerDeps { @@ -312,6 +629,7 @@ export interface TerminalMessageHandlerDeps { saveTabMemory: () => void setSelection: (sel: string | typeof LOCAL) => void showError: (message: string) => void + postMessage: (message: unknown) => void /** * Called with the context key ("local" or worktree id) and the new * terminal id once a `terminal.created` message lands. The main @@ -320,39 +638,86 @@ export interface TerminalMessageHandlerDeps { * than wherever `tabIds()`'s base composition happens to put it. */ onCreated?: (contextKey: string, terminalId: string) => void + /** Side terminal for a context finished creating. */ + onSideCreated?: (contextKey: string, terminalId: string) => void + /** Side terminal create failed for a context. */ + onSideError?: (contextKey: string) => void + /** Side terminal was closed (locally or by the extension). */ + onSideClosed?: (contextKey: string) => void + /** The destination setting changed (live settings sync). */ + onDestinationChanged?: (destination: TerminalDestination) => void +} + +type CreatedMessage = Extract + +function handleCreated(deps: TerminalMessageHandlerDeps, msg: CreatedMessage) { + const contextKey = msg.worktreeId === null ? LOCAL : msg.worktreeId + const term = { + id: msg.terminalId, + title: msg.title, + wsUrl: msg.wsUrl, + font: msg.font, + placement: msg.placement, + } + if (msg.placement === "side") { + // Side terminals are answered to a specific pending request. A + // missing or context-mismatched request means the webview was + // reloaded (or the context is gone) — close the PTY again instead + // of leaking it. + const request = deps.state.completeSide(msg.createId) + if (!request || request.contextKey !== contextKey) { + deps.postMessage({ type: "agentManager.terminal.close", terminalId: msg.terminalId }) + return + } + deps.state.add(msg.worktreeId, term) + // The newest terminal becomes the visible one in its panel. + deps.state.setSideActive(contextKey, msg.terminalId) + deps.onSideCreated?.(contextKey, msg.terminalId) + return + } + deps.state.add(msg.worktreeId, term) + deps.onCreated?.(contextKey, msg.terminalId) + deps.saveTabMemory() + deps.setSelection(contextKey) + deps.activate(msg.terminalId) } /** - * Wire handlers for the three inbound terminal messages. Returns a - * dispatcher that accepts each message type and returns true if it - * handled the payload. Keeps all the terminal-specific routing logic - * out of the main webview component. + * Wire handlers for the inbound terminal messages. Returns a dispatcher + * that accepts each message type and returns true if it handled the + * payload. Keeps all the terminal-specific routing logic out of the + * main webview component. */ export function createTerminalMessageHandler(deps: TerminalMessageHandlerDeps) { return (msg: ExtensionMessage): boolean => { if (msg.type === "agentManager.terminal.created") { - const contextKey = msg.worktreeId === null ? LOCAL : msg.worktreeId - deps.state.add(msg.worktreeId, { - id: msg.terminalId, - title: msg.title, - wsUrl: msg.wsUrl, - font: msg.font, - }) - deps.onCreated?.(contextKey, msg.terminalId) - deps.saveTabMemory() - deps.setSelection(contextKey) - deps.activate(msg.terminalId) + handleCreated(deps, msg) return true } if (msg.type === "agentManager.terminal.closed") { - deps.state.remove(msg.terminalId) + const removed = deps.state.remove(msg.terminalId) if (deps.state.activeId() === msg.terminalId) deps.state.setActiveId(undefined) + if (removed?.placement === "side") deps.onSideClosed?.(removed.contextKey) return true } if (msg.type === "agentManager.terminal.error") { + const request = msg.createId ? deps.state.completeSide(msg.createId) : undefined + if (request) deps.onSideError?.(request.contextKey) deps.showError(msg.message) return true } + if (msg.type === "agentManager.terminal.destinationChanged") { + deps.onDestinationChanged?.(msg.destination) + return true + } + // The initial destination rides along on the state message. Claimed + // here so the main webview handler stays free of terminal settings, + // but reported as unhandled because the rest of that payload belongs + // to the other subscribers. + if (msg.type === "agentManager.state" && msg.terminalDestination) { + deps.onDestinationChanged?.(msg.terminalDestination) + return false + } return false } } diff --git a/packages/kilo-vscode/webview-ui/agent-manager/worktree-diffs.ts b/packages/kilo-vscode/webview-ui/agent-manager/worktree-diffs.ts new file mode 100644 index 0000000000..fd56c87167 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/agent-manager/worktree-diffs.ts @@ -0,0 +1,118 @@ +/** + * Worktree diff data for Agent Manager. + * + * Owns the per-session diff map, the panel loading flag, and the per-file + * pending set, plus the backend message handlers that fill them and the + * helpers that request individual files. Extracted from `AgentManagerApp.tsx` + * so the app component only routes the diff messages and reads the signals. + */ + +import { createSignal, type Accessor } from "solid-js" +import { mergeWorktreeDiffs } from "../diff-viewer/diff-state" +import type { useVSCode } from "../src/context/vscode" +import type { + AgentManagerWorktreeDiffFileMessage, + AgentManagerWorktreeDiffLoadingMessage, + AgentManagerWorktreeDiffMessage, + WorktreeFileDiff, +} from "../src/types/messages" + +export function createWorktreeDiffs(vscode: ReturnType) { + const [diffDatas, setDiffDatas] = createSignal>({}) + const [diffLoading, setDiffLoading] = createSignal(false) + const [diffFileLoading, setDiffFileLoading] = createSignal>>({}) + + const setDiffFilePending = (sessionId: string, file: string, value: boolean) => { + setDiffFileLoading((prev) => { + const session = prev[sessionId] ?? {} + if (value) { + if (session[file]) return prev + return { + ...prev, + [sessionId]: { ...session, [file]: true }, + } + } + + if (!session[file]) return prev + const next = { ...session } + delete next[file] + if (Object.keys(next).length === 0) { + const result = { ...prev } + delete result[sessionId] + return result + } + return { + ...prev, + [sessionId]: next, + } + }) + } + + /** Lazily load a single file's full diff for the current session. */ + const requestDiffFile = (sessionId: string, file: string) => { + if (diffFileLoading()[sessionId]?.[file]) return + setDiffFilePending(sessionId, file, true) + vscode.postMessage({ type: "agentManager.requestWorktreeDiffFile", sessionId, file }) + } + + /** Files the backend flagged as stale in a merged update need a fresh fetch. */ + const refreshStaleDiffs = (sessionId: string, files: Set) => { + const loading = diffFileLoading()[sessionId] ?? {} + for (const file of files) { + if (loading[file]) continue + setDiffFilePending(sessionId, file, true) + vscode.postMessage({ type: "agentManager.requestWorktreeDiffFile", sessionId, file }) + } + } + + /** Files currently being fetched for a session, for per-file spinners. */ + const diffFileLoadingFor = (sessionId: Accessor) => { + const id = sessionId() + if (!id) return new Set() + return new Set(Object.keys(diffFileLoading()[id] ?? {})) + } + + // Backend messages. + + const onWorktreeDiff = (ev: AgentManagerWorktreeDiffMessage) => { + let staleFiles: Set | undefined + setDiffDatas((prev) => { + const existing = prev[ev.sessionId] + const merged = existing ? mergeWorktreeDiffs(existing, ev.diffs) : { diffs: ev.diffs, stale: new Set() } + staleFiles = merged.stale + const next = merged.diffs + if (existing && existing.length === next.length && existing.every((old, i) => old === next[i])) return prev + return { ...prev, [ev.sessionId]: next } + }) + if (staleFiles) refreshStaleDiffs(ev.sessionId, staleFiles) + } + + const onWorktreeDiffFile = (ev: AgentManagerWorktreeDiffFileMessage) => { + if (ev.diff) { + setDiffDatas((prev) => { + const existing = prev[ev.sessionId] ?? [] + const next = existing.map((item) => (item.file === ev.diff!.file ? ev.diff! : item)) + return { ...prev, [ev.sessionId]: next } + }) + setDiffFilePending(ev.sessionId, ev.diff.file, false) + return + } + setDiffFilePending(ev.sessionId, ev.file, false) + } + + const onWorktreeDiffLoading = (ev: AgentManagerWorktreeDiffLoadingMessage) => { + setDiffLoading(ev.loading) + } + + return { + diffDatas, + diffLoading, + setDiffLoading, + requestDiffFile, + refreshStaleDiffs, + diffFileLoadingFor, + onWorktreeDiff, + onWorktreeDiffFile, + onWorktreeDiffLoading, + } +} diff --git a/packages/kilo-vscode/webview-ui/kiloclaw/context/language.tsx b/packages/kilo-vscode/webview-ui/kiloclaw/context/language.tsx index e4e079638f..4a64ce3c7e 100644 --- a/packages/kilo-vscode/webview-ui/kiloclaw/context/language.tsx +++ b/packages/kilo-vscode/webview-ui/kiloclaw/context/language.tsx @@ -27,6 +27,7 @@ import { dict as tr } from "../i18n/tr" import { dict as zh } from "../i18n/zh" import { dict as uk } from "../i18n/uk" import { dict as zht } from "../i18n/zht" +import { dict as fa } from "../i18n/fa" const dicts: Record> = { en, @@ -49,6 +50,7 @@ const dicts: Record> = { uk: { ...en, ...uk }, zh: { ...en, ...zh }, zht: { ...en, ...zht }, + fa: { ...en, ...fa }, } type LanguageCtx = { diff --git a/packages/kilo-vscode/webview-ui/kiloclaw/i18n/fa.ts b/packages/kilo-vscode/webview-ui/kiloclaw/i18n/fa.ts new file mode 100644 index 0000000000..1c63633715 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/kiloclaw/i18n/fa.ts @@ -0,0 +1,95 @@ +export const dict = { + // App + "kiloClaw.loading": "در حال بارگذاری KiloClaw...", + + // Chat + "kiloClaw.chat.connecting": "در حال اتصال...", + "kiloClaw.chat.notRunning": "نمونه در حال اجرا نیست", + "kiloClaw.chat.placeholder": "پیامی بنویسید... (Enter برای ارسال)", + "kiloClaw.chat.online": "آنلاین", + "kiloClaw.chat.offline": "آفلاین", + "kiloClaw.chat.idle": "بیکار", + "kiloClaw.chat.unknown": "نامشخص", + "kiloClaw.chat.empty": "هنوز پیامی وجود ندارد. سلام کنید!", + "kiloClaw.chat.emptyWithBot": "هنوز پیامی وجود ندارد. به {bot} سلام کنید!", + "kiloClaw.chat.send": "ارسال", + "kiloClaw.chat.waitingBotStatus": "در انتظار وضعیت ربات...", + "kiloClaw.chat.botOffline": "ربات آفلاین است", + + // Conversations + "kiloClaw.conversations.title": "مکالمات", + "kiloClaw.conversations.new": "مکالمه جدید", + "kiloClaw.conversations.empty": "هنوز مکالمه‌ای وجود ندارد", + "kiloClaw.conversations.untitled": "بدون عنوان", + "kiloClaw.conversations.rename": "تغییر نام", + "kiloClaw.conversations.leave": "خروج", + "kiloClaw.conversations.selectOne": "یک مکالمه را برای شروع گفتگو انتخاب کنید", + "kiloClaw.conversations.groupToday": "امروز", + "kiloClaw.conversations.groupYesterday": "دیروز", + "kiloClaw.conversations.groupThisWeek": "این هفته", + "kiloClaw.conversations.groupOlder": "قدیمی‌تر", + + // Typing + "kiloClaw.typing.one": "{name} در حال تایپ است...", + "kiloClaw.typing.many": "{count} نفر در حال تایپ هستند...", + + // Messages + "kiloClaw.message.bot": "KiloClaw", + "kiloClaw.message.you": "شما", + "kiloClaw.message.thinking": "در حال فکر کردن...", + "kiloClaw.message.deleted": "[پیام حذف شد]", + "kiloClaw.message.replyDeleted": "پیام اصلی حذف شد", + "kiloClaw.message.notDelivered": "ارسال نشد", + "kiloClaw.message.edited": "(ویرایش شده)", + "kiloClaw.message.reply": "پاسخ", + "kiloClaw.message.replyTo": "در حال پاسخ به", + "kiloClaw.message.cancelReply": "لغو پاسخ", + "kiloClaw.message.edit": "ویرایش", + "kiloClaw.message.delete": "حذف", + "kiloClaw.message.confirmDelete": "این پیام حذف شود؟", + "kiloClaw.message.cancel": "انصراف", + "kiloClaw.message.save": "ذخیره", + "kiloClaw.message.react": "واکنش", + "kiloClaw.message.removeReaction": "حذف واکنش", + "kiloClaw.message.copy": "کپی", + "kiloClaw.message.copied": "در کلیپ‌بورد کپی شد", + "kiloClaw.message.copyFailed": "کپی انجام نشد", + + // Error + "kiloClaw.error.retry": "تلاش مجدد", + + // Setup + "kiloClaw.setup.title": "KiloClaw", + "kiloClaw.setup.subtitle": "هوش مصنوعی شخصی برای زندگی روزمره", + "kiloClaw.setup.description1": + "KiloClaw یک هوش مصنوعی شخصی در اختیار شما قرار می‌دهد که ایمیل‌ها را می‌خواند، تقویم شما را مدیریت می‌کند، پروژه‌هایتان را رصد می‌کند و در Telegram، Slack — هر چیزی که از قبل استفاده می‌کنید — در دسترس است.", + "kiloClaw.setup.description2": + "نیازی به نصب برنامه نیست. رابط جدیدی برای یادگیری وجود ندارد. فقط مثل یک دوست برایش پیام بفرستید.", + "kiloClaw.setup.learnMore": "بیشتر بدانید", + "kiloClaw.setup.tryKiloClaw": "امتحان KiloClaw", + + // Upgrade + "kiloClaw.upgrade.title": "KiloClaw Chat نیاز به ارتقا دارد", + "kiloClaw.upgrade.description1": "این نمونه قبل از فعال‌سازی چت راه‌اندازی شده است.", + "kiloClaw.upgrade.description2.before": "از دکمه ", + "kiloClaw.upgrade.description2.bold": "ارتقا به آخرین نسخه", + "kiloClaw.upgrade.description2.after": " در داشبورد KiloClaw برای فعال‌سازی چت زنده با ربات خود استفاده کنید.", + "kiloClaw.upgrade.openDashboard": "باز کردن داشبورد", + + // Sidebar + "kiloClaw.sidebar.title": "KiloClaw", + "kiloClaw.sidebar.instance": "نمونه", + "kiloClaw.sidebar.unknown": "ناشناخته", + "kiloClaw.sidebar.bot": "ربات", + "kiloClaw.sidebar.botStatus": "وضعیت ربات", + "kiloClaw.sidebar.context": "زمینه", + "kiloClaw.sidebar.used": "استفاده‌شده", + "kiloClaw.sidebar.tokens": "توکن‌ها", + "kiloClaw.sidebar.model": "مدل", + "kiloClaw.sidebar.provider": "ارائه‌دهنده", + "kiloClaw.sidebar.details": "جزئیات", + "kiloClaw.sidebar.region": "منطقه", + "kiloClaw.sidebar.version": "نسخه", + "kiloClaw.sidebar.channels": "کانال‌ها", + "kiloClaw.sidebar.noData": "داده‌ای برای نمونه وجود ندارد", +} diff --git a/packages/kilo-vscode/webview-ui/kiloclaw/kiloclaw.css b/packages/kilo-vscode/webview-ui/kiloclaw/kiloclaw.css index cd595c9c76..8cc42876aa 100644 --- a/packages/kilo-vscode/webview-ui/kiloclaw/kiloclaw.css +++ b/packages/kilo-vscode/webview-ui/kiloclaw/kiloclaw.css @@ -61,37 +61,6 @@ color: var(--vscode-descriptionForeground); } -/* Chat panel */ -.kiloclaw-chat { - flex: 1; - display: flex; - flex-direction: column; - min-width: 0; - overflow: hidden; -} - -/* Chat header */ -.kiloclaw-chat-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 16px; - border-bottom: 1px solid var(--vscode-panel-border); - flex-shrink: 0; -} - -.kiloclaw-chat-header-left { - display: flex; - align-items: center; - gap: 8px; -} - -.kiloclaw-chat-header-title { - font-size: var(--kilo-font-size-13); - font-weight: 500; - color: var(--vscode-foreground); -} - /* Status dots */ .kiloclaw-dot { display: inline-block; @@ -117,16 +86,6 @@ background: var(--vscode-editorError-foreground, #f14c4c); } -/* Messages area */ -.kiloclaw-messages { - flex: 1; - overflow-y: auto; - padding: 16px; - display: flex; - flex-direction: column; - gap: 4px; -} - .kiloclaw-empty { color: var(--vscode-descriptionForeground); font-size: var(--kilo-font-size-13); @@ -458,17 +417,6 @@ white-space: nowrap; } -/* ── Dot states ────────────────────────────────────────────────────── */ - -.kiloclaw-dot-idle { - background: var(--vscode-editorWarning-foreground, #cca700); -} - -.kiloclaw-dot-unknown { - background: var(--vscode-descriptionForeground, #888); - opacity: 0.5; -} - /* ── Message bubble ───────────────────────────────────────────────── * * Layout mirrors the web UI (cloud/apps/web/src/app/(app)/claw/kilo-chat/ diff --git a/packages/kilo-vscode/webview-ui/src/App.tsx b/packages/kilo-vscode/webview-ui/src/App.tsx index 1aa6768823..7fa356e848 100644 --- a/packages/kilo-vscode/webview-ui/src/App.tsx +++ b/packages/kilo-vscode/webview-ui/src/App.tsx @@ -1,34 +1,18 @@ import { Component, createSignal, createMemo, Switch, Match, Show, onMount, onCleanup } from "solid-js" -import { ThemeProvider } from "@kilocode/kilo-ui/theme" -import { DialogProvider } from "@kilocode/kilo-ui/context/dialog" -import { MarkedProvider } from "@kilocode/kilo-ui/context/marked" -import { CodeComponentProvider } from "@kilocode/kilo-ui/context/code" -import { DiffComponentProvider } from "@kilocode/kilo-ui/context/diff" -import { FileComponentProvider } from "@kilocode/kilo-ui/context/file" -import { Code } from "@kilocode/kilo-ui/code" -import { Diff } from "@kilocode/kilo-ui/diff" -import { File } from "@kilocode/kilo-ui/file" import { DataProvider } from "@kilocode/kilo-ui/context/data" -import { Toast } from "@kilocode/kilo-ui/toast" import Settings from "./components/settings/Settings" import ProfileView from "./components/profile/ProfileView" -import { VSCodeProvider, useVSCode } from "./context/vscode" -import { ServerProvider, useServer } from "./context/server" -import { ProviderProvider, useProvider } from "./context/provider" -import { ConfigProvider } from "./context/config" -import { DisplayProvider } from "./context/display" +import { useVSCode } from "./context/vscode" +import { useServer } from "./context/server" +import { useProvider } from "./context/provider" import { WorkStyleProvider } from "./context/work-style" -import { IndexingProvider } from "./context/indexing" -import { AgentRequirementsProvider } from "./context/agent-requirements" -import { MemoryProvider } from "./context/memory" -import { SessionProvider, useSession } from "./context/session" +import { useSession } from "./context/session" import { LocalTabsProvider, useLocalTabs } from "./context/local-tabs" -import { LanguageBridge } from "./context/language-bridge" +import { ProviderShell } from "./context/provider-shell" import { ChatView } from "./components/chat" import { SidebarEmptyState } from "./components/chat/SidebarEmptyState" import { registerExpandedTaskTool } from "./components/chat/TaskToolExpanded" import { registerVscodeToolOverrides } from "./components/chat/VscodeToolOverrides" -import { SpeechToTextPrewarm } from "./components/speech-to-text/SpeechToTextPrewarm" // Override the upstream "task" tool renderer with the fully-expanded version // that shows child session parts inline in the VS Code sidebar. @@ -37,10 +21,6 @@ registerExpandedTaskTool() registerVscodeToolOverrides() import HistoryView from "./components/history/HistoryView" import { MigrationWizard } from "./components/migration" // legacy-migration -import { NotificationsProvider } from "./context/notifications" -import { FeedbackProvider } from "./context/feedback" -import { KiloEmbeddingModelsProvider } from "./context/kilo-embedding-models" -import { ImageModelsProvider } from "./context/image-models" import type { Message as SDKMessage, Part as SDKPart } from "@kilocode/sdk/v2" import { cycleAgent as cycle } from "./context/session-agent" import "./styles/chat.css" @@ -210,27 +190,6 @@ export const DataBridge: Component<{ children: any }> = (props) => { ) } -type MermaidImageEvent = CustomEvent<{ dataUrl: string; filename: string }> - -export const MermaidDownloadBridge: Component = () => { - const vscode = useVSCode() - - onMount(() => { - const save = (event: Event) => { - const detail = (event as MermaidImageEvent).detail - if (!detail?.dataUrl || !detail.filename) return - event.preventDefault() - vscode.postMessage({ type: "saveImage", dataUrl: detail.dataUrl, filename: detail.filename }) - } - window.addEventListener("kilo:save-image", save) - onCleanup(() => { - window.removeEventListener("kilo:save-image", save) - }) - }) - - return null -} - // Inner app component that uses the contexts const AppContent: Component = () => { const [currentView, setCurrentView] = createSignal("newTask") @@ -412,59 +371,21 @@ const AppContent: Component = () => { ) } -// Main App component with context providers const App: Component = () => { return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + ) } 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 40e0b72876..d2daa6389b 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx @@ -70,6 +70,8 @@ import { type TranscriptHold, type TranscriptRow, } from "../../context/transcript-rows" +import { PromptRail } from "./PromptRail" +import { capacity, promptItems, railItems, type PromptRailItem } from "./prompt-rail" import { onTimelineHighlight, type TimelineHighlight } from "../../utils/timeline/highlight" import { useTranscriptSearch, type SearchMatch } from "../../context/transcript-search" import { applyTranscriptHighlights, clearTranscriptHighlights } from "./transcript-search-highlight" @@ -164,6 +166,8 @@ export const MessageList: Component = (props) => { const [scrollEl, setScrollEl] = createSignal() const [virtualizer, setVirtualizer] = createSignal() const [layout, setLayout] = createSignal("") + // Transcript height, kept reactive so the prompt rail re-caps on resize. + const [height, setHeight] = createSignal(0) const revert = () => session.revert() ?? undefined const turns = createMemo((prev: MessageTurn[] | undefined) => @@ -944,6 +948,21 @@ export const MessageList: Component = (props) => { const keys = createMemo(() => partition().virtual.map((row) => row.key)) const fingerprint = createMemo(() => rowFingerprint(keys())) + // Scrolls the transcript to a row by key. Virtualized rows jump through + // the virtualizer; direct/live/queued rows are mounted, so they use + // scrollIntoView. Pauses auto-follow first so the jump isn't snapped back. + const jump = (key: string) => { + autoScroll.pause() + const index = keys().indexOf(key) + if (index >= 0) { + virtualizer()?.scrollToIndex(index, { align: "start" }) + return + } + const el = scrollEl() + const target = el?.querySelector(`[data-row-key="${CSS.escape(key)}"]`) + target?.scrollIntoView({ block: "start" }) + } + // Clicking a bar in the task timeline scrolls the transcript to that message. // Jumps land instantly (no smooth animation): while pinned at the bottom, a // smooth scroll's initial frames sit within createAutoScroll's near-bottom @@ -956,19 +975,50 @@ export const MessageList: Component = (props) => { // actually contains the clicked part, not just the message's first chunk. const row = matches.find((r) => r.type === "assistant" && r.parts.some((p) => p.id === detail.partId)) ?? matches[0] if (!row) return - autoScroll.pause() - const index = keys().indexOf(row.key) - if (index >= 0) { - virtualizer()?.scrollToIndex(index, { align: "start" }) - return - } - const el = scrollEl() - const target = el?.querySelector(`[data-row-key="${CSS.escape(row.key)}"]`) - target?.scrollIntoView({ block: "start" }) + jump(row.key) } window.addEventListener("scrollToMessage", onScrollToMessage) onCleanup(() => window.removeEventListener("scrollToMessage", onScrollToMessage)) + // Prompt rail: one tick per user prompt, positioned to the left of the + // readable lane, opening a card of prompt/answer previews on hover. + const items = createMemo(() => promptItems(rows())) + // Until the transcript is measured there is no height to cap against, and + // rendering every prompt would spill ticks past the rail on long sessions. + const shown = createMemo(() => railItems(items(), capacity(height()))) + const [activeTurn, setActiveTurn] = createSignal() + const railActiveKey = createMemo(() => shown().find((item) => item.turn === activeTurn())?.key) + + const trackActive = () => { + const list = shown() + if (list.length === 0) return setActiveTurn(undefined) + const handle = virtualizer() + const offset = handle?.scrollOffset + if (handle && offset !== undefined && offset > 1) { + const row = partition().virtual[handle.findItemIndex(offset)] + if (row) return setActiveTurn(row.turn) + } + setActiveTurn(list.at(-1)?.turn) + } + let activeFrame: number | undefined + const scheduleActive = () => { + if (activeFrame !== undefined) return + activeFrame = requestAnimationFrame(() => { + activeFrame = undefined + trackActive() + }) + } + onCleanup(() => { + if (activeFrame !== undefined) cancelAnimationFrame(activeFrame) + }) + // Re-derive the active turn whenever the transcript changes so the rail + // reflects a newly started turn even before any scrolling happens. + createEffect(() => { + shown() + partition() + scheduleActive() + }) + // Highlights the part behind the currently hovered/focused timeline bar // (dispatched by TaskTimeline) so the two stay visually correlated. const [highlight, setHighlight] = createSignal() @@ -1018,6 +1068,7 @@ export const MessageList: Component = (props) => { const handleScroll = () => { autoScroll.handleScroll() maybeLoadOlder() + scheduleActive() if (search.active()) scheduleHighlight() } @@ -1026,6 +1077,7 @@ export const MessageList: Component = (props) => { const el = scrollEl() if (!el) return const style = getComputedStyle(el) + setHeight(el.clientHeight) setLayout( layoutFingerprint({ width: Math.round(el.clientWidth), @@ -1210,6 +1262,17 @@ export const MessageList: Component = (props) => {
+ railActiveKey()} + onSelect={(item: PromptRailItem) => jump(item.key)} + onWheel={(deltaY: number) => { + const el = scrollEl() + if (el) el.scrollTop += deltaY + }} + height={height} + /> +
- + + )} + + + + + {(position) => ( + + + + )} + + + ) +} 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 736c6355a0..74fa8f1ba7 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx @@ -348,7 +348,9 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
{summary()}
-
{questionText()}
+
+ {questionText()} +
e.stopPropagation()}> @@ -391,7 +393,9 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
-
{questionText()}
+
+ {questionText()} +
{language.t("ui.question.singleHint")}
}>
{language.t("ui.question.multiHint")}
@@ -419,9 +423,13 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) => - {localized.label()} + + {localized.label()} + - {localized.description()} + + {localized.description()} + @@ -498,8 +506,10 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) => const answered = () => Boolean(value()) return (
- {tr(language.t, q.questionKey, q.question)} - + + {tr(language.t, q.questionKey, q.question)} + + {answered() ? value() : language.t("ui.question.review.notAnswered")}
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/SuggestBar.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/SuggestBar.tsx index 2eac797698..63cc677ad6 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/SuggestBar.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/SuggestBar.tsx @@ -30,7 +30,9 @@ export const SuggestBar: Component<{ request: SuggestionRequest }> = (props) => - {props.request.text} + + {props.request.text} +
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/TabDnd.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/TabDnd.tsx index 37c650a228..bd86ada3d5 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/TabDnd.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/TabDnd.tsx @@ -27,13 +27,13 @@ export const ConstrainDragYAxis: Component = () => { return null } -export const SortableTabContainer: ParentComponent<{ id: string }> = (props) => { +export const SortableTabContainer: ParentComponent<{ id: string; class?: string }> = (props) => { const sortable = createSortable(props.id) void sortable return (
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/prompt-rail.ts b/packages/kilo-vscode/webview-ui/src/components/chat/prompt-rail.ts new file mode 100644 index 0000000000..e20de18cfc --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/components/chat/prompt-rail.ts @@ -0,0 +1,98 @@ +import type { Part } from "../../types/messages" +import type { TranscriptRow } from "../../context/transcript-rows" + +export interface PromptRailItem { + key: string + turn: string + queued: boolean + prompt: string + answer: string +} + +const PROMPT_LIMIT = 160 +const ANSWER_LIMIT = 220 + +/** + * Height of the tallest card row (padding + a one-line prompt + a two-line + * answer), and the unit the fit cap is measured in. Deliberately the worst + * case rather than an average: "only show what fits" should stay true for a + * card whose rows all wrap, not just for a lucky mix of short ones. + */ +export const ROW_HEIGHT = 76 +/** Vertical padding reserved at the top and bottom of the rail. */ +export const RAIL_INSET = 24 + +/** + * How many prompts fit the available transcript height. The card and the rail + * always render the same set, so this one number drives both. + */ +export function capacity(height: number): number { + return Math.floor((height - RAIL_INSET) / ROW_HEIGHT) +} + +// The card never renders markdown — user message text shows literally, and +// assistant text should too. Code spans (inline and fenced) are dropped, and +// link URLs / images are stripped rather than parsed (mirrors MessageList's +// stripMarkdownLinkUrls split so bracket text inside inline code stays out, +// not half-stripped). +export function previewText(raw: string): string { + const segments = raw.split(/(```[\s\S]*?```|`[^`\n]*`)/g) + const text = segments.map((segment, i) => (i % 2 === 1 ? "" : stripLinks(segment))).join(" ") + return text + .replace(/^#{1,6}\s+/gm, "") + .replace(/^\s*[-*>+]\s+/gm, "") + .replace(/\s+/g, " ") + .trim() +} + +function stripLinks(text: string) { + return text.replace(/!\[[^\]]*\]\([^)]*\)/g, "").replace(/\[([^\]]*)\]\([^)]*\)/g, "$1") +} + +function text(parts: Part[], limit: number): string { + const joined = parts + .filter((part) => part.type === "text" && !part.synthetic && part.text.trim()) + .map((part) => (part.type === "text" ? part.text : "")) + .join("\n") + return truncate(previewText(joined), limit) +} + +function truncate(value: string, limit: number) { + return value.length <= limit ? value : `${value.slice(0, limit - 1)}…` +} + +export function promptItems(rows: TranscriptRow[]): PromptRailItem[] { + const items: PromptRailItem[] = [] + for (const row of rows) { + if (row.type !== "user") continue + items.push({ key: row.key, turn: row.turn, queued: row.queued, prompt: text(row.parts, PROMPT_LIMIT), answer: "" }) + } + // Answer text is grouped by turn so one pass fills every item; assistant + // rows follow their user row and carry the same `turn` id. + let index = 0 + for (const row of rows) { + if (row.type === "user") { + index += 1 + continue + } + if (row.type !== "assistant" || index === 0) continue + const item = items[index - 1]! + if (item.turn !== row.turn || item.answer) continue + const value = text(row.parts, ANSWER_LIMIT) + if (value) item.answer = value + } + // A prompt can carry no text at all (image-only or file-only message). Rather + // than render a blank row, promote the answer into the label so the row still + // says something; if neither has text the card falls back to its placeholder. + for (const item of items) { + if (item.prompt || !item.answer) continue + item.prompt = truncate(item.answer, PROMPT_LIMIT) + item.answer = "" + } + return items +} + +export function railItems(items: PromptRailItem[], capacity: number): PromptRailItem[] { + if (capacity < 1) return [] + return items.slice(-capacity) +} diff --git a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css index a695688f17..8d889d93be 100644 --- a/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css +++ b/packages/kilo-vscode/webview-ui/src/components/marketplace/marketplace.css @@ -272,15 +272,6 @@ border-color: var(--border-critical-base) !important; } -/* Remove dialog actions */ - -.marketplace-remove-actions { - display: flex; - justify-content: flex-end; - gap: 8px; - margin-top: 16px; -} - /* Danger button (remove confirm) */ .danger-btn { diff --git a/packages/kilo-vscode/webview-ui/src/components/migration/migration.css b/packages/kilo-vscode/webview-ui/src/components/migration/migration.css index 69e288976f..222beb572c 100644 --- a/packages/kilo-vscode/webview-ui/src/components/migration/migration.css +++ b/packages/kilo-vscode/webview-ui/src/components/migration/migration.css @@ -302,50 +302,6 @@ margin-top: 1px; } -.migration-wizard__error-text { - font-size: var(--kilo-font-size-11); - color: var(--vscode-errorForeground); - line-height: 1.4; - margin-top: 4px; -} - -.migration-wizard__error-box { - margin-top: 12px; - padding: 12px 14px; - border: 1px solid color-mix(in srgb, var(--vscode-errorForeground) 28%, transparent); - border-radius: 12px; - background: - linear-gradient(180deg, color-mix(in srgb, var(--vscode-errorForeground) 8%, transparent), transparent), - color-mix(in srgb, var(--vscode-editor-background) 92%, var(--vscode-errorForeground) 8%); - box-shadow: - inset 0 1px 0 rgba(255, 255, 255, 0.04), - 0 10px 24px rgba(0, 0, 0, 0.14); -} - -.migration-wizard__error-box-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - margin-bottom: 8px; -} - -.migration-wizard__error-box-title { - font-size: var(--kilo-font-size-12); - font-weight: 700; - color: var(--vscode-foreground); - letter-spacing: 0.01em; -} - -.migration-wizard__error-box .migration-wizard__error-text { - margin-top: 0; - font-size: var(--kilo-font-size-12); - line-height: 1.55; - color: color-mix(in srgb, var(--vscode-errorForeground) 92%, white 8%); - white-space: pre-wrap; - word-break: break-word; -} - .migration-session-card { margin-top: 12px; padding: 12px; @@ -534,19 +490,12 @@ gap: 12px; } -.migration-session-summary__label, -.migration-session-summary__value { +.migration-session-summary__label { font-size: var(--kilo-font-size-12); line-height: 1.5; color: var(--vscode-foreground); } -.migration-session-summary__value { - color: var(--vscode-descriptionForeground); - white-space: pre-wrap; - word-break: break-word; -} - .migration-session-progress__header { font-size: var(--kilo-font-size-12); font-weight: 700; @@ -640,12 +589,6 @@ white-space: nowrap; } -.migration-session-progress__count { - flex-shrink: 0; - font-size: var(--kilo-font-size-11); - color: var(--vscode-descriptionForeground); -} - .migration-wizard__copy-btn { appearance: none; border: 1px solid var(--vscode-panel-border); @@ -679,16 +622,6 @@ outline-offset: 2px; } -/* Disabled items */ -.migration-wizard__item--disabled { - opacity: 0.45; - pointer-events: none; -} - -.migration-wizard__item--disabled .migration-wizard__checkmark { - border-color: color-mix(in srgb, var(--vscode-foreground) 8%, transparent); -} - /* Clickable item (cleanup row) — text area triggers checkbox */ .migration-wizard__item--clickable .migration-wizard__item-text { cursor: pointer; @@ -721,10 +654,6 @@ margin-top: 20px; } -.migration-wizard__footer--space-between { - justify-content: space-between; -} - .migration-wizard__btn-group { display: flex; gap: 10px; @@ -811,7 +740,6 @@ body.vscode-high-contrast-light { background: var(--vscode-contrastBorder, var(--vscode-panel-border)); } - .migration-wizard__error-box, .migration-wizard__copy-btn { border-color: var(--vscode-contrastBorder, var(--vscode-errorForeground)); } diff --git a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx index 63d45639c4..380f33efd9 100644 --- a/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/settings/AgentBehaviourTab.tsx @@ -198,8 +198,6 @@ const AgentBehaviourTab: Component = () => { )) } - const removableModes = createMemo(() => session.allAgents().filter((a) => !a.native)) - const confirmRemoveMode = (agent: AgentInfo) => { dialog.show(() => ( diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx index d3c50fbf0f..b6e83e2836 100644 --- a/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx @@ -726,334 +726,339 @@ export const ModelSelectorBase: Component = (props) => { {props.description} - - {triggerLabel()} - - - - - - - - - - - - } - class={`model-selector-popover${expanded() ? " model-selector-popover--expanded" : ""}`} - > - {(bodyH) => { - createEffect(() => { - if (!expanded()) return - const h = bodyH() - if (h === undefined) return - const chrome = (searchWrapperRef?.offsetHeight ?? 0) + (splitterRef?.offsetHeight ?? 0) - setPreviewHeight((h - chrome) / 2) - }) - return ( -
-
- { - setBrowsing(false) - setNavigating(false) - setSearch(e.currentTarget.value) - }} - onMouseDown={(e) => { - const input = e.currentTarget - if (input.selectionStart !== input.selectionEnd || input.selectionStart !== input.value.length) { + + + {triggerLabel()} + + + + + + + + + + + + } + class={`model-selector-popover${expanded() ? " model-selector-popover--expanded" : ""}`} + > + {(bodyH) => { + createEffect(() => { + if (!expanded()) return + const h = bodyH() + if (h === undefined) return + const chrome = (searchWrapperRef?.offsetHeight ?? 0) + (splitterRef?.offsetHeight ?? 0) + setPreviewHeight((h - chrome) / 2) + }) + return ( +
+
+ { setBrowsing(false) setNavigating(false) - } - }} - /> - - { - if (expanded()) { - setPreActiveKey(null) - setPreviewKey(null) + setSearch(e.currentTarget.value) + }} + onMouseDown={(e) => { + const input = e.currentTarget + if (input.selectionStart !== input.selectionEnd || input.selectionStart !== input.value.length) { + setBrowsing(false) + setNavigating(false) } - setExpanded(!expanded()) - requestAnimationFrame(() => { - searchRef?.focus() - scrollRow(preActiveKey() ?? selectedKey(), "nearest") - }) }} /> - -
- -
- -
- {language.t("dialog.model.empty")} -
-
- - 0}> - - { - // eslint-disable-next-line complexity - (node) => { - if (node.kind === "group" && node.group) { - const group = node.group - const key = groupKey(group.key) - const shown = () => isGroupOpen(group.key) + { + if (expanded()) { + setPreActiveKey(null) + setPreviewKey(null) + } + setExpanded(!expanded()) + requestAnimationFrame(() => { + searchRef?.focus() + scrollRow(preActiveKey() ?? selectedKey(), "nearest") + }) + }} + /> + +
+ +
+ +
+ {language.t("dialog.model.empty")} +
+
+ + 0}> + + { + // eslint-disable-next-line complexity + (node) => { + if (node.kind === "group" && node.group) { + const group = node.group + const key = groupKey(group.key) + const shown = () => isGroupOpen(group.key) + return ( +
e.preventDefault()} + onClick={() => toggleGroup(group.key)} + onMouseMove={() => setPointer(true)} + onMouseEnter={() => { + if (pointer()) setSelectedKey(key) + }} + > + + {group.label} + + +
+ ) + } + + const row = node.row + if (!row) return null + if (row.kind === "clear") { + return ( +
pickClear()} + onMouseMove={() => setPointer(true)} + onMouseEnter={() => { + if (pointer()) setSelectedKey(CLEAR_KEY) + }} + > + + {props.clearLabel ?? language.t("dialog.model.notSet")} + +
+ ) + } + if (!row.model) return null + + const model = row.model + const hovered = () => isSelected(row.key) + const preActive = () => isPreActive(row.key) + const starred = () => favoriteKeys().has(modelKey(model.providerID, model.id)) + const showProvider = () => row.kind === "favorite" + const showSelect = () => expanded() && preActive() && !isActive(model) + const starLabel = () => + `${starred() ? language.t("model.favorite.remove") : language.t("model.favorite.add")}: ${sanitizeName(model.name)}` return (
e.preventDefault()} - onClick={() => toggleGroup(group.key)} - onMouseMove={() => setPointer(true)} - onMouseEnter={() => { - if (pointer()) setSelectedKey(key) - }} + role="presentation" + class={`model-selector-row${hovered() || preActive() ? " selected" : ""}`} > - - {group.label} - -
+ + + + +
) } - - const row = node.row - if (!row) return null - if (row.kind === "clear") { - return ( -
pickClear()} - onMouseMove={() => setPointer(true)} - onMouseEnter={() => { - if (pointer()) setSelectedKey(CLEAR_KEY) - }} - > - - {props.clearLabel ?? language.t("dialog.model.notSet")} - -
- ) - } - if (!row.model) return null - - const model = row.model - const hovered = () => isSelected(row.key) - const preActive = () => isPreActive(row.key) - const starred = () => favoriteKeys().has(modelKey(model.providerID, model.id)) - const showProvider = () => row.kind === "favorite" - const showSelect = () => expanded() && preActive() && !isActive(model) - const starLabel = () => - `${starred() ? language.t("model.favorite.remove") : language.t("model.favorite.add")}: ${sanitizeName(model.name)}` - return ( - - ) } - } - - -
+ + +
- -
- -
- +
+
+ + + +
-
- ) - }} - + ) + }} + + ) } diff --git a/packages/kilo-vscode/webview-ui/src/components/shared/ThinkingSelector.tsx b/packages/kilo-vscode/webview-ui/src/components/shared/ThinkingSelector.tsx index fd03cdcb40..f49c649a9d 100644 --- a/packages/kilo-vscode/webview-ui/src/components/shared/ThinkingSelector.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/shared/ThinkingSelector.tsx @@ -151,6 +151,7 @@ export const ThinkingSelectorBase: Component = (props
} placement="top" + openDelay={0} > = (props) => { }) return ( - +