Просмотр исходного кода

Merge branch 'main' into removal_of_glm4_6

Seb Duerr 3 недель назад
Родитель
Сommit
fbb3b876db
100 измененных файлов с 3287 добавлено и 764 удалено
  1. 0 5
      .changeset/add-zai-glm-4-7-cerebras-model.md
  2. 5 0
      .changeset/cli-continue-reliability.md
  3. 10 0
      .changeset/cli-image-text-paste-loader.md
  4. 0 5
      .changeset/cute-flies-dance.md
  5. 7 2
      .github/dependabot.yml
  6. 8 0
      .husky/pre-commit
  7. 8 0
      .husky/pre-push
  8. 5 2
      .vscode/launch.json
  9. 62 7
      .vscode/tasks.json
  10. 35 1
      AGENTS.md
  11. 218 0
      CHANGELOG.md
  12. 1 1
      README.md
  13. 1 1
      apps/kilocode-docs/blog-posts/auto-generate-commit-messages.md
  14. 43 7
      apps/kilocode-docs/docs/advanced-usage/agent-manager.md
  15. 1 1
      apps/kilocode-docs/docs/advanced-usage/cloud-agent.md
  16. 38 0
      apps/kilocode-docs/docs/advanced-usage/migrating-from-cursor-windsurf.md
  17. 12 0
      apps/kilocode-docs/docs/agent-behavior/skills.md
  18. 7 3
      apps/kilocode-docs/docs/basic-usage/adding-credits.md
  19. BIN
      apps/kilocode-docs/docs/basic-usage/autocomplete-mistral-setup/10-save-settings.png
  20. 14 5
      apps/kilocode-docs/docs/basic-usage/autocomplete/index.md
  21. 86 0
      apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup.md
  22. BIN
      apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/01-open-kilo-code-settings.png
  23. BIN
      apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/02-add-configuration-profile.png
  24. BIN
      apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/03-name-your-profile.png
  25. BIN
      apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/04-select-mistral-provider.png
  26. BIN
      apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/05-get-api-key.png
  27. BIN
      apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/06-navigate-to-codestral.png
  28. BIN
      apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/07-confirm-key-generation.png
  29. BIN
      apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/08-copy-api-key.png
  30. BIN
      apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/09-paste-api-key.png
  31. BIN
      apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/10-save-settings.png
  32. 21 6
      apps/kilocode-docs/docs/basic-usage/the-chat-interface.md
  33. 13 0
      apps/kilocode-docs/docs/cli.md
  34. 145 0
      apps/kilocode-docs/docs/contributing/architecture/model-o11y.md
  35. 21 2
      apps/kilocode-docs/docs/faq.md
  36. 3 3
      apps/kilocode-docs/docs/features/api-configuration-profiles.md
  37. 9 0
      apps/kilocode-docs/docs/features/code-actions.md
  38. 1 1
      apps/kilocode-docs/docs/features/experimental/experimental-features.md
  39. 47 47
      apps/kilocode-docs/docs/features/mcp/using-mcp-in-cli.md
  40. 2 2
      apps/kilocode-docs/docs/getting-started/setting-up.mdx
  41. 12 1
      apps/kilocode-docs/docs/getting-started/your-first-task.md
  42. 4 1
      apps/kilocode-docs/docs/index.mdx
  43. BIN
      apps/kilocode-docs/docs/move-to-secondary.png
  44. 9 4
      apps/kilocode-docs/docs/providers/bedrock.md
  45. 39 20
      apps/kilocode-docs/docs/slack.md
  46. 8 1
      apps/kilocode-docs/docs/tips-and-tricks.md
  47. 10 1
      apps/kilocode-docs/docusaurus.config.ts
  48. 20 11
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/index.md
  49. 89 0
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup.md
  50. BIN
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/01-open-kilo-code-settings.png
  51. BIN
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/02-add-configuration-profile.png
  52. BIN
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/03-name-your-profile.png
  53. BIN
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/04-select-mistral-provider.png
  54. BIN
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/05-get-api-key.png
  55. BIN
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/06-navigate-to-codestral.png
  56. BIN
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/07-confirm-key-generation.png
  57. BIN
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/08-copy-api-key.png
  58. BIN
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/09-paste-api-key.png
  59. BIN
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/10-save-settings.png
  60. 4 4
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/api-configuration-profiles.md
  61. BIN
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/move-to-secondary.png
  62. 4 1
      apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/tips-and-tricks.md
  63. 10 2
      apps/kilocode-docs/sidebars.ts
  64. 3 0
      apps/kilocode-docs/static/videos/configure_free_codestral.mp4
  65. 2 1
      apps/playwright-e2e/tsconfig.json
  66. 2 0
      apps/storybook/.storybook/preview.ts
  67. 43 0
      apps/storybook/src/decorators/withPostMessageMock.tsx
  68. 1 4
      apps/storybook/src/utils/createExtensionStateMock.ts
  69. 71 0
      apps/storybook/stories/KilocodeNotifications.stories.tsx
  70. 0 4
      apps/vscode-e2e/src/suite/extension.test.ts
  71. 1 1
      apps/web-evals/package.json
  72. 25 7
      apps/web-evals/src/app/api/runs/[id]/logs/failed/route.ts
  73. 476 276
      apps/web-evals/src/app/runs/[id]/run.tsx
  74. 71 63
      apps/web-evals/src/components/home/run.tsx
  75. 384 91
      apps/web-evals/src/components/home/runs.tsx
  76. 30 0
      apps/web-evals/src/lib/__tests__/formatters.spec.ts
  77. 18 17
      apps/web-evals/src/lib/formatters.ts
  78. 1 1
      apps/web-roo-code/package.json
  79. 297 0
      apps/web-roo-code/src/app/cloud/team/page.tsx
  80. 2 2
      apps/web-roo-code/src/app/extension/page.tsx
  81. 14 3
      apps/web-roo-code/src/app/pricing/page.tsx
  82. 2 1
      apps/web-roo-code/src/app/privacy/page.tsx
  83. 0 5
      apps/web-roo-code/src/components/chromes/nav-bar.tsx
  84. 1 1
      apps/web-roo-code/src/components/homepage/pillars-section.tsx
  85. 1 0
      apps/web-roo-code/src/lib/constants.ts
  86. 141 0
      cli/CHANGELOG.md
  87. 42 1
      cli/README.md
  88. 35 4
      cli/docs/DEVELOPMENT.md
  89. 37 0
      cli/docs/DISK_SPACE_MANAGEMENT.md
  90. 18 5
      cli/esbuild.config.mjs
  91. 1 11
      cli/package.dist.json
  92. 85 89
      cli/package.json
  93. 148 0
      cli/src/__tests__/agent-manager-no-config.test.ts
  94. 207 0
      cli/src/__tests__/attach-flag.test.ts
  95. 3 3
      cli/src/__tests__/cli-provider-model.test.ts
  96. 62 10
      cli/src/auth/index.ts
  97. 0 1
      cli/src/auth/providers/config.ts
  98. 10 1
      cli/src/auth/providers/factory.ts
  99. 8 7
      cli/src/auth/providers/kilocode/shared.ts
  100. 13 8
      cli/src/auth/providers/kilocode/token-auth.ts

+ 0 - 5
.changeset/add-zai-glm-4-7-cerebras-model.md

@@ -1,5 +0,0 @@
----
-"kilo-code": patch
----
-
-Add `zai-glm-4.7` to Cerebras models

+ 5 - 0
.changeset/cli-continue-reliability.md

@@ -0,0 +1,5 @@
+---
+"@kilocode/cli": patch
+---
+
+Improve --continue flag reliability by replacing fixed 2-second timeout with Promise-based response handling

+ 10 - 0
.changeset/cli-image-text-paste-loader.md

@@ -0,0 +1,10 @@
+---
+"@kilocode/cli": patch
+---
+
+Fix missing visual feedback and input blocking during paste operations
+
+- Display "Pasting image..." loader when pasting images via Cmd+V/Ctrl+V
+- Display "Pasting text..." loader when pasting large text (10+ lines)
+- Block keyboard input during paste operations to prevent concurrent writes
+- Support multiple concurrent paste operations with counter-based tracking

+ 0 - 5
.changeset/cute-flies-dance.md

@@ -1,5 +0,0 @@
----
-"kilo-code": patch
----
-
-Improved prompt caching when using Anthropic models on OpenRouter with native tool calling

+ 7 - 2
.github/dependabot.yml

@@ -5,7 +5,12 @@
 
 version: 2
 updates:
-  - package-ecosystem: "npm" # See documentation for possible values
-    directory: "/" # Location of package manifests
+  - package-ecosystem: "npm"
+    directory: "/"
+    schedule:
+      interval: "weekly"
+
+  - package-ecosystem: "npm"
+    directory: "/cli"
     schedule:
       interval: "weekly"

+ 8 - 0
.husky/pre-commit

@@ -1,3 +1,11 @@
+#!/usr/bin/env sh
+# Add node_modules/.bin to PATH for local binaries
+# Use git to find the repository root reliably
+REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "$(dirname -- "$0")/../..")"
+if [ -d "$REPO_ROOT/node_modules/.bin" ]; then
+    export PATH="$REPO_ROOT/node_modules/.bin:$PATH"
+fi
+
 branch="$(git rev-parse --abbrev-ref HEAD)"
 
 if [ "$branch" = "main" ]; then

+ 8 - 0
.husky/pre-push

@@ -1,3 +1,11 @@
+#!/usr/bin/env sh
+# Add node_modules/.bin to PATH for local binaries
+# Use git to find the repository root reliably
+REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || echo "$(dirname -- "$0")/../..")"
+if [ -d "$REPO_ROOT/node_modules/.bin" ]; then
+    export PATH="$REPO_ROOT/node_modules/.bin:$PATH"
+fi
+
 branch="$(git rev-parse --abbrev-ref HEAD)"
 
 if [ "$branch" = "main" ]; then

+ 5 - 2
.vscode/launch.json

@@ -16,7 +16,8 @@
 			"preLaunchTask": "${defaultBuildTask}",
 			"env": {
 				"NODE_ENV": "development",
-				"VSCODE_DEBUG_MODE": "true"
+				"VSCODE_DEBUG_MODE": "true",
+				"KILOCODE_DEV_CLI_PATH": "${workspaceFolder}/cli/dist/index.js"
 			},
 			"resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"],
 			"presentation": {
@@ -40,7 +41,8 @@
 			"preLaunchTask": "${defaultBuildTask}",
 			"env": {
 				"NODE_ENV": "development",
-				"VSCODE_DEBUG_MODE": "true"
+				"VSCODE_DEBUG_MODE": "true",
+				"KILOCODE_DEV_CLI_PATH": "${workspaceFolder}/cli/dist/index.js"
 			},
 			"resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"],
 			"presentation": { "hidden": false, "group": "tasks", "order": 1 }
@@ -57,6 +59,7 @@
 			"env": {
 				"NODE_ENV": "development",
 				"VSCODE_DEBUG_MODE": "true",
+				"KILOCODE_DEV_CLI_PATH": "${workspaceFolder}/cli/dist/index.js",
 				"KILOCODE_BACKEND_BASE_URL": "${input:kilocodeBackendBaseUrl}"
 			},
 			"resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"],

+ 62 - 7
.vscode/tasks.json

@@ -5,7 +5,7 @@
 	"tasks": [
 		{
 			"label": "watch",
-			"dependsOn": ["watch:pnpm", "watch:webview", "watch:bundle", "watch:tsc"],
+			"dependsOn": ["watch:pnpm", "watch:webview", "watch:bundle", "watch:tsc", "watch:cli"],
 			"presentation": {
 				"reveal": "never"
 			},
@@ -34,10 +34,10 @@
 				"--exec",
 				"pnpm install"
 			],
-			"group": "none",
+			"group": "build",
 			"isBackground": true,
 			"presentation": {
-				"group": "none",
+				"group": "watch:core",
 				"reveal": "always"
 			},
 			"problemMatcher": {
@@ -66,7 +66,7 @@
 			},
 			"isBackground": true,
 			"presentation": {
-				"group": "watch",
+				"group": "watch:core",
 				"reveal": "always"
 			}
 		},
@@ -89,7 +89,7 @@
 			},
 			"isBackground": true,
 			"presentation": {
-				"group": "watch",
+				"group": "watch:core",
 				"reveal": "always"
 			}
 		},
@@ -98,11 +98,66 @@
 			"dependsOn": ["watch:pnpm"],
 			"type": "shell",
 			"command": "npx turbo watch:tsc",
-			"group": "none",
+			"group": "build",
 			"problemMatcher": "$tsc-watch",
 			"isBackground": true,
 			"presentation": {
-				"group": "none",
+				"group": "watch:core",
+				"reveal": "always"
+			}
+		},
+		{
+			"label": "watch:cli:setup",
+			"dependsOn": ["watch:pnpm"],
+			"type": "shell",
+			"command": "node -e \"require('fs').existsSync('cli/dist/node_modules') || process.exit(1)\" || pnpm --filter @kilocode/cli dev:setup",
+			"group": "build",
+			"presentation": {
+				"group": "watch:cli",
+				"reveal": "silent"
+			},
+			"problemMatcher": []
+		},
+		{
+			"label": "watch:cli:deps",
+			"dependsOn": ["watch:cli:setup"],
+			"type": "shell",
+			"command": "npx nodemon --watch cli/package.dist.json --exec \"pnpm --filter @kilocode/cli deps:install\"",
+			"group": "build",
+			"isBackground": true,
+			"presentation": {
+				"group": "watch:cli",
+				"reveal": "silent"
+			},
+			"problemMatcher": {
+				"pattern": { "regexp": "^$" },
+				"background": {
+					"activeOnStart": true,
+					"beginsPattern": "\\[nodemon\\] starting",
+					"endsPattern": "\\[nodemon\\] clean exit - waiting for changes before restart"
+				}
+			}
+		},
+		{
+			"label": "watch:cli",
+			"dependsOn": ["watch:cli:setup", "watch:cli:deps"],
+			"type": "shell",
+			"command": "pnpm --filter @kilocode/cli dev",
+			"group": "build",
+			"problemMatcher": {
+				"owner": "esbuild",
+				"pattern": {
+					"regexp": "^$"
+				},
+				"background": {
+					"activeOnStart": true,
+					"beginsPattern": "esbuild-problem-matcher#onStart",
+					"endsPattern": "esbuild-problem-matcher#onEnd"
+				}
+			},
+			"isBackground": true,
+			"presentation": {
+				"group": "watch:cli",
 				"reveal": "always"
 			}
 		},

+ 35 - 1
AGENTS.md

@@ -2,6 +2,32 @@
 
 Kilo Code is an open source AI coding agent for VS Code that generates code from natural language, automates tasks, and supports 500+ AI models.
 
+## Project Structure
+
+This is a pnpm monorepo using Turbo for task orchestration:
+
+- **`src/`** - VSCode extension (core logic, API providers, tools)
+- **`webview-ui/`** - React frontend (chat UI, settings)
+- **`cli/`** - Standalone CLI package
+- **`packages/`** - Shared packages (`types`, `ipc`, `telemetry`, `cloud`)
+- **`jetbrains/`** - JetBrains plugin (Kotlin + Node.js host)
+- **`apps/`** - E2E tests, Storybook, docs
+
+Key source directories:
+
+- `src/api/providers/` - AI provider implementations (50+ providers)
+- `src/core/tools/` - Tool implementations (ReadFile, ApplyDiff, ExecuteCommand, etc.)
+- `src/services/` - Services (MCP, browser, checkpoints, code-index)
+
+## Build Commands
+
+```bash
+pnpm install          # Install all dependencies
+pnpm build            # Build extension (.vsix)
+pnpm lint             # Run ESLint
+pnpm check-types      # TypeScript type checking
+```
+
 ## Skills
 
 - **Translation**: `.kilocode/skills/translation/SKILL.md` - Translation and localization guidelines
@@ -31,7 +57,11 @@ Brief description of the change
 - Use `patch` for fixes, `minor` for features, `major` for breaking changes
 - For CLI changes, use `"@kilocode/cli": patch` instead
 
-Keep changesets concise but well-written as they become part of release notes.
+Keep changesets concise and feature-oriented as they appear directly in release notes.
+
+- **Only for actual changes**: Documentation-only or internal tooling changes do not need a changeset.
+- **User-focused**: Avoid technical descriptions, code references, or PR numbers. Readers may not know the codebase.
+- **Concise**: Use a one-liner for small fixes. For larger features, a few words or a short sentence is sufficient.
 
 ## Fork Merge Process
 
@@ -42,11 +72,13 @@ Kilo Code is a fork of [Roo Code](https://github.com/RooVetGit/Roo-Code). We per
 To minimize merge conflicts when syncing with upstream, mark Kilo Code-specific changes in shared code with `kilocode_change` comments.
 
 **Single line:**
+
 ```typescript
 const value = 42 // kilocode_change
 ```
 
 **Multi-line:**
+
 ```typescript
 // kilocode_change start
 const foo = 1
@@ -55,6 +87,7 @@ const bar = 2
 ```
 
 **New files:**
+
 ```typescript
 // kilocode_change - new file
 ```
@@ -65,6 +98,7 @@ Code in these directories is Kilo Code-specific and doesn't need markers:
 
 - `cli/` - CLI package
 - `jetbrains/` - JetBrains plugin
+- `agent-manager/` directories
 - Any path containing `kilocode` in filename or directory name
 - `src/services/ghost/` - Ghost service
 

+ 218 - 0
CHANGELOG.md

@@ -1,5 +1,223 @@
 # kilo-code
 
+## 4.148.1
+
+### Patch Changes
+
+- [#5138](https://github.com/Kilo-Org/kilocode/pull/5138) [`e5d08e5`](https://github.com/Kilo-Org/kilocode/commit/e5d08e5464ee85a50cbded2af5a2d0bd3a5390e2) Thanks [@kevinvandijk](https://github.com/kevinvandijk)! - fix: prevent duplicate tool_result blocks causing API errors (thanks @daniel-lxs)
+
+- [#5118](https://github.com/Kilo-Org/kilocode/pull/5118) [`9ff3a91`](https://github.com/Kilo-Org/kilocode/commit/9ff3a919ecc9430c8c6c71659cfe1fa734d92877) Thanks [@lambertjosh](https://github.com/lambertjosh)! - Fix model search matching for free tags.
+
+## 4.148.0
+
+### Minor Changes
+
+- [#4903](https://github.com/Kilo-Org/kilocode/pull/4903) [`db67550`](https://github.com/Kilo-Org/kilocode/commit/db6755024b651ec8401e90935a8185f3c9a145c8) Thanks [@eliasto](https://github.com/eliasto)! - feat(ovhcloud): Add native function calling support
+
+### Patch Changes
+
+- [#5073](https://github.com/Kilo-Org/kilocode/pull/5073) [`ab88311`](https://github.com/Kilo-Org/kilocode/commit/ab883117517b2037e23ab67c68874846be3e5c7c) Thanks [@jrf0110](https://github.com/jrf0110)! - Supports AI Attribution and code formatters format on save. Previously, the AI attribution service would not account for the fact that after saving, the AI generated code would completely change based on the user's configured formatter. This change fixes the issue by using the formatted result for attribution.
+
+- [#5106](https://github.com/Kilo-Org/kilocode/pull/5106) [`a55d1a5`](https://github.com/Kilo-Org/kilocode/commit/a55d1a58a6d127d8649baa95c1a526e119b984fe) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix slow CLI termination when pressing Ctrl+C during prompt selection
+
+    MCP server connection cleanup now uses fire-and-forget pattern for transport.close() and client.close() calls, which could previously block for 2+ seconds if MCP servers were unresponsive. This ensures fast exit behavior when the user wants to quit quickly.
+
+- [#5102](https://github.com/Kilo-Org/kilocode/pull/5102) [`7a528c4`](https://github.com/Kilo-Org/kilocode/commit/7a528c42e1de49336b914ca0cbd58057a16259ad) Thanks [@chrarnoldus](https://github.com/chrarnoldus)! - Partial reads are now allowed by default, prevent the context to grow too quickly.
+
+- Updated dependencies [[`b2e2630`](https://github.com/Kilo-Org/kilocode/commit/b2e26304e562e516383fbf95a3fdc668d88e1487)]:
+    - @kilocode/[email protected]
+
+## 4.147.0
+
+### Minor Changes
+
+- [#5023](https://github.com/Kilo-Org/kilocode/pull/5023) [`879bd5d`](https://github.com/Kilo-Org/kilocode/commit/879bd5d6aa8d8e422cf0711ab2729abec10ee511) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Agent Manager now lets you choose which AI model to use when starting a new session. Your model selection is remembered across panel reopens, and active sessions display the model being used.
+
+### Patch Changes
+
+- [#5060](https://github.com/Kilo-Org/kilocode/pull/5060) [`ce99875`](https://github.com/Kilo-Org/kilocode/commit/ce998755310094117d687cc271e117005a46cd90) Thanks [@DoubleDoubleBonus](https://github.com/DoubleDoubleBonus)! - Add OpenAI Native model option gpt-5.2-codex.
+
+- [#4686](https://github.com/Kilo-Org/kilocode/pull/4686) [`2bd899e`](https://github.com/Kilo-Org/kilocode/commit/2bd899eede90bc1e11b32cce55dd52f3e7ac9323) Thanks [@Ashwinhegde19](https://github.com/Ashwinhegde19)! - Fix BrowserSessionRow crash on non-string inputs
+
+- [#4381](https://github.com/Kilo-Org/kilocode/pull/4381) [`e37b839`](https://github.com/Kilo-Org/kilocode/commit/e37b8397bcd1f8bd8742e29b1af8edabc5ddf9db) Thanks [@inj-src](https://github.com/inj-src)! - fix: better chat view by limiting the maximum width
+
+- [#5028](https://github.com/Kilo-Org/kilocode/pull/5028) [`885a54a`](https://github.com/Kilo-Org/kilocode/commit/885a54aae6c43620c431eeb055794f00f2dada0b) Thanks [@chrarnoldus](https://github.com/chrarnoldus)! - Visual Studio Code's telemetry setting is now respected
+
+- [#4406](https://github.com/Kilo-Org/kilocode/pull/4406) [`7dd14bd`](https://github.com/Kilo-Org/kilocode/commit/7dd14bd35c7aa82bdcbe179a6b1141735778b5a2) Thanks [@Secsys-FDU](https://github.com/Secsys-FDU)! - fix: block Windows CMD injection vectors in auto-approved commands
+
+## 4.146.0
+
+### Minor Changes
+
+- [#4865](https://github.com/Kilo-Org/kilocode/pull/4865) [`d9e65fe`](https://github.com/Kilo-Org/kilocode/commit/d9e65fe1027943a51cfc1dd97c2eed86ed104748) Thanks [@kevinvandijk](https://github.com/kevinvandijk)! - Include changes from Roo Code v3.36.7-v3.38.3
+
+    - Feat: Add option in Context settings to recursively load `.kilocode/rules` and `AGENTS.md` from subdirectories (PR #10446 by @mrubens)
+    - Fix: Stop frequent Claude Code sign-ins by hardening OAuth refresh token handling (PR #10410 by @hannesrudolph)
+    - Fix: Add `maxConcurrentFileReads` limit to native `read_file` tool schema (PR #10449 by @app/roomote)
+    - Fix: Add type check for `lastMessage.text` in TTS useEffect to prevent runtime errors (PR #10431 by @app/roomote)
+    - Align skills system with Agent Skills specification (PR #10409 by @hannesrudolph)
+    - Prevent write_to_file from creating files at truncated paths (PR #10415 by @mrubens and @daniel-lxs)
+    - Fix rate limit wait display (PR #10389 by @hannesrudolph)
+    - Remove human-relay provider (PR #10388 by @hannesrudolph)
+    - Fix: Flush pending tool results before condensing context (PR #10379 by @daniel-lxs)
+    - Fix: Revert mergeToolResultText for OpenAI-compatible providers (PR #10381 by @hannesrudolph)
+    - Fix: Enforce maxConcurrentFileReads limit in read_file tool (PR #10363 by @roomote)
+    - Fix: Improve feedback message when read_file is used on a directory (PR #10371 by @roomote)
+    - Fix: Handle custom tool use similarly to MCP tools for IPC schema purposes (PR #10364 by @jr)
+    - Add support for npm packages and .env files to custom tools, allowing custom tools to import dependencies and access environment variables (PR #10336 by @cte)
+    - Remove simpleReadFileTool feature, streamlining the file reading experience (PR #10254 by @app/roomote)
+    - Remove OpenRouter Transforms feature (PR #10341 by @app/roomote)
+    - Fix: Send native tool definitions by default for OpenAI to ensure proper tool usage (PR #10314 by @hannesrudolph)
+    - Fix: Preserve reasoning_details shape to prevent malformed responses when processing model output (PR #10313 by @hannesrudolph)
+    - Fix: Drain queued messages while waiting for ask to prevent message loss (PR #10315 by @hannesrudolph)
+    - Feat: Add grace retry for empty assistant messages to improve reliability (PR #10297 by @hannesrudolph)
+    - Feat: Enable mergeToolResultText for all OpenAI-compatible providers for better tool result handling (PR #10299 by @hannesrudolph)
+    - Feat: Strengthen native tool-use guidance in prompts for improved model behavior (PR #10311 by @hannesrudolph)
+    - Add MiniMax M2.1 and improve environment_details handling for Minimax thinking models (PR #10284 by @hannesrudolph)
+    - Add GLM-4.7 model with thinking mode support for Zai provider (PR #10282 by @hannesrudolph)
+    - Add experimental custom tool calling - define custom tools that integrate seamlessly with your AI workflow (PR #10083 by @cte)
+    - Deprecate XML tool protocol selection and force native tool format for new tasks (PR #10281 by @daniel-lxs)
+    - Fix: Emit tool_call_end events in OpenAI handler when streaming ends (#10275 by @torxeon, PR #10280 by @daniel-lxs)
+    - Fix: Emit tool_call_end events in BaseOpenAiCompatibleProvider (PR #10293 by @hannesrudolph)
+    - Fix: Disable strict mode for MCP tools to preserve optional parameters (PR #10220 by @daniel-lxs)
+    - Fix: Move array-specific properties into anyOf variant in normalizeToolSchema (PR #10276 by @daniel-lxs)
+    - Fix: Add graceful fallback for model parsing in Chutes provider (PR #10279 by @hannesrudolph)
+    - Fix: Enable Requesty refresh models with credentials (PR #10273 by @daniel-lxs)
+    - Fix: Improve reasoning_details accumulation and serialization (PR #10285 by @hannesrudolph)
+    - Fix: Preserve reasoning_content in condense summary for DeepSeek-reasoner (PR #10292 by @hannesrudolph)
+    - Refactor Zai provider to merge environment_details into tool result instead of system message (PR #10289 by @hannesrudolph)
+    - Remove parallel_tool_calls parameter from litellm provider (PR #10274 by @roomote)
+    - Fix: Normalize tool schemas for VS Code LM API to resolve error 400 when using VS Code Language Model API providers (PR #10221 by @hannesrudolph)
+    - Add 1M context window beta support for Claude Sonnet 4 on Vertex AI, enabling significantly larger context for complex tasks (PR #10209 by @hannesrudolph)
+    - Add native tool call defaults for OpenAI-compatible providers, expanding native function calling across more configurations (PR #10213 by @hannesrudolph)
+    - Enable native tool calls for Requesty provider (PR #10211 by @daniel-lxs)
+    - Improve API error handling and visibility with clearer error messages and better user feedback (PR #10204 by @brunobergher)
+    - Add downloadable error diagnostics from chat errors, making it easier to troubleshoot and report issues (PR #10188 by @brunobergher)
+    - Fix refresh models button not properly flushing the cache, ensuring model lists update correctly (#9682 by @tl-hbk, PR #9870 by @pdecat)
+    - Fix additionalProperties handling for strict mode compatibility, resolving schema validation issues with certain providers (PR #10210 by @daniel-lxs)
+    - Add native tool calling support for Claude models on Vertex AI, enabling more efficient and reliable tool interactions (PR #10197 by @hannesrudolph)
+    - Fix JSON Schema format value stripping for OpenAI compatibility, resolving issues with unsupported format values (PR #10198 by @daniel-lxs)
+    - Improve "no tools used" error handling with graceful retry mechanism for better reliability when tools fail to execute (PR #10196 by @hannesrudolph)
+    - Change default tool protocol from XML to native for improved reliability and performance (PR #10186 by @mrubens)
+    - Add native tool support for VS Code Language Model API providers (PR #10191 by @daniel-lxs)
+    - Lock task tool protocol for consistent task resumption, ensuring tasks resume with the same protocol they started with (PR #10192 by @daniel-lxs)
+    - Replace edit_file tool alias with actual edit_file tool for improved diff editing capabilities (PR #9983 by @hannesrudolph)
+    - Fix LiteLLM router models by merging default model info for native tool calling support (PR #10187 by @daniel-lxs)
+    - Fix: Add userAgentAppId to Bedrock embedder for code indexing (#10165 by @jackrein, PR #10166 by @roomote)
+    - Update OpenAI and Gemini tool preferences for improved model behavior (PR #10170 by @hannesrudolph)
+    - Add support for Claude Code Provider native tool calling, improving tool execution performance and reliability (PR #10077 by @hannesrudolph)
+    - Enable native tool calling by default for Z.ai models for better model compatibility (PR #10158 by @app/roomote)
+    - Enable native tools by default for OpenAI compatible provider to improve tool calling support (PR #10159 by @daniel-lxs)
+    - Fix: Normalize MCP tool schemas for Bedrock and OpenAI strict mode to ensure proper tool compatibility (PR #10148 by @daniel-lxs)
+    - Fix: Remove dots and colons from MCP tool names for Bedrock compatibility (PR #10152 by @daniel-lxs)
+    - Fix: Convert tool_result to XML text when native tools disabled for Bedrock (PR #10155 by @daniel-lxs)
+    - Fix: Support AWS GovCloud and China region ARNs in Bedrock provider for expanded regional support (PR #10157 by @app/roomote)
+    - Implement interleaved thinking mode for DeepSeek Reasoner, enabling streaming reasoning output (PR #9969 by @hannesrudolph)
+    - Fix: Preserve reasoning_content during tool call sequences in DeepSeek (PR #10141 by @hannesrudolph)
+    - Fix: Correct token counting for context truncation display (PR #9961 by @hannesrudolph)
+    - Fix: Normalize tool call IDs for cross-provider compatibility via OpenRouter, ensuring consistent handling across different AI providers (PR #10102 by @daniel-lxs)
+    - Fix: Add additionalProperties: false to nested MCP tool schemas, improving schema validation and preventing unexpected properties (PR #10109 by @daniel-lxs)
+    - Fix: Validate tool_result IDs in delegation resume flow, preventing errors when resuming delegated tasks (PR #10135 by @daniel-lxs)
+    - Feat: Add full error details to streaming failure dialog, providing more comprehensive information for debugging streaming issues (PR #10131 by @roomote)
+    - Implement incremental token-budgeted file reading for smarter, more efficient file content retrieval (PR #10052 by @jr)
+    - Enable native tools by default for multiple providers including OpenAI, Azure, Google, Vertex, and more (PR #10059 by @daniel-lxs)
+    - Enable native tools by default for Anthropic and add telemetry tracking for tool format usage (PR #10021 by @daniel-lxs)
+    - Fix: Prevent race condition from deleting wrong API messages during streaming (PR #10113 by @hannesrudolph)
+    - Fix: Prevent duplicate MCP tools error by deduplicating servers at source (PR #10096 by @daniel-lxs)
+    - Remove strict ARN validation for Bedrock custom ARN users allowing more flexibility (#10108 by @wisestmumbler, PR #10110 by @roomote)
+    - Add metadata to error details dialog for improved debugging (PR #10050 by @roomote)
+    - Remove description from Bedrock service tiers for cleaner UI (PR #10118 by @mrubens)
+    - Improve tool configuration for OpenAI models in OpenRouter (PR #10082 by @hannesrudolph)
+    - Capture more detailed provider-specific error information from OpenRouter for better debugging (PR #10073 by @jr)
+    - Add Amazon Nova 2 Lite model to Bedrock provider (#9802 by @Smartsheet-JB-Brown, PR #9830 by @roomote)
+    - Add AWS Bedrock service tier support (#9874 by @Smartsheet-JB-Brown, PR #9955 by @roomote)
+    - Remove auto-approve toggles for to-do and retry actions to simplify the approval workflow (PR #10062 by @hannesrudolph)
+    - Move isToolAllowedForMode out of shared directory for better code organization (PR #10089 by @cte)
+
+### Patch Changes
+
+- [#4950](https://github.com/Kilo-Org/kilocode/pull/4950) [`4b31180`](https://github.com/Kilo-Org/kilocode/commit/4b311806d571e115a6f6ab30d910e0bd39cc317b) Thanks [@markijbema](https://github.com/markijbema)! - Fix chat autocomplete to only show suggestions when textarea has focus, text hasn't changed, and clear suggestions on paste
+
+- [#4995](https://github.com/Kilo-Org/kilocode/pull/4995) [`95e9b6d`](https://github.com/Kilo-Org/kilocode/commit/95e9b6d234681d34f3903715de1ceba67e745516) Thanks [@kevinvandijk](https://github.com/kevinvandijk)! - fix: use correct api url for some endpoints
+
+- [#5008](https://github.com/Kilo-Org/kilocode/pull/5008) [`a86cd0c`](https://github.com/Kilo-Org/kilocode/commit/a86cd0c96a0aa0be112ccc5ee957ed3593caf2e8) Thanks [@markijbema](https://github.com/markijbema)! - Minor improvement to markdown autocomplete suggestions
+
+- [#4445](https://github.com/Kilo-Org/kilocode/pull/4445) [`91f9aa3`](https://github.com/Kilo-Org/kilocode/commit/91f9aa34d9f98e85c1500e204b8b576f82c9d606) Thanks [@chriscool](https://github.com/chriscool)! - fix: configure husky hooks for reliable execution
+
+## 4.145.0
+
+### Minor Changes
+
+- [#4955](https://github.com/Kilo-Org/kilocode/pull/4955) [`8789f84`](https://github.com/Kilo-Org/kilocode/commit/8789f84e7d652185fce1767dcc29893080c7da87) Thanks [@iscekic](https://github.com/iscekic)! - add /condense and /compact commands
+
+### Patch Changes
+
+- [#4876](https://github.com/Kilo-Org/kilocode/pull/4876) [`7010f60`](https://github.com/Kilo-Org/kilocode/commit/7010f60bec33b5e1cdeff4a5bc2ad3c638e584cc) Thanks [@markijbema](https://github.com/markijbema)! - Autocomplete: Show entire suggestion when first line has no word characters
+
+- [#4183](https://github.com/Kilo-Org/kilocode/pull/4183) [`de30ffa`](https://github.com/Kilo-Org/kilocode/commit/de30ffa307c2bf0ad72eec67782b67725172f71f) Thanks [@sebastiand-cerebras](https://github.com/sebastiand-cerebras)! - fix(cerebras): use conservative max_tokens and add integration header
+
+    **Conservative max_tokens:**
+    Cerebras rate limiter estimates token consumption using max_completion_tokens upfront rather than actual usage. When agentic tools automatically set this to the model maximum (e.g., 64K), users exhaust their quota prematurely and get rate-limited despite minimal actual token consumption.
+
+    This fix uses a conservative default of 8K tokens instead of the model maximum. This is sufficient for most agentic tool use while preserving rate limit headroom.
+
+    **Integration header:**
+    Added `X-Cerebras-3rd-Party-Integration: kilocode` header to all Cerebras API requests for tracking and analytics.
+
+- [#4856](https://github.com/Kilo-Org/kilocode/pull/4856) [`100462e`](https://github.com/Kilo-Org/kilocode/commit/100462e956f7f7799525ebddb7d10050435047da) Thanks [@markijbema](https://github.com/markijbema)! - Improve autocomplete tooltip messaging when there's no balance
+
+    When a user has a Kilo Code account with no credits, the autocomplete status bar now shows a helpful message explaining that they need to add credits to use autocomplete, rather than just showing a generic token error.
+
+- [#4793](https://github.com/Kilo-Org/kilocode/pull/4793) [`4fff873`](https://github.com/Kilo-Org/kilocode/commit/4fff873a4b28fa66afbcf837358bcd584665a8be) Thanks [@mcowger](https://github.com/mcowger)! - Restore various providers to modelCache endpoint to fix outdated entries.
+
+## 4.144.0
+
+### Minor Changes
+
+- [#4888](https://github.com/Kilo-Org/kilocode/pull/4888) [`334328d`](https://github.com/Kilo-Org/kilocode/commit/334328de5fa1825726b07be5d587550de2c52d91) Thanks [@hassoncs](https://github.com/hassoncs)! - Show notifications when skills are added or removed from the project or global config
+
+### Patch Changes
+
+- [#4880](https://github.com/Kilo-Org/kilocode/pull/4880) [`909bca7`](https://github.com/Kilo-Org/kilocode/commit/909bca7665b91753c3a9fd0435b13f1c91bcb2f2) Thanks [@markijbema](https://github.com/markijbema)! - Fixed that some tasks in task history were red
+
+- [#4862](https://github.com/Kilo-Org/kilocode/pull/4862) [`10ce725`](https://github.com/Kilo-Org/kilocode/commit/10ce72547d207b4f03538ebb3dc525d5bd92727d) Thanks [@catrielmuller](https://github.com/catrielmuller)! - Add Kilo icon to editor toolbar for quick access to open Kilo from any context
+
+- [#4940](https://github.com/Kilo-Org/kilocode/pull/4940) [`9809864`](https://github.com/Kilo-Org/kilocode/commit/9809864ce51474c29b0db2635a19a92520a2f1f1) Thanks [@Drilmo](https://github.com/Drilmo)! - Add KILOCODE_DEV_CLI_PATH support for easier extension + CLI development workflow
+
+- [#4899](https://github.com/Kilo-Org/kilocode/pull/4899) [`7a58919`](https://github.com/Kilo-Org/kilocode/commit/7a58919c7e4e12e0c954031081e12745419bf8b9) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Disable ask_followup_question tool when yolo mode is enabled to prevent the agent from asking itself questions and auto-answering them. Applied to:
+
+    - XML tool descriptions (system prompt)
+    - Native tool filtering
+    - Tool execution (returns error message if model still tries to use the tool from conversation history)
+
+- [#4863](https://github.com/Kilo-Org/kilocode/pull/4863) [`c65b798`](https://github.com/Kilo-Org/kilocode/commit/c65b798d99cd07bae2312d284663cd298a1b3f9e) Thanks [@hassoncs](https://github.com/hassoncs)! - Allow users to pick an input device for Speech-to-Text input
+
+- [#4892](https://github.com/Kilo-Org/kilocode/pull/4892) [`b37c944`](https://github.com/Kilo-Org/kilocode/commit/b37c944a8bea644660b6f2c4400d0b47cbdee979) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix Agent Manager session disappearing immediately after starting due to gitUrl race condition
+
+- [#4898](https://github.com/Kilo-Org/kilocode/pull/4898) [`14b22b6`](https://github.com/Kilo-Org/kilocode/commit/14b22b6b9b947ceab6418d6e43962b5535adad1e) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix session becoming non-interactable after clicking "Finish to Branch" button. The session now remains active so users can continue working after committing changes.
+
+- [#4835](https://github.com/Kilo-Org/kilocode/pull/4835) [`d55c093`](https://github.com/Kilo-Org/kilocode/commit/d55c093797c4a816a86ee5ee000f32a98f28199b) Thanks [@lambertjosh](https://github.com/lambertjosh)! - Add section headers to model selection dropdowns for "Recommended models" and "All models"
+
+- [#4891](https://github.com/Kilo-Org/kilocode/pull/4891) [`20f1a16`](https://github.com/Kilo-Org/kilocode/commit/20f1a16e2ed37bd79332bac8ea1358b01c4acbc0) Thanks [@kevinvandijk](https://github.com/kevinvandijk)! - Fix: prevent double display of MCP marketplace section in settings view
+
+- [#4873](https://github.com/Kilo-Org/kilocode/pull/4873) [`72ed20b`](https://github.com/Kilo-Org/kilocode/commit/72ed20b686f28062fb795beb44377a993bb40a7b) Thanks [@chrarnoldus](https://github.com/chrarnoldus)! - Improve support for VSCode's HTTP proxy settings
+
+- [#4901](https://github.com/Kilo-Org/kilocode/pull/4901) [`140bbf7`](https://github.com/Kilo-Org/kilocode/commit/140bbf7630a81591b18cc60a989690142e6b6039) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Agent Manager: Parallel mode no longer modifies .gitignore
+
+    Worktree exclusion rules are now written to `.git/info/exclude` instead, avoiding changes to tracked files in your repository.
+
+## 4.143.2
+
+### Patch Changes
+
+- [#4833](https://github.com/Kilo-Org/kilocode/pull/4833) [`2c7cd08`](https://github.com/Kilo-Org/kilocode/commit/2c7cd084bf4707eedda61fed554cf15fcc8b065b) Thanks [@sebastiand-cerebras](https://github.com/sebastiand-cerebras)! - Add `zai-glm-4.7` to Cerebras models
+
+- [#4853](https://github.com/Kilo-Org/kilocode/pull/4853) [`435c879`](https://github.com/Kilo-Org/kilocode/commit/435c879a29d55b75f5f6ffe7bf14854630e085cb) Thanks [@chrarnoldus](https://github.com/chrarnoldus)! - Improved prompt caching when using Anthropic models on OpenRouter with native tool calling
+
+- [#4859](https://github.com/Kilo-Org/kilocode/pull/4859) [`35fb2ad`](https://github.com/Kilo-Org/kilocode/commit/35fb2adc65dfb1e71e28f7368f96765062c43579) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix Architect mode unnecessarily switching to Code mode to edit markdown files
+
+- [#4829](https://github.com/Kilo-Org/kilocode/pull/4829) [`4e09e36`](https://github.com/Kilo-Org/kilocode/commit/4e09e36bba165a2ab6f5e07f71a420faa49ea3ec) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix browser action results displaying raw base64 screenshot data as hexadecimal garbage
+
 ## 4.143.1
 
 ### Patch Changes

+ 1 - 1
README.md

@@ -9,7 +9,7 @@
 # 🚀 Kilo
 
 > Kilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.
-> #1 on OpenRouter. 750k+ Kilo Coders. 6.1 trillion tokens used per month.
+> #1 on OpenRouter. 1M+ Kilo Coders. 20T+ tokens processed
 
 - ✨ Generate code from natural language
 - ✅ Checks its own work

+ 1 - 1
apps/kilocode-docs/blog-posts/auto-generate-commit-messages.md

@@ -105,4 +105,4 @@ Give it a try. I think you'll find yourself wondering how you ever managed witho
 
 ---
 
-_Want to learn more about Kilo Code's commit message generation? Check out the [full documentation](https://kilo.ai/docs/basic-usage/git-commit-generation/) I wrote for setup details. And let me know what you think about it or how could we improve it even more here in comments or on our [Discord Server](https://kilo.love/discord)!_
+_Want to learn more about Kilo Code's commit message generation? Check out the [full documentation](https://kilo.ai/docs/basic-usage/git-commit-generation/) I wrote for setup details. And let me know what you think about it or how could we improve it even more here in comments or on our [Discord Server](https://kilo.ai/discord)!_

+ 43 - 7
apps/kilocode-docs/docs/advanced-usage/agent-manager.md

@@ -13,6 +13,7 @@ This page reflects the actual implementation in the extension.
 
 - Install/update the Kilo Code CLI (latest) — see [CLI setup](/cli)
 - Open a project in VS Code (workspace required)
+- Authentication: You must be logged in via the extension settings OR use CLI with kilocode as provider (see [Authentication Requirements](#authentication-requirements))
 
 ## Opening the Agent Manager
 
@@ -48,14 +49,16 @@ Parallel Mode runs the agent in an isolated Git worktree branch, keeping your ma
 
 ### Worktree Location
 
-Worktrees are created in `.kilocode/worktrees/` within your project directory. This folder is automatically added to `.gitignore` to prevent accidental commits.
+Worktrees are created in `.kilocode/worktrees/` within your project directory. This folder is automatically excluded from git via `.git/info/exclude` (a local-only ignore file that doesn't require a commit).
 
 ```
 your-project/
+├── .git/
+│   └── info/
+│       └── exclude   # local ignore rules (includes .kilocode/worktrees/)
 ├── .kilocode/
 │   └── worktrees/
 │       └── feature-branch-1234567890/   # isolated working directory
-├── .gitignore                           # auto-updated with .kilocode/worktrees/
 └── ...
 ```
 
@@ -80,6 +83,35 @@ If you resume a Parallel Mode session later, the extension will:
 1. Reuse the existing worktree if it still exists
 2. Or recreate it from the session's branch
 
+## Authentication Requirements
+
+The Agent Manager requires proper authentication for full functionality, including session syncing and cloud features.
+
+### Supported Authentication Methods
+
+1. **Kilo Code Extension (Recommended)**
+
+    - Sign in through the extension settings
+    - Provides seamless authentication for the Agent Manager
+    - Enables session syncing and cloud features
+
+2. **CLI with Kilo Code Provider**
+    - Use the CLI configured with `kilocode` as the provider
+    - Run `kilocode config` to set up authentication
+    - See [CLI setup](/cli) for details
+
+### BYOK Limitations
+
+**Important:** Bring Your Own Key (BYOK) is not yet supported with the Agent Manager.
+
+If you're using BYOK with providers like Anthropic, OpenAI, or OpenRouter:
+
+- The Agent Manager will not have access to cloud-synced sessions
+- Session syncing features will be unavailable
+- You must use one of the supported authentication methods above for full functionality
+
+To use the Agent Manager with all features enabled, switch to the Kilo Code provider or sign in through the extension.
+
 ## Remote sessions (Cloud)
 
 When signed in (Kilo Cloud), the Agent Manager lists your recent cloud-synced sessions:
@@ -90,19 +122,23 @@ When signed in (Kilo Cloud), the Agent Manager lists your recent cloud-synced se
 - Selecting a remote session loads its message transcript
 - To continue the work locally, send a message — the Agent Manager will spawn a local process bound to that session
 
-Message transcripts are fetched from a signed blob and exclude internal checkpoint “save” markers as chat rows (checkpoints still appear as dedicated entries in the UI).
+Message transcripts are fetched from a signed blob and exclude internal checkpoint "save" markers as chat rows (checkpoints still appear as dedicated entries in the UI).
 
 ## Troubleshooting
 
 - CLI not found or outdated
     - Install/update the CLI: [CLI setup](/cli)
-    - If you see an “unknown option --json-io” error, update to the latest CLI
-- “Please open a folder…” error
+    - If you see an "unknown option --json-io" error, update to the latest CLI
+- "Please open a folder…" error
     - The Agent Manager requires a VS Code workspace folder
-- “Cannot use parallel mode from within a git worktree”
+- "Cannot use parallel mode from within a git worktree"
     - Open the main repository (where .git is a directory), not a worktree checkout
 - Remote sessions not visible
-    - Ensure you’re signed in and the repo’s remote URL matches the sessions you expect to see
+    - Ensure you're signed in and the repo's remote URL matches the sessions you expect to see
+    - If using BYOK, session syncing is not available — switch to Kilo Code provider or sign in through the extension
+- Authentication errors
+    - Verify you're logged in via extension settings or using CLI with kilocode provider
+    - BYOK configurations do not support Agent Manager authentication
 
 ## Related features
 

+ 1 - 1
apps/kilocode-docs/docs/advanced-usage/cloud-agent.md

@@ -31,7 +31,7 @@ Before using Cloud Agents:
 
 - **Compute is free during limited beta**
     - Please provide any feedback in our Cloud Agents beta Discord channel:
-        - [Kilo Discord](https://discord.gg/D2ExdEcq)
+        - [Kilo Discord](https://kilo.ai/discord)
 - **Kilo Code credits are still used** when the agent performs work (model usage, operations, etc.).
 
 ---

+ 38 - 0
apps/kilocode-docs/docs/advanced-usage/migrating-from-cursor-windsurf.md

@@ -6,6 +6,15 @@ sidebar_label: Migrating from Cursor or Windsurf
 
 Quickly migrate your custom rules from Cursor or Windsurf to Kilo Code. The process typically takes just a few minutes per project.
 
+:::info Two Workflow Approaches
+Kilo Code supports **two complementary workflows**—choose the one that fits your style, or use both:
+
+1. **Autocomplete (Ghost)**: Tab-to-accept inline suggestions as you type, similar to Cursor and Windsurf. Enable via Settings → Ghost.
+2. **Chat-driven**: Describe what you want in the chat panel and the AI generates complete implementations.
+
+Many developers combine both approaches: autocomplete for quick completions while typing, and chat for larger refactors or multi-file changes. See [Choosing Your Workflow](#choosing-your-workflow) for details.
+:::
+
 **Last Updated**: November 2025
 
 ## Why Kilo Code's Rules System?
@@ -309,6 +318,7 @@ After migration:
 - [ ] **Update team docs:** Document new `.kilocode/rules/` location
 - [ ] **Commit to version control:** `git add .kilocode/`
 - [ ] **Remove old directories:** Delete `.cursor/` or `.windsurf/` folders once verified
+- [ ] **Set up autocomplete:** If you used Cursor/Windsurf autocomplete, enable Ghost (Settings → Ghost) for the same Tab-to-accept experience
 
 ## Troubleshooting
 
@@ -363,6 +373,34 @@ Cursor's nested directories don't map to Kilo Code. Flatten with descriptive nam
 - **Check setting:** Verify "Use Agent Rules" is enabled in Kilo Code settings (enabled by default)
 - **Reload:** Restart VS Code if needed
 
+### Choosing Your Workflow
+
+Kilo Code supports **both autocomplete and chat-driven workflows**. Choose the approach that fits your coding style, or combine them:
+
+**Autocomplete (Ghost) — Tab-to-accept inline suggestions:**
+
+1. Open Settings → Ghost
+2. Enable Ghost autocomplete
+3. Configure your preferred model for completions
+4. Start typing and press Tab to accept suggestions
+
+This works the same way as Cursor and Windsurf's autocomplete. Ghost provides context-aware suggestions as you type.
+
+**Chat-driven — describe what you want:**
+
+- Open the chat panel and describe your intent: "Add error handling to this function" or "Create a React component for user profiles"
+- The AI generates complete implementations, refactors, or fixes
+- Review and approve changes before they're applied
+
+**Combining both workflows:**
+
+Many developers use both approaches together:
+
+- **Autocomplete** for quick completions while writing new code
+- **Chat** for larger refactors, bug fixes, or multi-file changes
+
+There's no "right" workflow—use whatever helps you code faster
+
 ## Advanced: Creating Custom Modes
 
 For complex workflows, define custom modes with their own rules and permissions:

+ 12 - 0
apps/kilocode-docs/docs/agent-behavior/skills.md

@@ -261,6 +261,18 @@ There are community efforts to build and share agent skills. Some resources incl
 
 4. **Check file location**: Ensure `SKILL.md` is directly inside the skill directory, not nested further.
 
+### Verifying a Skill is Activated
+
+To confirm a skill is properly loaded and available to the agent, you can ask the agent directly. Simply send a message like:
+
+- "Do you have access to skill X?"
+- "Is the skill called X loaded?"
+- "What skills do you have available?"
+
+The agent will respond with information about whether the skill is loaded and accessible. This is the most reliable way to verify that a skill has been activated after adding it or reloading VSCode.
+
+If the agent confirms the skill is available, you're ready to use it. If not, check the troubleshooting steps above to identify and resolve the issue.
+
 ### Common Errors
 
 | Error                           | Cause                                        | Solution                                         |

+ 7 - 3
apps/kilocode-docs/docs/basic-usage/adding-credits.md

@@ -4,9 +4,13 @@ import { DISCORD_URL } from '@site/src/constants.ts'
 
 Once you've used any initial free Kilo Credits, you can easily add more:
 
-- Add a credit card to your account (all payments are securely processed through Stripe)
-- We don't currently offer a subscription, so you only pay for the credits you use, and only when you choose to top up your credits!
-- Leverage your own API provider by [connecting your own API provider](/getting-started/connecting-api-provider.md)
+- Subscribe to the [Kilo Pass](https://kilo.ai/features/kilo-pass), the most cost effective way to add credits.
+- Purchase additional credits as a one-time transaction.
+- Enable automatic top-up, which purchases additional credits when your balance is below $5.
+
+These options are available to purchase from your [personal profile page](https://app.kilo.ai/profile).
+
+You can also use subscriptions or credits you may have purchased directly with an AI provider by adding your keys on the [Bring your own Key (BYOK)](https://app.kilo.ai/byok) settings screen. If your provider is not yet supported, you can also [directly connect your provider](/getting-started/connecting-api-provider.md) in the extension and CLI.
 
 ## Transparent Pricing
 

BIN
apps/kilocode-docs/docs/basic-usage/autocomplete-mistral-setup/10-save-settings.png


+ 14 - 5
apps/kilocode-docs/docs/basic-usage/autocomplete.md → apps/kilocode-docs/docs/basic-usage/autocomplete/index.md

@@ -1,6 +1,7 @@
 ---
 title: Autocomplete
 sidebar_position: 4
+slug: /basic-usage/autocomplete
 ---
 
 # Autocomplete
@@ -18,15 +19,17 @@ Autocomplete analyzes your code context and provides:
 
 ## Triggering Options
 
-### Pause to Complete
+### Code Editor Suggestions
 
-When enabled, Kilo Code automatically triggers autocomplete when you pause typing. This provides a seamless coding experience where suggestions appear naturally as you work.
+#### Auto-trigger suggestions
 
-- **Auto Trigger Delay**: Configure the delay (in seconds) before autocomplete triggers after you stop typing
+When enabled, Kilo Code automatically shows inline suggestions when you pause typing. This provides a seamless coding experience where suggestions appear naturally as you work.
+
+- **Auto Trigger Delay**: Configure the delay (in seconds) before suggestions appear after you stop typing
 - Default is 3 seconds, but this can be adjusted up or down
 - Shorter delays mean quicker suggestions but may be more resource-intensive
 
-### Manual Autocomplete (Cmd+L)
+#### Trigger on keybinding (Cmd+L)
 
 For more control over when suggestions appear:
 
@@ -44,6 +47,12 @@ This is ideal for:
 
 You can customize this keyboard shortcut as well in your VS Code settings.
 
+### Chat Suggestions
+
+#### Enable Chat Autocomplete
+
+When enabled, Kilo Code will suggest completions as you type in the chat input. Press Tab to accept suggestions.
+
 ## Provider and Model Selection
 
 Autocomplete currently uses **Codestral** (by Mistral AI) as the underlying model. This model is specifically optimized for code completion tasks and provides fast, high-quality suggestions.
@@ -102,4 +111,4 @@ If using Cursor, go to **Settings** > **Cursor Settings** > **Tab**, and toggle
 
 ## Related Features
 
-- [Code Actions](../features/code-actions) - Context menu options for common coding tasks
+- [Code Actions](/features/code-actions) - Context menu options for common coding tasks

+ 86 - 0
apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup.md

@@ -0,0 +1,86 @@
+---
+title: Setting Up Mistral for Free Autocomplete
+sidebar_position: 1
+---
+
+# Setting Up Mistral for Free Autocomplete
+
+This guide walks you through setting up Mistral's Codestral model for free autocomplete in Kilo Code. Mistral offers a free tier that's perfect for getting started with AI-powered code completions.
+
+## Video Walkthrough
+
+<iframe width="100%" height="400" src="https://www.youtube.com/embed/0aqBbB8fPho" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
+
+## Step 1: Open Kilo Code Settings
+
+In VS Code, open the Kilo Code panel and click the **Settings** icon (gear) in the top-right corner.
+
+![Open Kilo Code Settings](./mistral-setup/01-open-kilo-code-settings.png)
+
+## Step 2: Add a New Configuration Profile
+
+Navigate to **Settings → Providers** and click **Add Profile** to create a new configuration profile for Mistral.
+
+![Add Configuration Profile](./mistral-setup/02-add-configuration-profile.png)
+
+## Step 3: Name Your Profile
+
+In the "New Configuration Profile" dialog, enter a name like "Mistral profile" (the name can be anything you prefer) and click **Create Profile**.
+
+:::note
+The profile name is just a label for your reference—it doesn't affect functionality. Choose any name that helps you identify this configuration.
+:::
+
+![Create Mistral Profile](./mistral-setup/03-name-your-profile.png)
+
+## Step 4: Select Mistral as Provider
+
+In the **API Provider** dropdown, search for and select **Mistral**.
+
+:::note
+When creating an autocomplete profile, you don't need to select a specific model—Kilo Code will automatically use the appropriate Codestral model optimized for code completions.
+:::
+
+![Select Mistral Provider](./mistral-setup/04-select-mistral-provider.png)
+
+## Step 5: Get Your API Key
+
+You'll see a warning that you need a valid API key. Click **Get Mistral / Codestral API Key** to open the Mistral console.
+
+![Get API Key Button](./mistral-setup/05-get-api-key.png)
+
+## Step 6: Navigate to Codestral in Mistral AI Studio
+
+In the Mistral AI Studio sidebar, click **Codestral** under the Code section.
+
+![Select Codestral](./mistral-setup/06-navigate-to-codestral.png)
+
+## Step 7: Generate API Key
+
+Click the **Generate API Key** button to create your new Codestral API key.
+
+![Confirm Generate](./mistral-setup/07-confirm-key-generation.png)
+
+## Step 8: Copy Your API Key
+
+Once generated, click the **copy** button next to your API key to copy it to your clipboard.
+
+![Copy API Key](./mistral-setup/08-copy-api-key.png)
+
+## Step 9: Paste API Key in Kilo Code
+
+Return to Kilo Code settings and paste your API key into the **Mistral API Key** field.
+
+![Paste API Key](./mistral-setup/09-paste-api-key.png)
+
+## Step 10: Save Your Settings
+
+Click **Save** to apply your Mistral configuration. You're now ready to use free autocomplete!
+
+![Save Settings](./mistral-setup/10-save-settings.png)
+
+## Next Steps
+
+- Learn more about [Autocomplete features](./index.md)
+- Explore [triggering options](./index.md#triggering-options) for autocomplete
+- Check out [best practices](./index.md#best-practices) for optimal results

BIN
apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/01-open-kilo-code-settings.png


BIN
apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/02-add-configuration-profile.png


BIN
apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/03-name-your-profile.png


BIN
apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/04-select-mistral-provider.png


BIN
apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/05-get-api-key.png


BIN
apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/06-navigate-to-codestral.png


BIN
apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/07-confirm-key-generation.png


BIN
apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/08-copy-api-key.png


BIN
apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/09-paste-api-key.png


BIN
apps/kilocode-docs/docs/basic-usage/autocomplete/mistral-setup/10-save-settings.png


+ 21 - 6
apps/kilocode-docs/docs/basic-usage/the-chat-interface.md

@@ -8,6 +8,12 @@ import Image from '@site/src/components/Image';
 
 :::
 
+:::note[Prefer quick completions?]
+
+If you're typing code in the editor and want AI to finish your line or block, check out [Autocomplete](/basic-usage/autocomplete) instead. Chat is best for larger tasks, explanations, and multi-file changes.
+
+:::
+
 ## Quick Setup
 
 Find the Kilo Code icon (<img src="/docs/img/kilo-v1.svg" width="12" />) in VS Code's Primary Side Bar. Click it to open the chat panel.
@@ -41,6 +47,14 @@ find all instances of the variable oldValue in @/src/App.js and replace them wit
 - **One task at a time** - Break complex work into manageable steps
 - **Include examples** - Show the style or format you want
 
+:::info[Chat vs Autocomplete]
+
+**Use chat** when you need to describe what you want, ask questions, or make changes across multiple files.
+
+**Use [autocomplete](/basic-usage/autocomplete)** when you're already typing code and want the AI to finish your thought inline.
+
+:::
+
 ## The Chat Interface
 
 <Image 
@@ -74,12 +88,13 @@ find all instances of the variable oldValue in @/src/App.js and replace them wit
 
 ## Common Mistakes to Avoid
 
-| Instead of this...          | Try this                                                         |
-| --------------------------- | ---------------------------------------------------------------- |
-| "Fix the code"              | "Fix the bug in `calculateTotal` that returns incorrect results" |
-| Assuming Kilo knows context | Use `@` to reference specific files                              |
-| Multiple unrelated tasks    | Submit one focused request at a time                             |
-| Technical jargon overload   | Clear, straightforward language works best                       |
+| Instead of this...                | Try this                                                             |
+| --------------------------------- | -------------------------------------------------------------------- |
+| "Fix the code"                    | "Fix the bug in `calculateTotal` that returns incorrect results"     |
+| Assuming Kilo knows context       | Use `@` to reference specific files                                  |
+| Multiple unrelated tasks          | Submit one focused request at a time                                 |
+| Technical jargon overload         | Clear, straightforward language works best                           |
+| Using chat for tiny code changes. | Use [autocomplete](/basic-usage/autocomplete) for inline completions |
 
 **Why it matters:** Kilo Code works best when you communicate like you're talking to a smart teammate who needs clear direction.
 

+ 13 - 0
apps/kilocode-docs/docs/cli.md

@@ -42,6 +42,19 @@ Upgrade the Kilo CLI package:
 
 ## CLI reference
 
+### Keyboard shortcuts
+
+| Shortcut    | Description                                                                        |
+| ----------- | ---------------------------------------------------------------------------------- |
+| `Shift+Tab` | Cycle through modes (architect → code → ask → debug → orchestrator → custom modes) |
+| `Ctrl+C`    | Exit (press twice to confirm)                                                      |
+| `Ctrl+X`    | Cancel current task                                                                |
+| `Esc`       | Cancel current task (while streaming) or clear input                               |
+| `Ctrl+Y`    | Toggle YOLO mode (auto-approve all operations)                                     |
+| `Ctrl+R`    | Resume task (when a task is ready to resume)                                       |
+| `!`         | Enter shell mode (when input is empty)                                             |
+| `↑/↓`       | Navigate command history (when input is empty)                                     |
+
 ### CLI commands
 
 | Command               | Description                                                      | Example                        |

+ 145 - 0
apps/kilocode-docs/docs/contributing/architecture/model-o11y.md

@@ -0,0 +1,145 @@
+---
+sidebar_position: 11
+title: "Agent Observability"
+---
+
+# Kilo Code - Agent Observability
+
+## Problem Statement
+
+Agentic coding systems like Kilo Code operate with significant autonomy, executing multi-step tasks that involve LLM inference, tool execution, file manipulation, and external API calls. These systems mix traditional systems observability (i.e. request/response) with agentic behavior (i.e. planning, reasoning, and tool use).
+
+At the lower level, we can observe the system as a traditional API, but at the higher level, we need to observe the agent's behavior and the quality of its outputs.
+
+Some examples of customer-facing error modes:
+
+- Model API calls may be slow or fail due to rate limits, network issues, or model unavailability
+- Model API calls may produce invalid JSON or malformed responses
+- An agent may get stuck in a loop, repeatedly attempting the same failing operation
+- Sessions may degrade gradually as context windows fill up
+- The agent may complete a task technically but produce incorrect or unhelpful output
+- Users may abandon sessions out of frustration without explicit error signals
+
+All of these contribute to the overall reliability and user experience of the system.
+
+## Goals
+
+1. Detect and alert on acute incidents within minutes
+2. Surface slow-burn degradations within hours
+3. Facilitate root cause analysis when issues occur
+4. Track quality and efficiency trends over time
+5. Build a foundation for continuous improvement of the agent
+
+**Non-goals for this proposal:**
+
+- Automated remediation
+- A/B testing infrastructure
+
+## Proposed Approach
+
+Focus on the lower-level systems observability first, then build up to higher-level agentic behavior observability.
+
+## Phase 1: Systems Observability
+
+**Objective:** Establish awareness and alerting for hard failures.
+
+This phase focuses on systems metrics we can capture with minimal changes, providing immediate operational visibility.
+
+### Phase 1a: LLM observability and alerting
+
+#### Metrics to Capture
+
+Capture these metrics per LLM API call:
+
+- Provider
+- Model
+- Tool
+- Latency
+- Success / Failure
+- Error type and message (if failed)
+- Token counts
+
+#### Dashboards
+
+Common dashboards which offer filtering based on provider, model, and tool:
+
+- Error rate
+- Latency
+- Token usage
+
+#### Alerting
+
+Implement [multi-window, multi-burn-rate alerting](https://sre.google/workbook/alerting-on-slos/) against error budgets:
+
+| Window | Burn Rate | Action | Use Case            |
+| ------ | --------- | ------ | ------------------- |
+| 5 min  | 14.4x     | Page   | Major Outage              |
+| 30 min | 6x        | Page   | Incident            |
+| 6 hr   | 1x        | Ticket | Change in behavior |
+
+Paging should **only occur on Recommended Models when using the Kilo Gateway**. All other alerts should be tickets, and some may be configured to be ignored.
+
+**Initial alert conditions:**
+
+- LLM API error rate exceeds SLO (per tool/model/provider)
+- Tool error rate exceeds SLO (per tool/model/provider)
+- p50/p90 latency exceeds SLO (per tool/model/provider)
+
+### Phase 1b: Session metrics
+
+#### Metrics to Capture
+
+**Per-session (aggregated at session close or timeout):**
+
+- Session duration
+- Time from user input to first model response
+- Total turns/steps
+- Total tool calls by tool type
+- Total errors by error type
+- Total tokens consumed
+- Termination reason (user closed, timeout, explicit completion, error)
+
+#### Alerting
+
+None.
+
+## Phase 2: Agent Tool Usage
+
+**Objective:** Detect how agents are using tools in a given session.
+
+### Metrics to Capture
+
+**Loop and repetition detection:**
+
+- Count of identical tool calls within a session (same tool + same arguments)
+- Count of identical failing tool calls (same tool + same arguments + same error)
+- Detection of oscillation patterns (alternating between two states)
+
+**Progress indicators:**
+
+- Unique files touched per session
+- Unique tools used per session
+- Ratio of repeated to unique operations
+
+### Alerting
+
+None to start, we will learn.
+
+## Phase 3: Session Outcome Tracking
+
+**Objective:** Understand whether sessions are successful from the user's perspective.
+
+Hard errors and behavior metrics tell us about failures, but we also need signal on overall session health.
+
+### Metrics to Capture
+
+**Explicit signals:**
+
+- User feedback (thumbs up/down) rate and sentiment
+- User abandonment patterns (session ends mid-task without completion signal)
+
+**Implicit signals:**
+
+May require LLM analysis of session transcripts to detect:
+
+- Session termination classification (completed, abandoned, errored, timed out)

+ 21 - 2
apps/kilocode-docs/docs/faq.md

@@ -24,7 +24,7 @@ Kilo Code uses large language models (LLMs) to understand your requests and tran
 - Perform web browsing (if enabled).
 - Use external tools via the Model Context Protocol (MCP).
 
-You interact with Kilo Code through a chat interface, where you provide instructions and review/approve its proposed actions.
+You interact with Kilo Code through a chat interface, where you provide instructions and review/approve its proposed actions, or you can use the inline autocomplete feature which helps you as you type.
 
 ### What can Kilo Code do?
 
@@ -49,7 +49,7 @@ You can also use Kilo Code with a [local model](advanced-usage/local-models) or
 
 ### How do I pay for model usage via Kilo Code?
 
-If you choose to pay for models via Kilo Code, you do so by buying Kilo Credits. You can [buy Kilo Credits](basic-usage/adding-credits) securely via Stripe with a credit card. We do not charge a markup on Kilo Credits. $1 you give us is $1 in Kilo Credits.
+If you choose to pay for models via Kilo Code, you do so by buying Kilo Credits. You can [purchase Kilo Credits](basic-usage/adding-credits) and receive bonus credits. We do not charge a markup on Kilo Credits. $1 you give us is $1 in Kilo Credits.
 
 Model usage is metered by the providers in terms of different kinds of tokens. When you use a model, we debit your Kilo credits by the amount the provider charges us -- with no markup.
 
@@ -105,6 +105,25 @@ Yes, Kilo Code supports running models locally using [Ollama](/providers/ollama)
 
 Open the Kilo Code panel (<img src="/docs/img/kilo-v1.svg" width="12" />) and type your task in the chat box. Be clear and specific about what you want Kilo Code to do. See [The Chat Interface](/basic-usage/the-chat-interface) for best practices.
 
+### When should I use chat vs autocomplete?
+
+Use **chat** when you need to:
+
+- Make complex, multi-file changes
+- Refactor code across your project
+- Get explanations or ask questions
+- Have Kilo Code execute commands or browse the web
+- Work on tasks that require planning and multiple steps
+
+Use **autocomplete** when you need to:
+
+- Complete the current line or block of code quickly
+- Get suggestions for common patterns and boilerplate
+- Make quick, localized edits without context switching
+- Speed up typing repetitive code
+
+In general, autocomplete is best for quick, in-flow coding assistance, while chat is better for larger tasks that require more context and interaction.
+
 ### What are modes in Kilo Code?
 
 [Modes](/basic-usage/using-modes) are different personas that Kilo Code can adopt, each with a specific focus and set of capabilities. The built-in modes are:

+ 3 - 3
apps/kilocode-docs/docs/features/api-configuration-profiles.md

@@ -38,15 +38,15 @@ Note that available settings vary by provider and model. Each provider offers di
       <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-2.png" alt="Provider selection dropdown" width="550" />
     - Enter API key
 
-                 <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-3.png" alt="API key entry field" width="550" />
+                   <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-3.png" alt="API key entry field" width="550" />
 
     - Choose a model
 
-                 <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-8.png" alt="Model selection interface" width="550" />
+                   <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-8.png" alt="Model selection interface" width="550" />
 
     - Adjust model parameters
 
-                 <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-5.png" alt="Model parameter adjustment controls" width="550" />
+                   <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-5.png" alt="Model parameter adjustment controls" width="550" />
 
 ### Switching Profiles
 

+ 9 - 0
apps/kilocode-docs/docs/features/code-actions.md

@@ -4,6 +4,15 @@ import Image from '@site/src/components/Image';
 
 Code Actions are a powerful feature of VS Code that provide quick fixes, refactorings, and other code-related suggestions directly within the editor. Kilo Code integrates with this system to offer AI-powered assistance for common coding tasks.
 
+:::tip Code Actions vs Autocomplete
+**Code Actions** and **Autocomplete** are two different ways Kilo Code can assist you:
+
+- **Code Actions** (this page) are **explicit and user-triggered**. You invoke them via the lightbulb icon, right-click context menu, or keyboard shortcut (`Ctrl+.` / `Cmd+.`). They operate on selected code and open a conversation in the chat panel.
+- **[Autocomplete](/basic-usage/autocomplete)** is **inline and automatic**. It suggests completions as you type, appearing as ghost text directly in the editor. You accept suggestions with `Tab`.
+
+Use Code Actions when you want to explain, fix, or improve existing code. Use Autocomplete when you want AI to help you write new code as you type.
+:::
+
 ## What are Code Actions?
 
 Code Actions appear as a lightbulb icon (💡) in the editor gutter (the area to the left of the line numbers). They can also be accessed via the right-click context menu, or via keyboard shortcut. They are triggered when:

+ 1 - 1
apps/kilocode-docs/docs/features/experimental/experimental-features.md

@@ -42,6 +42,6 @@ When enabled, Kilo Code will remind the model about the details of its current m
 
 ## Providing Feedback
 
-If you encounter any issues with experimental features, or if you have suggestions for improvements, please report them on the [Kilo Code Code GitHub Issues page](https://github.com/Kilo-Org/kilocode) or join our [Discord server](https://kilo.love/discord) where we have channels dedciated to many experimental features.
+If you encounter any issues with experimental features, or if you have suggestions for improvements, please report them on the [Kilo Code Code GitHub Issues page](https://github.com/Kilo-Org/kilocode) or join our [Discord server](https://kilo.ai/discord) where we have channels dedciated to many experimental features.
 
 Your feedback is valuable and helps us improve Kilo Code!

+ 47 - 47
apps/kilocode-docs/docs/features/mcp/using-mcp-in-cli.md

@@ -9,10 +9,10 @@ The Kilo Code CLI supports MCP servers, but uses a **different configuration pat
 
 ## Configuration Location
 
-| Environment | MCP Settings Path |
-|-------------|-------------------|
-| **CLI** | `~/.kilocode/cli/global/settings/mcp_settings.json` |
-| **VS Code** | VS Code's global storage directory |
+| Environment | MCP Settings Path                                   |
+| ----------- | --------------------------------------------------- |
+| **CLI**     | `~/.kilocode/cli/global/settings/mcp_settings.json` |
+| **VS Code** | VS Code's global storage directory                  |
 
 MCP servers configured in VS Code are **not** automatically available in the CLI. You must configure them separately.
 
@@ -22,17 +22,17 @@ Edit `~/.kilocode/cli/global/settings/mcp_settings.json`:
 
 ```json
 {
-  "mcpServers": {
-    "server-name": {
-      "command": "node",
-      "args": ["/path/to/server.js"],
-      "env": {
-        "API_KEY": "your_api_key"
-      },
-      "alwaysAllow": ["tool1", "tool2"],
-      "disabled": false
-    }
-  }
+	"mcpServers": {
+		"server-name": {
+			"command": "node",
+			"args": ["/path/to/server.js"],
+			"env": {
+				"API_KEY": "your_api_key"
+			},
+			"alwaysAllow": ["tool1", "tool2"],
+			"disabled": false
+		}
+	}
 }
 ```
 
@@ -42,13 +42,13 @@ Edit `~/.kilocode/cli/global/settings/mcp_settings.json`:
 
 ```json
 {
-  "mcpServers": {
-    "local-server": {
-      "command": "node",
-      "args": ["/path/to/server.js"],
-      "env": {}
-    }
-  }
+	"mcpServers": {
+		"local-server": {
+			"command": "node",
+			"args": ["/path/to/server.js"],
+			"env": {}
+		}
+	}
 }
 ```
 
@@ -56,15 +56,15 @@ Edit `~/.kilocode/cli/global/settings/mcp_settings.json`:
 
 ```json
 {
-  "mcpServers": {
-    "remote-server": {
-      "type": "streamable-http",
-      "url": "https://your-server.com/mcp",
-      "headers": {
-        "Authorization": "Bearer token"
-      }
-    }
-  }
+	"mcpServers": {
+		"remote-server": {
+			"type": "streamable-http",
+			"url": "https://your-server.com/mcp",
+			"headers": {
+				"Authorization": "Bearer token"
+			}
+		}
+	}
 }
 ```
 
@@ -74,17 +74,17 @@ You can define MCP servers per-project by creating `.kilocode/mcp.json` in your
 
 ## Configuration Options
 
-| Option | Description |
-|--------|-------------|
-| `command` | Executable to run (STDIO) |
-| `args` | Command arguments (STDIO) |
-| `env` | Environment variables |
-| `type` | Transport type: `stdio` (default), `streamable-http`, `sse` |
-| `url` | Server URL (HTTP transports) |
-| `headers` | HTTP headers (HTTP transports) |
-| `alwaysAllow` | Array of tool names to auto-approve |
-| `disabled` | Set `true` to disable without removing |
-| `timeout` | Request timeout in seconds (default: 60) |
+| Option        | Description                                                 |
+| ------------- | ----------------------------------------------------------- |
+| `command`     | Executable to run (STDIO)                                   |
+| `args`        | Command arguments (STDIO)                                   |
+| `env`         | Environment variables                                       |
+| `type`        | Transport type: `stdio` (default), `streamable-http`, `sse` |
+| `url`         | Server URL (HTTP transports)                                |
+| `headers`     | HTTP headers (HTTP transports)                              |
+| `alwaysAllow` | Array of tool names to auto-approve                         |
+| `disabled`    | Set `true` to disable without removing                      |
+| `timeout`     | Request timeout in seconds (default: 60)                    |
 
 ## Auto-Approval
 
@@ -92,11 +92,11 @@ MCP auto-approval is controlled via CLI config (`kilocode config`):
 
 ```json
 {
-  "autoApproval": {
-    "mcp": {
-      "enabled": true
-    }
-  }
+	"autoApproval": {
+		"mcp": {
+			"enabled": true
+		}
+	}
 }
 ```
 

+ 2 - 2
apps/kilocode-docs/docs/getting-started/setting-up.mdx

@@ -7,12 +7,12 @@ import useDocusaurusContext from "@docusaurus/useDocusaurusContext"
 
 # Setting up Kilo Code
 
-When you sign up for Kilo Code, you can start immediately with free models, or top up your account for the first time to get bonus credits.
+When you sign up for Kilo Code, you can start immediately with free models, or [purchase Kilo credits](../basic-usage/adding-credits) and receive bonus credits.
 
 To claim your bonus credits:
 
 1. **Sign up:** Complete the registration process
-2. **First top-up:** Add funds to your account and get $20 bonus credits
+2. **First top-up:** [Add credits to your account](https://app.kilo.ai/profile) and get $20 bonus credits, or sign up for [Kilo Pass](https://kilo.ai/features/kilo-pass).
 3. **Start Coding:** Enjoy your $20 in free credits
 
 ## Registration process

+ 12 - 1
apps/kilocode-docs/docs/getting-started/your-first-task.md

@@ -30,6 +30,13 @@ Type a clear, concise description of what you want Kilo Code to do in the chat b
 
 No special commands or syntax needed—just use plain English.
 
+<details>
+<summary>💡 Optional: Try Autocomplete</summary>
+
+While chat is great for complex tasks, Kilo Code also offers **inline autocomplete** for quick code suggestions. Open any code file, start typing, and watch for ghost text suggestions. Press `Tab` to accept. [Learn more about Autocomplete →](/basic-usage/autocomplete)
+
+</details>
+
 <img src="/docs/img/your-first-task/your-first-task-6.png" alt="Typing a task in the Kilo Code chat interface" width="500" />
 *Enter your task in natural language - no special syntax required.*
 
@@ -76,4 +83,8 @@ You've completed your first task. Along the way you learned:
 - Why approval keeps you in control
 - How iteration lets the AI refine its work
 
-Ready for more? Explore different [modes](/basic-usage/using-modes) or try [auto-approval](/features/auto-approving-actions) to speed up repetitive tasks.
+Ready for more? Here are some next steps:
+
+- **[Autocomplete](/basic-usage/autocomplete)** — Get inline code suggestions as you type
+- **[Modes](/basic-usage/using-modes)** — Explore different modes for different tasks
+- **[Auto-approval](/features/auto-approving-actions)** — Speed up repetitive tasks

+ 4 - 1
apps/kilocode-docs/docs/index.mdx

@@ -23,12 +23,14 @@ Kilo Code **accelerates** development with AI-driven code generation and task au
 - 🤔 **Answer Questions** about your codebase
 - 🔄 **Automate** repetitive tasks
 - 🏗️ **Create** new files and projects
+- ⚡ **Autocomplete** code as you type with AI-powered suggestions
 
 ## Quick Start
 
 1. [Install Kilo Code](/getting-started/installing)
 2. [Set up Kilo Code](/getting-started/setting-up)
 3. [Try Your First Task](/getting-started/your-first-task)
+4. [Enable Autocomplete](/basic-usage/autocomplete) for inline code suggestions
 
 ## Features
 
@@ -40,7 +42,7 @@ Kilo Code **accelerates** development with AI-driven code generation and task au
 
 ### Basics
 
-Use [the chat interface](/basic-usage/the-chat-interface) to tell Kilo Code what you need. It relies on coding‑optimized AI models to complete each request.
+Use [the chat interface](/basic-usage/the-chat-interface) to tell Kilo Code what you need, or let [Autocomplete](/basic-usage/autocomplete) suggest code as you type. It relies on coding‑optimized AI models to complete each request.
 
 - Switch [modes](/basic-usage/using-modes) to fit the task
 - Control allowed [actions](/features/auto-approving-actions)
@@ -91,6 +93,7 @@ Make Kilo Code work your way with:
 ### Documentation
 
 - [Using Kilo Code](/basic-usage/the-chat-interface) - Learn the basics
+- [Autocomplete](/basic-usage/autocomplete) - Get AI-powered code suggestions as you type
 - [Core Concepts](/features/auto-approving-actions) - Master key features
 - [Advanced Usage](/agent-behavior/prompt-engineering) - Take your skills further
 - [Frequently Asked Questions](/faq) - Get answers to common questions

BIN
apps/kilocode-docs/docs/move-to-secondary.png


+ 9 - 4
apps/kilocode-docs/docs/providers/bedrock.md

@@ -4,7 +4,7 @@ sidebar_label: AWS Bedrock
 
 # Using AWS Bedrock With Kilo Code
 
-Kilo Code supports accessing models through Amazon Bedrock, a fully managed service that makes a selection of high-performing foundation models (FMs) from leading AI companies available via a single API.
+Kilo Code supports accessing models through Amazon Bedrock, a fully managed service that makes a selection of high-performing foundation models (FMs) from leading AI companies available via a single API. This provider connects directly to AWS Bedrock and authenticates with the provided credentials.
 
 **Website:** [https://aws.amazon.com/bedrock/](https://aws.amazon.com/bedrock/)
 
@@ -20,13 +20,16 @@ Kilo Code supports accessing models through Amazon Bedrock, a fully managed serv
 
 ## Getting Credentials
 
-You have two main options for configuring AWS credentials:
+You have three options for configuring AWS credentials:
 
-1.  **AWS Access Keys (Recommended for Development):**
+1.  **Bedrock API Key:**
+    - Create a Bedrock-specific API key in the AWS Console. This is a simple service-specific authentication method.
+    - See the [AWS documentation on Bedrock credentials](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_bedrock.html) for instructions on creating an API key.
+2.  **AWS Access Keys (Recommended for Development):**
     - Create an IAM user with the necessary permissions (at least `bedrock:InvokeModel`).
     - Generate an access key ID and secret access key for that user.
     - _(Optional)_ Create a session token if required by your IAM configuration.
-2.  **AWS Profile:**
+3.  **AWS Profile:**
     - Configure an AWS profile using the AWS CLI or by manually editing your AWS credentials file. See the [AWS CLI documentation](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) for details.
 
 ## Supported Models
@@ -75,6 +78,8 @@ Refer to the [Amazon Bedrock documentation](https://docs.aws.amazon.com/bedrock/
 1.  **Open Kilo Code Settings:** Click the gear icon (<Codicon name="gear" />) in the Kilo Code panel.
 2.  **Select Provider:** Choose "Bedrock" from the "API Provider" dropdown.
 3.  **Select Authentication Method:**
+    - **Bedrock API Key:**
+        - Enter your Bedrock API key directly. This is the simplest setup option.
     - **AWS Credentials:**
         - Enter your "AWS Access Key" and "AWS Secret Key."
         - (Optional) Enter your "AWS Session Token" if you're using temporary credentials.

+ 39 - 20
apps/kilocode-docs/docs/advanced-usage/slackbot.md → apps/kilocode-docs/docs/slack.md

@@ -1,15 +1,15 @@
 ---
-title: Kilo Slackbot
-sidebar_label: Kilo Slackbot
+title: Kilo for Slack
+sidebar_label: Kilo for Slack
 ---
 
-# Kilo Slackbot
+# Kilo for Slack
 
-The Kilo Slackbot brings the power of Kilo Code directly into your Slack workspace. Ask questions about your repositories, request code implementations, or get help with issues—all without leaving Slack.
+Kilo for Slack brings the power of Kilo Code directly into your Slack workspace. Ask questions about your repositories, request code implementations, or get help with issues—all without leaving Slack.
 
 ---
 
-## What You Can Do With the Slackbot
+## What You Can Do With Kilo for Slack
 
 - **Ask questions about your repositories** — Get explanations about code, architecture, or implementation details
 - **Request code implementations** — Tell the bot to implement fixes or features suggested in Slack threads
@@ -20,26 +20,27 @@ The Kilo Slackbot brings the power of Kilo Code directly into your Slack workspa
 
 ## Prerequisites
 
-Before using the Kilo Slackbot:
+Before using Kilo for Slack:
 
 - You must have a **Kilo Code account** with available credits
-- Your **GitHub Integration must be configured** via the [Integrations tab](https://app.kilo.ai/integrations) so the Slackbot can access your repositories 
+- Your **GitHub Integration must be configured** via the [Integrations tab](https://app.kilo.ai/integrations) so Kilo can access your repositories
 
-To install the Kilo Slackbot, simply go to the integrations menu in the sidebar on https://app.kilo.ai and set up the Slack integration.
+To install Kilo for Slack, simply go to the integrations menu in the sidebar on https://app.kilo.ai and set up the Slack integration.
 
 ---
 
-## How to Interact with the Slackbot
+## How to Interact with Kilo
 
 ### Direct Messages
 
-You can message the Kilo Slackbot directly through Slack DMs for private conversations:
+You can message Kilo directly through Slack DMs for private conversations:
 
 1. Find **Kilo** in your Slack workspace's app list
 2. Start a direct message conversation
 3. Ask your question or describe what you need
 
 This is ideal for:
+
 - Private questions about your code
 - Sensitive debugging sessions
 - Personal productivity tasks
@@ -53,6 +54,7 @@ Mention the bot in any channel where it's been added:
 ```
 
 This is great for:
+
 - Team discussions where AI assistance would help
 - Collaborative debugging sessions
 - Getting quick answers during code reviews
@@ -82,6 +84,7 @@ When your team identifies an issue or improvement in a Slack thread, ask the bot
 ```
 
 The bot can:
+
 - Read the context from the thread
 - Understand the proposed solution
 - Create a branch with the implementation
@@ -101,16 +104,16 @@ Can you help me understand what's causing it?
 
 ## How It Works
 
-1. **Message the bot** — Either through DMs or by mentioning it in a channel
-2. **Bot processes your request** — The Slackbot uses your connected GitHub repositories to understand context
+1. **Message Kilo** — Either through DMs or by mentioning it in a channel
+2. **Kilo processes your request** — Kilo uses your connected GitHub repositories to understand context
 3. **AI generates a response** — Kilo Code's AI analyzes your request and provides helpful responses
-4. **Code changes (if requested)** — For implementation requests, the bot can create pull requests
+4. **Code changes (if requested)** — For implementation requests, Kilo can create pull requests
 
 ---
 
 ## Cost
 
-- **Kilo Code credits are used** when the Slackbot performs work (model usage, operations, etc.)
+- **Kilo Code credits are used** when Kilo performs work (model usage, operations, etc.)
 - Credit usage is similar to using Kilo Code through other interfaces
 
 ---
@@ -126,22 +129,38 @@ Can you help me understand what's causing it?
 
 ## Limitations
 
-- The Slackbot can only access repositories you've connected through the [Integrations](https://app.kilo.ai/integrations) page
+- Kilo can only access repositories you've connected through the [Integrations](https://app.kilo.ai/integrations) page
 - Complex multi-step implementations may require follow-up messages
 - Response times may vary based on the complexity of your request
 
 ---
 
+## Changing the Model
+
+You can customize which AI model Kilo uses for generating responses. The model affects the quality, speed, and capabilities of Kilo's responses.
+
+1. Go to your [Kilo Workspace](https://app.kilo.ai/)
+2. Navigate to **Integrations** > **Slack**
+3. Select your preferred model for Kilo for Slack
+
+Kilo will start using the new model immediately for subsequent requests.
+
+### Available Models
+
+Kilo for Slack supports over 400+ models across different providers.
+
+---
+
 ## Troubleshooting
 
-**"The bot isn't responding."**  
-Ensure the Kilo Slackbot is installed in your workspace and has been added to the channel you're using.
+**"Kilo isn't responding."**
+Ensure Kilo for Slack is installed in your workspace and has been added to the channel you're using.
 
-**"The bot can't access my repository."**  
+**"Kilo can't access my repository."**
 Verify your GitHub integration is configured correctly in the [Integrations tab](https://app.kilo.ai/integrations).
 
-**"I'm getting incomplete responses."**  
+**"I'm getting incomplete responses."**
 Try breaking your request into smaller, more specific questions.
 
-**"The bot doesn't understand my codebase."**  
+**"Kilo doesn't understand my codebase."**
 Make sure the repository you're asking about is connected and accessible through your GitHub integration.

+ 8 - 1
apps/kilocode-docs/docs/tips-and-tricks.md

@@ -2,8 +2,15 @@
 
 A collection of quick tips to help you get the most out of Kilo Code.
 
-- Drag Kilo Code to the [Secondary Sidebar](https://code.visualstudio.com/api/ux-guidelines/sidebars#secondary-sidebar) so you can see the Explorer, Search, Source Control, etc.
+- Right-click on the Kilo Code icon in the Activity Bar and select **Move To → Secondary Side Bar** to move Kilo Code to the [Secondary Sidebar](https://code.visualstudio.com/api/ux-guidelines/sidebars#secondary-sidebar), so you can see the Explorer, Search, Source Control, etc. alongside Kilo Code.
+
+    ![Move to Secondary Side Bar](./move-to-secondary.png)
+
 - Once you have Kilo Code in a separate sidebar from the file explorer, you can drag files from the explorer into the chat window (and even multiple at once). Just make sure to hold down the shift key after you start dragging the files.
+- **Autocomplete Tips:**
+    - Use chat for multi-file changes, refactoring, or when you need to explain intent. Use autocomplete for quick, localized edits where the context is already clear from surrounding code.
+    - Steer autocomplete by writing a comment describing what you want before triggering it, or by typing a function signature—autocomplete will fill in the implementation.
+    - Treat autocomplete suggestions as drafts: accept them quickly, then refine. It's often faster to fix a 90% correct suggestion than to craft the perfect prompt.
 - If you're not using [MCP](/features/mcp/overview), turn it off in the <Codicon name="notebook" /> Prompts tab to significantly cut down the size of the system prompt.
 - To keep your [custom modes](/agent-behavior/custom-modes) on track, limit the types of files that they're allowed to edit.
 - If you hit the dreaded `input length and max tokens exceed context limit` error, you can recover by deleting a message, rolling back to a previous checkpoint, or switching over to a model with a long context window like Gemini for a message.

+ 10 - 1
apps/kilocode-docs/docusaurus.config.ts

@@ -120,7 +120,11 @@ const config: Config = {
 					},
 					{
 						to: "/agent-behavior/custom-instructions",
-						from: ["/advanced-usage/custom-instructions", "/features/custom-instructions", "/customization/custom-instructions"],
+						from: [
+							"/advanced-usage/custom-instructions",
+							"/features/custom-instructions",
+							"/customization/custom-instructions",
+						],
 					},
 					{
 						to: "/agent-behavior/custom-modes",
@@ -167,6 +171,11 @@ const config: Config = {
 						from: ["/advanced-usage/api-configuration-profiles"],
 					},
 
+					{
+						to: "/slack",
+						from: ["/advanced-usage/slackbot"],
+					},
+
 					// MCP related redirects
 					{
 						to: "/features/mcp/overview",

+ 20 - 11
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete.md → apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/index.md

@@ -1,6 +1,7 @@
 ---
 title: 自动补全
 sidebar_position: 4
+slug: /basic-usage/autocomplete
 ---
 
 # 自动补全
@@ -20,11 +21,13 @@ Kilo Code 的自动补全功能在您输入时提供智能代码建议和补全
 
 ## 触发选项
 
-### 暂停以补全
+### 代码编辑器建议
 
-启用后,当您暂停输入时,Kilo Code 会自动触发自动补全。这提供了无缝的编码体验,让您在工作时自然地获得建议。
+#### 自动触发建议
 
-- **自动触发延迟**:配置停止输入后触发自动补全的延迟(以秒为单位)
+启用后,当您暂停输入时,Kilo Code 会自动显示内联建议。这提供了无缝的编码体验,让您在工作时自然地获得建议。
+
+- **自动触发延迟**:配置停止输入后建议出现的延迟(以秒为单位)
 - 默认为 3 秒,但可以向上或向下调整
 - 较短的延迟意味着更快的建议,但可能更耗费资源
 
@@ -39,14 +42,14 @@ Kilo Code 的自动补全功能在您输入时提供智能代码建议和补全
 
 **示例:**
 
-- “创建具有这些属性的 React 组件”
-- “为这个函数添加错误处理”
-- “转换为 TypeScript”
-- “优化此循环以提高性能”
+- "创建具有这些属性的 React 组件"
+- "为这个函数添加错误处理"
+- "转换为 TypeScript"
+- "优化此循环以提高性能"
 
 您可以在 VS Code 的键盘快捷键设置中自定义键盘快捷键。
 
-### 手动触发自动补全 (Cmd+L)
+#### 按快捷键触发建议 (Cmd+L)
 
 为了更好地控制建议出现的时机:
 
@@ -64,9 +67,15 @@ Kilo Code 的自动补全功能在您输入时提供智能代码建议和补全
 
 您也可以在 VS Code 设置中自定义此键盘快捷键。
 
+### 聊天建议
+
+#### 启用聊天自动补全
+
+启用后,Kilo Code 会在您输入聊天内容时建议补全。按 Tab 接受建议。
+
 ## 禁用竞争对手的自动补全
 
-我们建议禁用竞争对手的自动补全以优化您使用 Kilo Code 的体验。要禁用 VSCode 中的 GitHub Copilot 自动补全,请转到**设置**并导航到**GitHub** > **Copilot: Advanced**(或搜索“copilot”)。
+我们建议禁用竞争对手的自动补全以优化您使用 Kilo Code 的体验。要禁用 VSCode 中的 GitHub Copilot 自动补全,请转到**设置**并导航到**GitHub** > **Copilot: Advanced**(或搜索"copilot")。
 
 然后,切换为'禁用':
 
@@ -76,7 +85,7 @@ Kilo Code 的自动补全功能在您输入时提供智能代码建议和补全
   width="800"
 />
 
-如果你使用 Cursor,请转到**设置** > **Cursor 设置** > **Tab**,并关闭“Cursor Tab”
+如果你使用 Cursor,请转到**设置** > **Cursor 设置** > **Tab**,并关闭"Cursor Tab"
 
 <img
   src="https://github.com/user-attachments/assets/fd2eeae2-f770-40ca-8a72-a9d5a1c17d47"
@@ -100,4 +109,4 @@ Kilo Code 的自动补全功能在您输入时提供智能代码建议和补全
 
 ## 相关功能
 
-- [代码操作](../features/code-actions) - 用于常见编码任务的上下文菜单选项
+- [代码操作](/features/code-actions) - 用于常见编码任务的上下文菜单选项

+ 89 - 0
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup.md

@@ -0,0 +1,89 @@
+---
+title: 设置 Mistral 免费自动补全
+sidebar_position: 1
+---
+
+# 设置 Mistral 免费自动补全
+
+本指南将引导您在 Kilo Code 中设置 Mistral 的 Codestral 模型以获得免费的自动补全功能。Mistral 提供免费套餐,非常适合开始使用 AI 驱动的代码补全。
+
+## 视频教程
+
+<video controls width="100%">
+  <source src="/docs/videos/configure_free_codestral.mp4" type="video/mp4" />
+  您的浏览器不支持视频标签。
+</video>
+
+## 步骤 1:打开 Kilo Code 设置
+
+在 VS Code 中,打开 Kilo Code 面板,点击右上角的**设置**图标(齿轮)。
+
+![打开 Kilo Code 设置](./mistral-setup/01-open-kilo-code-settings.png)
+
+## 步骤 2:添加新的配置文件
+
+导航到**设置 → 提供商**,点击**添加配置文件**为 Mistral 创建新的配置文件。
+
+![添加配置文件](./mistral-setup/02-add-configuration-profile.png)
+
+## 步骤 3:命名您的配置文件
+
+在"新建配置文件"对话框中,输入名称如"Mistral profile"(名称可以是您喜欢的任何内容),然后点击**创建配置文件**。
+
+:::note
+配置文件名称只是供您参考的标签——它不会影响功能。选择任何有助于您识别此配置的名称。
+:::
+
+![创建 Mistral 配置文件](./mistral-setup/03-name-your-profile.png)
+
+## 步骤 4:选择 Mistral 作为提供商
+
+在 **API 提供商**下拉菜单中,搜索并选择 **Mistral**。
+
+:::note
+创建自动补全配置文件时,您无需选择特定模型——Kilo Code 将自动使用为代码补全优化的适当 Codestral 模型。
+:::
+
+![选择 Mistral 提供商](./mistral-setup/04-select-mistral-provider.png)
+
+## 步骤 5:获取您的 API 密钥
+
+您会看到需要有效 API 密钥的警告。点击**获取 Mistral / Codestral API 密钥**打开 Mistral 控制台。
+
+![获取 API 密钥按钮](./mistral-setup/05-get-api-key.png)
+
+## 步骤 6:在 Mistral AI Studio 中导航到 Codestral
+
+在 Mistral AI Studio 侧边栏中,点击代码部分下的 **Codestral**。
+
+![选择 Codestral](./mistral-setup/06-navigate-to-codestral.png)
+
+## 步骤 7:生成 API 密钥
+
+点击**生成 API 密钥**按钮创建您的新 Codestral API 密钥。
+
+![确认生成](./mistral-setup/07-confirm-key-generation.png)
+
+## 步骤 8:复制您的 API 密钥
+
+生成后,点击 API 密钥旁边的**复制**按钮将其复制到剪贴板。
+
+![复制 API 密钥](./mistral-setup/08-copy-api-key.png)
+
+## 步骤 9:在 Kilo Code 中粘贴 API 密钥
+
+返回 Kilo Code 设置,将您的 API 密钥粘贴到 **Mistral API 密钥**字段中。
+
+![粘贴 API 密钥](./mistral-setup/09-paste-api-key.png)
+
+## 步骤 10:保存您的设置
+
+点击**保存**应用您的 Mistral 配置。现在您可以使用免费的自动补全了!
+
+![保存设置](./mistral-setup/10-save-settings.png)
+
+## 后续步骤
+
+- 了解更多关于[自动补全功能](./index.md)
+- 探索自动补全的[触发选项](./index.md#triggering-options)
+- 查看[最佳实践](./index.md#best-practices)以获得最佳效果

BIN
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/01-open-kilo-code-settings.png


BIN
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/02-add-configuration-profile.png


BIN
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/03-name-your-profile.png


BIN
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/04-select-mistral-provider.png


BIN
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/05-get-api-key.png


BIN
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/06-navigate-to-codestral.png


BIN
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/07-confirm-key-generation.png


BIN
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/08-copy-api-key.png


BIN
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/09-paste-api-key.png


BIN
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/basic-usage/autocomplete/mistral-setup/10-save-settings.png


+ 4 - 4
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/features/api-configuration-profiles.md

@@ -36,19 +36,19 @@ API 配置配置文件允许您创建和切换不同的 AI 设置集。每个配
 
     - 选择您的 API 提供商
 
-                          <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-2.png" alt="提供商选择下拉菜单" width="550" />
+                            <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-2.png" alt="提供商选择下拉菜单" width="550" />
 
     - 输入 API 密钥
 
-                          <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-3.png" alt="API 密钥输入字段" width="550" />
+                            <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-3.png" alt="API 密钥输入字段" width="550" />
 
     - 选择模型
 
-                          <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-8.png" alt="模型选择界面" width="550" />
+                            <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-8.png" alt="模型选择界面" width="550" />
 
     - 调整模型参数
 
-                          <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-5.png" alt="模型参数调整控件" width="550" />
+                            <img src="/docs/img/api-configuration-profiles/api-configuration-profiles-5.png" alt="模型参数调整控件" width="550" />
 
 ### 切换配置文件
 

BIN
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/move-to-secondary.png


+ 4 - 1
apps/kilocode-docs/i18n/zh-CN/docusaurus-plugin-content-docs/current/tips-and-tricks.md

@@ -2,7 +2,10 @@
 
 一些快速提示,帮助你充分利用 Kilo Code。
 
-- 将 Kilo Code 拖到[侧边栏](https://code.visualstudio.com/api/ux-guidelines/sidebars#secondary-sidebar),以便同时查看资源管理器、搜索、源代码控制等。
+- 右键点击活动栏中的 Kilo Code 图标,选择 **移动到 → 辅助侧边栏**,将 Kilo Code 移动到[辅助侧边栏](https://code.visualstudio.com/api/ux-guidelines/sidebars#secondary-sidebar),这样你可以同时查看资源管理器、搜索、源代码控制等。
+
+    ![移动到辅助侧边栏](./move-to-secondary.png)
+
 - 当 Kilo Code 位于与文件资源管理器分开的侧边栏时,你可以将文件从资源管理器拖到聊天窗口中(甚至可以一次拖多个文件)。只需在开始拖动文件后按住 shift 键。
 - 如果你不使用[MCP](/features/mcp/overview),请在 <Codicon name="notebook" /> 提示选项卡中关闭它,以显著减少系统提示的大小。
 - 为了保持[自定义模式](/agent-behavior/custom-modes)的正轨,请限制它们可以编辑的文件类型。

+ 10 - 2
apps/kilocode-docs/sidebars.ts

@@ -24,7 +24,15 @@ const sidebars: SidebarsConfig = {
 				"basic-usage/the-chat-interface",
 				"basic-usage/model-selection-guide",
 				"basic-usage/using-modes",
-				"basic-usage/autocomplete",
+				{
+					type: "category",
+					label: "Autocomplete",
+					link: {
+						type: "doc",
+						id: "basic-usage/autocomplete/index",
+					},
+					items: ["basic-usage/autocomplete/mistral-setup"],
+				},
 				"basic-usage/context-mentions",
 				{
 					type: "category",
@@ -171,7 +179,6 @@ const sidebars: SidebarsConfig = {
 				"features/auto-launch-configuration",
 				"advanced-usage/auto-cleanup",
 				"advanced-usage/integrations",
-				"advanced-usage/slackbot",
 				"advanced-usage/appbuilder",
 				"advanced-usage/cloud-agent",
 				"advanced-usage/code-reviews",
@@ -253,6 +260,7 @@ const sidebars: SidebarsConfig = {
 			],
 		},
 		"cli",
+		"slack",
 	],
 }
 

+ 3 - 0
apps/kilocode-docs/static/videos/configure_free_codestral.mp4

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:06397286f4a68a7b174f4cec80e6bc1cec7bf9e514f517cc805097d1d849aa81
+size 5525874

+ 2 - 1
apps/playwright-e2e/tsconfig.json

@@ -22,7 +22,8 @@
 		"playwright.globalSetup.ts",
 		"types/**/*",
 		"helpers/**/*",
-		"scripts/theme-extraction-script.test.ts"
+		"scripts/theme-extraction-script.test.ts",
+		"../../src/shared/string-extensions.d.ts"
 	],
 	"exclude": ["node_modules", "test-results", "tests/**/__tests__/**/*"]
 }

+ 2 - 0
apps/storybook/.storybook/preview.ts

@@ -1,6 +1,7 @@
 import type { Preview } from "@storybook/react-vite"
 
 import { withExtensionState } from "../src/decorators/withExtensionState"
+import { withPostMessageMock } from "../src/decorators/withPostMessageMock"
 import { withQueryClient } from "../src/decorators/withQueryClient"
 import { withTheme } from "../src/decorators/withTheme"
 import { withI18n } from "../src/decorators/withI18n"
@@ -49,6 +50,7 @@ const preview: Preview = {
 		withI18n,
 		withQueryClient,
 		withExtensionState,
+		withPostMessageMock,
 		withTheme,
 		withTooltipProvider,
 		withFixedContainment,

+ 43 - 0
apps/storybook/src/decorators/withPostMessageMock.tsx

@@ -0,0 +1,43 @@
+import type { Decorator } from "@storybook/react-vite"
+import React from "react"
+
+type PostMessage = Record<string, unknown>
+
+/**
+ * Decorator to mock VSCode postMessage for components that listen to messages.
+ *
+ * To override in a story, use parameters.postMessages:
+ * ```tsx
+ * export const MyStory: Story = {
+ *   parameters: {
+ *     postMessages: [
+ *       { type: "kilocodeNotificationsResponse", notifications: [...] },
+ *     ],
+ *   },
+ * }
+ * ```
+ *
+ * Multiple messages are sent sequentially
+ */
+export const withPostMessageMock: Decorator = (Story, context) => {
+	const messages = context.parameters?.postMessages as PostMessage[] | undefined
+
+	React.useEffect(() => {
+		if (!messages || messages.length === 0) {
+			return
+		}
+
+		const timers: NodeJS.Timeout[] = []
+		messages.forEach((message, index) => {
+			const event = new MessageEvent("message", { data: message })
+			const timer = setTimeout(() => {
+				window.dispatchEvent(event)
+			})
+			timers.push(timer)
+		})
+
+		return () => timers.forEach(clearTimeout)
+	}, [messages])
+
+	return <Story />
+}

+ 1 - 4
apps/storybook/src/utils/createExtensionStateMock.ts

@@ -17,10 +17,7 @@ export const createExtensionStateMock = (
 ): ExtensionStateContextType => {
 	// Only define properties that Storybook stories actually use
 	const knownProperties: Partial<ExtensionStateContextType> = {
-		// Add properties here as they're needed in stories
-		// For example:
-		// theme: {},
-		// apiConfiguration: null,
+		kilocodeDefaultModel: "claude-sonnet-4",
 	}
 
 	// Merge with overrides

+ 71 - 0
apps/storybook/stories/KilocodeNotifications.stories.tsx

@@ -0,0 +1,71 @@
+import type { Meta, StoryObj } from "@storybook/react-vite"
+import { fn } from "storybook/test"
+import { KilocodeNotifications } from "@/components/kilocode/KilocodeNotifications"
+
+const meta = {
+	title: "Components/KilocodeNotifications",
+	component: KilocodeNotifications,
+	parameters: {
+		extensionState: {
+			dismissedNotificationIds: [],
+		},
+	},
+	args: {
+		onDismiss: fn(),
+	},
+} satisfies Meta<typeof KilocodeNotifications>
+
+export default meta
+type Story = StoryObj<typeof meta>
+
+const defaultNotification = {
+	id: "1",
+	title: "Welcome to Kilo Code!",
+	message: "Get started by setting up your API configuration in the settings.",
+	action: {
+		actionText: "Open Settings",
+		actionURL: "https://example.com/settings",
+	},
+}
+
+export const Default: Story = {
+	parameters: {
+		postMessages: [
+			{
+				type: "kilocodeNotificationsResponse",
+				notifications: [defaultNotification],
+			},
+		],
+	},
+}
+
+export const MultipleNotifications: Story = {
+	parameters: {
+		postMessages: [
+			{
+				type: "kilocodeNotificationsResponse",
+				notifications: [
+					{
+						id: "1",
+						title: "First Notification",
+						message: "This is the first notification in a series.",
+					},
+					{
+						id: "2",
+						title: "Second Notification",
+						message: "You can navigate between notifications using the arrows.",
+					},
+					{
+						id: "3",
+						title: "Third Notification",
+						message: "This is the last notification in this set.",
+						action: {
+							actionText: "Visit Website",
+							actionURL: "https://example.com",
+						},
+					},
+				],
+			},
+		],
+	},
+}

+ 0 - 4
apps/vscode-e2e/src/suite/extension.test.ts

@@ -14,10 +14,6 @@ suite("Kilo Code Extension", function () {
 			"openInNewTab",
 			"settingsButtonClicked",
 			"historyButtonClicked",
-			"popoutButtonClicked",
-			"accountButtonClicked",
-			"settingsButtonClicked",
-			"openInNewTab",
 			"showHumanRelayDialog",
 			"registerHumanRelayCallback",
 			"unregisterHumanRelayCallback",

+ 1 - 1
apps/web-evals/package.json

@@ -35,7 +35,7 @@
 		"cmdk": "^1.1.0",
 		"fuzzysort": "^3.1.0",
 		"lucide-react": "^0.518.0",
-		"next": "~15.2.6",
+		"next": "~15.2.8",
 		"next-themes": "^0.4.6",
 		"p-map": "^7.0.3",
 		"react": "^18.3.1",

+ 25 - 7
apps/web-evals/src/app/api/runs/[id]/logs/failed/route.ts

@@ -61,7 +61,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
 			archive.on("error", reject)
 		})
 
-		// Add each failed task's log file to the archive
+		// Add each failed task's log file and history files to the archive
 		const logDir = path.join(LOG_BASE_PATH, String(runId))
 		let filesAdded = 0
 
@@ -69,18 +69,36 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
 			// Sanitize language and exercise to prevent path traversal
 			const safeLanguage = sanitizePathComponent(task.language)
 			const safeExercise = sanitizePathComponent(task.exercise)
+			const expectedBase = path.resolve(LOG_BASE_PATH)
+
+			// Add the log file
 			const logFileName = `${safeLanguage}-${safeExercise}.log`
 			const logFilePath = path.join(logDir, logFileName)
 
 			// Verify the resolved path is within the expected directory (defense in depth)
-			const resolvedPath = path.resolve(logFilePath)
-			const expectedBase = path.resolve(LOG_BASE_PATH)
-			if (!resolvedPath.startsWith(expectedBase)) {
-				continue // Skip files with suspicious paths
+			const resolvedLogPath = path.resolve(logFilePath)
+			if (resolvedLogPath.startsWith(expectedBase) && fs.existsSync(logFilePath)) {
+				archive.file(logFilePath, { name: logFileName })
+				filesAdded++
 			}
 
-			if (fs.existsSync(logFilePath)) {
-				archive.file(logFilePath, { name: logFileName })
+			// Add the API conversation history file
+			// Format: {language}-{exercise}.{iteration}_api_conversation_history.json
+			const apiHistoryFileName = `${safeLanguage}-${safeExercise}.${task.iteration}_api_conversation_history.json`
+			const apiHistoryFilePath = path.join(logDir, apiHistoryFileName)
+			const resolvedApiHistoryPath = path.resolve(apiHistoryFilePath)
+			if (resolvedApiHistoryPath.startsWith(expectedBase) && fs.existsSync(apiHistoryFilePath)) {
+				archive.file(apiHistoryFilePath, { name: apiHistoryFileName })
+				filesAdded++
+			}
+
+			// Add the UI messages file
+			// Format: {language}-{exercise}.{iteration}_ui_messages.json
+			const uiMessagesFileName = `${safeLanguage}-${safeExercise}.${task.iteration}_ui_messages.json`
+			const uiMessagesFilePath = path.join(logDir, uiMessagesFileName)
+			const resolvedUiMessagesPath = path.resolve(uiMessagesFilePath)
+			if (resolvedUiMessagesPath.startsWith(expectedBase) && fs.existsSync(uiMessagesFilePath)) {
+				archive.file(uiMessagesFilePath, { name: uiMessagesFileName })
 				filesAdded++
 			}
 		}

+ 476 - 276
apps/web-evals/src/app/runs/[id]/run.tsx

@@ -1,8 +1,8 @@
 "use client"
 
-import { useMemo, useState, useCallback, useEffect } from "react"
+import { useMemo, useState, useCallback, useEffect, Fragment } from "react"
 import { toast } from "sonner"
-import { LoaderCircle, FileText, Copy, Check, StopCircle } from "lucide-react"
+import { LoaderCircle, FileText, Copy, Check, StopCircle, List, Layers } from "lucide-react"
 
 import type { Run, TaskMetrics as _TaskMetrics, Task } from "@roo-code/evals"
 import type { ToolName } from "@roo-code/types"
@@ -41,6 +41,9 @@ import { RunStatus } from "./run-status"
 
 type TaskMetrics = Pick<_TaskMetrics, "tokensIn" | "tokensOut" | "tokensContext" | "duration" | "cost">
 
+// Extended Task type with taskMetrics from useRunStatus
+type TaskWithMetrics = Task & { taskMetrics: _TaskMetrics | null }
+
 type ToolUsageEntry = { attempts: number; failures: number }
 type ToolUsage = Record<string, ToolUsageEntry>
 
@@ -250,6 +253,19 @@ export function Run({ run }: { run: Run }) {
 	const [copied, setCopied] = useState(false)
 	const [showKillDialog, setShowKillDialog] = useState(false)
 	const [isKilling, setIsKilling] = useState(false)
+	const [groupByStatus, setGroupByStatus] = useState(() => {
+		// Initialize from localStorage if available (client-side only)
+		if (typeof window !== "undefined") {
+			const stored = localStorage.getItem("evals-group-by-status")
+			return stored === "true"
+		}
+		return false
+	})
+
+	// Persist groupByStatus to localStorage
+	useEffect(() => {
+		localStorage.setItem("evals-group-by-status", String(groupByStatus))
+	}, [groupByStatus])
 
 	// Determine if run is still active (has heartbeat or runners)
 	const isRunActive = !run.taskMetricsId && (!!heartbeat || (runners && runners.length > 0))
@@ -300,46 +316,20 @@ export function Run({ run }: { run: Run }) {
 		return () => document.removeEventListener("keydown", handleKeyDown)
 	}, [selectedTask])
 
-	const onViewTaskLog = useCallback(
-		async (task: Task) => {
-			// Only allow viewing logs for tasks that have started
-			if (!task.startedAt && !tokenUsage.get(task.id)) {
-				toast.error("Task has not started yet")
-				return
-			}
-
-			setSelectedTask(task)
-			setIsLoadingLog(true)
-			setTaskLog(null)
-
-			try {
-				const response = await fetch(`/api/runs/${run.id}/logs/${task.id}`)
-
-				if (!response.ok) {
-					const error = await response.json()
-					toast.error(error.error || "Failed to load log")
-					setSelectedTask(null)
-					return
-				}
-
-				const data = await response.json()
-				setTaskLog(data.logContent)
-			} catch (error) {
-				console.error("Error loading task log:", error)
-				toast.error("Failed to load log")
-				setSelectedTask(null)
-			} finally {
-				setIsLoadingLog(false)
-			}
-		},
-		[run.id, tokenUsage],
-	)
-
 	const taskMetrics: Record<number, TaskMetrics> = useMemo(() => {
 		// Reference usageUpdatedAt to trigger recomputation when Map contents change
 		void usageUpdatedAt
 		const metrics: Record<number, TaskMetrics> = {}
 
+		// Helper to calculate duration from database timestamps when streaming duration
+		// is unavailable (e.g., page was loaded after TaskStarted event was published)
+		const calculateDurationFromTimestamps = (task: TaskWithMetrics): number => {
+			if (!task.startedAt) return 0
+			const startTime = new Date(task.startedAt).getTime()
+			const endTime = task.finishedAt ? new Date(task.finishedAt).getTime() : Date.now()
+			return endTime - startTime
+		}
+
 		tasks?.forEach((task) => {
 			const streamingUsage = tokenUsage.get(task.id)
 			const dbMetrics = task.taskMetrics
@@ -350,32 +340,98 @@ export function Run({ run }: { run: Run }) {
 				// Check if DB metrics have meaningful values (not just default/empty)
 				const dbHasData = dbMetrics && (dbMetrics.tokensIn > 0 || dbMetrics.tokensOut > 0 || dbMetrics.cost > 0)
 				if (dbHasData) {
-					metrics[task.id] = dbMetrics
+					// If DB duration is 0 but we have timestamps, calculate from timestamps
+					const duration = dbMetrics.duration || calculateDurationFromTimestamps(task)
+					metrics[task.id] = { ...dbMetrics, duration }
 				} else if (streamingUsage) {
 					// Fall back to streaming values if DB is empty/stale
+					// Use streaming duration, or calculate from timestamps if not available
+					const duration = streamingUsage.duration || calculateDurationFromTimestamps(task)
 					metrics[task.id] = {
 						tokensIn: streamingUsage.totalTokensIn,
 						tokensOut: streamingUsage.totalTokensOut,
 						tokensContext: streamingUsage.contextTokens,
-						duration: streamingUsage.duration ?? 0,
+						duration,
 						cost: streamingUsage.totalCost,
 					}
+				} else {
+					// Task finished but no DB metrics and no streaming data
+					// (e.g., page loaded after task completed, metrics not persisted)
+					// Still provide duration calculated from timestamps
+					metrics[task.id] = {
+						tokensIn: 0,
+						tokensOut: 0,
+						tokensContext: 0,
+						duration: calculateDurationFromTimestamps(task),
+						cost: 0,
+					}
 				}
 			} else if (streamingUsage) {
 				// For running tasks, use streaming values
+				// Use streaming duration, or calculate from task.startedAt if not available
+				// (happens when page loads after TaskStarted event was already published)
+				const duration = streamingUsage.duration || calculateDurationFromTimestamps(task)
 				metrics[task.id] = {
 					tokensIn: streamingUsage.totalTokensIn,
 					tokensOut: streamingUsage.totalTokensOut,
 					tokensContext: streamingUsage.contextTokens,
-					duration: streamingUsage.duration ?? 0,
+					duration,
 					cost: streamingUsage.totalCost,
 				}
+			} else if (task.startedAt) {
+				// Task has started (has startedAt in DB) but no streaming data yet
+				// This can happen when page loads after TaskStarted but before TokenUsageUpdated
+				metrics[task.id] = {
+					tokensIn: 0,
+					tokensOut: 0,
+					tokensContext: 0,
+					duration: calculateDurationFromTimestamps(task),
+					cost: 0,
+				}
 			}
 		})
 
 		return metrics
 	}, [tasks, tokenUsage, usageUpdatedAt])
 
+	const onViewTaskLog = useCallback(
+		async (task: Task) => {
+			// Only allow viewing logs for tasks that have started.
+			// Note: we treat presence of derived metrics as evidence of a started task,
+			// since this page may be rendered without streaming `tokenUsage` populated.
+			const hasStarted = !!task.startedAt || !!tokenUsage.get(task.id) || !!taskMetrics[task.id]
+			if (!hasStarted) {
+				toast.error("Task has not started yet")
+				return
+			}
+
+			setSelectedTask(task)
+			setIsLoadingLog(true)
+			setTaskLog(null)
+
+			try {
+				const response = await fetch(`/api/runs/${run.id}/logs/${task.id}`)
+
+				if (!response.ok) {
+					const error = await response.json()
+					toast.error(error.error || "Failed to load log")
+					setSelectedTask(null)
+					return
+				}
+
+				const data = await response.json()
+				setTaskLog(data.logContent)
+			} catch (error) {
+				console.error("Error loading task log:", error)
+				toast.error("Failed to load log")
+				setSelectedTask(null)
+			} finally {
+				setIsLoadingLog(false)
+			}
+		},
+		[run.id, tokenUsage, taskMetrics],
+	)
+
 	// Collect all unique tool names from all tasks and sort by total attempts
 	const toolColumns = useMemo<ToolName[]>(() => {
 		// Reference usageUpdatedAt to trigger recomputation when Map contents change
@@ -463,10 +519,13 @@ export function Run({ run }: { run: Run }) {
 			}
 		}
 
+		const remaining = tasks.length - completed
+
 		return {
 			passed,
 			failed,
 			completed,
+			remaining,
 			passRate: completed > 0 ? ((passed / completed) * 100).toFixed(1) : null,
 			totalTokensIn,
 			totalTokensOut,
@@ -501,258 +560,399 @@ export function Run({ run }: { run: Run }) {
 		return Date.now() - startTime
 	}, [tasks, run.createdAt, run.taskMetricsId, usageUpdatedAt])
 
-	return (
-		<>
-			<div>
-				{stats && (
-					<div className="mb-4 p-4 border rounded-lg bg-muted sticky top-0 z-10">
-						{/* Provider, Model title and status */}
-						<div className="flex items-center justify-center gap-3 mb-3 relative">
-							{run.settings?.apiProvider && (
-								<span className="text-sm text-muted-foreground">{run.settings.apiProvider}</span>
-							)}
-							<div className="font-mono">{run.model}</div>
-							<RunStatus runStatus={runStatus} isComplete={!!run.taskMetricsId} />
-							{run.description && (
-								<span className="text-sm text-muted-foreground">- {run.description}</span>
-							)}
-							{isRunActive && (
-								<Tooltip>
-									<TooltipTrigger asChild>
-										<Button
-											variant="ghost"
-											size="sm"
-											onClick={() => setShowKillDialog(true)}
-											disabled={isKilling}
-											className="absolute right-0 flex items-center gap-1 text-muted-foreground hover:text-destructive">
-											{isKilling ? (
-												<LoaderCircle className="size-4 animate-spin" />
-											) : (
-												<StopCircle className="size-4" />
-											)}
-											Kill
-										</Button>
-									</TooltipTrigger>
-									<TooltipContent>Stop all containers for this run</TooltipContent>
-								</Tooltip>
-							)}
-						</div>
-						{/* Main Stats Row */}
-						<div className="flex items-start justify-center gap-x-8 gap-y-3">
-							{/* Passed/Failed */}
-							<div className="text-center min-w-[80px]">
-								<div className="text-2xl font-bold whitespace-nowrap">
-									<span className="text-green-600">{stats.passed}</span>
-									<span className="text-muted-foreground mx-1">/</span>
-									<span className="text-red-600">{stats.failed}</span>
-								</div>
-								<div className="text-xs text-muted-foreground">Passed / Failed</div>
-							</div>
+	// Task status categories
+	type TaskStatusCategory = "failed" | "in_progress" | "passed" | "not_started"
+
+	const getTaskStatusCategory = useCallback(
+		(task: TaskWithMetrics): TaskStatusCategory => {
+			if (task.passed === false) return "failed"
+			if (task.passed === true) return "passed"
+			// Check streaming data, DB metrics, or startedAt timestamp
+			const hasStarted = !!task.startedAt || !!tokenUsage.get(task.id) || !!taskMetrics[task.id]
+			if (hasStarted) return "in_progress"
+			return "not_started"
+		},
+		[tokenUsage, taskMetrics],
+	)
 
-							{/* Pass Rate */}
-							<div className="text-center min-w-[80px]">
-								<div
-									className={`text-2xl font-bold ${
-										stats.passRate === null
-											? ""
-											: parseFloat(stats.passRate) === 100
-												? ""
-												: parseFloat(stats.passRate) >= 80
-													? "text-yellow-500"
-													: "text-red-500"
-									}`}>
-									{stats.passRate ? `${stats.passRate}%` : "-"}
-								</div>
-								<div className="text-xs text-muted-foreground">Pass Rate</div>
-							</div>
+	// Group tasks by status while preserving original index
+	const groupedTasks = useMemo(() => {
+		if (!tasks || !groupByStatus) return null
 
-							{/* Tokens */}
-							<div className="text-center min-w-[140px]">
-								<div className="text-xl font-bold font-mono whitespace-nowrap">
-									{formatTokens(stats.totalTokensIn)}
-									<span className="text-muted-foreground mx-1">/</span>
-									{formatTokens(stats.totalTokensOut)}
-								</div>
-								<div className="text-xs text-muted-foreground">Tokens In / Out</div>
-							</div>
+		const groups: Record<TaskStatusCategory, Array<{ task: TaskWithMetrics; originalIndex: number }>> = {
+			failed: [],
+			in_progress: [],
+			passed: [],
+			not_started: [],
+		}
 
-							{/* Cost */}
-							<div className="text-center min-w-[70px]">
-								<div className="text-2xl font-bold font-mono">{formatCurrency(stats.totalCost)}</div>
-								<div className="text-xs text-muted-foreground">Cost</div>
-							</div>
+		tasks.forEach((task, index) => {
+			const status = getTaskStatusCategory(task)
+			groups[status].push({ task, originalIndex: index })
+		})
 
-							{/* Duration */}
-							<div className="text-center min-w-[90px]">
-								<div className="text-2xl font-bold font-mono whitespace-nowrap">
-									{stats.totalDuration > 0 ? formatDuration(stats.totalDuration) : "-"}
-								</div>
-								<div className="text-xs text-muted-foreground">Duration</div>
-							</div>
+		return groups
+	}, [tasks, groupByStatus, getTaskStatusCategory])
+
+	const statusLabels = useMemo(
+		(): Record<TaskStatusCategory, { label: string; className: string; count: number }> => ({
+			failed: { label: "Failed", className: "text-red-500", count: groupedTasks?.failed.length ?? 0 },
+			in_progress: {
+				label: "In Progress",
+				className: "text-yellow-500",
+				count: groupedTasks?.in_progress.length ?? 0,
+			},
+			passed: { label: "Passed", className: "text-green-500", count: groupedTasks?.passed.length ?? 0 },
+			not_started: {
+				label: "Not Started",
+				className: "text-muted-foreground",
+				count: groupedTasks?.not_started.length ?? 0,
+			},
+		}),
+		[groupedTasks],
+	)
 
-							{/* Elapsed Time */}
-							<div className="text-center min-w-[90px]">
-								<div className="text-2xl font-bold font-mono whitespace-nowrap">
-									{elapsedTime !== null ? formatDuration(elapsedTime) : "-"}
-								</div>
-								<div className="text-xs text-muted-foreground">Elapsed</div>
-							</div>
-						</div>
+	const statusOrder: TaskStatusCategory[] = ["failed", "in_progress", "passed", "not_started"]
 
-						{/* Tool Usage Row */}
-						{Object.keys(stats.toolUsage).length > 0 && (
-							<div className="flex items-center justify-center gap-2 flex-wrap mt-3">
-								{Object.entries(stats.toolUsage)
-									.sort(([, a], [, b]) => b.attempts - a.attempts)
-									.map(([toolName, usage]) => {
-										const abbr = getToolAbbreviation(toolName)
-										const successRate =
-											usage.attempts > 0
-												? ((usage.attempts - usage.failures) / usage.attempts) * 100
-												: 100
-										const rateColor =
-											successRate === 100
-												? "text-green-500"
-												: successRate >= 80
-													? "text-yellow-500"
-													: "text-red-500"
-										return (
-											<Tooltip key={toolName}>
-												<TooltipTrigger asChild>
-													<div className="flex items-center gap-1 px-2 py-1 rounded bg-background/50 border border-border/50 hover:border-border transition-colors cursor-default text-xs">
-														<span className="font-medium text-muted-foreground">
-															{abbr}
-														</span>
-														<span className="font-bold tabular-nums">{usage.attempts}</span>
-														<span className={`${rateColor}`}>
-															{formatToolUsageSuccessRate(usage)}
-														</span>
-													</div>
-												</TooltipTrigger>
-												<TooltipContent side="bottom">{toolName}</TooltipContent>
-											</Tooltip>
-										)
-									})}
-							</div>
-						)}
+	// Helper to render a task row
+	const renderTaskRow = (task: TaskWithMetrics, originalIndex: number) => {
+		const hasStarted = !!task.startedAt || !!tokenUsage.get(task.id) || !!taskMetrics[task.id]
+		return (
+			<TableRow
+				key={task.id}
+				className={`${hasStarted ? "cursor-pointer hover:bg-muted/50" : ""} ${task.passed === false ? "bg-red-950/30 border-l-2 border-l-red-500" : ""}`}
+				onClick={() => hasStarted && onViewTaskLog(task)}>
+				<TableCell className="text-center text-muted-foreground font-mono text-xs">
+					{originalIndex + 1}
+				</TableCell>
+				<TableCell>
+					<div className="flex items-center gap-2">
+						<TaskStatus task={task} running={hasStarted} />
+						<div className="flex items-center gap-2">
+							<span>
+								{task.language}/{task.exercise}
+								{task.iteration > 1 && (
+									<span className="text-muted-foreground ml-1">(#{task.iteration})</span>
+								)}
+							</span>
+							{hasStarted && (
+								<Tooltip>
+									<TooltipTrigger asChild>
+										<FileText className="size-3 text-muted-foreground" />
+									</TooltipTrigger>
+									<TooltipContent>Click to view log</TooltipContent>
+								</Tooltip>
+							)}
+						</div>
 					</div>
+				</TableCell>
+				{taskMetrics[task.id] ? (
+					<>
+						<TableCell className="font-mono text-xs">
+							<div className="flex items-center justify-evenly">
+								<div>{formatTokens(taskMetrics[task.id]!.tokensIn)}</div>/
+								<div>{formatTokens(taskMetrics[task.id]!.tokensOut)}</div>
+							</div>
+						</TableCell>
+						<TableCell className="font-mono text-xs">
+							{formatTokens(taskMetrics[task.id]!.tokensContext)}
+						</TableCell>
+						{toolColumns.map((toolName) => {
+							const dbUsage = task.taskMetrics?.toolUsage?.[toolName]
+							const streamingUsage = toolUsage.get(task.id)?.[toolName]
+							const usage = task.finishedAt ? (dbUsage ?? streamingUsage) : streamingUsage
+
+							const successRate =
+								usage && usage.attempts > 0
+									? ((usage.attempts - usage.failures) / usage.attempts) * 100
+									: 100
+							const rateColor =
+								successRate === 100
+									? "text-muted-foreground"
+									: successRate >= 80
+										? "text-yellow-500"
+										: "text-red-500"
+							return (
+								<TableCell key={toolName} className="text-xs text-center">
+									{usage ? (
+										<div className="flex flex-col items-center">
+											<span className="font-medium">{usage.attempts}</span>
+											<span className={rateColor}>{formatToolUsageSuccessRate(usage)}</span>
+										</div>
+									) : (
+										<span className="text-muted-foreground">-</span>
+									)}
+								</TableCell>
+							)
+						})}
+						<TableCell className="font-mono text-xs">
+							{taskMetrics[task.id]!.duration ? formatDuration(taskMetrics[task.id]!.duration) : "-"}
+						</TableCell>
+						<TableCell className="font-mono text-xs">
+							{formatCurrency(taskMetrics[task.id]!.cost)}
+						</TableCell>
+					</>
+				) : (
+					<TableCell colSpan={5 + toolColumns.length} />
 				)}
+			</TableRow>
+		)
+	}
+
+	return (
+		<>
+			<div>
 				{!tasks ? (
 					<LoaderCircle className="size-4 animate-spin" />
 				) : (
-					<Table className="border">
-						<TableHeader>
-							<TableRow>
-								<TableHead>Exercise</TableHead>
-								<TableHead className="text-center">Tokens In / Out</TableHead>
-								<TableHead>Context</TableHead>
-								{toolColumns.map((toolName) => (
-									<TableHead key={toolName} className="text-xs text-center">
-										<Tooltip>
-											<TooltipTrigger>{getToolAbbreviation(toolName)}</TooltipTrigger>
-											<TooltipContent>{toolName}</TooltipContent>
-										</Tooltip>
-									</TableHead>
-								))}
-								<TableHead>Duration</TableHead>
-								<TableHead>Cost</TableHead>
-							</TableRow>
-						</TableHeader>
-						<TableBody>
-							{tasks.map((task) => {
-								const hasStarted = !!task.startedAt || !!tokenUsage.get(task.id)
-								return (
-									<TableRow
-										key={task.id}
-										className={`${hasStarted ? "cursor-pointer hover:bg-muted/50" : ""} ${task.passed === false ? "bg-red-950/30 border-l-2 border-l-red-500" : ""}`}
-										onClick={() => hasStarted && onViewTaskLog(task)}>
-										<TableCell>
-											<div className="flex items-center gap-2">
-												<TaskStatus task={task} running={hasStarted} />
-												<div className="flex items-center gap-2">
-													<span>
-														{task.language}/{task.exercise}
-														{task.iteration > 1 && (
-															<span className="text-muted-foreground ml-1">
-																(#{task.iteration})
-															</span>
-														)}
-													</span>
-													{hasStarted && (
-														<Tooltip>
-															<TooltipTrigger asChild>
-																<FileText className="size-3 text-muted-foreground" />
-															</TooltipTrigger>
-															<TooltipContent>Click to view log</TooltipContent>
-														</Tooltip>
-													)}
-												</div>
-											</div>
-										</TableCell>
-										{taskMetrics[task.id] ? (
+					<>
+						{/* View Toggle */}
+						<div className="flex justify-end mb-2">
+							<Tooltip>
+								<TooltipTrigger asChild>
+									<Button
+										variant="outline"
+										size="sm"
+										onClick={() => setGroupByStatus(!groupByStatus)}
+										className="flex items-center gap-2">
+										{groupByStatus ? (
 											<>
-												<TableCell className="font-mono text-xs">
-													<div className="flex items-center justify-evenly">
-														<div>{formatTokens(taskMetrics[task.id]!.tokensIn)}</div>/
-														<div>{formatTokens(taskMetrics[task.id]!.tokensOut)}</div>
-													</div>
-												</TableCell>
-												<TableCell className="font-mono text-xs">
-													{formatTokens(taskMetrics[task.id]!.tokensContext)}
-												</TableCell>
-												{toolColumns.map((toolName) => {
-													// Use DB values for finished tasks, but fall back to streaming values
-													// if DB values are missing (handles race condition during timeout)
-													const dbUsage = task.taskMetrics?.toolUsage?.[toolName]
-													const streamingUsage = toolUsage.get(task.id)?.[toolName]
-													const usage = task.finishedAt
-														? (dbUsage ?? streamingUsage)
-														: streamingUsage
-
-													const successRate =
-														usage && usage.attempts > 0
-															? ((usage.attempts - usage.failures) / usage.attempts) * 100
-															: 100
-													const rateColor =
-														successRate === 100
-															? "text-muted-foreground"
-															: successRate >= 80
-																? "text-yellow-500"
-																: "text-red-500"
-													return (
-														<TableCell key={toolName} className="text-xs text-center">
-															{usage ? (
-																<div className="flex flex-col items-center">
-																	<span className="font-medium">
-																		{usage.attempts}
-																	</span>
-																	<span className={rateColor}>
-																		{formatToolUsageSuccessRate(usage)}
-																	</span>
-																</div>
-															) : (
-																<span className="text-muted-foreground">-</span>
-															)}
-														</TableCell>
-													)
-												})}
-												<TableCell className="font-mono text-xs">
-													{taskMetrics[task.id]!.duration
-														? formatDuration(taskMetrics[task.id]!.duration)
-														: "-"}
-												</TableCell>
-												<TableCell className="font-mono text-xs">
-													{formatCurrency(taskMetrics[task.id]!.cost)}
-												</TableCell>
+												<List className="size-4" />
+												<span>Show Order</span>
 											</>
 										) : (
-											<TableCell colSpan={4 + toolColumns.length} />
+											<>
+												<Layers className="size-4" />
+												<span>Group by Status</span>
+											</>
 										)}
+									</Button>
+								</TooltipTrigger>
+								<TooltipContent>
+									{groupByStatus ? "Show tasks in run order" : "Group tasks by status"}
+								</TooltipContent>
+							</Tooltip>
+						</div>
+						<Table className="border">
+							<TableHeader className="sticky top-0 z-10">
+								{stats && (
+									<TableRow>
+										<TableHead colSpan={6 + toolColumns.length} className="bg-muted p-4">
+											{/* Provider, Model title and status */}
+											<div className="flex items-center justify-center gap-3 mb-3 relative">
+												{run.settings?.apiProvider && (
+													<span className="text-sm text-muted-foreground">
+														{run.settings.apiProvider}
+													</span>
+												)}
+												<div className="font-mono">{run.model}</div>
+												<RunStatus runStatus={runStatus} isComplete={!!run.taskMetricsId} />
+												{run.description && (
+													<span className="text-sm text-muted-foreground">
+														- {run.description}
+													</span>
+												)}
+												{isRunActive && (
+													<Tooltip>
+														<TooltipTrigger asChild>
+															<Button
+																variant="ghost"
+																size="sm"
+																onClick={() => setShowKillDialog(true)}
+																disabled={isKilling}
+																className="absolute right-0 flex items-center gap-1 text-muted-foreground hover:text-destructive">
+																{isKilling ? (
+																	<LoaderCircle className="size-4 animate-spin" />
+																) : (
+																	<StopCircle className="size-4" />
+																)}
+																Kill
+															</Button>
+														</TooltipTrigger>
+														<TooltipContent>
+															Stop all containers for this run
+														</TooltipContent>
+													</Tooltip>
+												)}
+											</div>
+											{/* Main Stats Row */}
+											<div className="flex items-start justify-center gap-x-8 gap-y-3">
+												{/* Pass Rate / Fail Rate / Remaining % */}
+												<div className="text-center min-w-[160px]">
+													<div className="text-2xl font-bold whitespace-nowrap">
+														<span className="text-green-600">
+															{stats.completed > 0
+																? `${((stats.passed / stats.completed) * 100).toFixed(1)}%`
+																: "-"}
+														</span>
+														<span className="text-muted-foreground mx-1">/</span>
+														<span className="text-red-600">
+															{stats.completed > 0
+																? `${((stats.failed / stats.completed) * 100).toFixed(1)}%`
+																: "-"}
+														</span>
+														<span className="text-muted-foreground mx-1">/</span>
+														<span className="text-muted-foreground">
+															{tasks.length > 0
+																? `${((stats.remaining / tasks.length) * 100).toFixed(1)}%`
+																: "-"}
+														</span>
+													</div>
+													<div className="text-xs text-muted-foreground">
+														<span className="text-green-600">{stats.passed}</span>
+														{" / "}
+														<span className="text-red-600">{stats.failed}</span>
+														{" / "}
+														<span>{stats.remaining}</span>
+														{" of "}
+														{tasks.length}
+													</div>
+												</div>
+
+												{/* Tokens */}
+												<div className="text-center min-w-[140px]">
+													<div className="text-xl font-bold font-mono whitespace-nowrap">
+														{formatTokens(stats.totalTokensIn)}
+														<span className="text-muted-foreground mx-1">/</span>
+														{formatTokens(stats.totalTokensOut)}
+													</div>
+													<div className="text-xs text-muted-foreground">Tokens In / Out</div>
+												</div>
+
+												{/* Cost */}
+												<div className="text-center min-w-[70px]">
+													<div className="text-2xl font-bold font-mono">
+														{formatCurrency(stats.totalCost)}
+													</div>
+													<div className="text-xs text-muted-foreground">Cost</div>
+												</div>
+
+												{/* Duration */}
+												<div className="text-center min-w-[90px]">
+													<div className="text-2xl font-bold font-mono whitespace-nowrap">
+														{stats.totalDuration > 0
+															? formatDuration(stats.totalDuration)
+															: "-"}
+													</div>
+													<div className="text-xs text-muted-foreground">Duration</div>
+												</div>
+
+												{/* Elapsed Time */}
+												<div className="text-center min-w-[90px]">
+													<div className="text-2xl font-bold font-mono whitespace-nowrap">
+														{elapsedTime !== null ? formatDuration(elapsedTime) : "-"}
+													</div>
+													<div className="text-xs text-muted-foreground">Elapsed</div>
+												</div>
+
+												{/* Estimated Time Remaining - only show if run is active and we have data */}
+												{!run.taskMetricsId &&
+													elapsedTime !== null &&
+													stats.completed > 0 &&
+													stats.remaining > 0 && (
+														<div className="text-center min-w-[90px]">
+															<div className="text-2xl font-bold font-mono whitespace-nowrap text-muted-foreground">
+																~
+																{formatDuration(
+																	(elapsedTime / stats.completed) * stats.remaining,
+																)}
+															</div>
+															<div className="text-xs text-muted-foreground">
+																Est. Remaining
+															</div>
+														</div>
+													)}
+											</div>
+
+											{/* Tool Usage Row */}
+											{Object.keys(stats.toolUsage).length > 0 && (
+												<div className="flex items-center justify-center gap-2 flex-wrap mt-3">
+													{Object.entries(stats.toolUsage)
+														.sort(([, a], [, b]) => b.attempts - a.attempts)
+														.map(([toolName, usage]) => {
+															const abbr = getToolAbbreviation(toolName)
+															const successRate =
+																usage.attempts > 0
+																	? ((usage.attempts - usage.failures) /
+																			usage.attempts) *
+																		100
+																	: 100
+															const rateColor =
+																successRate === 100
+																	? "text-green-500"
+																	: successRate >= 80
+																		? "text-yellow-500"
+																		: "text-red-500"
+															return (
+																<Tooltip key={toolName}>
+																	<TooltipTrigger asChild>
+																		<div className="flex items-center gap-1 px-2 py-1 rounded bg-background/50 border border-border/50 hover:border-border transition-colors cursor-default text-xs">
+																			<span className="font-medium text-muted-foreground">
+																				{abbr}
+																			</span>
+																			<span className="font-bold tabular-nums">
+																				{usage.attempts}
+																			</span>
+																			<span className={`${rateColor}`}>
+																				{formatToolUsageSuccessRate(usage)}
+																			</span>
+																		</div>
+																	</TooltipTrigger>
+																	<TooltipContent side="bottom">
+																		{toolName}
+																	</TooltipContent>
+																</Tooltip>
+															)
+														})}
+												</div>
+											)}
+										</TableHead>
 									</TableRow>
-								)
-							})}
-						</TableBody>
-					</Table>
+								)}
+								<TableRow>
+									<TableHead className="w-12 text-center">#</TableHead>
+									<TableHead>Exercise</TableHead>
+									<TableHead className="text-center">Tokens In / Out</TableHead>
+									<TableHead>Context</TableHead>
+									{toolColumns.map((toolName) => (
+										<TableHead key={toolName} className="text-xs text-center">
+											<Tooltip>
+												<TooltipTrigger>{getToolAbbreviation(toolName)}</TooltipTrigger>
+												<TooltipContent>{toolName}</TooltipContent>
+											</Tooltip>
+										</TableHead>
+									))}
+									<TableHead>Duration</TableHead>
+									<TableHead>Cost</TableHead>
+								</TableRow>
+							</TableHeader>
+							<TableBody>
+								{groupByStatus && groupedTasks
+									? // Grouped view
+										statusOrder.map((status) => {
+											const group = groupedTasks[status]
+											if (group.length === 0) return null
+											const { label, className } = statusLabels[status]
+											return (
+												<Fragment key={status}>
+													<TableRow className="bg-muted/50 hover:bg-muted/50">
+														<TableCell colSpan={6 + toolColumns.length} className="py-2">
+															<span className={`font-semibold ${className}`}>
+																{label} ({group.length})
+															</span>
+														</TableCell>
+													</TableRow>
+													{group.map(({ task, originalIndex }) =>
+														renderTaskRow(task, originalIndex),
+													)}
+												</Fragment>
+											)
+										})
+									: // Default order view
+										tasks.map((task, index) => renderTaskRow(task, index))}
+							</TableBody>
+						</Table>
+					</>
 				)}
 			</div>
 

+ 71 - 63
apps/web-evals/src/components/home/run.tsx

@@ -44,14 +44,22 @@ import {
 	ScrollArea,
 } from "@/components/ui"
 
+// Tool group type (same as in runs.tsx)
+type ToolGroup = {
+	id: string
+	name: string
+	icon: string
+	tools: string[]
+}
+
 type RunProps = {
 	run: EvalsRun
 	taskMetrics: EvalsTaskMetrics | null
 	toolColumns: ToolName[]
-	consolidatedToolColumns: string[]
+	toolGroups: ToolGroup[]
 }
 
-export function Run({ run, taskMetrics, toolColumns, consolidatedToolColumns }: RunProps) {
+export function Run({ run, taskMetrics, toolColumns, toolGroups }: RunProps) {
 	const router = useRouter()
 	const [deleteRunId, setDeleteRunId] = useState<number>()
 	const [showSettings, setShowSettings] = useState(false)
@@ -143,6 +151,62 @@ export function Run({ run, taskMetrics, toolColumns, consolidatedToolColumns }:
 		[router, run.id],
 	)
 
+	// Helper to render a tool group cell
+	const renderToolGroupCell = (group: ToolGroup) => {
+		if (!taskMetrics?.toolUsage) {
+			return <span className="text-muted-foreground">-</span>
+		}
+
+		let totalAttempts = 0
+		let totalFailures = 0
+		const breakdown: Array<{ tool: string; attempts: number; rate: string }> = []
+
+		for (const toolName of group.tools) {
+			const usage = taskMetrics.toolUsage[toolName as ToolName]
+			if (usage) {
+				totalAttempts += usage.attempts
+				totalFailures += usage.failures
+				const rate =
+					usage.attempts > 0
+						? `${Math.round(((usage.attempts - usage.failures) / usage.attempts) * 100)}%`
+						: "0%"
+				breakdown.push({ tool: toolName, attempts: usage.attempts, rate })
+			}
+		}
+
+		if (totalAttempts === 0) {
+			return <span className="text-muted-foreground">-</span>
+		}
+
+		const successRate = ((totalAttempts - totalFailures) / totalAttempts) * 100
+		const rateColor =
+			successRate === 100 ? "text-muted-foreground" : successRate >= 80 ? "text-yellow-500" : "text-red-500"
+
+		return (
+			<Tooltip>
+				<TooltipTrigger>
+					<div className="flex flex-col items-center">
+						<span className="font-medium">{totalAttempts}</span>
+						<span className={rateColor}>{Math.round(successRate)}%</span>
+					</div>
+				</TooltipTrigger>
+				<TooltipContent>
+					<div className="text-xs">
+						<div className="font-semibold mb-1">{group.name}</div>
+						{breakdown.map(({ tool, attempts, rate }) => (
+							<div key={tool} className="flex justify-between gap-4">
+								<span>{tool}:</span>
+								<span>
+									{attempts} ({rate})
+								</span>
+							</div>
+						))}
+					</div>
+				</TooltipContent>
+			</Tooltip>
+		)
+	}
+
 	return (
 		<>
 			<TableRow className="cursor-pointer hover:bg-muted/50" onClick={handleRowClick}>
@@ -170,68 +234,12 @@ export function Run({ run, taskMetrics, toolColumns, consolidatedToolColumns }:
 						</div>
 					)}
 				</TableCell>
-				{consolidatedToolColumns.length > 0 && (
-					<TableCell className="text-xs text-center">
-						{taskMetrics?.toolUsage ? (
-							(() => {
-								// Calculate aggregated stats for consolidated tools
-								let totalAttempts = 0
-								let totalFailures = 0
-								const breakdown: Array<{ tool: string; attempts: number; rate: string }> = []
-
-								for (const toolName of consolidatedToolColumns) {
-									const usage = taskMetrics.toolUsage[toolName as ToolName]
-									if (usage) {
-										totalAttempts += usage.attempts
-										totalFailures += usage.failures
-										const rate =
-											usage.attempts > 0
-												? `${Math.round(((usage.attempts - usage.failures) / usage.attempts) * 100)}%`
-												: "0%"
-										breakdown.push({ tool: toolName, attempts: usage.attempts, rate })
-									}
-								}
-
-								const consolidatedRate =
-									totalAttempts > 0 ? ((totalAttempts - totalFailures) / totalAttempts) * 100 : 100
-								const rateColor =
-									consolidatedRate === 100
-										? "text-muted-foreground"
-										: consolidatedRate >= 80
-											? "text-yellow-500"
-											: "text-red-500"
-
-								return totalAttempts > 0 ? (
-									<Tooltip>
-										<TooltipTrigger>
-											<div className="flex flex-col items-center">
-												<span className="font-medium">{totalAttempts}</span>
-												<span className={rateColor}>{Math.round(consolidatedRate)}%</span>
-											</div>
-										</TooltipTrigger>
-										<TooltipContent>
-											<div className="text-xs">
-												<div className="font-semibold mb-1">Consolidated Tools:</div>
-												{breakdown.map(({ tool, attempts, rate }) => (
-													<div key={tool} className="flex justify-between gap-4">
-														<span>{tool}:</span>
-														<span>
-															{attempts} ({rate})
-														</span>
-													</div>
-												))}
-											</div>
-										</TooltipContent>
-									</Tooltip>
-								) : (
-									<span className="text-muted-foreground">-</span>
-								)
-							})()
-						) : (
-							<span className="text-muted-foreground">-</span>
-						)}
+				{/* Tool Group Columns */}
+				{toolGroups.map((group) => (
+					<TableCell key={group.id} className="text-xs text-center">
+						{renderToolGroupCell(group)}
 					</TableCell>
-				)}
+				))}
 				{toolColumns.map((toolName) => {
 					const usage = taskMetrics?.toolUsage?.[toolName]
 					const successRate =

+ 384 - 91
apps/web-evals/src/components/home/runs.tsx

@@ -1,19 +1,49 @@
 "use client"
 
-import { useCallback, useEffect, useMemo, useState } from "react"
+import { useCallback, useEffect, useMemo, useState, memo } from "react"
 import { useRouter } from "next/navigation"
 import {
 	ArrowDown,
 	ArrowUp,
 	ArrowUpDown,
+	Box,
+	Boxes,
+	Check,
+	CheckCircle,
+	CircleDot,
+	ClipboardList,
+	Cog,
 	Combine,
 	Ellipsis,
+	File,
+	FileText,
+	Folder,
+	FolderOpen,
+	Hammer,
+	Hexagon,
+	Layers,
+	List,
+	ListChecks,
+	ListTodo,
 	LoaderCircle,
+	Package,
+	Pencil,
+	PencilLine,
+	Plus,
 	Rocket,
-	RotateCcw,
+	Search,
+	Settings2,
+	Shapes,
+	Square,
+	Star,
+	Tag,
+	Terminal,
 	Trash2,
+	Wrench,
 	X,
+	Zap,
 } from "lucide-react"
+import type { LucideIcon } from "lucide-react"
 import { toast } from "sonner"
 
 import type { Run, TaskMetrics } from "@roo-code/evals"
@@ -30,10 +60,17 @@ import {
 	AlertDialogHeader,
 	AlertDialogTitle,
 	Button,
+	Dialog,
+	DialogContent,
+	DialogFooter,
+	DialogHeader,
+	DialogTitle,
 	DropdownMenu,
 	DropdownMenuContent,
 	DropdownMenuItem,
+	DropdownMenuSeparator,
 	DropdownMenuTrigger,
+	Input,
 	MultiSelect,
 	Select,
 	SelectContent,
@@ -52,6 +89,166 @@ import {
 } from "@/components/ui"
 import { Run as Row } from "@/components/home/run"
 
+// Available icons for tool groups
+const TOOL_GROUP_ICONS: { name: string; icon: LucideIcon }[] = [
+	{ name: "combine", icon: Combine },
+	{ name: "layers", icon: Layers },
+	{ name: "box", icon: Box },
+	{ name: "boxes", icon: Boxes },
+	{ name: "package", icon: Package },
+	{ name: "folder", icon: Folder },
+	{ name: "folder-open", icon: FolderOpen },
+	{ name: "file", icon: File },
+	{ name: "file-text", icon: FileText },
+	{ name: "list", icon: List },
+	{ name: "list-todo", icon: ListTodo },
+	{ name: "list-checks", icon: ListChecks },
+	{ name: "clipboard-list", icon: ClipboardList },
+	{ name: "check", icon: Check },
+	{ name: "check-circle", icon: CheckCircle },
+	{ name: "pencil", icon: PencilLine },
+	{ name: "trash", icon: Trash2 },
+	{ name: "x", icon: X },
+	{ name: "search", icon: Search },
+	{ name: "terminal", icon: Terminal },
+	{ name: "shapes", icon: Shapes },
+	{ name: "hexagon", icon: Hexagon },
+	{ name: "square", icon: Square },
+	{ name: "circle-dot", icon: CircleDot },
+	{ name: "star", icon: Star },
+	{ name: "zap", icon: Zap },
+	{ name: "hammer", icon: Hammer },
+	{ name: "wrench", icon: Wrench },
+	{ name: "cog", icon: Cog },
+	{ name: "settings", icon: Settings2 },
+	{ name: "tag", icon: Tag },
+]
+
+// Tool group type
+export type ToolGroup = {
+	id: string
+	name: string
+	icon: string
+	tools: string[]
+}
+
+// Helper to get icon component by name
+function getIconByName(name: string): LucideIcon {
+	return TOOL_GROUP_ICONS.find((i) => i.name === name)?.icon ?? Combine
+}
+
+// Generate a unique ID for tool groups
+function generateGroupId(): string {
+	return `group-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
+}
+
+// Isolated dialog component to prevent parent re-renders on state changes
+const ToolGroupEditorDialog = memo(function ToolGroupEditorDialog({
+	open,
+	onOpenChange,
+	editingGroup,
+	availableTools,
+	onSave,
+}: {
+	open: boolean
+	onOpenChange: (open: boolean) => void
+	editingGroup: ToolGroup | null
+	availableTools: { label: string; value: string }[]
+	onSave: (group: ToolGroup) => void
+}) {
+	const [groupName, setGroupName] = useState(editingGroup?.name ?? "")
+	const [groupIcon, setGroupIcon] = useState(editingGroup?.icon ?? "combine")
+	const [groupTools, setGroupTools] = useState<string[]>(editingGroup?.tools ?? [])
+
+	// Reset form when dialog opens or editingGroup changes
+	useEffect(() => {
+		if (open) {
+			setGroupName(editingGroup?.name ?? "")
+			setGroupIcon(editingGroup?.icon ?? "combine")
+			setGroupTools(editingGroup?.tools ?? [])
+		}
+	}, [open, editingGroup])
+
+	const canSaveGroup = groupName.trim().length > 0 && groupTools.length > 0
+
+	const handleSave = () => {
+		if (!canSaveGroup) return
+		const group: ToolGroup = {
+			id: editingGroup?.id ?? generateGroupId(),
+			name: groupName.trim(),
+			icon: groupIcon,
+			tools: groupTools,
+		}
+		onSave(group)
+		onOpenChange(false)
+	}
+
+	return (
+		<Dialog open={open} onOpenChange={onOpenChange}>
+			<DialogContent className="max-w-md">
+				<DialogHeader>
+					<DialogTitle>{editingGroup ? "Edit Tool Group" : "Create Tool Group"}</DialogTitle>
+				</DialogHeader>
+				<div className="space-y-4 py-4">
+					<div className="space-y-2">
+						<label className="text-sm font-medium">
+							Group Name <span className="text-destructive">*</span>
+						</label>
+						<Input
+							placeholder="e.g., File Operations"
+							value={groupName}
+							onChange={(e) => setGroupName(e.target.value)}
+							className={!groupName.trim() ? "border-muted-foreground/30" : ""}
+						/>
+					</div>
+					<div className="space-y-2">
+						<label className="text-sm font-medium">Icon</label>
+						<div className="flex flex-wrap gap-2">
+							{TOOL_GROUP_ICONS.map(({ name, icon: IconComponent }) => (
+								<Button
+									key={name}
+									variant={groupIcon === name ? "default" : "outline"}
+									size="icon"
+									className="h-8 w-8"
+									onClick={() => setGroupIcon(name)}>
+									<IconComponent className="h-4 w-4" />
+								</Button>
+							))}
+						</div>
+					</div>
+					<div className="space-y-2">
+						<label className="text-sm font-medium">
+							Tools <span className="text-destructive">*</span>
+						</label>
+						<MultiSelect
+							options={availableTools}
+							value={groupTools}
+							onValueChange={setGroupTools}
+							placeholder="Select tools..."
+							className="w-full"
+							maxCount={3}
+							modalPopover
+						/>
+						<div className="text-xs text-muted-foreground">
+							{groupTools.length > 0
+								? `${groupTools.length} tool${groupTools.length !== 1 ? "s" : ""} selected`
+								: "Select at least one tool"}
+						</div>
+					</div>
+				</div>
+				<DialogFooter>
+					<Button variant="outline" onClick={() => onOpenChange(false)}>
+						Cancel
+					</Button>
+					<Button onClick={handleSave} disabled={!canSaveGroup}>
+						{editingGroup ? "Save Changes" : "Create Group"}
+					</Button>
+				</DialogFooter>
+			</DialogContent>
+		</Dialog>
+	)
+})
+
 type RunWithTaskMetrics = Run & { taskMetrics: TaskMetrics | null }
 
 type SortColumn = "model" | "provider" | "passed" | "failed" | "percent" | "cost" | "duration" | "createdAt"
@@ -72,7 +269,7 @@ const STORAGE_KEYS = {
 	TIMEFRAME: "evals-runs-timeframe",
 	MODEL_FILTER: "evals-runs-model-filter",
 	PROVIDER_FILTER: "evals-runs-provider-filter",
-	CONSOLIDATED_TOOLS: "evals-runs-consolidated-tools",
+	TOOL_GROUPS: "evals-runs-tool-groups",
 }
 
 function getTimeframeStartDate(timeframe: TimeframeOption): Date | null {
@@ -137,13 +334,24 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
 		return stored ? JSON.parse(stored) : []
 	})
 
-	// Tool column consolidation state - initialize from localStorage
-	const [consolidatedToolColumns, setConsolidatedToolColumns] = useState<string[]>(() => {
+	// Tool groups state - initialize from localStorage
+	const [toolGroups, setToolGroups] = useState<ToolGroup[]>(() => {
 		if (typeof window === "undefined") return []
-		const stored = localStorage.getItem(STORAGE_KEYS.CONSOLIDATED_TOOLS)
-		return stored ? JSON.parse(stored) : []
+		const stored = localStorage.getItem(STORAGE_KEYS.TOOL_GROUPS)
+		if (stored) {
+			try {
+				return JSON.parse(stored)
+			} catch {
+				return []
+			}
+		}
+		return []
 	})
 
+	// Tool group editor dialog state
+	const [showGroupDialog, setShowGroupDialog] = useState(false)
+	const [editingGroup, setEditingGroup] = useState<ToolGroup | null>(null)
+
 	// Delete runs state
 	const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
 	const [showDeleteOldConfirm, setShowDeleteOldConfirm] = useState(false)
@@ -163,8 +371,8 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
 	}, [providerFilter])
 
 	useEffect(() => {
-		localStorage.setItem(STORAGE_KEYS.CONSOLIDATED_TOOLS, JSON.stringify(consolidatedToolColumns))
-	}, [consolidatedToolColumns])
+		localStorage.setItem(STORAGE_KEYS.TOOL_GROUPS, JSON.stringify(toolGroups))
+	}, [toolGroups])
 
 	// Count incomplete runs (runs without taskMetricsId)
 	const incompleteRunsCount = useMemo(() => {
@@ -300,7 +508,7 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
 			.map(([name]): ToolName => name)
 	}, [filteredRuns])
 
-	// Tool column options for the consolidation dropdown
+	// Tool column options for the group editor
 	const toolColumnOptions = useMemo(() => {
 		return allToolColumns.map((tool) => ({
 			label: tool,
@@ -308,13 +516,21 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
 		}))
 	}, [allToolColumns])
 
-	// Separate consolidated and individual tool columns
-	const individualToolColumns = useMemo(() => {
-		return allToolColumns.filter((tool) => !consolidatedToolColumns.includes(tool))
-	}, [allToolColumns, consolidatedToolColumns])
+	// Get all tools that are in any group
+	const groupedTools = useMemo(() => {
+		const grouped = new Set<string>()
+		for (const group of toolGroups) {
+			for (const tool of group.tools) {
+				grouped.add(tool)
+			}
+		}
+		return grouped
+	}, [toolGroups])
 
-	// Create a "consolidated" column if any tools are selected for consolidation
-	const hasConsolidatedColumn = consolidatedToolColumns.length > 0
+	// Separate grouped and individual tool columns
+	const individualToolColumns = useMemo(() => {
+		return allToolColumns.filter((tool) => !groupedTools.has(tool))
+	}, [allToolColumns, groupedTools])
 
 	// Use individualToolColumns for rendering
 	const toolColumns = individualToolColumns
@@ -377,13 +593,11 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
 		})
 	}, [filteredRuns, sortColumn, sortDirection])
 
-	// Calculate colSpan for empty state (7 base columns + dynamic tools + consolidated column + 3 end columns)
-	const totalColumns = 7 + toolColumns.length + (hasConsolidatedColumn ? 1 : 0) + 3
+	// Calculate colSpan for empty state (7 base columns + tool groups + dynamic tools + 3 end columns)
+	const totalColumns = 7 + toolGroups.length + toolColumns.length + 3
 
-	// Check if any filters or settings are active
+	// Check if any filters are active
 	const hasActiveFilters = timeframeFilter !== "all" || modelFilter.length > 0 || providerFilter.length > 0
-	const hasConsolidatedTools = consolidatedToolColumns.length > 0
-	const hasAnyCustomization = hasActiveFilters || hasConsolidatedTools
 
 	const clearAllFilters = () => {
 		setTimeframeFilter("all")
@@ -391,16 +605,52 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
 		setProviderFilter([])
 	}
 
-	const resetAll = () => {
-		setTimeframeFilter("all")
-		setModelFilter([])
-		setProviderFilter([])
-		setConsolidatedToolColumns([])
-		localStorage.removeItem(STORAGE_KEYS.TIMEFRAME)
-		localStorage.removeItem(STORAGE_KEYS.MODEL_FILTER)
-		localStorage.removeItem(STORAGE_KEYS.PROVIDER_FILTER)
-		localStorage.removeItem(STORAGE_KEYS.CONSOLIDATED_TOOLS)
-	}
+	// Tool group management handlers
+	const openNewGroupDialog = useCallback(() => {
+		setEditingGroup(null)
+		setShowGroupDialog(true)
+	}, [])
+
+	const openEditGroupDialog = useCallback((group: ToolGroup) => {
+		setEditingGroup(group)
+		setShowGroupDialog(true)
+	}, [])
+
+	const handleSaveGroup = useCallback(
+		(group: ToolGroup) => {
+			setToolGroups((prev) => {
+				const existingIndex = prev.findIndex((g) => g.id === group.id)
+				if (existingIndex >= 0) {
+					// Update existing group
+					const newGroups = [...prev]
+					newGroups[existingIndex] = group
+					return newGroups
+				} else {
+					// Add new group
+					return [...prev, group]
+				}
+			})
+			toast.success(editingGroup ? "Group updated" : "Group created")
+		},
+		[editingGroup],
+	)
+
+	const handleDeleteGroup = useCallback((groupId: string) => {
+		setToolGroups((prev) => prev.filter((g) => g.id !== groupId))
+		toast.success("Group deleted")
+	}, [])
+
+	// Get available tools for group editor (tools not in other groups)
+	const availableToolsForEditor = useMemo(() => {
+		const usedInOtherGroups = new Set<string>()
+		for (const group of toolGroups) {
+			if (editingGroup && group.id === editingGroup.id) continue
+			for (const tool of group.tools) {
+				usedInOtherGroups.add(tool)
+			}
+		}
+		return toolColumnOptions.filter((opt) => !usedInOtherGroups.has(opt.value))
+	}, [toolColumnOptions, toolGroups, editingGroup])
 
 	return (
 		<>
@@ -448,49 +698,76 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
 					/>
 				</div>
 
+				{/* Tool Groups Dropdown */}
 				<div className="flex items-center gap-2">
-					<Tooltip>
-						<TooltipTrigger asChild>
-							<div className="flex items-center gap-2">
-								<Combine className="h-4 w-4 text-muted-foreground" />
-								<span className="text-sm font-medium text-muted-foreground">Consolidate:</span>
-							</div>
-						</TooltipTrigger>
-						<TooltipContent>Select tool columns to consolidate into a combined column</TooltipContent>
-					</Tooltip>
-					<div className="relative min-w-[100px] w-fit max-w-[140px]">
-						<div className={consolidatedToolColumns.length > 0 ? "[&>div>div]:invisible" : ""}>
-							<MultiSelect
-								options={toolColumnOptions}
-								value={consolidatedToolColumns}
-								onValueChange={setConsolidatedToolColumns}
-								placeholder="None"
-								className="w-full min-w-[100px]"
-								maxCount={0}
-								popoverAutoWidth
-								footer={
-									hasAnyCustomization && (
-										<Button
-											variant="ghost"
-											size="sm"
-											className="w-full justify-start text-muted-foreground hover:text-foreground"
-											onClick={resetAll}>
-											<RotateCcw className="h-4 w-4 mr-2" />
-											Reset all filters & consolidation
-										</Button>
-									)
-								}
-							/>
-						</div>
-						{consolidatedToolColumns.length > 0 && (
-							<div className="absolute inset-0 flex items-center px-3 pointer-events-none">
-								<span className="text-sm font-medium whitespace-nowrap">
-									{consolidatedToolColumns.length} tool
-									{consolidatedToolColumns.length !== 1 ? "s" : ""}
-								</span>
-							</div>
-						)}
-					</div>
+					<DropdownMenu>
+						<DropdownMenuTrigger asChild>
+							<Button variant="outline" size="sm" className="flex items-center gap-2">
+								<Layers className="h-4 w-4" />
+								<span>Groups</span>
+								{toolGroups.length > 0 && (
+									<span className="bg-primary text-primary-foreground text-xs px-1.5 rounded-full">
+										{toolGroups.length}
+									</span>
+								)}
+							</Button>
+						</DropdownMenuTrigger>
+						<DropdownMenuContent align="start" className="w-64">
+							{toolGroups.length > 0 ? (
+								<>
+									{toolGroups.map((group) => {
+										const IconComponent = getIconByName(group.icon)
+										return (
+											<DropdownMenuItem
+												key={group.id}
+												className="flex items-center justify-between"
+												onClick={(e) => {
+													e.preventDefault()
+													openEditGroupDialog(group)
+												}}>
+												<div className="flex items-center gap-2">
+													<IconComponent className="h-4 w-4" />
+													<span>{group.name}</span>
+													<span className="text-xs text-muted-foreground">
+														({group.tools.length})
+													</span>
+												</div>
+												<div className="flex items-center gap-1">
+													<Button
+														variant="ghost"
+														size="icon"
+														className="h-6 w-6"
+														onClick={(e) => {
+															e.stopPropagation()
+															openEditGroupDialog(group)
+														}}>
+														<Pencil className="h-3 w-3" />
+													</Button>
+													<Button
+														variant="ghost"
+														size="icon"
+														className="h-6 w-6 text-destructive hover:text-destructive"
+														onClick={(e) => {
+															e.stopPropagation()
+															handleDeleteGroup(group.id)
+														}}>
+														<Trash2 className="h-3 w-3" />
+													</Button>
+												</div>
+											</DropdownMenuItem>
+										)
+									})}
+									<DropdownMenuSeparator />
+								</>
+							) : (
+								<div className="px-2 py-1.5 text-sm text-muted-foreground">No groups yet</div>
+							)}
+							<DropdownMenuItem onClick={openNewGroupDialog}>
+								<Plus className="h-4 w-4 mr-2" />
+								Add Group
+							</DropdownMenuItem>
+						</DropdownMenuContent>
+					</DropdownMenu>
 				</div>
 
 				{hasActiveFilters && (
@@ -580,23 +857,30 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
 							</div>
 						</TableHead>
 						<TableHead>Tokens</TableHead>
-						{hasConsolidatedColumn && (
-							<TableHead className="text-xs text-center">
-								<Tooltip>
-									<TooltipTrigger>
-										<Combine className="h-3 w-3 inline" />
-									</TooltipTrigger>
-									<TooltipContent>
-										<div className="text-xs">
-											<div className="font-semibold mb-1">Consolidated Tools:</div>
-											{consolidatedToolColumns.map((tool) => (
-												<div key={tool}>{tool}</div>
-											))}
-										</div>
-									</TooltipContent>
-								</Tooltip>
-							</TableHead>
-						)}
+						{/* Tool Group Columns */}
+						{toolGroups.map((group) => {
+							const IconComponent = getIconByName(group.icon)
+							return (
+								<TableHead key={group.id} className="text-center">
+									<div className="flex justify-center">
+										<Tooltip>
+											<TooltipTrigger>
+												<IconComponent className="h-4 w-4" />
+											</TooltipTrigger>
+											<TooltipContent>
+												<div className="text-xs">
+													<div className="font-semibold mb-1">{group.name}</div>
+													{group.tools.map((tool) => (
+														<div key={tool}>{tool}</div>
+													))}
+												</div>
+											</TooltipContent>
+										</Tooltip>
+									</div>
+								</TableHead>
+							)
+						})}
+						{/* Individual Tool Columns */}
 						{toolColumns.map((toolName) => (
 							<TableHead key={toolName} className="text-xs text-center">
 								<Tooltip>
@@ -628,7 +912,7 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
 								run={run}
 								taskMetrics={taskMetrics}
 								toolColumns={toolColumns}
-								consolidatedToolColumns={consolidatedToolColumns}
+								toolGroups={toolGroups}
 							/>
 						))
 					) : (
@@ -663,6 +947,15 @@ export function Runs({ runs }: { runs: RunWithTaskMetrics[] }) {
 				<Rocket className="size-6" />
 			</Button>
 
+			{/* Tool Group Editor Dialog */}
+			<ToolGroupEditorDialog
+				open={showGroupDialog}
+				onOpenChange={setShowGroupDialog}
+				editingGroup={editingGroup}
+				availableTools={availableToolsForEditor}
+				onSave={handleSaveGroup}
+			/>
+
 			{/* Delete Incomplete Runs Confirmation Dialog */}
 			<AlertDialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
 				<AlertDialogContent>

+ 30 - 0
apps/web-evals/src/lib/__tests__/formatters.spec.ts

@@ -0,0 +1,30 @@
+import { formatDuration, formatTokens } from "../formatters"
+
+describe("formatDuration()", () => {
+	it("formats as H:MM:SS", () => {
+		expect(formatDuration(0)).toBe("0:00:00")
+		expect(formatDuration(1_000)).toBe("0:00:01")
+		expect(formatDuration(61_000)).toBe("0:01:01")
+		expect(formatDuration(3_661_000)).toBe("1:01:01")
+	})
+})
+
+describe("formatTokens()", () => {
+	it("formats small numbers without suffix", () => {
+		expect(formatTokens(0)).toBe("0")
+		expect(formatTokens(999)).toBe("999")
+	})
+
+	it("formats thousands without decimals and clamps to 1.0M at boundary", () => {
+		expect(formatTokens(1_000)).toBe("1k")
+		expect(formatTokens(72_500)).toBe("73k")
+		expect(formatTokens(999_499)).toBe("999k")
+		expect(formatTokens(999_500)).toBe("1.0M")
+	})
+
+	it("formats millions with one decimal and clamps to 1.0B at boundary", () => {
+		expect(formatTokens(1_000_000)).toBe("1.0M")
+		expect(formatTokens(3_240_000)).toBe("3.2M")
+		expect(formatTokens(999_950_000)).toBe("1.0B")
+	})
+})

+ 18 - 17
apps/web-evals/src/lib/formatters.ts

@@ -11,21 +11,10 @@ export const formatDuration = (durationMs: number) => {
 	const minutes = Math.floor((seconds % 3600) / 60)
 	const remainingSeconds = seconds % 60
 
-	const parts = []
-
-	if (hours > 0) {
-		parts.push(`${hours}h`)
-	}
-
-	if (minutes > 0) {
-		parts.push(`${minutes}m`)
-	}
-
-	if (remainingSeconds > 0 || parts.length === 0) {
-		parts.push(`${remainingSeconds}s`)
-	}
-
-	return parts.join(" ")
+	// Format as H:MM:SS
+	const mm = minutes.toString().padStart(2, "0")
+	const ss = remainingSeconds.toString().padStart(2, "0")
+	return `${hours}:${mm}:${ss}`
 }
 
 export const formatTokens = (tokens: number) => {
@@ -34,11 +23,23 @@ export const formatTokens = (tokens: number) => {
 	}
 
 	if (tokens < 1000000) {
-		return `${(tokens / 1000).toFixed(1)}k`
+		// No decimal for thousands (e.g., 72k not 72.5k)
+		const rounded = Math.round(tokens / 1000)
+		// If rounding crosses the boundary to 1000k, show as 1.0M instead
+		if (rounded >= 1000) {
+			return "1.0M"
+		}
+		return `${rounded}k`
 	}
 
 	if (tokens < 1000000000) {
-		return `${(tokens / 1000000).toFixed(1)}M`
+		// Keep decimal for millions (e.g., 3.2M)
+		const rounded = Math.round(tokens / 100000) / 10 // Round to 1 decimal
+		// If rounding crosses the boundary to 1000M, show as 1.0B instead
+		if (rounded >= 1000) {
+			return "1.0B"
+		}
+		return `${rounded.toFixed(1)}M`
 	}
 
 	return `${(tokens / 1000000000).toFixed(1)}B`

+ 1 - 1
apps/web-roo-code/package.json

@@ -25,7 +25,7 @@
 		"embla-carousel-react": "^8.6.0",
 		"framer-motion": "12.15.0",
 		"lucide-react": "^0.518.0",
-		"next": "~15.2.6",
+		"next": "~15.2.8",
 		"next-themes": "^0.4.6",
 		"posthog-js": "^1.248.1",
 		"react": "^18.3.1",

+ 297 - 0
apps/web-roo-code/src/app/cloud/team/page.tsx

@@ -0,0 +1,297 @@
+import {
+	ArrowRight,
+	Users,
+	Settings,
+	BarChart3,
+	Lock,
+	Puzzle,
+	ShieldCheck,
+	DollarSign,
+	Share2,
+	LucideIcon,
+	Server,
+	ServerIcon,
+	RefreshCcw,
+} from "lucide-react"
+import type { Metadata } from "next"
+
+import { Button } from "@/components/ui"
+import { AnimatedBackground } from "@/components/homepage"
+import { SEO } from "@/lib/seo"
+import { ogImageUrl } from "@/lib/og"
+import { EXTERNAL_LINKS } from "@/lib/constants"
+
+const TITLE = "Roo Code Cloud Team Plan"
+const DESCRIPTION =
+	"Scale your development with team collaboration features. Centralized billing, shared configuration, team-wide analytics, and unified GitHub and Slack integrations."
+const OG_DESCRIPTION = "Team collaboration for AI-powered development"
+const PATH = "/cloud/team"
+
+export const metadata: Metadata = {
+	title: TITLE,
+	description: DESCRIPTION,
+	alternates: {
+		canonical: `${SEO.url}${PATH}`,
+	},
+	openGraph: {
+		title: TITLE,
+		description: DESCRIPTION,
+		url: `${SEO.url}${PATH}`,
+		siteName: SEO.name,
+		images: [
+			{
+				url: ogImageUrl(TITLE, OG_DESCRIPTION),
+				width: 1200,
+				height: 630,
+				alt: TITLE,
+			},
+		],
+		locale: SEO.locale,
+		type: "website",
+	},
+	twitter: {
+		card: SEO.twitterCard,
+		title: TITLE,
+		description: DESCRIPTION,
+		images: [ogImageUrl(TITLE, OG_DESCRIPTION)],
+	},
+	keywords: [
+		...SEO.keywords,
+		"team",
+		"collaboration",
+		"enterprise",
+		"organization",
+		"centralized billing",
+		"team management",
+	],
+}
+
+const keyBenefits = [
+	{
+		title: "No Per-Seat Costs",
+		description: "Add unlimited team members without worrying about escalating per-seat charges.",
+		icon: Users,
+	},
+	{
+		title: "Centralized Billing",
+		description:
+			"Single billing point for all team members using Cloud Agents and the Roo Code Cloud Provider. No more API key management.",
+		icon: DollarSign,
+	},
+	{
+		title: "Unified Integrations",
+		description:
+			"Connect GitHub and Slack once for the entire team. No need for each member to set up individual integrations.",
+		icon: Settings,
+	},
+	{
+		title: "Team-Wide Visibility",
+		description: "Access task history and usage analytics across your entire team with granular per-user filters.",
+		icon: BarChart3,
+	},
+	{
+		title: "Configuration Enforcement",
+		description:
+			"Set policies for providers, models, and MCP servers to ensure your team follows organizational standards.",
+		icon: ShieldCheck,
+	},
+	{
+		title: "Secure Environment Variables",
+		description:
+			"Centrally manage secrets, API keys, and environment variables for Cloud Agents in our encrypted secret store.",
+		icon: Lock,
+	},
+]
+
+interface Feature {
+	icon: LucideIcon
+	title: string
+	description: string
+}
+
+const features: Feature[] = [
+	{
+		icon: ShieldCheck,
+		title: "Configuration Enforcement",
+		description:
+			"Require team members to log in to the VS Code Extension so policies can be enforced via MDM distribution.",
+	},
+	{
+		icon: Server,
+		title: "Provider Management",
+		description:
+			"Configure and manage the model providers your team can access for both the Extension and Cloud Agents, with API-key-free management.",
+	},
+	{
+		icon: Puzzle,
+		title: "Centralized Integration",
+		description:
+			"Centralized GitHub and Slack connection for the entire team. Agents can review PRs, collaborate on your repositories and respond on your team channels.",
+	},
+	{
+		icon: RefreshCcw,
+		title: "Extension Task Sync Config",
+		description:
+			"Require task syncing from VS Code Extension and control visibility settings for who can view each other's tasks.",
+	},
+	{
+		icon: Share2,
+		title: "Task Sharing Controls",
+		description: "Enable per-task sharing with customizable audience controls and link expiration times.",
+	},
+	{
+		icon: ServerIcon,
+		title: "MCP Server Controls",
+		description: "Control access to the Roo MCP Marketplace and what custom MCPs to make available to your team.",
+	},
+]
+
+export default function CloudTeamPage() {
+	return (
+		<>
+			{/* Hero Section */}
+			<section className="relative flex pt-32 pb-20 items-center overflow-hidden">
+				<AnimatedBackground />
+				<div className="container relative flex flex-col items-center h-full z-10 mx-auto px-4 sm:px-6 lg:px-8">
+					<div className="text-center max-w-4xl mx-auto mb-12">
+						<h1 className="text-4xl font-bold tracking-tight mb-6 md:text-6xl">Roo Code Cloud Team</h1>
+						<h2 className="text-2xl font-bold tracking-tight mb-6 md:text-4xl">
+							Built for <span className="text-violet-500">AI-Forward Teams</span>
+						</h2>
+						<p className="text-xl text-muted-foreground mb-8 mx-auto">
+							Empower your entire team with confidence with team-wide configuration, centralized billing,
+							analytics and more. No per-seat costs, no API key juggling.
+						</p>
+						<div className="flex flex-col sm:flex-row gap-4 justify-center">
+							<Button
+								size="xl"
+								className="bg-violet-600 hover:bg-violet-700 text-white transition-all duration-300 shadow-lg hover:shadow-violet-500/25"
+								asChild>
+								<a
+									href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP + "?redirect_url=/checkout/team"}
+									target="_blank"
+									rel="noopener noreferrer"
+									className="flex items-center justify-center">
+									Start 14-Day Free Trial
+									<ArrowRight className="ml-2 size-5" />
+								</a>
+							</Button>
+							<Button variant="outline" size="xl" className="backdrop-blur-sm" asChild>
+								<a href="/pricing" className="flex items-center justify-center">
+									View Pricing
+								</a>
+							</Button>
+						</div>
+					</div>
+				</div>
+			</section>
+
+			{/* Key Benefits Section */}
+			<section className="relative overflow-hidden border-t border-border py-32">
+				<div className="container relative z-10 mx-auto px-4 sm:px-6 lg:px-8">
+					<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2 z-1">
+						<div className="absolute left-1/2 top-1/2 h-[400px] w-full -translate-x-1/2 -translate-y-1/2 rounded-full bg-blue-500/10 dark:bg-blue-700/20 blur-[140px]" />
+					</div>
+
+					<div className="mx-auto mb-12 md:mb-24 max-w-5xl text-center">
+						<div>
+							<h2 className="text-4xl font-bold tracking-tight mb-4">Why Teams Choose Roo</h2>
+							<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
+								Streamline collaboration and scale your development capacity with team-first features.
+							</p>
+						</div>
+					</div>
+
+					<div className="relative mx-auto md:max-w-[1200px]">
+						<ul className="grid grid-cols-1 place-items-center gap-6 md:grid-cols-2 lg:grid-cols-3 lg:gap-6">
+							{keyBenefits.map((benefit, index) => {
+								const Icon = benefit.icon
+								return (
+									<li
+										key={index}
+										className="rounded-3xl bg-card outline outline-border/50 hover:outline-8 shadow-xl p-8 h-full w-full group transition-all hover:shadow-2xl hover:shadow-violet-800/30 relative">
+										{Icon && (
+											<div className="size-15 p-3 rounded-full flex items-center justify-center shadow-lg absolute -top-4 -right-2 transition-all outline outline-foreground/5 bg-card group-hover:outline-3 group-hover:scale-105">
+												<Icon className="size-5 text-violet-500" />
+											</div>
+										)}
+										<h3 className="text-xl font-bold tracking-tight mb-2">{benefit.title}</h3>
+										<div className="text-sm text-muted-foreground leading-relaxed">
+											{benefit.description}
+										</div>
+									</li>
+								)
+							})}
+						</ul>
+					</div>
+				</div>
+			</section>
+
+			{/* Features Grid */}
+			<section className="py-24 bg-muted/30">
+				<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative">
+					<div className="absolute inset-y-0 left-1/2 h-full w-full max-w-[1200px] -translate-x-1/2 z-1">
+						<div className="absolute left-1/2 top-1/2 h-[800px] w-full -translate-x-1/2 -translate-y-1/2 rounded-full bg-violet-500/10 dark:bg-violet-700/20 blur-[140px]" />
+					</div>
+					<div className="text-center mb-16">
+						<h2 className="text-4xl font-bold tracking-tight mb-4">Complete Team Management</h2>
+						<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
+							Access all capabilities from your Organization Settings. Everything you need to manage your
+							team in one place.
+						</p>
+					</div>
+					<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto relative">
+						{features.map((feature, index) => {
+							const Icon = feature.icon
+							return (
+								<div
+									key={index}
+									className="relative h-full border border-border rounded-2xl bg-background p-8 transition-all duration-300">
+									<Icon className="size-6 text-foreground/80" />
+									<h3 className="mb-3 mt-3 text-xl font-semibold text-foreground">{feature.title}</h3>
+									<p className="leading-relaxed font-light text-muted-foreground">
+										{feature.description}
+									</p>
+								</div>
+							)
+						})}
+					</div>
+				</div>
+			</section>
+
+			{/* CTA Section */}
+			<section className="py-24">
+				<div className="container mx-auto px-4 sm:px-6 lg:px-8">
+					<div className="mx-auto max-w-4xl rounded-3xl border border-border/50 bg-gradient-to-br from-violet-500/10 via-purple-500/5 to-blue-500/5 p-8 text-center shadow-2xl backdrop-blur-xl dark:border-white/10 sm:p-16">
+						<h2 className="mb-6 text-3xl font-bold tracking-tight sm:text-4xl">
+							Ready to scale your team&apos;s development?
+						</h2>
+						<p className="mx-auto mb-10 max-w-2xl text-lg text-muted-foreground">
+							Start your free 14-day trial today. Got questions? Get in touch.
+						</p>
+						<div className="flex flex-col justify-center space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
+							<Button
+								size="lg"
+								className="bg-foreground text-background hover:bg-foreground/90 transition-all duration-300"
+								asChild>
+								<a
+									href={EXTERNAL_LINKS.CLOUD_APP_SIGNUP + "?redirect_url=/checkout/team"}
+									target="_blank"
+									rel="noopener noreferrer"
+									className="flex items-center justify-center">
+									Start Free Trial
+									<ArrowRight className="ml-2 h-4 w-4" />
+								</a>
+							</Button>
+							<Button variant="outline" size="lg" className="backdrop-blur-sm" asChild>
+								<a href={EXTERNAL_LINKS.SUPPORT} className="flex items-center justify-center">
+									Contact Support
+								</a>
+							</Button>
+						</div>
+					</div>
+				</div>
+			</section>
+		</>
+	)
+}

+ 2 - 2
apps/web-roo-code/src/app/extension/page.tsx

@@ -37,8 +37,8 @@ export default async function ExtensionPage() {
 								<p className="mt-4 max-w-lg text-lg text-muted-foreground text-center md:text-left sm:mt-6">
 									Specialized modes stay on task and ship great code.
 									<br />
-									Fully model-agnostic so you can use the best (or most cost-effective) model for
-									each task.
+									Fully model-agnostic so you can use the best (or most cost-effective) model for each
+									task.
 								</p>
 								<p className="max-w-lg text-lg text-muted-foreground text-center md:text-left sm:mt-6">
 									Stop chasing this week&apos;s hot new model or CLI tool and go deep with Roo Code.

+ 14 - 3
apps/web-roo-code/src/app/pricing/page.tsx

@@ -71,6 +71,7 @@ interface PricingTier {
 		text: string
 		href?: string
 	}
+	learnMoreLink?: string
 }
 
 const pricingTiers: PricingTier[] = [
@@ -123,6 +124,7 @@ const pricingTiers: PricingTier[] = [
 			text: "Sign up",
 			href: EXTERNAL_LINKS.CLOUD_APP_SIGNUP + "?redirect_url=/billing",
 		},
+		learnMoreLink: "/cloud/team",
 	},
 ]
 
@@ -164,11 +166,11 @@ export default function PricingPage() {
 										<Icon className="size-6" strokeWidth={1.5} />
 									</div>
 
-									<div className="grow mb-8">
+									<div className="grow mb-8 md:h-[214px]">
 										<p className="text-sm text-muted-foreground font-light mb-2">
 											{tier.featuresIntro}&nbsp;
 										</p>
-										<ul className="space-y-3 my-0 md:h-[192px]">
+										<ul className="space-y-3 my-0">
 											{tier.features.map((feature) => (
 												<li key={feature} className="flex items-start gap-2">
 													<Check className="mt-0.5 h-4 w-4 text-muted-foreground shrink-0" />
@@ -176,6 +178,15 @@ export default function PricingPage() {
 												</li>
 											))}
 										</ul>
+										{tier.learnMoreLink && (
+											<div className="mt-2">
+												<Link
+													href={tier.learnMoreLink}
+													className="text-sm text-violet-600 dark:text-violet-400 hover:underline">
+													Learn more →
+												</Link>
+											</div>
+										)}
 									</div>
 
 									<p className="text-base font-light">{tier.trial}</p>
@@ -225,7 +236,7 @@ export default function PricingPage() {
 									On any plan, you can use your own LLM provider API key or use the built-in Roo Code
 									Cloud provider – curated models to work with Roo with no markup, including the
 									latest Gemini, GPT and Claude. Paid with credits.
-									<Link href="/provider/pricing" className="underline hover:no-underline ml-1">
+									<Link href="/provider" className="underline hover:no-underline ml-1">
 										See per model pricing.
 									</Link>
 								</p>

+ 2 - 1
apps/web-roo-code/src/app/privacy/page.tsx

@@ -283,7 +283,8 @@ export default function Privacy() {
 						</li>
 						<li>
 							<strong>Delete your Cloud account</strong> at any time from{" "}
-							<strong>Security Settings</strong> inside Roo Code Cloud.
+							<strong>Security Settings</strong> inside Roo Code Cloud (User Menu &rarr; My Settings
+							&rarr; Open Profile).
 						</li>
 						<li>
 							<strong>Marketing communications:</strong> You can unsubscribe from marketing and

+ 0 - 5
apps/web-roo-code/src/components/chromes/nav-bar.tsx

@@ -70,11 +70,6 @@ export function NavBar({ stars, downloads }: NavBarProps) {
 						</button>
 						{/* Dropdown Menu */}
 						<div className="absolute left-0 top-12 mt-2 w-40 rounded-md border border-border bg-background py-1 shadow-lg opacity-0 -translate-y-2 pointer-events-none group-hover:opacity-100 group-hover:translate-y-0 group-hover:pointer-events-auto transition-all duration-200">
-							<ScrollButton
-								targetId="faq"
-								className="block px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-foreground">
-								FAQ
-							</ScrollButton>
 							<Link
 								href="/evals"
 								className="block px-4 py-2 text-sm transition-colors hover:bg-accent hover:text-foreground">

+ 1 - 1
apps/web-roo-code/src/components/homepage/pillars-section.tsx

@@ -181,7 +181,7 @@ export function PillarsSection() {
 								<div className="text-muted-foreground my-4 space-y-1">
 									<p>
 										The Roo Code Extension is{" "}
-										<Link target="_blank" href="https://github.com/Roo-Code-Inc/Roo-Code">
+										<Link target="_blank" href="https://github.com/RooCodeInc/Roo-Code">
 											open source
 										</Link>{" "}
 										so you can see for yourself exactly what it&apos;s doing and we don&apos;t use

+ 1 - 0
apps/web-roo-code/src/lib/constants.ts

@@ -28,6 +28,7 @@ export const EXTERNAL_LINKS = {
 	CLOUD_APP_SIGNUP: "https://app.roocode.com/sign-up",
 	CLOUD_APP_SIGNUP_HOME: "https://app.roocode.com/sign-up?redirect_url=/cloud-agents/setup",
 	CLOUD_APP_SIGNUP_PRO: "https://app.roocode.com/sign-up?redirect_url=/cloud-agents/setup",
+	SUPPORT: "mailto:[email protected]",
 }
 
 export const INTERNAL_LINKS = {

+ 141 - 0
cli/CHANGELOG.md

@@ -1,5 +1,146 @@
 # @kilocode/cli
 
+## 0.23.1
+
+### Patch Changes
+
+- [#5164](https://github.com/Kilo-Org/kilocode/pull/5164) [`d63378c`](https://github.com/Kilo-Org/kilocode/commit/d63378c5698a0117177d86143185cb46c66f3c73) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Show auth prompt instead of timeout when CLI not configured in Agent Manager
+
+- [#5169](https://github.com/Kilo-Org/kilocode/pull/5169) [`18a9da4`](https://github.com/Kilo-Org/kilocode/commit/18a9da440f4905ce45c80bb0fa622767880de6c6) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fixed escape sequences appearing as raw text on Windows cmd.exe
+
+## 0.23.0
+
+### Minor Changes
+
+- [#5084](https://github.com/Kilo-Org/kilocode/pull/5084) [`f0c79d2`](https://github.com/Kilo-Org/kilocode/commit/f0c79d27c4952e0359ebc97d41bb50aebd2ef577) Thanks [@montanaflynn](https://github.com/montanaflynn)! - Improved CLI welcome flow: added interactive model selection list using `@inquirer/prompts`, updated provider selection to display a scrollable list, and refactored model fetching logic into a reusable service.
+
+### Patch Changes
+
+- [#5116](https://github.com/Kilo-Org/kilocode/pull/5116) [`cf00ed8`](https://github.com/Kilo-Org/kilocode/commit/cf00ed870c1af723924177372da1054411e269cd) Thanks [@PeterDaveHello](https://github.com/PeterDaveHello)! - Make .env file optional in CLI - users can configure via KILO\_\* environment variables instead
+
+## 0.22.2
+
+### Patch Changes
+
+- [#5113](https://github.com/Kilo-Org/kilocode/pull/5113) [`6d04a15`](https://github.com/Kilo-Org/kilocode/commit/6d04a150383af75ed42b954fc3c42e9e010bbed9) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix CLI crash when config file is empty or contains invalid JSON
+
+## 0.22.1
+
+### Patch Changes
+
+- [#5098](https://github.com/Kilo-Org/kilocode/pull/5098) [`e811ebe`](https://github.com/Kilo-Org/kilocode/commit/e811ebe287f187bac11239fddfab7067f428872d) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Show total session cost in status bar instead of per-request costs. Remove "API Request in progress" messages for cleaner UI.
+
+- [#5100](https://github.com/Kilo-Org/kilocode/pull/5100) [`a49868e`](https://github.com/Kilo-Org/kilocode/commit/a49868e17842d252a9a28d61aa0683267e8e3020) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix CLI context indicator showing incorrect values by skipping placeholder api_req_started messages
+
+- [#5104](https://github.com/Kilo-Org/kilocode/pull/5104) [`15a8d77`](https://github.com/Kilo-Org/kilocode/commit/15a8d77fdbe78314b448714e9812fc0857393cf5) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix CLI interactive prompts (arrow key navigation) not working on Windows
+
+    The inquirer v13+ upgrade introduced stricter TTY raw mode requirements. This fix ensures raw mode is properly enabled before inquirer prompts, restoring arrow key navigation in list selections like provider choice during `kilocode auth`.
+
+- [#5092](https://github.com/Kilo-Org/kilocode/pull/5092) [`42cdb11`](https://github.com/Kilo-Org/kilocode/commit/42cdb11b77552cb87fce9ee591bd68cbe419c3be) Thanks [@Drilmo](https://github.com/Drilmo)! - Fix Cmd+V image paste regression in VSCode terminal
+
+    Restores the ability to paste images using Cmd+V in VSCode terminal, which was broken in #4916. VSCode sends empty bracketed paste sequences for Cmd+V (unlike regular terminals that send key events), so we need to check the clipboard for images when receiving an empty paste.
+
+- Updated dependencies [[`b2e2630`](https://github.com/Kilo-Org/kilocode/commit/b2e26304e562e516383fbf95a3fdc668d88e1487)]:
+    - @kilocode/[email protected]
+
+## 0.22.0
+
+### Minor Changes
+
+- [#5046](https://github.com/Kilo-Org/kilocode/pull/5046) [`fd2029c`](https://github.com/Kilo-Org/kilocode/commit/fd2029c1de9adeedb4ac4974c10f43c936d60914) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Add `--on-task-completed <prompt>` flag to send a custom prompt to the agent when the task completes. This flag requires `--auto` mode and allows users to define any follow-up action (e.g., creating a PR, running tests, generating documentation). The prompt is sent to the agent after the main task completes, enabling flexible post-task automation.
+
+- [#5022](https://github.com/Kilo-Org/kilocode/pull/5022) [`2fc244c`](https://github.com/Kilo-Org/kilocode/commit/2fc244c85c7b1b3758e1139667e3615822656e10) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Add syntax highlighting to code edit diffs in the CLI. Diffs now display with language-aware syntax coloring using Shiki, making code changes easier to read. Includes support for 60+ languages, automatic language detection from file extensions, and theme-aware highlighting that works with both light and dark themes. Also increased the diff display limit from 20 to 50 lines with smart context collapsing around changes.
+
+### Patch Changes
+
+- [#4988](https://github.com/Kilo-Org/kilocode/pull/4988) [`7253ac0`](https://github.com/Kilo-Org/kilocode/commit/7253ac0457bf226688cad475002123a84916ea44) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix Bedrock provider validation to support API key authentication without requiring access keys
+
+- [#5064](https://github.com/Kilo-Org/kilocode/pull/5064) [`2713d06`](https://github.com/Kilo-Org/kilocode/commit/2713d069e9775e3e7b7e7f5954152b275029bd0d) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Add default autocomplete suggestions for select commands (`/model select`, `/provider select`, `/tasks select`, `/session select`)
+
+- [#5066](https://github.com/Kilo-Org/kilocode/pull/5066) [`8055f15`](https://github.com/Kilo-Org/kilocode/commit/8055f153d1491c39eb2254d74c3842e4616a79d2) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix CLI dispose, randomUUID, and debug UX issues
+
+- [#5011](https://github.com/Kilo-Org/kilocode/pull/5011) [`9c8bb7b`](https://github.com/Kilo-Org/kilocode/commit/9c8bb7b9bde56eb4d32093a2ee4bb72ac0906e92) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix multiline paste regression where pasting text with newlines would submit after the first line
+
+- [#5000](https://github.com/Kilo-Org/kilocode/pull/5000) [`1c88a66`](https://github.com/Kilo-Org/kilocode/commit/1c88a66caaacef3b96bc819456181d84174b82b2) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix empty files being created when project path contains non-Latin characters (e.g., Cyrillic, Chinese)
+
+    The CLI's `write_to_file` command was creating empty files when the project directory path contained non-Latin characters. This was caused by improper handling of `Uint8Array` content in the `FileSystemAPI.writeFile` method. The fix ensures proper `Buffer.from()` conversion before writing to the filesystem.
+
+- [#5058](https://github.com/Kilo-Org/kilocode/pull/5058) [`c9f1f6a`](https://github.com/Kilo-Org/kilocode/commit/c9f1f6afe32cdec374e7138c997c2a0b89b4989b) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix autocomplete for `/teams select` and other multi-argument commands
+
+- [#4985](https://github.com/Kilo-Org/kilocode/pull/4985) [`69a541a`](https://github.com/Kilo-Org/kilocode/commit/69a541a6d85cf79580c7d80c691bf3f5a6aa6b89) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix Windows cmd.exe display bug with escape sequences
+
+    On Windows cmd.exe, the `\x1b[3J` (clear scrollback buffer) escape sequence is not properly supported and causes display artifacts like raw escape sequences appearing in the output (e.g., `[\r\n\t...]`).
+
+## 0.21.0
+
+### Minor Changes
+
+- [#4916](https://github.com/Kilo-Org/kilocode/pull/4916) [`f02364c`](https://github.com/Kilo-Org/kilocode/commit/f02364c5a75729b5d17f447dee7570ee1e7490e6) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Abbreviate large pasted text in CLI input as `[Pasted text #N +X lines]` to prevent input field overflow when pasting logs or large code blocks
+
+- [#4997](https://github.com/Kilo-Org/kilocode/pull/4997) [`2a663be`](https://github.com/Kilo-Org/kilocode/commit/2a663bedc2a0b129a9d79321dea0ad280ec3a5da) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Add `kilocode models --json` command to expose available models as JSON for programmatic use
+
+- [#4936](https://github.com/Kilo-Org/kilocode/pull/4936) [`bfcd1d5`](https://github.com/Kilo-Org/kilocode/commit/bfcd1d5f38a887a9e0c736410ef2ff84ec0f5f3b) Thanks [@idreesmuhammadqazi-create](https://github.com/idreesmuhammadqazi-create)! - Add colorblind theme support to CLI
+
+    - Colorblind-friendly theme with high contrast colors for accessibility
+
+### Patch Changes
+
+- [#4983](https://github.com/Kilo-Org/kilocode/pull/4983) [`82ef9b0`](https://github.com/Kilo-Org/kilocode/commit/82ef9b0ad09f1b75f66db116bf9cf7c1a34edd01) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Add `/checkpoint enable` and `/checkpoint disable` subcommands to toggle checkpoint creation and save disk space
+
+- [#4982](https://github.com/Kilo-Org/kilocode/pull/4982) [`7d02d43`](https://github.com/Kilo-Org/kilocode/commit/7d02d4364b1dc4c04ce55b2feb368329b3b9c3c4) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - fix(cli): improve error message for custom mode not found
+
+- [#4996](https://github.com/Kilo-Org/kilocode/pull/4996) [`d7016fa`](https://github.com/Kilo-Org/kilocode/commit/d7016faa01dc0d0eefeff0b7abd5cf873ab54616) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Add maxConcurrentFileReads configuration support to CLI with documentation
+
+- [#4981](https://github.com/Kilo-Org/kilocode/pull/4981) [`0268494`](https://github.com/Kilo-Org/kilocode/commit/0268494f53276e4c5411204b01e50c15c9b02787) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix CLI `/model list` returning "No models available" for nano-gpt provider
+
+- [#4977](https://github.com/Kilo-Org/kilocode/pull/4977) [`c71cff8`](https://github.com/Kilo-Org/kilocode/commit/c71cff8451927052c00b5306c0b552b4afe33dbd) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Add proper display for deleteFile tool in CLI instead of showing "Unknown tool: deleteFile"
+
+- [#4978](https://github.com/Kilo-Org/kilocode/pull/4978) [`ed5073c`](https://github.com/Kilo-Org/kilocode/commit/ed5073ccb6ffc8acc53cb9e7191b1f618001ed40) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix number key hotkeys (1, 2, 3) not working in command approval menu
+
+- [#4993](https://github.com/Kilo-Org/kilocode/pull/4993) [`c3c7bbe`](https://github.com/Kilo-Org/kilocode/commit/c3c7bbe70ed1832e62c8cb05f3a0db4cdbc0dd25) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix CLI hanging on rate limit errors in autonomous mode by enabling auto-retry for API failures
+
+- [#4995](https://github.com/Kilo-Org/kilocode/pull/4995) [`95e9b6d`](https://github.com/Kilo-Org/kilocode/commit/95e9b6d234681d34f3903715de1ceba67e745516) Thanks [@kevinvandijk](https://github.com/kevinvandijk)! - fix: use correct api url for some endpoints
+
+## 0.20.0
+
+### Minor Changes
+
+- [#4943](https://github.com/Kilo-Org/kilocode/pull/4943) [`eef76d0`](https://github.com/Kilo-Org/kilocode/commit/eef76d0e4b962c7b9680e5c9226b22ecaa3fa79b) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Add Shift+Tab keyboard shortcut to cycle through modes in the CLI
+
+### Patch Changes
+
+- [#4941](https://github.com/Kilo-Org/kilocode/pull/4941) [`b7052cc`](https://github.com/Kilo-Org/kilocode/commit/b7052cc2030466626a832e19061675d91edb6f94) Thanks [@Drilmo](https://github.com/Drilmo)! - Add extension path resolution for F5 debug workflow
+
+    - CLI resolves extension from src/dist/ when KILOCODE_DEV_CLI_PATH is set
+    - Add watch:cli:setup and watch:cli:deps tasks for reliable CLI builds
+
+- [#4967](https://github.com/Kilo-Org/kilocode/pull/4967) [`99029a5`](https://github.com/Kilo-Org/kilocode/commit/99029a556253b82ee8a8b56445dabd65b56e4fef) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Improved file update display in CLI with compact format and colored diffs
+
+- [#4949](https://github.com/Kilo-Org/kilocode/pull/4949) [`f56d88a`](https://github.com/Kilo-Org/kilocode/commit/f56d88af3697993b2b33863741d5c47cd06e17be) Thanks [@eshurakov](https://github.com/eshurakov)! - Add --attach flag for file attachments in CLI
+
+- [#4959](https://github.com/Kilo-Org/kilocode/pull/4959) [`2dce098`](https://github.com/Kilo-Org/kilocode/commit/2dce098cb2f2476fb9978dcbb49b5070ba96a296) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - feat(cli): add word-by-word cursor navigation
+
+    Adds support for word-by-word cursor navigation in the CLI text input:
+
+    - `Meta+b` / `Meta+Left` to move to the beginning of the previous word
+    - `Meta+f` / `Meta+Right` to move to the beginning of the next word
+
+    This enhances the editing experience with Emacs-style keybindings and standard Meta+Arrow key navigation.
+
+## 0.19.3
+
+### Patch Changes
+
+- [#4827](https://github.com/Kilo-Org/kilocode/pull/4827) [`2a66afb`](https://github.com/Kilo-Org/kilocode/commit/2a66afb98b582a73d43b2147d941df32f3eb43a4) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix slash commands being intercepted by followup suggestions during `ask_followup_question` prompts.
+
+- [#4940](https://github.com/Kilo-Org/kilocode/pull/4940) [`9809864`](https://github.com/Kilo-Org/kilocode/commit/9809864ce51474c29b0db2635a19a92520a2f1f1) Thanks [@Drilmo](https://github.com/Drilmo)! - Add KILOCODE_DEV_CLI_PATH support for easier extension + CLI development workflow
+
+## 0.19.2
+
+### Patch Changes
+
+- [#4829](https://github.com/Kilo-Org/kilocode/pull/4829) [`4e09e36`](https://github.com/Kilo-Org/kilocode/commit/4e09e36bba165a2ab6f5e07f71a420faa49ea3ec) Thanks [@marius-kilocode](https://github.com/marius-kilocode)! - Fix browser action results displaying raw base64 screenshot data as hexadecimal garbage
+
 ## 0.19.1
 
 ### Patch Changes

+ 42 - 1
cli/README.md

@@ -85,6 +85,33 @@ echo "Fix the bug in app.ts" | kilocode --auto
 kilocode --auto "Run tests" --timeout 300
 ```
 
+#### Task Completion Hook
+
+Use `--on-task-completed` to send a follow-up prompt to the agent when the main task completes. This is useful for automating post-task actions like creating pull requests.
+
+```bash
+# Create a PR after the task completes
+kilocode --auto "Implement feature X" --on-task-completed "Create a pull request with a descriptive title and body"
+
+# Run tests and report results
+kilocode --auto "Fix the failing tests" --on-task-completed "Summarize what was fixed and verify all tests pass"
+
+# Commit changes with a specific message format
+kilocode --auto "Refactor the auth module" --on-task-completed "Commit all changes with a conventional commit message"
+```
+
+**Requirements:**
+
+- Requires `--auto` mode
+- Prompt cannot be empty
+- Maximum prompt length: 50,000 characters
+
+**Behavior:**
+
+- After the main task completes, the prompt is sent to the agent as a follow-up message
+- The agent has 90 seconds to complete the follow-up action
+- Supports markdown, special characters, and multi-line prompts
+
 #### Autonomous mode Behavior
 
 When running in Autonomous mode (`--auto` flag):
@@ -161,6 +188,20 @@ Autonomous mode respects your auto-approval configuration. Edit your config file
 - `retry`: Auto-approve API retry requests
 - `todo`: Auto-approve todo list updates
 
+#### Context Management Configuration
+
+You can also configure context management settings:
+
+```json
+{
+	"maxConcurrentFileReads": 5
+}
+```
+
+**Configuration Options:**
+
+- `maxConcurrentFileReads`: Maximum number of files that can be read in a single `read_file` request (default: 5, minimum: 1). The AI will be instructed about this limit and requests exceeding it will be rejected. Set to `1` to disable multi-file reads.
+
 #### Command Approval Patterns
 
 The `execute.allowed` and `execute.denied` lists support hierarchical pattern matching:
@@ -245,7 +286,7 @@ To build and run the CLI locally off your branch:
 cd src
 pnpm bundle
 pnpm vsix
-pnpm vsix:unpackged
+pnpm vsix:unpacked
 cd ..
 ```
 

+ 35 - 4
cli/docs/DEVELOPMENT.md

@@ -6,13 +6,44 @@ We use `pnpm` for package management. Please make sure `pnpm` is installed.
 
 The CLI is currently built by bundling the extension core and replacing the vscode rendering parts with a cli rendering engine. To _develop_ on the CLI you need to follow a few steps:
 
-1. Build the extension core from the root workspace folder by running `pnpm cli:bundle`
+1. Install dependencies from the root workspace folder:
 
-2. Change into the cli folder `cd ./cli`
+    ```bash
+    pnpm install
+    ```
 
-3. Build & run the extension by running `pnpm start:dev`. If you want to use the CLI to work on its own code, you can run `pnpm start:dev -w ../` which will start it within the root workspace folder.
+2. Set up your environment file. Copy the sample and configure your API keys:
 
-4. While not required, it's pretty helpful to view log output of the cli in a separate terminal while you're developing. To do this, open a new terminal window and run `pnpm logs`. You can also run `pnpm logs:clear` to truncate any on-disk logs during development.
+    ```bash
+    cp .env.sample cli/dist/.env
+    # Edit cli/dist/.env with your API keys
+    ```
+
+3. Build the extension core from the root workspace folder:
+
+    ```bash
+    pnpm cli:bundle
+    ```
+
+4. Change into the cli folder:
+
+    ```bash
+    cd ./cli
+    ```
+
+5. Build & run the extension by running `pnpm start:dev`. If you want to use the CLI to work on its own code, you can run `pnpm start:dev -w ../` which will start it within the root workspace folder.
+
+6. While not required, it's pretty helpful to view log output of the cli in a separate terminal while you're developing. To do this, open a new terminal window and run `pnpm logs`. You can also run `pnpm logs:clear` to truncate any on-disk logs during development.
+
+### Quick Start (Testing Changes)
+
+If you just want to quickly test your changes without the dev server, you can run the bundled CLI directly:
+
+```bash
+# From the root workspace folder
+pnpm install && pnpm cli:bundle
+node cli/dist/index.js
+```
 
 ## Code Hygiene
 

+ 37 - 0
cli/docs/DISK_SPACE_MANAGEMENT.md

@@ -0,0 +1,37 @@
+# Managing Disk Space
+
+The Kilo Code CLI stores task history in `~/.kilocode/cli/` (or `%USERPROFILE%\.kilocode\cli\` on Windows). With heavy usage, this directory can grow significantly.
+
+## Manual Cleanup
+
+Delete the CLI data directory to free disk space:
+
+**macOS/Linux:**
+
+```bash
+rm -rf ~/.kilocode/cli
+```
+
+**Windows (PowerShell):**
+
+```powershell
+Remove-Item -Recurse -Force "$env:USERPROFILE\.kilocode\cli"
+```
+
+**Windows (Command Prompt):**
+
+```cmd
+rmdir /s /q "%USERPROFILE%\.kilocode\cli"
+```
+
+> **Note:** This deletes all task history. The directory will be recreated on next CLI use.
+
+## Using the VS Code Extension
+
+If you also use the Kilo Code VS Code extension, you can enable automatic task history cleanup:
+
+1. Open VS Code Settings (`Cmd+,` / `Ctrl+,`)
+2. Search for "Kilo Code auto purge"
+3. Enable **Auto Purge** and configure retention periods
+
+The extension's auto-purge feature runs daily and removes old tasks based on configurable retention periods for different task types (favorited, completed, incomplete).

+ 18 - 5
cli/esbuild.config.mjs

@@ -56,6 +56,23 @@ const afterBuildPlugin = {
 	},
 }
 
+// Problem matcher plugin for VS Code task integration
+const esbuildProblemMatcherPlugin = {
+	name: "esbuild-problem-matcher",
+	setup(build) {
+		build.onStart(() => console.log("[esbuild-problem-matcher#onStart]"))
+		build.onEnd((result) => {
+			result.errors.forEach(({ text, location }) => {
+				console.error(`✘ [ERROR] ${text}`)
+				if (location && location.file) {
+					console.error(`    ${location.file}:${location.line}:${location.column}:`)
+				}
+			})
+			console.log("[esbuild-problem-matcher#onEnd]")
+		})
+	},
+}
+
 const config = {
 	entryPoints: ["src/index.ts"],
 	bundle: true,
@@ -98,7 +115,6 @@ const __dirname = __dirname__(__filename);
 		"diff",
 		"diff-match-patch",
 		"dotenv",
-		"eventemitter3",
 		"fast-deep-equal",
 		"fast-glob",
 		"fast-xml-parser",
@@ -152,13 +168,10 @@ const __dirname = __dirname__(__filename);
 		"tiktoken",
 		"tmp",
 		"tree-sitter-wasms",
-		"ts-node",
 		"turndown",
-		"undici",
 		"uri-js",
 		"uuid",
 		"vscode-material-icons",
-		"vscode-uri",
 		"web-tree-sitter",
 		"workerpool",
 		"xlsx",
@@ -169,7 +182,7 @@ const __dirname = __dirname__(__filename);
 	minify: false,
 	treeShaking: true,
 	logLevel: "info",
-	plugins: [afterBuildPlugin],
+	plugins: [esbuildProblemMatcherPlugin, afterBuildPlugin],
 	alias: {
 		'is-in-ci': path.resolve(__dirname, 'src/patches/is-in-ci.ts'),
 	}

+ 1 - 11
cli/package.dist.json

@@ -1,6 +1,6 @@
 {
 	"name": "@kilocode/cli",
-	"version": "0.19.1",
+	"version": "0.23.1",
 	"description": "Terminal User Interface for Kilo Code",
 	"type": "module",
 	"main": "index.js",
@@ -34,7 +34,6 @@
 		"diff": "^5.2.0",
 		"diff-match-patch": "^1.0.5",
 		"dotenv": "^16.4.7",
-		"eventemitter3": "^5.0.1",
 		"fast-deep-equal": "^3.1.3",
 		"fast-glob": "^3.3.2",
 		"fast-xml-parser": "^5.0.0",
@@ -48,12 +47,6 @@
 		"i18next": "^25.0.0",
 		"ignore": "^7.0.3",
 		"ink": "^6.3.1",
-		"ink-big-text": "^2.0.0",
-		"ink-gradient": "^3.0.0",
-		"ink-select-input": "^6.2.0",
-		"ink-spinner": "^5.0.0",
-		"ink-table": "^3.1.0",
-		"ink-text-input": "^6.0.0",
 		"is-wsl": "^3.1.0",
 		"isbinaryfile": "^5.0.2",
 		"jotai": "^2.14.0",
@@ -101,13 +94,10 @@
 		"tiktoken": "^1.0.21",
 		"tmp": "^0.2.3",
 		"tree-sitter-wasms": "^0.1.12",
-		"ts-node": "^10.9.1",
 		"turndown": "^7.2.0",
-		"undici": "^7.13.0",
 		"uri-js": "^4.4.1",
 		"uuid": "^11.1.0",
 		"vscode-material-icons": "^0.1.1",
-		"vscode-uri": "^3.0.8",
 		"web-tree-sitter": "^0.25.6",
 		"workerpool": "^9.2.0",
 		"xlsx": "^0.18.5",

+ 85 - 89
cli/package.json

@@ -1,6 +1,6 @@
 {
 	"name": "@kilocode/cli",
-	"version": "0.19.1",
+	"version": "0.23.1",
 	"description": "Terminal User Interface for Kilo Code",
 	"type": "module",
 	"main": "dist/index.js",
@@ -11,6 +11,7 @@
 	"scripts": {
 		"build": "node esbuild.config.mjs",
 		"dev": "pnpm build --watch",
+		"dev:setup": "pnpm build && pnpm deps:install",
 		"deps:install": "npm install --omit=dev --prefix ./dist",
 		"deps:freeze": "npm shrinkwrap --prefix ./dist",
 		"shrinkwrap": "pnpm run deps:install && pnpm run deps:freeze && cp ./dist/npm-shrinkwrap.json ./npm-shrinkwrap.dist.json",
@@ -32,113 +33,108 @@
 		"changeset:version": "jq --arg version \"$(jq -r '.version' package.json)\" '.version = $version' package.dist.json > tmp.json && mv tmp.json package.dist.json && prettier --write package.dist.json"
 	},
 	"dependencies": {
-		"@anthropic-ai/bedrock-sdk": "^0.22.0",
-		"@anthropic-ai/sdk": "^0.51.0",
-		"@anthropic-ai/vertex-sdk": "^0.11.3",
-		"@aws-sdk/client-bedrock-runtime": "^3.812.0",
-		"@aws-sdk/credential-providers": "^3.806.0",
-		"@google/genai": "^1.0.0",
-		"@lmstudio/sdk": "^1.1.1",
-		"@mistralai/mistralai": "^1.9.18",
-		"@modelcontextprotocol/sdk": "^1.24.0",
-		"@qdrant/js-client-rest": "^1.14.0",
+		"@anthropic-ai/bedrock-sdk": "^0.26.0",
+		"@anthropic-ai/sdk": "^0.71.2",
+		"@anthropic-ai/vertex-sdk": "^0.14.0",
+		"@aws-sdk/client-bedrock-runtime": "^3.966.0",
+		"@aws-sdk/credential-providers": "^3.966.0",
+		"@google/genai": "^1.35.0",
+		"@inquirer/prompts": "^8.2.0",
+		"@kilocode/core-schemas": "workspace:^",
+		"@lmstudio/sdk": "^1.5.0",
+		"@mistralai/mistralai": "^1.11.0",
+		"@modelcontextprotocol/sdk": "^1.25.2",
+		"@qdrant/js-client-rest": "^1.16.2",
 		"@roo-code/cloud": "workspace:^",
 		"@roo-code/telemetry": "workspace:^",
 		"@roo-code/types": "workspace:^",
-		"@vscode/codicons": "^0.0.36",
-		"@vscode/ripgrep": "^1.15.11",
-		"ajv": "^8.12.0",
+		"@vscode/codicons": "^0.0.44",
+		"@vscode/ripgrep": "^1.17.0",
+		"ajv": "^8.17.1",
 		"async-mutex": "^0.5.0",
-		"axios": "^1.7.4",
-		"chalk": "^5.3.0",
-		"cheerio": "^1.0.0",
-		"chokidar": "^4.0.1",
+		"axios": "^1.13.2",
+		"chalk": "^5.6.2",
+		"cheerio": "^1.1.2",
+		"chokidar": "^5.0.0",
 		"clone-deep": "^4.0.1",
-		"commander": "^12.1.0",
+		"commander": "^14.0.2",
 		"default-shell": "^2.2.0",
-		"delay": "^6.0.0",
-		"diff": "^7.0.0",
+		"delay": "^7.0.0",
+		"diff": "^8.0.2",
 		"diff-match-patch": "^1.0.5",
-		"dotenv": "^16.4.7",
-		"eventemitter3": "^5.0.1",
+		"dotenv": "^17.2.3",
 		"fast-deep-equal": "^3.1.3",
-		"fast-glob": "^3.3.2",
-		"fast-xml-parser": "^5.0.0",
+		"fast-glob": "^3.3.3",
+		"fast-xml-parser": "^5.3.3",
 		"fastest-levenshtein": "^1.0.16",
-		"fs-extra": "^11.2.0",
+		"fs-extra": "^11.3.3",
 		"fuse.js": "^7.1.0",
 		"fzf": "^0.5.2",
 		"get-folder-size": "^5.0.0",
-		"google-auth-library": "^9.15.1",
+		"google-auth-library": "^10.5.0",
 		"gray-matter": "^4.0.3",
-		"i18next": "^25.0.0",
-		"ignore": "^7.0.3",
-		"ink": "^6.3.1",
+		"i18next": "^25.7.4",
+		"ignore": "^7.0.5",
+		"ink": "^6.6.0",
 		"ink-link": "^5.0.0",
-		"ink-select-input": "^6.2.0",
-		"ink-spinner": "^5.0.0",
-		"ink-table": "^3.1.0",
-		"ink-text-input": "^6.0.0",
-		"inquirer": "^12.10.0",
+		"inquirer": "^13.1.0",
 		"is-wsl": "^3.1.0",
-		"isbinaryfile": "^5.0.2",
-		"jotai": "^2.14.0",
-		"jsdom": "^26.0.0",
+		"isbinaryfile": "^6.0.0",
+		"jotai": "^2.16.1",
+		"jsdom": "^27.4.0",
 		"json5": "^2.2.3",
 		"jwt-decode": "^4.0.0",
+		"linguist-languages": "^9.2.0",
 		"lodash.debounce": "^4.0.8",
-		"lru-cache": "^11.1.0",
+		"lru-cache": "^11.2.4",
 		"mammoth": "^1.11.0",
-		"marked": "^11.2.0",
-		"marked-terminal": "^6.2.0",
+		"marked": "^15.0.0",
+		"marked-terminal": "^7.3.0",
 		"monaco-vscode-textmate-theme-converter": "^0.1.7",
 		"node-cache": "^5.1.2",
 		"node-ipc": "npm:[email protected]",
 		"node-machine-id": "^1.1.12",
-		"ollama": "^0.5.17",
-		"openai": "^5.12.2",
-		"os-name": "^6.0.0",
-		"p-limit": "^6.2.0",
-		"p-wait-for": "^5.0.2",
+		"ollama": "^0.6.3",
+		"openai": "^6.16.0",
+		"os-name": "^6.1.0",
+		"p-limit": "^7.2.0",
+		"p-wait-for": "^6.0.0",
 		"package-json": "^10.0.1",
-		"pdf-parse": "^1.1.1",
-		"pkce-challenge": "^5.0.0",
-		"posthog-node": "^4.2.1",
-		"pretty-bytes": "^7.0.0",
+		"pdf-parse": "^2.4.5",
+		"pkce-challenge": "^5.0.1",
+		"posthog-node": "^5.20.0",
+		"pretty-bytes": "^7.1.0",
 		"proper-lockfile": "^4.1.2",
-		"ps-list": "^8.1.1",
-		"puppeteer-chromium-resolver": "^24.0.2",
-		"puppeteer-core": "^23.4.0",
-		"react": "^19.2.0",
-		"react-devtools-core": "^6.1.5",
-		"react-error-boundary": "^6.0.0",
+		"ps-list": "^9.0.0",
+		"puppeteer-chromium-resolver": "^24.0.3",
+		"puppeteer-core": "^24.34.0",
+		"react": "^19.2.3",
+		"react-devtools-core": "^7.0.1",
+		"react-error-boundary": "^6.0.3",
 		"reconnecting-eventsource": "^1.6.4",
 		"sanitize-filename": "^1.6.3",
 		"say": "^0.16.0",
 		"semver": "^7.7.3",
-		"serialize-error": "^11.0.3",
-		"shiki": "^3.6.0",
-		"simple-git": "^3.27.0",
-		"socket.io-client": "^4.8.1",
+		"serialize-error": "^12.0.0",
+		"shiki": "^3.21.0",
+		"simple-git": "^3.30.0",
+		"socket.io-client": "^4.8.3",
 		"sound-play": "^1.1.0",
-		"stream-json": "^1.8.0",
+		"stream-json": "^1.9.1",
 		"string-width": "^8.1.0",
 		"strip-bom": "^5.0.0",
-		"tiktoken": "^1.0.21",
-		"tmp": "^0.2.3",
-		"tree-sitter-wasms": "^0.1.12",
-		"ts-node": "^10.9.1",
-		"turndown": "^7.2.0",
-		"undici": "^7.13.0",
+		"tiktoken": "^1.0.22",
+		"tmp": "^0.2.5",
+		"tree-sitter-wasms": "^0.1.13",
+		"turndown": "^7.2.2",
 		"uri-js": "^4.4.1",
-		"uuid": "^11.1.0",
+		"uuid": "^13.0.0",
 		"vscode-material-icons": "^0.1.1",
-		"vscode-uri": "^3.0.8",
-		"web-tree-sitter": "^0.25.6",
-		"workerpool": "^9.2.0",
+		"web-tree-sitter": "^0.26.3",
+		"workerpool": "^10.0.1",
 		"xlsx": "^0.18.5",
-		"yaml": "^2.6.1",
-		"zod": "^3.25.61"
+		"yaml": "^2.8.2",
+		"zod": "^4.3.5"
 	},
 	"overrides": {
 		"event-pubsub": {
@@ -146,28 +142,28 @@
 		}
 	},
 	"devDependencies": {
-		"@eslint/js": "^9.22.0",
-		"@lydell/node-pty": "^1.1.0",
+		"@eslint/js": "^9.39.2",
+		"@lydell/node-pty": "1.2.0-beta.3",
 		"@roo-code/config-eslint": "workspace:^",
 		"@roo-code/config-typescript": "workspace:^",
 		"@types/fs-extra": "^11.0.4",
 		"@types/marked-terminal": "^6.1.1",
-		"@types/node": "20.x",
-		"@types/react": "^18.3.23",
-		"@types/semver": "^7.5.8",
-		"cpy-cli": "^5.0.0",
-		"del-cli": "^5.1.0",
-		"eslint-config-prettier": "^10.1.1",
-		"eslint-plugin-turbo": "^2.4.4",
+		"@types/node": "~25.0.3",
+		"@types/react": "^19.2.7",
+		"@types/semver": "^7.7.1",
+		"cpy-cli": "^6.0.0",
+		"del-cli": "^7.0.0",
+		"eslint-config-prettier": "^10.1.8",
+		"eslint-plugin-turbo": "^2.7.3",
 		"ink-testing-library": "^4.0.0",
 		"mkdirp": "^3.0.1",
-		"prettier": "^3.4.2",
-		"rimraf": "^6.1.0",
-		"strip-ansi": "^7.1.0",
-		"tsx": "^4.19.3",
-		"typescript": "^5.4.5",
-		"typescript-eslint": "^8.26.0",
-		"vitest": "^3.2.3"
+		"prettier": "^3.7.4",
+		"rimraf": "^6.1.2",
+		"strip-ansi": "^7.1.2",
+		"tsx": "^4.21.0",
+		"typescript": "^5.9.3",
+		"typescript-eslint": "^8.52.0",
+		"vitest": "^4.0.16"
 	},
 	"engines": {
 		"node": ">=20.19.2"

+ 148 - 0
cli/src/__tests__/agent-manager-no-config.test.ts

@@ -0,0 +1,148 @@
+import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
+
+/**
+ * Tests for CLI behavior when spawned from Agent Manager without configuration.
+ *
+ * When the CLI is spawned from the Agent Manager (KILO_PLATFORM=agent-manager)
+ * and no config exists, it should output a JSON welcome message with instructions
+ * instead of trying to show the interactive auth wizard (which would hang without TTY).
+ */
+
+// Mock the config persistence module
+vi.mock("../config/persistence.js", () => ({
+	configExists: vi.fn(),
+	loadConfig: vi.fn(),
+}))
+
+// Mock the env-config module
+vi.mock("../config/env-config.js", () => ({
+	envConfigExists: vi.fn(),
+	getMissingEnvVars: vi.fn(),
+}))
+
+// Mock the auth wizard to ensure it's not called
+vi.mock("../auth/index.js", () => ({
+	default: vi.fn(),
+}))
+
+// Mock the logs service
+vi.mock("../services/logs.js", () => ({
+	logs: {
+		info: vi.fn(),
+		debug: vi.fn(),
+		error: vi.fn(),
+		warn: vi.fn(),
+	},
+}))
+
+describe("Agent Manager No Config Behavior", () => {
+	let originalEnv: NodeJS.ProcessEnv
+	let consoleLogSpy: ReturnType<typeof vi.spyOn>
+	let processExitSpy: ReturnType<typeof vi.spyOn>
+
+	beforeEach(() => {
+		// Save original environment
+		originalEnv = { ...process.env }
+
+		// Spy on console.log to capture JSON output
+		consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {})
+
+		// Spy on process.exit to prevent actual exit
+		processExitSpy = vi.spyOn(process, "exit").mockImplementation(() => {
+			throw new Error("process.exit called")
+		})
+	})
+
+	afterEach(() => {
+		// Restore original environment
+		process.env = originalEnv
+
+		// Restore spies
+		consoleLogSpy.mockRestore()
+		processExitSpy.mockRestore()
+
+		vi.clearAllMocks()
+	})
+
+	it("should output JSON welcome message with instructions when KILO_PLATFORM=agent-manager and no config", async () => {
+		// Set up environment for agent-manager mode
+		process.env.KILO_PLATFORM = "agent-manager"
+
+		// Mock config functions to return no config
+		const { configExists } = await import("../config/persistence.js")
+		const { envConfigExists } = await import("../config/env-config.js")
+		vi.mocked(configExists).mockResolvedValue(false)
+		vi.mocked(envConfigExists).mockReturnValue(false)
+
+		// We can't easily test the full CLI entry point, but we can test the logic
+		// by checking that the welcome message format matches what CliOutputParser expects
+		const welcomeMessage = {
+			type: "welcome",
+			timestamp: Date.now(),
+			metadata: {
+				welcomeOptions: {
+					instructions: ["Configuration required: No provider configured."],
+				},
+			},
+		}
+
+		// Verify the structure matches what the Agent Manager expects
+		expect(welcomeMessage.type).toBe("welcome")
+		expect(welcomeMessage.metadata.welcomeOptions.instructions).toBeInstanceOf(Array)
+		expect(welcomeMessage.metadata.welcomeOptions.instructions.length).toBeGreaterThan(0)
+
+		// Verify the JSON can be parsed correctly
+		const jsonString = JSON.stringify(welcomeMessage)
+		const parsed = JSON.parse(jsonString)
+		expect(parsed.type).toBe("welcome")
+		expect(parsed.metadata.welcomeOptions.instructions).toContain("Configuration required: No provider configured.")
+	})
+
+	it("should have instructions that trigger cli_configuration_error in Agent Manager", () => {
+		// The welcome message format that the CLI outputs
+		const welcomeMessage = {
+			type: "welcome",
+			timestamp: Date.now(),
+			metadata: {
+				welcomeOptions: {
+					instructions: ["Configuration required: No provider configured."],
+				},
+			},
+		}
+
+		// Simulate what CliOutputParser.toStreamEvent does for welcome events
+		const parsed = welcomeMessage
+		const metadata = parsed.metadata as Record<string, unknown>
+		const welcomeOptions = metadata?.welcomeOptions as Record<string, unknown>
+		const instructions = welcomeOptions?.instructions as string[]
+
+		// Verify instructions are present and non-empty (this triggers cli_configuration_error)
+		expect(instructions).toBeDefined()
+		expect(Array.isArray(instructions)).toBe(true)
+		expect(instructions.length).toBeGreaterThan(0)
+
+		// The CliProcessHandler.extractConfigErrorFromWelcome joins instructions with newlines
+		const configurationError = instructions.join("\n")
+		expect(configurationError).toBe("Configuration required: No provider configured.")
+	})
+
+	it("should not include showInstructions flag (only instructions array matters)", () => {
+		// The simplified welcome message format
+		const welcomeMessage = {
+			type: "welcome",
+			timestamp: Date.now(),
+			metadata: {
+				welcomeOptions: {
+					instructions: ["Configuration required: No provider configured."],
+				},
+			},
+		}
+
+		// Verify showInstructions is not present (it's not needed)
+		const welcomeOptions = welcomeMessage.metadata.welcomeOptions as Record<string, unknown>
+		expect(welcomeOptions.showInstructions).toBeUndefined()
+
+		// Only the instructions array matters for triggering the error
+		expect(welcomeOptions.instructions).toBeDefined()
+	})
+})

+ 207 - 0
cli/src/__tests__/attach-flag.test.ts

@@ -0,0 +1,207 @@
+import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"
+import { existsSync } from "fs"
+import {
+	accumulateAttachments,
+	validateAttachmentExists,
+	validateAttachmentFormat,
+	validateAttachments,
+	validateAttachRequiresAuto,
+} from "../validation/attachments.js"
+import { SUPPORTED_IMAGE_EXTENSIONS } from "../media/images.js"
+
+// Mock fs.existsSync
+vi.mock("fs", () => ({
+	existsSync: vi.fn(),
+}))
+
+/**
+ * Tests for the --attach flag behavior.
+ *
+ * The --attach flag allows users to attach files (currently images) to CLI prompts
+ * in auto mode.
+ */
+describe("CLI --attach flag", () => {
+	beforeEach(() => {
+		vi.clearAllMocks()
+	})
+
+	afterEach(() => {
+		vi.restoreAllMocks()
+	})
+
+	describe("Flag accumulation", () => {
+		/**
+		 * Tests the real accumulateAttachments function from validation/attachments.ts
+		 * This function is used by Commander.js to accumulate --attach flags
+		 */
+		it("should accept a single --attach flag", () => {
+			const result = accumulateAttachments("./screenshot.png", [])
+			expect(result).toEqual(["./screenshot.png"])
+		})
+
+		it("should accumulate multiple --attach flags", () => {
+			let attachments: string[] = []
+			attachments = accumulateAttachments("./screenshot.png", attachments)
+			attachments = accumulateAttachments("./diagram.png", attachments)
+			attachments = accumulateAttachments("./photo.jpg", attachments)
+
+			expect(attachments).toEqual(["./screenshot.png", "./diagram.png", "./photo.jpg"])
+			expect(attachments.length).toBe(3)
+		})
+
+		it("should start with an empty array by default", () => {
+			const defaultValue: string[] = []
+			expect(defaultValue).toEqual([])
+		})
+
+		it("should not mutate the previous array", () => {
+			const previous = ["./first.png"]
+			const result = accumulateAttachments("./second.png", previous)
+
+			// Result should contain both
+			expect(result).toEqual(["./first.png", "./second.png"])
+			// Original array should be unchanged
+			expect(previous).toEqual(["./first.png"])
+		})
+	})
+
+	describe("Mode validation", () => {
+		it("should reject --attach without --auto", () => {
+			const result = validateAttachRequiresAuto({
+				attach: ["./screenshot.png"],
+			})
+			expect(result.valid).toBe(false)
+			expect(result.error).toBe("Error: --attach option requires --auto flag")
+		})
+
+		it("should accept --attach with --auto flag", () => {
+			const result = validateAttachRequiresAuto({
+				attach: ["./screenshot.png"],
+				auto: true,
+			})
+			expect(result.valid).toBe(true)
+		})
+
+		it("should accept when no attachments are provided", () => {
+			const result = validateAttachRequiresAuto({})
+			expect(result.valid).toBe(true)
+		})
+
+		it("should accept when attachments array is empty", () => {
+			const result = validateAttachRequiresAuto({ attach: [] })
+			expect(result.valid).toBe(true)
+		})
+	})
+
+	describe("File existence validation", () => {
+		it("should error on non-existent attachment file", () => {
+			vi.mocked(existsSync).mockReturnValue(false)
+
+			const result = validateAttachmentExists("/non/existent/path.png")
+			expect(result.valid).toBe(false)
+			expect(result.error).toBe("Error: Attachment file not found: /non/existent/path.png")
+		})
+
+		it("should accept existing attachment file", () => {
+			vi.mocked(existsSync).mockReturnValue(true)
+
+			const result = validateAttachmentExists("./existing-image.png")
+			expect(result.valid).toBe(true)
+		})
+	})
+
+	describe("File format validation", () => {
+		it("should error on unsupported file format with helpful message", () => {
+			const result = validateAttachmentFormat("./document.pdf")
+			expect(result.valid).toBe(false)
+			expect(result.error).toContain('Unsupported attachment format ".pdf"')
+			expect(result.error).toContain("Currently supported:")
+			expect(result.error).toContain("Other file types can be read using @path mentions or the read_file tool")
+		})
+
+		it("should error on text file format", () => {
+			const result = validateAttachmentFormat("./readme.txt")
+			expect(result.valid).toBe(false)
+			expect(result.error).toContain('Unsupported attachment format ".txt"')
+		})
+
+		it("should error on unknown extension", () => {
+			const result = validateAttachmentFormat("./file.xyz")
+			expect(result.valid).toBe(false)
+			expect(result.error).toContain('Unsupported attachment format ".xyz"')
+		})
+
+		it.each(SUPPORTED_IMAGE_EXTENSIONS)("should accept %s format", (ext) => {
+			const result = validateAttachmentFormat(`./image${ext}`)
+			expect(result.valid).toBe(true)
+		})
+
+		it.each(SUPPORTED_IMAGE_EXTENSIONS)("should handle case-insensitive %s extension", (ext) => {
+			const upperExt = ext.toUpperCase()
+			const result = validateAttachmentFormat(`./image${upperExt}`)
+			expect(result.valid).toBe(true)
+		})
+	})
+
+	describe("Complete validation flow", () => {
+		it("should return valid for empty attachments array", () => {
+			const result = validateAttachments([])
+			expect(result.valid).toBe(true)
+			expect(result.errors).toEqual([])
+		})
+
+		it("should validate multiple attachments", () => {
+			vi.mocked(existsSync).mockReturnValue(true)
+
+			const result = validateAttachments(["./image1.png", "./image2.jpg", "./image3.webp"])
+
+			expect(result.valid).toBe(true)
+			expect(result.errors).toEqual([])
+		})
+
+		it("should report file not found error", () => {
+			vi.mocked(existsSync).mockReturnValue(false)
+
+			const result = validateAttachments(["./missing.png"])
+
+			expect(result.valid).toBe(false)
+			expect(result.errors.length).toBe(1)
+			expect(result.errors[0]).toContain("Attachment file not found")
+		})
+
+		it("should report unsupported format error", () => {
+			vi.mocked(existsSync).mockReturnValue(true)
+
+			const result = validateAttachments(["./document.pdf"])
+
+			expect(result.valid).toBe(false)
+			expect(result.errors.length).toBe(1)
+			expect(result.errors[0]).toContain("Unsupported attachment format")
+		})
+
+		it("should report all validation errors", () => {
+			vi.mocked(existsSync)
+				.mockReturnValueOnce(true) // first file exists
+				.mockReturnValueOnce(false) // second file doesn't exist
+
+			const result = validateAttachments(["./invalid.pdf", "./missing.png"])
+
+			expect(result.valid).toBe(false)
+			expect(result.errors.length).toBe(2)
+			expect(result.errors[0]).toContain("Unsupported attachment format")
+			expect(result.errors[1]).toContain("Attachment file not found")
+		})
+
+		it("should skip format validation for non-existent files", () => {
+			vi.mocked(existsSync).mockReturnValue(false)
+
+			// Even though the file has a valid extension, we should get a "not found" error
+			const result = validateAttachments(["./missing.png"])
+
+			expect(result.valid).toBe(false)
+			expect(result.errors.length).toBe(1)
+			expect(result.errors[0]).toContain("Attachment file not found")
+			expect(result.errors[0]).not.toContain("Unsupported attachment format")
+		})
+	})
+})

+ 3 - 3
cli/src/__tests__/cli-provider-model.test.ts

@@ -25,8 +25,8 @@ describe("Provider and Model CLI Options", () => {
 			expect(field).toBe("apiModelId")
 		})
 
-		it("should return apiModelId as default for unknown providers", () => {
-			const field = getModelIdKey("human-relay")
+		it("should return apiModelId as default for providers without specific model fields", () => {
+			const field = getModelIdKey("fake-ai")
 			expect(field).toBe("apiModelId")
 		})
 	})
@@ -41,7 +41,7 @@ describe("Provider and Model CLI Options", () => {
 		it("should return null for providers without router support", () => {
 			expect(getModelFieldForProvider("anthropic")).toBeNull()
 			expect(getModelFieldForProvider("openai")).toBeNull()
-			expect(getModelFieldForProvider("human-relay")).toBeNull()
+			expect(getModelFieldForProvider("fake-ai")).toBeNull()
 		})
 	})
 

+ 62 - 10
cli/src/auth/index.ts

@@ -1,6 +1,15 @@
-import inquirer from "inquirer"
+import { select } from "@inquirer/prompts"
 import { loadConfig, saveConfig, CLIConfig } from "../config/index.js"
 import { authProviders } from "./providers/index.js"
+import { fetchRouterModels } from "../services/models/fetcher.js"
+import {
+	getModelsByProvider,
+	providerSupportsModelList,
+	getModelIdKey,
+	sortModelsByPreference,
+} from "../constants/providers/models.js"
+import type { ProviderName } from "../types/messages.js"
+import { withRawMode } from "./utils/terminal.js"
 
 /**
  * Main authentication wizard
@@ -17,15 +26,14 @@ export default async function authWizard(): Promise<void> {
 		}))
 
 		// Prompt user to select a provider
-		const { selectedProvider } = await inquirer.prompt<{ selectedProvider: string }>([
-			{
-				type: "list",
-				name: "selectedProvider",
-				message: "Please select which provider you would like to use:",
-				choices: providerChoices,
-				loop: false,
-			},
-		])
+		const selectedProvider = await withRawMode(() =>
+       select({
+         message: "Select an AI provider:",
+         choices: providerChoices,
+         loop: false,
+         pageSize: process.stdout.rows ? Math.min(20, process.stdout.rows - 2) : 10,
+       })
+     )
 
 		// Find the selected provider
 		const provider = authProviders.find((p) => p.value === selectedProvider)
@@ -47,6 +55,50 @@ export default async function authWizard(): Promise<void> {
 			process.exit(1)
 		}
 
+		// Model Selection
+		const providerId = authResult.providerConfig.provider as ProviderName
+
+		let routerModels = null
+		if (providerSupportsModelList(providerId)) {
+			console.log("\nFetching available models...")
+			try {
+				routerModels = await fetchRouterModels(authResult.providerConfig)
+			} catch (_) {
+				console.warn("Failed to fetch models, using defaults if available.")
+			}
+		}
+
+		const { models, defaultModel } = getModelsByProvider({
+			provider: providerId,
+			routerModels,
+			kilocodeDefaultModel: "",
+		})
+
+		const modelIds = sortModelsByPreference(models)
+
+		if (modelIds.length > 0) {
+			const modelChoices = modelIds.map((id) => {
+				const model = models[id]
+				return {
+					name: model?.displayName || id,
+					value: id,
+				}
+			})
+
+			const selectedModel = await withRawMode(() =>
+         select({
+           message: "Select a model to use:",
+           choices: modelChoices,
+           default: defaultModel,
+           loop: false,
+           pageSize: 10,
+         })
+       )
+
+			const modelKey = getModelIdKey(providerId)
+			authResult.providerConfig[modelKey] = selectedModel
+		}
+
 		// Save the configuration
 		const newConfig: CLIConfig = {
 			...config.config,

+ 0 - 1
cli/src/auth/providers/config.ts

@@ -6,7 +6,6 @@ export const CUSTOM_AUTH_PROVIDERS: Set<string> = new Set([
 	"kilocode", // Has device auth and token auth variants
 	"other", // Opens config file manually (not a ProviderName, but an auth provider value)
 	"vscode-lm", // Uses VSCode's built-in auth
-	"human-relay", // No auth needed - human responses
 	"fake-ai", // No auth needed - testing only
 	"roo", // Special case - no API key required
 	"virtual-quota-fallback", // Complex nested config with multiple profiles

+ 10 - 1
cli/src/auth/providers/factory.ts

@@ -3,7 +3,9 @@ import type { ProviderName } from "../../types/messages.js"
 import { PROVIDER_REQUIRED_FIELDS } from "../../constants/providers/validation.js"
 import { FIELD_REGISTRY, isOptionalField, getProviderDefaultModel } from "../../constants/providers/settings.js"
 import { PROVIDER_LABELS } from "../../constants/providers/labels.js"
+import { isModelField } from "../../constants/providers/models.js"
 import inquirer from "inquirer"
+import { withRawMode } from "../utils/terminal.js"
 
 /**
  * Creates a generic authentication function for a provider
@@ -19,6 +21,11 @@ function createGenericAuthFunction(providerName: ProviderName) {
 
 		// Build prompts from required fields
 		for (const field of requiredFields) {
+			// Skip model fields as they are handled by the main auth wizard
+			if (isModelField(field) || field === "apiModelId") {
+				continue
+			}
+
 			const fieldMeta = FIELD_REGISTRY[field]
 			if (!fieldMeta) {
 				// Skip fields without metadata
@@ -69,7 +76,9 @@ function createGenericAuthFunction(providerName: ProviderName) {
 		}
 
 		// Prompt user for all fields
-		const answers = await inquirer.prompt(prompts)
+		// Use withRawMode to ensure arrow key navigation works in list prompts
+		// (required for inquirer v13+ which uses @inquirer/prompts internally)
+		const answers = await withRawMode(() => inquirer.prompt(prompts))
 
 		// Build provider config
 		// eslint-disable-next-line @typescript-eslint/no-explicit-any

+ 8 - 7
cli/src/auth/providers/kilocode/shared.ts

@@ -1,9 +1,10 @@
 import { getApiUrl } from "@roo-code/types"
 import { openRouterDefaultModelId } from "@roo-code/types"
 import { z } from "zod"
-import inquirer from "inquirer"
+import { select } from "@inquirer/prompts"
 import { logs } from "../../../services/logs.js"
 import type { KilocodeOrganization, KilocodeProfileData } from "../../types.js"
+import { withRawMode } from "../../utils/terminal.js"
 
 const API_TIMEOUT_MS = 5000
 
@@ -132,14 +133,14 @@ export async function promptOrganizationSelection(organizations: KilocodeOrganiz
 		})),
 	]
 
-	const { accountType } = await inquirer.prompt<{ accountType: string }>([
-		{
-			type: "list",
-			name: "accountType",
+	// Use withRawMode to ensure arrow key navigation works in list prompts
+	const accountType = await withRawMode(() =>
+		select({
 			message: "Select account type:",
 			choices: accountChoices,
-		},
-	])
+			loop: false,
+		}),
+	)
 
 	// Return organization ID if not personal
 	return accountType !== "personal" ? accountType : undefined

+ 13 - 8
cli/src/auth/providers/kilocode/token-auth.ts

@@ -6,6 +6,7 @@ import {
 	promptOrganizationSelection,
 	INVALID_TOKEN_ERROR,
 } from "./shared.js"
+import { withRawMode } from "../../utils/terminal.js"
 
 /**
  * Execute the manual token authentication flow
@@ -22,14 +23,18 @@ export async function authenticateWithToken(): Promise<AuthResult> {
 
 	// Loop until we get a valid token
 	while (!isValidToken) {
-		const { token } = await inquirer.prompt<{ token: string }>([
-			{
-				type: "password",
-				name: "token",
-				message: "API Key:",
-				mask: true,
-			},
-		])
+		// Use withRawMode to ensure interactive prompts work correctly
+		// (required for inquirer v13+ which uses @inquirer/prompts internally)
+		const { token } = await withRawMode(() =>
+			inquirer.prompt<{ token: string }>([
+				{
+					type: "password",
+					name: "token",
+					message: "API Key:",
+					mask: true,
+				},
+			]),
+		)
 
 		kilocodeToken = token
 

Некоторые файлы не были показаны из-за большого количества измененных файлов