mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
Merge remote-tracking branch 'origin/main' into johnnyeric/kilo-opencode-v1.17.4
# Conflicts: # packages/opencode/src/cli/cmd/tui.ts # packages/opencode/src/cli/tui/worker.ts # packages/opencode/test/kilocode/cli/tui/thread.test.ts
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Allow agents to stop and remove a targeted Agent Manager session.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Improve CLI sidebar usage sections with cent-formatted costs, collapsible details, and aligned model totals.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Respect the Terminal Command Blocks display setting for background process tool cards.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-gateway": patch
|
||||
---
|
||||
|
||||
Show the actual routed model and upstream cost for Kilo Gateway Anthropic Messages and OpenAI Responses requests, including requests served through OpenRouter.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Support bidirectional text in chat messages so right-to-left and mixed-language conversations render in the correct direction.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Open the Agent Manager new-worktree dialog from primary actions and keyboard shortcuts, with an explicit quick-create action in its dropdown.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Speed up local conversation recall searches on large histories.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep sessions usable after deprecated local review commands complete without a session status transition.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Restore directory `@`-mentions by listing their entries without inlining child file contents. Untrusted external directory attachments remain denied.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Show newly submitted messages immediately after reverting a conversation.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-ui": patch
|
||||
---
|
||||
|
||||
Keep chat auto-scroll detached after a small upward wheel scroll near the latest message.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Fix Grok 4.5 reasoning variants not showing up in the model picker.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Support editing custom OpenAI-compatible providers from JetBrains settings and replace their Disconnect action with Edit and Delete. Added or edited providers stay selected, and the custom provider dialog now closes after a successful save.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Fix adding a Custom OpenAI-Compatible Provider silently failing. The dialog now requires at least one model and reports save errors inline so you can correct your input and retry without re-entering the form.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Match the model picker Close button styling to JetBrains dialog primary buttons.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Use a trash icon for provider delete and show provider edit/delete actions on selection, matching the other settings lists.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
---
|
||||
|
||||
Improve CLI project-memory controls, status, activity indicators, and optional recall details.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-memory": minor
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Improve project-memory activity in VS Code with a task-header menu, quick actions, and optional verbose details.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
"@kilocode/sdk": minor
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Support verbose project-memory settings and show recalled memory snippets in conversation markers when enabled.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-indexing": patch
|
||||
---
|
||||
|
||||
Allow remote embedder validation timeouts to retry before indexing reports a connection failure.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
---
|
||||
|
||||
Remote CLI: expose slash command discovery, execution, and `/new` session creation over the relay. `list_commands` and `send_command` (including the built-in `compact` flow) are scoped to the current session's directory. `create_session` creates a root session in that directory, attaches it to the relay heartbeat set, and returns the new session id only after the heartbeat completes so the mobile client can navigate immediately. Failures are sanitized; the command is not auto-retried and the user may retry manually after a transient relay failure.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Retry incomplete model responses that end without final output or tool activity while preserving partial answers and completed tools.
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -127,7 +127,7 @@
|
||||
},
|
||||
"packages/effect-drizzle-sqlite": {
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -141,7 +141,7 @@
|
||||
},
|
||||
"packages/effect-sqlite-node": {
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
},
|
||||
@@ -153,7 +153,7 @@
|
||||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "4.0.0-beta.74",
|
||||
"@effect/platform-node-shared": "4.0.0-beta.74",
|
||||
@@ -174,7 +174,7 @@
|
||||
},
|
||||
"packages/kilo-console": {
|
||||
"name": "@kilocode/kilo-console",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@kilocode/kilo-web-ui": "workspace:*",
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
@@ -196,7 +196,7 @@
|
||||
},
|
||||
"packages/kilo-docs": {
|
||||
"name": "@kilocode/kilo-docs",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@docsearch/css": "^4",
|
||||
"@docsearch/js": "^4",
|
||||
@@ -226,7 +226,7 @@
|
||||
},
|
||||
"packages/kilo-gateway": {
|
||||
"name": "@kilocode/kilo-gateway",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@ai-sdk/alibaba": "1.0.17",
|
||||
"@ai-sdk/anthropic": "3.0.71",
|
||||
@@ -262,7 +262,7 @@
|
||||
},
|
||||
"packages/kilo-i18n": {
|
||||
"name": "@kilocode/kilo-i18n",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
@@ -272,7 +272,7 @@
|
||||
},
|
||||
"packages/kilo-indexing": {
|
||||
"name": "@kilocode/kilo-indexing",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-bedrock-runtime": "3.1005.0",
|
||||
"@aws-sdk/credential-provider-ini": "3.972.31",
|
||||
@@ -304,11 +304,11 @@
|
||||
},
|
||||
"packages/kilo-jetbrains": {
|
||||
"name": "@kilocode/kilo-jetbrains",
|
||||
"version": "7.4.5",
|
||||
"version": "7.4.9",
|
||||
},
|
||||
"packages/kilo-memory": {
|
||||
"name": "@kilocode/kilo-memory",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
"zod": "catalog:",
|
||||
@@ -322,7 +322,7 @@
|
||||
},
|
||||
"packages/kilo-sandbox": {
|
||||
"name": "@kilocode/sandbox",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sandbox-runtime": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -337,7 +337,7 @@
|
||||
},
|
||||
"packages/kilo-telemetry": {
|
||||
"name": "@kilocode/kilo-telemetry",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
"posthog-node": "4.4.0",
|
||||
@@ -351,7 +351,7 @@
|
||||
},
|
||||
"packages/kilo-ui": {
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@kobalte/core": "0.13.11",
|
||||
@@ -388,7 +388,7 @@
|
||||
},
|
||||
"packages/kilo-vscode": {
|
||||
"name": "kilo-code",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
@@ -457,7 +457,7 @@
|
||||
},
|
||||
"packages/kilo-web-ui": {
|
||||
"name": "@kilocode/kilo-web-ui",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@kilocode/kilo-ui": "workspace:*",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -474,7 +474,7 @@
|
||||
},
|
||||
"packages/llm": {
|
||||
"name": "@opencode-ai/llm",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@smithy/eventstream-codec": "4.2.14",
|
||||
"@smithy/util-utf8": "4.2.2",
|
||||
@@ -492,7 +492,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "@kilocode/cli",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"bin": {
|
||||
"kilo": "./bin/kilo",
|
||||
"kilocode": "./bin/kilo",
|
||||
@@ -659,7 +659,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@kilocode/plugin",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -687,7 +687,7 @@
|
||||
},
|
||||
"packages/plugin-atomic-chat": {
|
||||
"name": "@kilocode/plugin-atomic-chat",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@kilocode/plugin": "workspace:*",
|
||||
},
|
||||
@@ -701,7 +701,7 @@
|
||||
},
|
||||
"packages/script": {
|
||||
"name": "@opencode-ai/script",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"semver": "^7.6.3",
|
||||
},
|
||||
@@ -712,7 +712,7 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@kilocode/sdk",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
@@ -727,7 +727,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"drizzle-orm": "catalog:",
|
||||
@@ -741,7 +741,7 @@
|
||||
},
|
||||
"packages/storybook": {
|
||||
"name": "@opencode-ai/storybook",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"devDependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@solidjs/meta": "catalog:",
|
||||
@@ -791,7 +791,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -833,6 +833,7 @@
|
||||
"@types/katex": "0.16.7",
|
||||
"@types/luxon": "catalog:",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"happy-dom": "20.8.9",
|
||||
"tailwindcss": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"vite": "catalog:",
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-yNHrsBXfCWeuX13frVTjIL7U4k71wSimDtdTjoBndoY=",
|
||||
"aarch64-linux": "sha256-zbRBPcPduV1a2EH4BiZWPJ+LyZMLCEPkQ3s+U7tDxtk=",
|
||||
"aarch64-darwin": "sha256-NLLlE2t3vs1E5fDwyiFGoneJAdTb6uXrFuVa+LwBtlY=",
|
||||
"x86_64-darwin": "sha256-BH31+FPgkt1xXJixkj+DgXIveJ49fWk4AIc3Hx+4XzM="
|
||||
"x86_64-linux": "sha256-SS/pjldHwh2DdsWAvKJ8NivbUWuzo1GOgOoPCuJEd08=",
|
||||
"aarch64-linux": "sha256-57nGVTiyjyjl2/27kd1rHQLgbO3XfeZp6xXBhMXb12Q=",
|
||||
"aarch64-darwin": "sha256-OTtzy5ytPFMVKTE1tqi55aKxcdvdPe1yyqthvEZrVTY=",
|
||||
"x86_64-darwin": "sha256-4It5t+dcvwv6UMOFXX67Kxp2qxoDOrvHKeuWTGzYYX4="
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -165,6 +165,6 @@
|
||||
"pacote@21.5.1": "patches/pacote@21.5.1.patch",
|
||||
"mammoth@1.12.0": "patches/mammoth@1.12.0.patch"
|
||||
},
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "kilo"
|
||||
name = "Kilo"
|
||||
description = "The open source coding agent."
|
||||
version = "7.4.9"
|
||||
version = "7.4.11"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/Kilo-Org/kilocode"
|
||||
@@ -11,26 +11,26 @@ name = "Kilo"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.9/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.11/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.9/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.11/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.9/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.11/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.9/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.11/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.9/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.4.11/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-console",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-docs",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --webpack --port 3002",
|
||||
|
||||
@@ -35,7 +35,7 @@ Parallel work pays off when sessions are **independent** — neither one's outpu
|
||||
|
||||
Every productive worktree session follows the same rhythm:
|
||||
|
||||
1. **Create a worktree** (`Cmd+N` / `Ctrl+N`) and describe the task.
|
||||
1. **Open a new worktree dialog** (`Cmd+N` / `Ctrl+N`), then describe the task.
|
||||
2. **Let the agent run.** Switch to another worktree, another session, or step away.
|
||||
3. **Verify manually.** Before you trust "all tests pass", run the app with the run script (`Cmd+E` / `Ctrl+E`) or open the worktree's terminal (`Cmd+/` / `Ctrl+/`) and run the tests yourself.
|
||||
4. **Review the diff** (`Cmd+D` / `Ctrl+D`). Drop inline comments, then **Send to chat** to feed them back to the agent.
|
||||
@@ -48,7 +48,7 @@ The single biggest lever on this loop is **keeping each worktree's scope small**
|
||||
|
||||
### 1. Side quest
|
||||
|
||||
Something unrelated came up while you are mid-task. Create a new worktree for it (`Cmd+N`), let the agent work, review when it is done. Your main work is unaffected.
|
||||
Something unrelated came up while you are mid-task. Open a new worktree dialog for it (`Cmd+N`), let the agent work, review when it is done. Your main work is unaffected.
|
||||
|
||||
### 2. Build a skeleton, then split the work
|
||||
|
||||
@@ -64,7 +64,7 @@ This mirrors how a human team works: agree the API contract first, then split ba
|
||||
|
||||
For genuinely hard tasks where you do not know which approach will work:
|
||||
|
||||
1. Open the advanced new-worktree dialog (`Cmd+Shift+N` / `Ctrl+Shift+N`) and pick 2–4 versions.
|
||||
1. Open the new-worktree dialog (`Cmd+N` / `Ctrl+N`) and pick 2–4 versions.
|
||||
2. Optionally assign a different model to each.
|
||||
3. Review the diffs side by side, pick the winner, apply it, discard the rest.
|
||||
|
||||
@@ -76,7 +76,7 @@ A related pattern: use the sidebar as an investigation surface. Start two or thr
|
||||
|
||||
### 5. A worktree per bug
|
||||
|
||||
For a day of small fixes: one worktree per bug (`Cmd+N`), one branch per fix, merge each quickly so none drift. Close the worktree when the fix lands.
|
||||
For a day of small fixes: one worktree per bug. Use `Cmd+N` to configure each worktree or `Cmd+Shift+N` to create one immediately from the default branch, merge each quickly so none drift. Close the worktree when the fix lands.
|
||||
|
||||
### 6. Multiple sessions on one branch
|
||||
|
||||
@@ -225,8 +225,9 @@ Merge the most foundational one first. Then, in each remaining worktree, ask the
|
||||
| Situation | Where |
|
||||
|---|---|
|
||||
| Small, interactive task | Sidebar |
|
||||
| Long task, want to do something else meanwhile | New worktree (`Cmd+N`) |
|
||||
| Two or three approaches, pick the winner | Multi-version (`Cmd+Shift+N`) |
|
||||
| Long task, want to do something else meanwhile | Configure a new worktree (`Cmd+N`) |
|
||||
| Quick task on the default base branch | Create a new worktree (`Cmd+Shift+N`) |
|
||||
| Two or three approaches, pick the winner | Configure multi-version worktrees (`Cmd+N`) |
|
||||
| Sidebar task outgrew the sidebar | Continue in Worktree |
|
||||
| Separate conversation on the same branch | New tab (`Cmd+T`) |
|
||||
| Long conversation, want a fresh context on same branch | New tab, summarize |
|
||||
|
||||
@@ -69,7 +69,7 @@ The extension uses `gh` to automatically discover PRs for each worktree branch.
|
||||
2. **Branch name** — `gh pr view <branch>` matches same-repo branches pushed to origin
|
||||
3. **HEAD commit SHA** — `gh pr list --search "<sha>"` as a last resort, matching PRs whose head ref points to the exact same commit
|
||||
|
||||
You can also import a PR directly from the advanced new worktree dialog: open the **New Worktree** dropdown and select **Advanced**, or press `Cmd+Shift+N` (macOS) / `Ctrl+Shift+N` (Windows/Linux), switch to the **Import** tab, then paste the GitHub PR URL. The branch is checked out and the badge appears automatically.
|
||||
You can also import a PR directly from the new worktree dialog: click **New Worktree** or press `Cmd+N` (macOS) / `Ctrl+N` (Windows/Linux), switch to the **Import** tab, then paste the GitHub PR URL. The branch is checked out and the badge appears automatically.
|
||||
|
||||
#### Badge colors
|
||||
|
||||
@@ -106,12 +106,14 @@ PR badges update automatically in the background. The active worktree refreshes
|
||||
|
||||
### Creating a New Worktree Session
|
||||
|
||||
1. Click **New Worktree** or press `Cmd+N` (macOS) / `Ctrl+N` (Windows/Linux) to create a new worktree
|
||||
1. Click **New Worktree** or press `Cmd+N` (macOS) / `Ctrl+N` (Windows/Linux) to open the new worktree dialog
|
||||
2. Enter a branch name (or let Kilo generate one)
|
||||
3. Type your first message to start the agent
|
||||
3. Type your first message, then create the worktree
|
||||
|
||||
A new git worktree is created from your current branch. The agent works in isolation — your main branch is unaffected.
|
||||
|
||||
To create a worktree immediately from the default base branch, press `Cmd+Shift+N` (macOS) / `Ctrl+Shift+N` (Windows/Linux).
|
||||
|
||||
### Multi-Version Mode
|
||||
|
||||
You can run up to 4 parallel implementations of the same prompt across separate worktrees:
|
||||
@@ -352,8 +354,8 @@ Closing a managed worktree removes it from Agent Manager, deletes its `.kilo/wor
|
||||
| Shortcut (macOS) | Shortcut (Windows/Linux) | Action |
|
||||
|---|---|---|
|
||||
| `Cmd+Shift+M` | `Ctrl+Shift+M` | Open / focus Agent Manager (works from anywhere) |
|
||||
| `Cmd+N` | `Ctrl+N` | New worktree |
|
||||
| `Cmd+Shift+N` | `Ctrl+Shift+N` | New worktree (advanced options) |
|
||||
| `Cmd+N` | `Ctrl+N` | Configure a new worktree |
|
||||
| `Cmd+Shift+N` | `Ctrl+Shift+N` | Create a new worktree immediately |
|
||||
| `Cmd+Shift+O` | `Ctrl+Shift+O` | Import/open worktree |
|
||||
| `Cmd+Shift+W` | `Ctrl+Shift+W` | Close current worktree |
|
||||
| `Cmd+T` | `Ctrl+T` | New tab (session) in worktree |
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6a89d1a9a31f7de1780d772b7caf71d96f0b4b895df954c77dcd508a7febe7c9
|
||||
size 1950
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:9a864b6a5c0e88bc00292f8a38685acdf2ecdd9eb5d880a663ba830152b7280b
|
||||
size 3523
|
||||
oid sha256:431a0c12cb844b5d4e535cc4c7bf5546fd9e66a178b8023b00978d07c291a531
|
||||
size 3919
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:08f1498ef61670dfec83a2de45f1b9d7008c6c9987705a39175eb990182ee5c2
|
||||
size 6156
|
||||
oid sha256:f68544cdf994abca3004e652911aa8edd77cf2bb3f91678e5dc6210022dff2f9
|
||||
size 6188
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-gateway",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration",
|
||||
|
||||
@@ -54,7 +54,7 @@ type Data = z.infer<typeof dataSchema>
|
||||
function routed(meta: Gateway) {
|
||||
const route = meta.routing
|
||||
if (!route) return
|
||||
const hit = route.modelAttempts?.findLast((item) => item.success === true)
|
||||
const hit = route.modelAttempts?.slice().reverse().find((item) => item.success === true)
|
||||
const id = hit?.canonicalSlug ?? route.canonicalSlug
|
||||
if (!id) return
|
||||
const value = id.trim()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-i18n",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Kilo-specific i18n translations and overrides",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-indexing",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Standalone indexing engine and host helpers for Kilo Code",
|
||||
|
||||
@@ -16,7 +16,7 @@ import { VoyageEmbedder } from "./embedders/voyage"
|
||||
import { QdrantVectorStore } from "./vector-store/qdrant-client"
|
||||
import { LanceDBVectorStore } from "./vector-store/lancedb-vector-store"
|
||||
import { codeParser, DirectoryScanner, FileWatcher } from "./processors"
|
||||
import type { ICodeParser, IEmbedder, IFileWatcher, IVectorStore } from "./interfaces"
|
||||
import type { AvailableEmbedders, ICodeParser, IEmbedder, IFileWatcher, IVectorStore } from "./interfaces"
|
||||
import type { CodeIndexConfigManager } from "./config-manager"
|
||||
import type { CacheManager } from "./cache-manager"
|
||||
import type { IndexingTelemetryMeta, IndexingTelemetryReporter } from "./interfaces/telemetry"
|
||||
@@ -31,10 +31,28 @@ import type { IgnoreMatcher } from "./shared/load-ignore"
|
||||
|
||||
const log = Log.create({ service: "indexing-factory" })
|
||||
|
||||
function timeout(provider: string): number {
|
||||
if (provider === "ollama") return OLLAMA_EMBEDDER_REQUEST_TIMEOUT_MS
|
||||
return REMOTE_EMBEDDER_VALIDATION_TIMEOUT_MS
|
||||
}
|
||||
// RATIONALE: The OpenAI SDK applies the per-attempt timeout and retries internally.
|
||||
const policy = {
|
||||
openai: undefined,
|
||||
openrouter: undefined,
|
||||
"openai-compatible": undefined,
|
||||
kilo: undefined,
|
||||
gemini: undefined,
|
||||
mistral: undefined,
|
||||
"vercel-ai-gateway": undefined,
|
||||
ollama: {
|
||||
timeout: OLLAMA_EMBEDDER_REQUEST_TIMEOUT_MS,
|
||||
error: "Connection to embedding service failed (timeout)",
|
||||
},
|
||||
voyage: {
|
||||
timeout: REMOTE_EMBEDDER_VALIDATION_TIMEOUT_MS,
|
||||
error: "Connection failed. Please check the endpoint URL and network connectivity.",
|
||||
},
|
||||
bedrock: {
|
||||
timeout: REMOTE_EMBEDDER_VALIDATION_TIMEOUT_MS,
|
||||
error: "Connection failed. Please check the endpoint URL and network connectivity.",
|
||||
},
|
||||
} satisfies Record<AvailableEmbedders, { timeout: number; error: string } | undefined>
|
||||
|
||||
/**
|
||||
* Factory class responsible for creating and configuring code indexing service dependencies.
|
||||
@@ -128,26 +146,26 @@ export class CodeIndexServiceFactory {
|
||||
}
|
||||
|
||||
public async validateEmbedder(embedder: IEmbedder): Promise<{ valid: boolean; error?: string }> {
|
||||
const ms = timeout(embedder.embedderInfo.name)
|
||||
const deadline = policy[embedder.embedderInfo.name]
|
||||
let timer: ReturnType<typeof setTimeout> | undefined
|
||||
const wait = embedder.validateConfiguration()
|
||||
const fail = new Promise<{ valid: boolean; error?: string }>((resolve) => {
|
||||
timer = setTimeout(
|
||||
() =>
|
||||
resolve({
|
||||
valid: false,
|
||||
error:
|
||||
embedder.embedderInfo.name === "ollama"
|
||||
? "Connection to embedding service failed (timeout)"
|
||||
: "Connection failed. Please check the endpoint URL and network connectivity.",
|
||||
}),
|
||||
ms,
|
||||
)
|
||||
})
|
||||
const fail =
|
||||
deadline === undefined
|
||||
? undefined
|
||||
: new Promise<{ valid: boolean; error?: string }>((resolve) => {
|
||||
timer = setTimeout(
|
||||
() =>
|
||||
resolve({
|
||||
valid: false,
|
||||
error: deadline.error,
|
||||
}),
|
||||
deadline.timeout,
|
||||
)
|
||||
})
|
||||
|
||||
try {
|
||||
log.info("validating embedder", { provider: embedder.embedderInfo.name })
|
||||
const result = await Promise.race([wait, fail])
|
||||
const result = fail ? await Promise.race([wait, fail]) : await wait
|
||||
if (result.valid) {
|
||||
log.info("embedder validation succeeded", { provider: embedder.embedderInfo.name })
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, test, mock, beforeEach } from "bun:test"
|
||||
import path from "path"
|
||||
import { mockEmbeddingsCreate, openAIMockFactory, setOpenAIConstructorHook } from "./embedders/__helpers__/openai-mock"
|
||||
import {
|
||||
OLLAMA_EMBEDDER_REQUEST_TIMEOUT_MS,
|
||||
REMOTE_EMBEDDER_VALIDATION_TIMEOUT_MS,
|
||||
} from "../../../src/indexing/constants"
|
||||
import type { AvailableEmbedders, IEmbedder } from "../../../src/indexing/interfaces/embedder"
|
||||
|
||||
mock.module("openai", openAIMockFactory)
|
||||
import { CodeIndexServiceFactory } from "../../../src/indexing/service-factory"
|
||||
@@ -23,6 +28,20 @@ function createFactory(input?: Partial<ConstructorParameters<typeof CodeIndexCon
|
||||
return new CodeIndexServiceFactory(cfg, workspacePath, cache, cacheDirectory)
|
||||
}
|
||||
|
||||
function createEmbedder(name: AvailableEmbedders): IEmbedder {
|
||||
return {
|
||||
async createEmbeddings() {
|
||||
return { embeddings: [] }
|
||||
},
|
||||
async validateConfiguration() {
|
||||
return { valid: true }
|
||||
},
|
||||
get embedderInfo() {
|
||||
return { name }
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe("CodeIndexServiceFactory", () => {
|
||||
beforeEach(() => {
|
||||
mockEmbeddingsCreate.mockReset()
|
||||
@@ -39,6 +58,55 @@ describe("CodeIndexServiceFactory", () => {
|
||||
expect(factory.createEmbedder().embedderInfo).toEqual({ name: "openai-compatible" })
|
||||
})
|
||||
|
||||
test("lets SDK-backed embedders own validation timeouts", async () => {
|
||||
const original = globalThis.setTimeout
|
||||
const timer = mock((...args: Parameters<typeof setTimeout>) => original(...args))
|
||||
globalThis.setTimeout = timer
|
||||
|
||||
try {
|
||||
const factory = createFactory()
|
||||
const providers = [
|
||||
"openai",
|
||||
"openrouter",
|
||||
"openai-compatible",
|
||||
"kilo",
|
||||
"gemini",
|
||||
"mistral",
|
||||
"vercel-ai-gateway",
|
||||
] satisfies AvailableEmbedders[]
|
||||
|
||||
for (const provider of providers) {
|
||||
await expect(factory.validateEmbedder(createEmbedder(provider))).resolves.toEqual({ valid: true })
|
||||
}
|
||||
|
||||
expect(timer).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
globalThis.setTimeout = original
|
||||
}
|
||||
})
|
||||
|
||||
test("retains factory deadlines for non-SDK embedders", async () => {
|
||||
const original = globalThis.setTimeout
|
||||
const timer = mock((...args: Parameters<typeof setTimeout>) => original(...args))
|
||||
globalThis.setTimeout = timer
|
||||
|
||||
try {
|
||||
const factory = createFactory()
|
||||
|
||||
await factory.validateEmbedder(createEmbedder("voyage"))
|
||||
await factory.validateEmbedder(createEmbedder("bedrock"))
|
||||
await factory.validateEmbedder(createEmbedder("ollama"))
|
||||
|
||||
expect(timer.mock.calls.map((call) => call[1])).toEqual([
|
||||
REMOTE_EMBEDDER_VALIDATION_TIMEOUT_MS,
|
||||
REMOTE_EMBEDDER_VALIDATION_TIMEOUT_MS,
|
||||
OLLAMA_EMBEDDER_REQUEST_TIMEOUT_MS,
|
||||
])
|
||||
} finally {
|
||||
globalThis.setTimeout = original
|
||||
}
|
||||
})
|
||||
|
||||
test("uses default LanceDB directory when config is unset", () => {
|
||||
const factory = createFactory({ vectorStoreProvider: "lancedb", lancedbVectorStoreDirectory: undefined })
|
||||
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## 7.4.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#12217](https://github.com/Kilo-Org/kilocode/pull/12217) [`d6b36a0`](https://github.com/Kilo-Org/kilocode/commit/d6b36a028cc0a4b7bfd158d75e287c110e2838f7) - Support editing custom OpenAI-compatible providers from JetBrains settings and replace their Disconnect action with Edit and Delete. Added or edited providers stay selected, and the custom provider dialog now closes after a successful save.
|
||||
|
||||
- [#12217](https://github.com/Kilo-Org/kilocode/pull/12217) [`6077c1c`](https://github.com/Kilo-Org/kilocode/commit/6077c1c3b36d4c5cd68f206fc146ca472d841c5e) - Fix adding a Custom OpenAI-Compatible Provider silently failing. The dialog now requires at least one model and reports save errors inline so you can correct your input and retry without re-entering the form.
|
||||
|
||||
- [#12217](https://github.com/Kilo-Org/kilocode/pull/12217) [`cae3270`](https://github.com/Kilo-Org/kilocode/commit/cae3270c9dacc4097a681539cf3e07cfadceaca7) - Match the model picker Close button styling to JetBrains dialog primary buttons.
|
||||
|
||||
- [#12217](https://github.com/Kilo-Org/kilocode/pull/12217) [`cae3270`](https://github.com/Kilo-Org/kilocode/commit/cae3270c9dacc4097a681539cf3e07cfadceaca7) - Use a trash icon for provider delete and show provider edit/delete actions on selection, matching the other settings lists.
|
||||
|
||||
## 7.4.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+66
-36
@@ -264,58 +264,86 @@ class KiloBackendSessionManager(
|
||||
|
||||
// ------ mapping (generated API model → DTO) ------
|
||||
|
||||
private fun dto(s: ai.kilocode.jetbrains.api.model.Session) = SessionDto(
|
||||
private fun dto(s: ai.kilocode.jetbrains.api.model.Session) = dto(
|
||||
id = s.id,
|
||||
projectID = s.projectID,
|
||||
directory = s.directory,
|
||||
parentID = s.parentID,
|
||||
project = s.projectID,
|
||||
dir = s.directory,
|
||||
parent = s.parentID,
|
||||
title = s.title,
|
||||
version = s.version,
|
||||
time = SessionTimeDto(
|
||||
created = time(s.id, "created", s.time.created),
|
||||
updated = time(s.id, "updated", s.time.updated),
|
||||
archived = s.time.archived,
|
||||
),
|
||||
summary = s.summary?.let {
|
||||
SessionSummaryDto(
|
||||
additions = it.additions.safeInt(),
|
||||
deletions = it.deletions.safeInt(),
|
||||
files = it.files.safeInt(),
|
||||
)
|
||||
},
|
||||
created = s.time.created,
|
||||
updated = s.time.updated,
|
||||
archived = s.time.archived,
|
||||
summary = s.summary?.let { summary(it.additions, it.deletions, it.files) },
|
||||
revert = revertDto(s.revert),
|
||||
)
|
||||
|
||||
private fun dto(s: GlobalSession) = SessionDto(
|
||||
private fun dto(s: ai.kilocode.jetbrains.api.model.Session1) = dto(
|
||||
id = s.id,
|
||||
projectID = s.projectID,
|
||||
directory = s.directory,
|
||||
parentID = s.parentID,
|
||||
project = s.projectID,
|
||||
dir = s.directory,
|
||||
parent = s.parentID,
|
||||
title = s.title,
|
||||
version = s.version,
|
||||
time = SessionTimeDto(
|
||||
created = time(s.id, "created", s.time.created),
|
||||
updated = time(s.id, "updated", s.time.updated),
|
||||
archived = s.time.archived,
|
||||
),
|
||||
summary = s.summary?.let {
|
||||
SessionSummaryDto(
|
||||
additions = it.additions?.safeInt() ?: 0,
|
||||
deletions = it.deletions?.safeInt() ?: 0,
|
||||
files = it.files?.safeInt() ?: 0,
|
||||
)
|
||||
},
|
||||
created = s.time.created,
|
||||
updated = s.time.updated,
|
||||
archived = s.time.archived,
|
||||
summary = s.summary?.let { summary(it.additions, it.deletions, it.files) },
|
||||
revert = revertDto(s.revert),
|
||||
)
|
||||
|
||||
private fun dto(s: GlobalSession) = dto(
|
||||
id = s.id,
|
||||
project = s.projectID,
|
||||
dir = s.directory,
|
||||
parent = s.parentID,
|
||||
title = s.title,
|
||||
version = s.version,
|
||||
created = s.time.created,
|
||||
updated = s.time.updated,
|
||||
archived = s.time.archived,
|
||||
summary = s.summary?.let { summary(it.additions, it.deletions, it.files) },
|
||||
revert = revertDto(s.revert),
|
||||
)
|
||||
|
||||
private fun dto(
|
||||
id: String,
|
||||
project: String,
|
||||
dir: String,
|
||||
parent: String?,
|
||||
title: String,
|
||||
version: String,
|
||||
created: Number?,
|
||||
updated: Number?,
|
||||
archived: Double?,
|
||||
summary: SessionSummaryDto?,
|
||||
revert: SessionRevertDto?,
|
||||
) = SessionDto(
|
||||
id = id,
|
||||
projectID = project,
|
||||
directory = dir,
|
||||
parentID = parent,
|
||||
title = title,
|
||||
version = version,
|
||||
time = SessionTimeDto(
|
||||
created = time(id, "created", created),
|
||||
updated = time(id, "updated", updated),
|
||||
archived = archived,
|
||||
),
|
||||
summary = summary,
|
||||
revert = revert,
|
||||
)
|
||||
|
||||
private fun summary(add: Double?, del: Double?, files: Double?) = SessionSummaryDto(
|
||||
additions = count(add),
|
||||
deletions = count(del),
|
||||
files = count(files),
|
||||
)
|
||||
|
||||
private fun revertDto(s: ai.kilocode.jetbrains.api.model.SessionRevert?) = s?.let {
|
||||
revertDto(it.messageID, it.partID, it.snapshot, it.diff)
|
||||
}
|
||||
|
||||
private fun revertDto(s: ai.kilocode.jetbrains.api.model.GlobalSessionRevert?) = s?.let {
|
||||
revertDto(it.messageID, it.partID, it.snapshot, it.diff)
|
||||
}
|
||||
|
||||
private fun revertDto(message: String, part: String?, snapshot: String?, diff: String?) =
|
||||
SessionRevertDto(
|
||||
messageID = message,
|
||||
@@ -334,6 +362,8 @@ class KiloBackendSessionManager(
|
||||
|
||||
private fun encode(value: String) = java.net.URLEncoder.encode(value, Charsets.UTF_8)
|
||||
|
||||
private fun count(value: Double?) = value?.safeInt() ?: 0
|
||||
|
||||
private fun time(id: String, field: String, value: Number?): Double {
|
||||
if (value != null) return value.toDouble()
|
||||
log.warn("Session $id missing $field timestamp; defaulting to 0.0")
|
||||
|
||||
+35
-9
@@ -124,16 +124,19 @@ internal object OpenApiSpecNormalizer {
|
||||
as? JsonObject ?: return root
|
||||
val props = schema["properties"] as? JsonObject ?: return root
|
||||
|
||||
val nullable = setOf("balance", "kiloPass", "currentOrgId")
|
||||
val keys = setOf("balance", "kiloPass", "currentOrgId")
|
||||
val fixed = JsonObject(props.mapValues { (key, value) ->
|
||||
if (key !in nullable) return@mapValues value
|
||||
val obj = value as? JsonObject ?: return@mapValues value
|
||||
// Skip if already wrapped (has anyOf containing {type:null}).
|
||||
val existing = obj["anyOf"] as? JsonArray
|
||||
if (existing != null && existing.any {
|
||||
(it as? JsonObject)?.get("type")?.let { t -> (t as? JsonPrimitive)?.content } == "null"
|
||||
}) return@mapValues value
|
||||
JsonObject(mapOf("anyOf" to JsonArray(listOf(obj, JsonObject(mapOf("type" to JsonPrimitive("null")))))))
|
||||
if (key !in keys) return@mapValues value
|
||||
val wrapped = nullable(value)
|
||||
when (key) {
|
||||
"balance" -> nullableProps(wrapped, setOf("balance"))
|
||||
"kiloPass" -> nullableProps(wrapped, setOf(
|
||||
"currentPeriodBaseCreditsUsd",
|
||||
"currentPeriodUsageUsd",
|
||||
"currentPeriodBonusCreditsUsd",
|
||||
))
|
||||
else -> wrapped
|
||||
}
|
||||
})
|
||||
|
||||
// Rebuild nested objects up to root.
|
||||
@@ -152,6 +155,29 @@ internal object OpenApiSpecNormalizer {
|
||||
return JsonObject(root + mapOf("paths" to newPaths))
|
||||
}
|
||||
|
||||
private fun nullable(value: JsonElement): JsonElement {
|
||||
val obj = value as? JsonObject ?: return value
|
||||
val any = obj["anyOf"] as? JsonArray
|
||||
if (any != null && any.any(::nullSchema)) return obj
|
||||
return JsonObject(mapOf("anyOf" to JsonArray(listOf(obj, JsonObject(mapOf("type" to JsonPrimitive("null")))))))
|
||||
}
|
||||
|
||||
private fun nullableProps(value: JsonElement, names: Set<String>): JsonElement {
|
||||
val obj = value as? JsonObject ?: return value
|
||||
val any = obj["anyOf"] as? JsonArray
|
||||
if (any != null) {
|
||||
return JsonObject(obj + mapOf("anyOf" to JsonArray(any.map { nullableProps(it, names) })))
|
||||
}
|
||||
val props = obj["properties"] as? JsonObject ?: return obj
|
||||
val fixed = JsonObject(props.mapValues { (key, prop) ->
|
||||
if (key in names) nullable(prop) else prop
|
||||
})
|
||||
return JsonObject(obj + mapOf("properties" to fixed))
|
||||
}
|
||||
|
||||
private fun nullSchema(value: JsonElement) =
|
||||
(value as? JsonObject)?.get("type")?.let { (it as? JsonPrimitive)?.content } == "null"
|
||||
|
||||
/**
|
||||
* Deduplicate the root-level "tags" array by name — the spec validator
|
||||
* rejects repeated tag names even when they describe different things.
|
||||
|
||||
+11
@@ -148,6 +148,12 @@ class OpenApiSpecNormalizerTest {
|
||||
val balanceTypes = balanceAnyOf.map { (it as? JsonObject)?.get("type").let { t -> (t as? JsonPrimitive)?.content } }
|
||||
assert("null" in balanceTypes) { "balance anyOf should include null but got $balanceTypes" }
|
||||
assert(balanceAnyOf.any { it is JsonObject && "properties" in it }) { "balance anyOf should include the object schema" }
|
||||
val balanceObject = balanceAnyOf.filterIsInstance<JsonObject>().first { "properties" in it }
|
||||
val balanceValue = obj(obj(balanceObject["properties"])["balance"])
|
||||
val balanceValueTypes = arr(balanceValue["anyOf"]).map {
|
||||
(it as? JsonObject)?.get("type").let { t -> (t as? JsonPrimitive)?.content }
|
||||
}
|
||||
assert("null" in balanceValueTypes) { "inner balance value should include null but got $balanceValueTypes" }
|
||||
|
||||
// kiloPass must be anyOf [object, null]
|
||||
val pass = obj(props["kiloPass"])
|
||||
@@ -156,6 +162,11 @@ class OpenApiSpecNormalizerTest {
|
||||
val passTypes = passAnyOf.map { (it as? JsonObject)?.get("type").let { t -> (t as? JsonPrimitive)?.content } }
|
||||
assert("null" in passTypes) { "kiloPass anyOf should include null but got $passTypes" }
|
||||
assert(passAnyOf.any { it is JsonObject && "properties" in it }) { "kiloPass anyOf should include the object schema" }
|
||||
val passObject = passAnyOf.filterIsInstance<JsonObject>().first { "properties" in it }
|
||||
val passProps = obj(passObject["properties"])
|
||||
val base = obj(passProps["currentPeriodBaseCreditsUsd"])
|
||||
val baseTypes = arr(base["anyOf"]).map { (it as? JsonObject)?.get("type").let { t -> (t as? JsonPrimitive)?.content } }
|
||||
assert("null" in baseTypes) { "inner kiloPass value should include null but got $baseTypes" }
|
||||
|
||||
// currentOrgId must be anyOf [string, null]
|
||||
val orgId = obj(props["currentOrgId"])
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"test": "./gradlew test",
|
||||
"test:ci": "bun script/test-ci.ts"
|
||||
},
|
||||
"version": "7.4.5",
|
||||
"version": "7.4.9",
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"peerDependencies": {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-memory",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Project memory storage, indexing, recall, and command helpers for Kilo Code",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/sandbox",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-telemetry",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Telemetry for Kilo CLI - PostHog analytics integration",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -3,6 +3,10 @@ import { splitProps } from "solid-js"
|
||||
|
||||
const icons: Record<string, { path: string; viewBox: string }> = {
|
||||
memory: {
|
||||
viewBox: "0 0 24 24",
|
||||
path: `<path d="M2 7h4M2 12h4M2 17h4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><rect width="16" height="20" x="4" y="2" rx="2" stroke="currentColor" stroke-width="2"/>`,
|
||||
},
|
||||
database: {
|
||||
viewBox: "0 0 24 24",
|
||||
path: `<ellipse cx="12" cy="5" rx="7" ry="3" stroke="currentColor" stroke-width="2"/><path d="M5 5v7c0 1.66 3.13 3 7 3s7-1.34 7-3V5M5 12v7c0 1.66 3.13 3 7 3s7-1.34 7-3v-7" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>`,
|
||||
},
|
||||
|
||||
@@ -392,6 +392,9 @@ html[data-theme="kilo-vscode"] [data-component="bash-output"] {
|
||||
}
|
||||
|
||||
[data-slot="user-message-text"] {
|
||||
unicode-bidi: plaintext; /* Match the prompt input */
|
||||
background-color: color-mix(in srgb, var(--text-base) 7%, transparent);
|
||||
border: 1px solid transparent;
|
||||
transition: opacity 0.3s ease;
|
||||
|
||||
&[data-queued] {
|
||||
@@ -408,6 +411,11 @@ html[data-theme="kilo-vscode"] [data-component="bash-output"] {
|
||||
}
|
||||
}
|
||||
|
||||
body.vscode-high-contrast [data-component="user-message"] [data-slot="user-message-text"],
|
||||
body.vscode-high-contrast-light [data-component="user-message"] [data-slot="user-message-text"] {
|
||||
border-color: var(--border-weak-base);
|
||||
}
|
||||
|
||||
[data-component="tool-output"] {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
@@ -857,7 +857,7 @@ export function UserMessageDisplay(props: {
|
||||
<div data-slot="user-message-body">
|
||||
{props.header}
|
||||
<Show when={text()}>
|
||||
<div data-slot="user-message-text" data-queued={props.queued ? "" : undefined}>
|
||||
<div data-slot="user-message-text" dir="auto" data-queued={props.queued ? "" : undefined}>
|
||||
<HighlightedText text={text()} references={inlineFiles()} agents={agents()} />
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# kilo-code
|
||||
|
||||
## 7.4.11
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#12254](https://github.com/Kilo-Org/kilocode/pull/12254) [`30e7ec4`](https://github.com/Kilo-Org/kilocode/commit/30e7ec4ab45fac724b41ec0b4342e272e7f584d2) - Improve project-memory activity in VS Code with a task-header menu, quick actions, and optional verbose details.
|
||||
|
||||
- [#12250](https://github.com/Kilo-Org/kilocode/pull/12250) [`bd69158`](https://github.com/Kilo-Org/kilocode/commit/bd69158131aafdcc2f44aede22b573c2b0432f21) - Support verbose project-memory settings and show recalled memory snippets in conversation markers when enabled.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#12287](https://github.com/Kilo-Org/kilocode/pull/12287) [`bb75928`](https://github.com/Kilo-Org/kilocode/commit/bb759287495fabc1f3fa7e0fbecff13ab4d41cb2) - Use distinct icons for context, project memory, and code indexing, and group context controls to preserve task titles in narrow VS Code sidebars.
|
||||
|
||||
- [#12285](https://github.com/Kilo-Org/kilocode/pull/12285) [`9fa2e18`](https://github.com/Kilo-Org/kilocode/commit/9fa2e187047a4d2b6e76066af3c139c21851e854) - Prevent sessions from freezing after submitting or dismissing a question.
|
||||
|
||||
- [#12274](https://github.com/Kilo-Org/kilocode/pull/12274) [`5180c10`](https://github.com/Kilo-Org/kilocode/commit/5180c10c4f69500ce303437646371500a71dba46) - Show newly submitted messages immediately after reverting a conversation.
|
||||
|
||||
- Updated dependencies [[`319f159`](https://github.com/Kilo-Org/kilocode/commit/319f159ac333d18855a72ddb1fa61ed471ebf2d9), [`bb75928`](https://github.com/Kilo-Org/kilocode/commit/bb759287495fabc1f3fa7e0fbecff13ab4d41cb2), [`9fa8a13`](https://github.com/Kilo-Org/kilocode/commit/9fa8a13e26db552373149e1275597eb07861de91), [`30e7ec4`](https://github.com/Kilo-Org/kilocode/commit/30e7ec4ab45fac724b41ec0b4342e272e7f584d2), [`bd69158`](https://github.com/Kilo-Org/kilocode/commit/bd69158131aafdcc2f44aede22b573c2b0432f21)]:
|
||||
- @kilocode/kilo-gateway@7.4.10
|
||||
- @kilocode/kilo-ui@7.4.10
|
||||
- @kilocode/kilo-memory@7.5.0
|
||||
- @kilocode/sdk@7.5.0
|
||||
- @opencode-ai/core@7.4.10
|
||||
- @kilocode/kilo-indexing@7.4.10
|
||||
- @kilocode/plugin@7.4.10
|
||||
- @opencode-ai/ui@7.4.10
|
||||
|
||||
## 7.4.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "kilo-code",
|
||||
"displayName": "Kilo Code: AI Coding Agent, Copilot, and Autocomplete",
|
||||
"description": "Open Source AI coding agent that generates code from natural language, automates tasks, and runs terminal commands. Features inline autocomplete, browser automation, automated refactoring, and custom modes for planning, coding, and debugging. Supports 500+ AI models including Claude (Anthropic), Gemini, Grok, GPT, Codex and GLM.",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"icon": "assets/icons/logo-outline-black.png",
|
||||
"galleryBanner": {
|
||||
"color": "#FFFFFF",
|
||||
@@ -291,6 +291,11 @@
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.agentManager.newWorktree",
|
||||
"title": "Agent Manager: Configure New Worktree",
|
||||
"category": "Kilo Code"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.agentManager.quickWorktree",
|
||||
"title": "Agent Manager: New Worktree",
|
||||
"category": "Kilo Code"
|
||||
},
|
||||
@@ -311,7 +316,7 @@
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.agentManager.advancedWorktree",
|
||||
"title": "Agent Manager: Advanced New Worktree",
|
||||
"title": "Agent Manager: Configure New Worktree",
|
||||
"category": "Kilo Code"
|
||||
},
|
||||
{
|
||||
@@ -472,6 +477,10 @@
|
||||
"command": "kilo-code.new.sidebarTitle.kiloClawOpen",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.agentManager.advancedWorktree",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.marketplaceButtonClicked",
|
||||
"when": "false"
|
||||
@@ -713,6 +722,12 @@
|
||||
"mac": "cmd+n",
|
||||
"when": "activeWebviewPanelId == 'kilo-code.new.AgentManagerPanel'"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.agentManager.quickWorktree",
|
||||
"key": "ctrl+shift+n",
|
||||
"mac": "cmd+shift+n",
|
||||
"when": "activeWebviewPanelId == 'kilo-code.new.AgentManagerPanel'"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.agentManager.openWorktree",
|
||||
"key": "ctrl+shift+o",
|
||||
@@ -731,12 +746,6 @@
|
||||
"mac": "cmd+shift+w",
|
||||
"when": "activeWebviewPanelId == 'kilo-code.new.AgentManagerPanel'"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.agentManager.advancedWorktree",
|
||||
"key": "ctrl+shift+n",
|
||||
"mac": "cmd+shift+n",
|
||||
"when": "activeWebviewPanelId == 'kilo-code.new.AgentManagerPanel'"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.agentManager.jumpTo1",
|
||||
"key": "ctrl+1",
|
||||
@@ -1159,8 +1168,7 @@
|
||||
"pretest": "bun run compile-tests && bun run compile && bun run lint",
|
||||
"check-types": "tsc --noEmit",
|
||||
"check-types:webview": "bun script/typecheck.ts --project webview-ui/tsconfig.json",
|
||||
"typecheck": "bun run check-types:extension && bun run check-types:webview",
|
||||
"check-types:extension": "bun script/typecheck.ts",
|
||||
"typecheck": "bun run check-types && bun run check-types:webview",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"knip": "knip",
|
||||
|
||||
@@ -139,6 +139,7 @@ import {
|
||||
import { fetchAndSendPendingSuggestions } from "./kilo-provider/handlers/suggestion"
|
||||
import { nativeTitle } from "./kilo-provider/native-tab-title"
|
||||
import { parseReview, reviewMetadata, type ReviewMessageData } from "./shared/review-comments"
|
||||
import { completesWithoutStatus } from "./kilo-provider/command-completion"
|
||||
import { KiloProviderMemory } from "./kilo-provider/memory"
|
||||
|
||||
import {
|
||||
@@ -3413,6 +3414,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
messageID,
|
||||
),
|
||||
)
|
||||
if (messageID && completesWithoutStatus(command)) {
|
||||
this.postMessage({ type: "sessionCommandCompleted", messageID })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("[Kilo New] KiloProvider: Failed to send command:", error)
|
||||
this.postMessage({
|
||||
|
||||
@@ -194,6 +194,11 @@ export class AgentManagerProvider implements Disposable {
|
||||
},
|
||||
stats: (refresh) => this.statsPoller.snapshot(refresh),
|
||||
prs: () => this.prBridge.snapshot(),
|
||||
managed: (id) => this.panelSessions.has(id) || !!this.state?.getSession(id),
|
||||
close: async (id) => {
|
||||
await this.onCloseSession(id)
|
||||
this.postToWebview({ type: "agentManager.sessionClosed", sessionId: id })
|
||||
},
|
||||
log: (...args) => this.log(...args),
|
||||
})
|
||||
this.unsubTool = this.connectionService.onEventFiltered(
|
||||
|
||||
@@ -25,10 +25,12 @@ interface RequestBase {
|
||||
type Request =
|
||||
| (RequestBase & { operation: "overview"; filter?: OverviewFilter })
|
||||
| (RequestBase & { operation: "prompt"; targetSessionID: string; prompt: string })
|
||||
| (RequestBase & { operation: "stop"; targetSessionID: string })
|
||||
|
||||
type Result =
|
||||
| { operation: "overview"; overview: Overview }
|
||||
| { operation: "prompt"; sessionID: string; delivered: true }
|
||||
| { operation: "stop"; sessionID: string; stopped: true }
|
||||
|
||||
interface Failure {
|
||||
code: FailureCode | "cancelled" | "disconnected" | "timeout"
|
||||
@@ -41,6 +43,8 @@ interface Options {
|
||||
state(): WorktreeStateManager | undefined
|
||||
stats(refresh?: boolean): Promise<{ worktrees: WorktreeStats[]; local?: LocalStats }>
|
||||
prs(): Map<string, PRStatus>
|
||||
managed(sessionID: string): boolean
|
||||
close(sessionID: string): Promise<void>
|
||||
log(...args: unknown[]): void
|
||||
}
|
||||
|
||||
@@ -262,17 +266,25 @@ export class AgentManagerOrchestrationBridge {
|
||||
})
|
||||
return { result: { operation: "overview", overview: result } }
|
||||
}
|
||||
await prompt({
|
||||
client,
|
||||
root,
|
||||
state,
|
||||
sessionID: request.targetSessionID,
|
||||
text: request.prompt,
|
||||
messageID: request.id,
|
||||
signal: active.controller.signal,
|
||||
})
|
||||
if (request.operation === "prompt") {
|
||||
await prompt({
|
||||
client,
|
||||
root,
|
||||
state,
|
||||
sessionID: request.targetSessionID,
|
||||
text: request.prompt,
|
||||
messageID: request.id,
|
||||
signal: active.controller.signal,
|
||||
})
|
||||
if (this.disposed || active.cancelled) return
|
||||
return { result: { operation: "prompt", sessionID: request.targetSessionID, delivered: true } }
|
||||
}
|
||||
if (!this.options.managed(request.targetSessionID)) {
|
||||
throw new OrchestrationError("unknown_session", "The session is not managed by this Agent Manager workspace")
|
||||
}
|
||||
await this.options.close(request.targetSessionID)
|
||||
if (this.disposed || active.cancelled) return
|
||||
return { result: { operation: "prompt", sessionID: request.targetSessionID, delivered: true } }
|
||||
return { result: { operation: "stop", sessionID: request.targetSessionID, stopped: true } }
|
||||
} catch (error) {
|
||||
if (this.disposed || active.cancelled) return
|
||||
return { error: failure(error) }
|
||||
|
||||
@@ -190,6 +190,11 @@ interface SessionForkedMessage {
|
||||
worktreeId?: string
|
||||
}
|
||||
|
||||
interface SessionClosedMessage {
|
||||
type: "agentManager.sessionClosed"
|
||||
sessionId: string
|
||||
}
|
||||
|
||||
interface MultiVersionProgressMessage {
|
||||
type: "agentManager.multiVersionProgress"
|
||||
status: "creating" | "done"
|
||||
@@ -307,6 +312,7 @@ export type AgentManagerOutMessage =
|
||||
| ErrorOutMessage
|
||||
| SessionAddedMessage
|
||||
| SessionForkedMessage
|
||||
| SessionClosedMessage
|
||||
| MultiVersionProgressMessage
|
||||
| SetSessionModelMessage
|
||||
| SendInitialMessage
|
||||
|
||||
@@ -498,6 +498,9 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
vscode.commands.registerCommand("kilo-code.new.agentManager.newWorktree", () => {
|
||||
agentManagerProvider.postMessage({ type: "action", action: "newWorktree" })
|
||||
}),
|
||||
vscode.commands.registerCommand("kilo-code.new.agentManager.quickWorktree", () => {
|
||||
agentManagerProvider.postMessage({ type: "action", action: "quickWorktree" })
|
||||
}),
|
||||
vscode.commands.registerCommand("kilo-code.new.agentManager.openWorktree", () => {
|
||||
agentManagerProvider.postMessage({ type: "action", action: "openWorktree" })
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export function completesWithoutStatus(command: string): boolean {
|
||||
return command === "local-review" || command === "local-review-uncommitted"
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import type { CloudSessionData, EditorContext } from "../../services/cli-backend
|
||||
import { getErrorMessage, sessionToWebview, mapCloudSessionMessageToWebviewMessage } from "../../kilo-provider-utils"
|
||||
import type { MessageFile } from "../message-files"
|
||||
import { reviewMetadata, type ReviewMessageData } from "../../shared/review-comments"
|
||||
import { completesWithoutStatus } from "../command-completion"
|
||||
|
||||
const TIMEOUT = 30_000
|
||||
|
||||
@@ -232,6 +233,9 @@ export async function handleImportAndSend(
|
||||
{ throwOnError: true },
|
||||
)
|
||||
})
|
||||
if (messageID && command && completesWithoutStatus(command)) {
|
||||
ctx.postMessage({ type: "sessionCommandCompleted", messageID })
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("[Kilo New] Failed to send message after cloud import:", err)
|
||||
ctx.postMessage({
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import { Window } from "happy-dom"
|
||||
import type { QuestionRequest } from "../../webview-ui/src/types/messages"
|
||||
|
||||
const window = new Window()
|
||||
Object.assign(globalThis, {
|
||||
window,
|
||||
document: window.document,
|
||||
Node: window.Node,
|
||||
Element: window.Element,
|
||||
HTMLElement: window.HTMLElement,
|
||||
SVGElement: window.SVGElement,
|
||||
requestAnimationFrame: () => 0,
|
||||
})
|
||||
|
||||
const { Show, createSignal } = await import("solid-js")
|
||||
const { render } = await import("solid-js/web")
|
||||
const { SessionContext } = await import("../../webview-ui/src/context/session")
|
||||
const { LanguageContext } = await import("../../webview-ui/src/context/language")
|
||||
const { QuestionDock } = await import("../../webview-ui/src/components/chat/QuestionDock")
|
||||
|
||||
const request: QuestionRequest = {
|
||||
id: "question-1",
|
||||
sessionID: "session-1",
|
||||
questions: [
|
||||
{
|
||||
question: "Continue?",
|
||||
header: "Confirm",
|
||||
options: [{ label: "Yes", description: "Continue" }],
|
||||
},
|
||||
],
|
||||
}
|
||||
const [active, setActive] = createSignal<QuestionRequest | undefined>(request)
|
||||
const calls: Array<{ id: string; answers: string[][] }> = []
|
||||
const session = {
|
||||
questionErrors: () => new Set<string>(),
|
||||
selectedAgent: () => "code",
|
||||
selectAgent: () => {},
|
||||
replyToQuestion: (id: string, answers: string[][]) => {
|
||||
calls.push({ id, answers })
|
||||
setActive(undefined)
|
||||
},
|
||||
rejectQuestion: () => {},
|
||||
closeQuestion: () => {},
|
||||
}
|
||||
const language = {
|
||||
locale: () => "en",
|
||||
setLocale: () => {},
|
||||
userOverride: () => "",
|
||||
t: (key: string) => key,
|
||||
}
|
||||
const root = document.createElement("div")
|
||||
document.body.append(root)
|
||||
const dispose = render(
|
||||
() => (
|
||||
<SessionContext.Provider value={session as never}>
|
||||
<LanguageContext.Provider value={language as never}>
|
||||
<Show when={active()}>{(item) => <QuestionDock request={item()} />}</Show>
|
||||
</LanguageContext.Provider>
|
||||
</SessionContext.Provider>
|
||||
),
|
||||
root,
|
||||
)
|
||||
|
||||
const option = root.querySelector<HTMLButtonElement>('[data-slot="question-option"]')
|
||||
const submit = root.querySelector<HTMLButtonElement>('[data-slot="question-footer-actions"] button')
|
||||
if (!option || !submit) throw new Error("Question controls did not render")
|
||||
option.click()
|
||||
if (submit.disabled) throw new Error("Submit did not enable after selecting an answer")
|
||||
submit.click()
|
||||
if (calls.length !== 1 || calls[0]?.id !== request.id || calls[0]?.answers[0]?.[0] !== "Yes") {
|
||||
throw new Error(`Unexpected question reply: ${JSON.stringify(calls)}`)
|
||||
}
|
||||
if (root.querySelector('[data-component="question-dock"]')) throw new Error("Question dock did not unmount")
|
||||
dispose()
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"version": "7.4.9",
|
||||
"version": "7.4.11",
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"peerDependencies": {}
|
||||
|
||||
@@ -20,6 +20,7 @@ const CSS_FILES = [
|
||||
]
|
||||
const TSX_FILES = [
|
||||
path.join(ROOT, "webview-ui/agent-manager/AgentManagerApp.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/UnassignedSessionsSection.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/NewWorktreeDialog.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/sortable-tab.tsx"),
|
||||
path.join(ROOT, "webview-ui/agent-manager/DiffPanel.tsx"),
|
||||
@@ -269,6 +270,89 @@ describe("Agent Manager Model Picker", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("Agent Manager Worktree Actions", () => {
|
||||
it("opens the configuration dialog from the primary plus action", () => {
|
||||
const source = fs.readFileSync(path.join(ROOT, "webview-ui/agent-manager/WorktreeSectionActions.tsx"), "utf-8")
|
||||
const start = source.indexOf('<div class="am-split-button">')
|
||||
const end = source.indexOf("</div>", start)
|
||||
const actions = source.slice(start, end)
|
||||
|
||||
expect(start).toBeGreaterThanOrEqual(0)
|
||||
expect(end).toBeGreaterThan(start)
|
||||
expect(actions).toContain("onClick={props.onNew}")
|
||||
expect(actions).toContain('keybind={props.bindings.newWorktree ?? ""}')
|
||||
expect(actions).toContain("<DropdownMenu.Item onSelect={props.onCreate}>")
|
||||
expect(actions).toContain('props.t("sidebar.session.newWorktree.from")')
|
||||
expect(actions).toContain('props.bindings.quickWorktree ?? ""')
|
||||
expect(actions).not.toContain("onSelect={props.onNew}")
|
||||
})
|
||||
|
||||
it("opens the configuration dialog from the new-worktree shortcut", () => {
|
||||
const source = fs.readFileSync(TSX_FILE, "utf-8")
|
||||
const start = source.indexOf('else if (msg.action === "newWorktree")')
|
||||
const end = source.indexOf('else if (msg.action === "quickWorktree")', start)
|
||||
const action = source.slice(start, end)
|
||||
|
||||
expect(start).toBeGreaterThanOrEqual(0)
|
||||
expect(end).toBeGreaterThan(start)
|
||||
expect(action).toContain("showNewWorktreeDialog()")
|
||||
})
|
||||
|
||||
it("creates a worktree from the quick-worktree shortcut", () => {
|
||||
const source = fs.readFileSync(TSX_FILE, "utf-8")
|
||||
const start = source.indexOf('else if (msg.action === "quickWorktree")')
|
||||
const end = source.indexOf('else if (msg.action === "openWorktree")', start)
|
||||
const action = source.slice(start, end)
|
||||
|
||||
expect(start).toBeGreaterThanOrEqual(0)
|
||||
expect(end).toBeGreaterThan(start)
|
||||
expect(action).toContain("handleCreateWorktree()")
|
||||
})
|
||||
|
||||
it("registers distinct dialog and quick-create worktree shortcuts", () => {
|
||||
const manifest = JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf-8")) as {
|
||||
contributes: { keybindings: { command: string; key?: string; mac?: string }[] }
|
||||
}
|
||||
const source = fs.readFileSync(TSX_FILE, "utf-8")
|
||||
const dialog = manifest.contributes.keybindings.find(
|
||||
(item) => item.command === "kilo-code.new.agentManager.newWorktree",
|
||||
)
|
||||
const quick = manifest.contributes.keybindings.find(
|
||||
(item) => item.command === "kilo-code.new.agentManager.quickWorktree",
|
||||
)
|
||||
|
||||
expect(dialog).toMatchObject({ key: "ctrl+n", mac: "cmd+n" })
|
||||
expect(quick).toMatchObject({ key: "ctrl+shift+n", mac: "cmd+shift+n" })
|
||||
expect(source).toContain('newWorktree: isMac ? "⌘N" : "Ctrl+N"')
|
||||
expect(source).toContain('quickWorktree: isMac ? "⌘⇧N" : "Ctrl+Shift+N"')
|
||||
})
|
||||
|
||||
it("forwards the quick-worktree command to immediate creation", () => {
|
||||
const source = fs.readFileSync(path.join(ROOT, "src/extension.ts"), "utf-8")
|
||||
const start = source.indexOf('vscode.commands.registerCommand("kilo-code.new.agentManager.quickWorktree"')
|
||||
const end = source.indexOf('vscode.commands.registerCommand("kilo-code.new.agentManager.openWorktree"', start)
|
||||
const command = source.slice(start, end)
|
||||
|
||||
expect(start).toBeGreaterThanOrEqual(0)
|
||||
expect(end).toBeGreaterThan(start)
|
||||
expect(command).toContain('agentManagerProvider.postMessage({ type: "action", action: "quickWorktree" })')
|
||||
})
|
||||
|
||||
it("shows the same mapping in the keyboard shortcuts dialog", () => {
|
||||
const source = fs.readFileSync(path.join(ROOT, "webview-ui/agent-manager/shortcuts.ts"), "utf-8")
|
||||
|
||||
expect(source).toContain('t("agentManager.shortcuts.advancedWorktree"), binding: bindings.newWorktree')
|
||||
expect(source).toContain('t("agentManager.shortcuts.newWorktree"), binding: bindings.quickWorktree')
|
||||
})
|
||||
|
||||
it("does not attribute the new-worktree shortcut to session promotion", () => {
|
||||
const source = fs.readFileSync(path.join(ROOT, "webview-ui/agent-manager/UnassignedSessionsSection.tsx"), "utf-8")
|
||||
|
||||
expect(source).toContain('<Tooltip value={t("agentManager.session.openInWorktree")}')
|
||||
expect(source).not.toContain("TooltipKeybind")
|
||||
})
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Provider message routing — static-analysis regression tests
|
||||
//
|
||||
|
||||
@@ -5,6 +5,7 @@ const { AgentManagerProvider } = await import("../../src/agent-manager/AgentMana
|
||||
type Manager = {
|
||||
connectionService: { getClient: () => unknown }
|
||||
panel: {
|
||||
postMessage: (message: unknown) => void
|
||||
sessions: {
|
||||
getSessionDirectories: () => ReadonlyMap<string, string>
|
||||
clearSessionDirectory: (id: string) => void
|
||||
@@ -24,6 +25,7 @@ function createManager(options?: { dir?: string; panelDir?: string; state?: bool
|
||||
const aborted: string[][] = []
|
||||
const cleared: string[] = []
|
||||
const removed: string[] = []
|
||||
const messages: unknown[] = []
|
||||
const events: string[] = []
|
||||
const client = {
|
||||
backgroundProcess: {
|
||||
@@ -44,6 +46,7 @@ function createManager(options?: { dir?: string; panelDir?: string; state?: bool
|
||||
const manager = Object.create(AgentManagerProvider.prototype) as Manager
|
||||
manager.connectionService = { getClient: () => client }
|
||||
manager.panel = {
|
||||
postMessage: (message) => messages.push(message),
|
||||
sessions: {
|
||||
getSessionDirectories: () => new Map(options?.panelDir ? [["s1", options.panelDir]] : []),
|
||||
clearSessionDirectory: (id) => cleared.push(id),
|
||||
@@ -59,12 +62,12 @@ function createManager(options?: { dir?: string; panelDir?: string; state?: bool
|
||||
manager.pushState = mock(() => undefined)
|
||||
manager.log = mock(() => undefined)
|
||||
|
||||
return { manager, stopped, aborted, cleared, removed, events }
|
||||
return { manager, stopped, aborted, cleared, removed, messages, events }
|
||||
}
|
||||
|
||||
describe("AgentManagerProvider closeSession", () => {
|
||||
it("aborts the agent before stopping processes and removing its tab", async () => {
|
||||
const { manager, stopped, aborted, cleared, removed, events } = createManager({ dir: "/repo/worktree" })
|
||||
const { manager, stopped, aborted, cleared, removed, messages, events } = createManager({ dir: "/repo/worktree" })
|
||||
|
||||
await manager.onCloseSession("s1")
|
||||
|
||||
@@ -73,6 +76,7 @@ describe("AgentManagerProvider closeSession", () => {
|
||||
expect(events).toEqual(["abort", "processes", "remove"])
|
||||
expect(removed).toEqual(["s1"])
|
||||
expect(cleared).toEqual(["s1"])
|
||||
expect(messages).toEqual([])
|
||||
expect(manager.panelSessions.has("s1")).toBe(false)
|
||||
})
|
||||
|
||||
|
||||
@@ -42,7 +42,9 @@ describe("AgentManagerOrchestrationBridge", () => {
|
||||
state?: (state: "connecting" | "connected" | "disconnected" | "error") => void
|
||||
} = {}
|
||||
const status = { failList: "", failReply: false }
|
||||
const managed = new Set(["ses_target"])
|
||||
const promptAsync = mock(async () => ({ data: undefined }))
|
||||
const close = mock(async () => undefined)
|
||||
const client = {
|
||||
session: {
|
||||
get: mock(async () => ({
|
||||
@@ -95,6 +97,8 @@ describe("AgentManagerOrchestrationBridge", () => {
|
||||
state: () => state,
|
||||
stats: async () => ({ worktrees: [] }),
|
||||
prs: () => new Map(),
|
||||
managed: (id) => managed.has(id),
|
||||
close,
|
||||
log: () => undefined,
|
||||
})
|
||||
const request = (value: AgentManagerRequest, directory = root) =>
|
||||
@@ -102,7 +106,7 @@ describe("AgentManagerOrchestrationBridge", () => {
|
||||
{ id: `event-${value.id}`, type: "kilocode.agent_manager.requested", properties: value } as SSEPayload,
|
||||
directory,
|
||||
)
|
||||
return { bridge, client, handlers, lists, promptAsync, rejections, replies, request, status }
|
||||
return { bridge, client, close, handlers, lists, managed, promptAsync, rejections, replies, request, status }
|
||||
}
|
||||
|
||||
const request: AgentManagerRequest = {
|
||||
@@ -148,6 +152,85 @@ describe("AgentManagerOrchestrationBridge", () => {
|
||||
test.bridge.dispose()
|
||||
})
|
||||
|
||||
it("stops a managed session through the same close operation as the UI", async () => {
|
||||
const test = harness()
|
||||
test.status.failReply = true
|
||||
const stop: AgentManagerRequest = {
|
||||
id: "amr_stop",
|
||||
sessionID: "ses_caller",
|
||||
operation: "stop",
|
||||
targetSessionID: "ses_target",
|
||||
}
|
||||
|
||||
test.request(stop)
|
||||
await waitFor(() => test.replies.length === 1)
|
||||
test.status.failReply = false
|
||||
test.request(stop)
|
||||
await waitFor(() => test.replies.length === 2)
|
||||
|
||||
expect(test.close).toHaveBeenCalledTimes(1)
|
||||
expect(test.close).toHaveBeenCalledWith("ses_target")
|
||||
expect(test.replies).toEqual([
|
||||
{
|
||||
requestID: "amr_stop",
|
||||
directory: root,
|
||||
result: { operation: "stop", sessionID: "ses_target", stopped: true },
|
||||
},
|
||||
{
|
||||
requestID: "amr_stop",
|
||||
directory: root,
|
||||
result: { operation: "stop", sessionID: "ses_target", stopped: true },
|
||||
},
|
||||
])
|
||||
test.bridge.dispose()
|
||||
})
|
||||
|
||||
it("stops a live panel session before it is persisted", async () => {
|
||||
const test = harness()
|
||||
test.managed.add("ses_live")
|
||||
|
||||
test.request({
|
||||
id: "amr_stop_live",
|
||||
sessionID: "ses_caller",
|
||||
operation: "stop",
|
||||
targetSessionID: "ses_live",
|
||||
})
|
||||
await waitFor(() => test.replies.length === 1)
|
||||
|
||||
expect(state.getSession("ses_live")).toBeUndefined()
|
||||
expect(test.close).toHaveBeenCalledWith("ses_live")
|
||||
expect(test.replies[0]).toEqual({
|
||||
requestID: "amr_stop_live",
|
||||
directory: root,
|
||||
result: { operation: "stop", sessionID: "ses_live", stopped: true },
|
||||
})
|
||||
test.bridge.dispose()
|
||||
})
|
||||
|
||||
it("rejects stopping a session not managed by the current workspace", async () => {
|
||||
const test = harness()
|
||||
test.request({
|
||||
id: "amr_stop_unknown",
|
||||
sessionID: "ses_caller",
|
||||
operation: "stop",
|
||||
targetSessionID: "ses_unknown",
|
||||
})
|
||||
await waitFor(() => test.rejections.length === 1)
|
||||
|
||||
expect(test.close).not.toHaveBeenCalled()
|
||||
expect(test.rejections).toEqual([
|
||||
{
|
||||
requestID: "amr_stop_unknown",
|
||||
directory: root,
|
||||
error: {
|
||||
code: "unknown_session",
|
||||
message: "The session is not managed by this Agent Manager workspace",
|
||||
},
|
||||
},
|
||||
])
|
||||
test.bridge.dispose()
|
||||
})
|
||||
|
||||
it("rejects request origins outside the current Agent Manager workspace", async () => {
|
||||
const test = harness()
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { completesWithoutStatus } from "../../src/kilo-provider/command-completion"
|
||||
|
||||
describe("completesWithoutStatus", () => {
|
||||
it("matches only deprecated static review aliases", () => {
|
||||
expect(completesWithoutStatus("local-review")).toBe(true)
|
||||
expect(completesWithoutStatus("local-review-uncommitted")).toBe(true)
|
||||
expect(completesWithoutStatus("review")).toBe(false)
|
||||
expect(completesWithoutStatus("init")).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -21,6 +21,7 @@ const CHATVIEW_FILE = path.join(ROOT, "webview-ui/src/components/chat/ChatView.t
|
||||
const PROMPT_UTILS_FILE = path.join(ROOT, "webview-ui/src/components/chat/prompt-input-utils.ts")
|
||||
const PROMPT_FILE = path.join(ROOT, "webview-ui/src/components/chat/PromptInput.tsx")
|
||||
const KILOPROVIDER_FILE = path.join(ROOT, "src/KiloProvider.ts")
|
||||
const CLOUD_SESSION_FILE = path.join(ROOT, "src/kilo-provider/handlers/cloud-session.ts")
|
||||
const CONNECTION_SERVICE_FILE = path.join(ROOT, "src/services/cli-backend/connection-service.ts")
|
||||
|
||||
function readFile(filePath: string): string {
|
||||
@@ -79,6 +80,32 @@ describe("sendCommand dismisses pending tool requests", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("static command completion contract", () => {
|
||||
const source = readFile(SESSION_FILE)
|
||||
|
||||
it("finishes only the acknowledged command submission", () => {
|
||||
const body = extractFunctionBody(source, "handleCommandCompletion")
|
||||
expect(body).toMatch(/message\.type === "sessionCommandCompleted"\) finishSubmission\(message\.messageID\)/)
|
||||
})
|
||||
|
||||
it("acknowledges aliases after direct and cloud command confirmation", () => {
|
||||
const provider = readFile(KILOPROVIDER_FILE)
|
||||
const cloud = readFile(CLOUD_SESSION_FILE)
|
||||
expect(provider).toMatch(
|
||||
/await runWithMessageConfirmation[\s\S]*?if \(messageID && completesWithoutStatus\(command\)\)[\s\S]*?sessionCommandCompleted/,
|
||||
)
|
||||
expect(cloud).toMatch(
|
||||
/await run\(messageID, "Cloud import send"[\s\S]*?if \(messageID && command && completesWithoutStatus\(command\)\)[\s\S]*?sessionCommandCompleted/,
|
||||
)
|
||||
})
|
||||
|
||||
it("does not clear every session submission or active abort", () => {
|
||||
const body = extractFunctionBody(source, "handleCommandCompletion")
|
||||
expect(body).not.toContain("confirmSubmissions")
|
||||
expect(body).not.toContain("aborts.clear")
|
||||
})
|
||||
})
|
||||
|
||||
describe("ChatView prompt-block contract", () => {
|
||||
const source = readFile(CHATVIEW_FILE)
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { unlinkSync } from "node:fs"
|
||||
import path from "node:path"
|
||||
import { build } from "esbuild"
|
||||
import { solidPlugin } from "esbuild-plugin-solid"
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, "../..")
|
||||
const WEBVIEW = path.join(ROOT, "webview-ui")
|
||||
const FIXTURE = path.join(ROOT, "tests/fixtures/question-dock-disposal.tsx")
|
||||
|
||||
describe("QuestionDock disposal", () => {
|
||||
it("does not read a stale callback-form Show accessor", async () => {
|
||||
const solid = path.dirname(Bun.resolveSync("solid-js/package.json", WEBVIEW))
|
||||
const aliases: Record<string, string> = {
|
||||
"solid-js": path.join(solid, "dist/solid.js"),
|
||||
"solid-js/web": path.join(solid, "web/dist/web.js"),
|
||||
"solid-js/store": path.join(solid, "store/dist/store.js"),
|
||||
}
|
||||
const dedupe = {
|
||||
name: "solid-dedupe",
|
||||
setup(ctx: Parameters<NonNullable<Parameters<typeof build>[0]["plugins"]>[number]["setup"]>[0]) {
|
||||
ctx.onResolve({ filter: /^solid-js(\/web|\/store)?$/ }, (args) => ({ path: aliases[args.path] }))
|
||||
},
|
||||
}
|
||||
const result = await build({
|
||||
entryPoints: [FIXTURE],
|
||||
bundle: true,
|
||||
conditions: ["browser"],
|
||||
external: ["happy-dom"],
|
||||
format: "esm",
|
||||
logLevel: "silent",
|
||||
platform: "node",
|
||||
plugins: [dedupe, solidPlugin()],
|
||||
target: "es2022",
|
||||
write: false,
|
||||
})
|
||||
const file = path.join(ROOT, `.question-dock-disposal-${crypto.randomUUID()}.mjs`)
|
||||
await Bun.write(file, result.outputFiles[0]!.contents)
|
||||
const child = Bun.spawnSync(["bun", file], { cwd: WEBVIEW, stdout: "pipe", stderr: "pipe" })
|
||||
unlinkSync(file)
|
||||
|
||||
const output = child.stdout.toString() + child.stderr.toString()
|
||||
expect(child.exitCode, output).toBe(0)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import type { Part, ToolPart } from "@kilocode/sdk/v2"
|
||||
import { toolDefaultOpen } from "../../webview-ui/src/components/chat/tool-default-open"
|
||||
|
||||
function tool(name: string) {
|
||||
return { type: "tool", tool: name } as ToolPart
|
||||
}
|
||||
|
||||
describe("toolDefaultOpen", () => {
|
||||
it.each(["bash", "background_process"])("uses the terminal preference for %s", (name) => {
|
||||
expect(toolDefaultOpen(tool(name), false, true)).toBe(false)
|
||||
expect(toolDefaultOpen(tool(name), true, false)).toBe(true)
|
||||
})
|
||||
|
||||
it.each(["edit", "write", "apply_patch"])("uses the code edit preference for %s", (name) => {
|
||||
expect(toolDefaultOpen(tool(name), true, false)).toBe(false)
|
||||
expect(toolDefaultOpen(tool(name), false, true)).toBe(true)
|
||||
})
|
||||
|
||||
it("leaves unrelated parts unchanged", () => {
|
||||
expect(toolDefaultOpen(tool("read"), true, true)).toBeUndefined()
|
||||
expect(toolDefaultOpen({ type: "text" } as Part, true, true)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -56,7 +56,6 @@ import { ThemeProvider } from "@kilocode/kilo-ui/theme"
|
||||
import { DialogProvider, useDialog } from "@kilocode/kilo-ui/context/dialog"
|
||||
import { Dialog } from "@kilocode/kilo-ui/dialog"
|
||||
import { DropdownMenu } from "@kilocode/kilo-ui/dropdown-menu"
|
||||
import { ContextMenu } from "@kilocode/kilo-ui/context-menu"
|
||||
import { MarkedProvider } from "@kilocode/kilo-ui/context/marked"
|
||||
import { CodeComponentProvider } from "@kilocode/kilo-ui/context/code"
|
||||
import { DiffComponentProvider } from "@kilocode/kilo-ui/context/diff"
|
||||
@@ -92,7 +91,6 @@ import { NewWorktreeDialog } from "./NewWorktreeDialog"
|
||||
import { DataBridge, MermaidDownloadBridge } from "../src/App"
|
||||
import { LanguageBridge } from "../src/context/language-bridge"
|
||||
import { useLanguage } from "../src/context/language"
|
||||
import { formatRelativeDate } from "../src/utils/date"
|
||||
import { createTabFocus } from "../src/utils/tab-navigation"
|
||||
import {
|
||||
canOpenRootSession,
|
||||
@@ -137,6 +135,7 @@ import { createSidebarSearch, type SidebarSearchItem } from "./sidebar-search"
|
||||
import { WorktreeSectionActions } from "./WorktreeSectionActions"
|
||||
import { BranchSelect } from "../src/components/shared/BranchSelect"
|
||||
import { WorktreeItem } from "./WorktreeItem"
|
||||
import { UnassignedSessionsSection } from "./UnassignedSessionsSection"
|
||||
import SectionHeader from "./SectionHeader"
|
||||
import { randomColor } from "./section-colors"
|
||||
import { createNewTaskDrafts } from "./new-task-drafts"
|
||||
@@ -204,7 +203,7 @@ const defaultBindings: Record<string, string> = {
|
||||
newTab: isMac ? "⌘T" : "Ctrl+T",
|
||||
closeTab: isMac ? "⌘W" : "Ctrl+W",
|
||||
newWorktree: isMac ? "⌘N" : "Ctrl+N",
|
||||
advancedWorktree: isMac ? "⌘⇧N" : "Ctrl+Shift+N",
|
||||
quickWorktree: isMac ? "⌘⇧N" : "Ctrl+Shift+N",
|
||||
closeWorktree: isMac ? "⌘⇧W" : "Ctrl+Shift+W",
|
||||
openWorktree: isMac ? "⌘⇧O" : "Ctrl+Shift+O",
|
||||
openPR: isMac ? "⌘⇧R" : "Ctrl+Shift+R",
|
||||
@@ -256,6 +255,11 @@ const AgentManagerContent: Component = () => {
|
||||
setLocalSessionIDs((prev) => (prev.includes(sid) ? prev.filter((id) => id !== sid) : prev))
|
||||
const [sidebarWidth, setSidebarWidth] = createSignal(persisted?.sidebarWidth ?? DEFAULT_SIDEBAR_WIDTH)
|
||||
const [sessionsCollapsed, setSessionsCollapsed] = createSignal(true)
|
||||
const toggleSessions = () => {
|
||||
const collapsed = !sessionsCollapsed()
|
||||
setSessionsCollapsed(collapsed)
|
||||
vscode.postMessage({ type: "agentManager.setSessionsCollapsed", collapsed })
|
||||
}
|
||||
const sidebar = createSidebarCollapse(vscode)
|
||||
const sidebarCollapsed = sidebar.collapsed
|
||||
const expandSidebar = sidebar.expand
|
||||
@@ -925,15 +929,17 @@ const AgentManagerContent: Component = () => {
|
||||
|
||||
const scrollIntoView = (el: HTMLElement) => el.scrollIntoView({ block: "nearest", behavior: "smooth" })
|
||||
|
||||
const selectUnassigned = (id: string) => {
|
||||
saveTabMemory()
|
||||
setSelection(null)
|
||||
setReviewActive(false)
|
||||
session.selectSession(id)
|
||||
}
|
||||
|
||||
const focusSidebarItem = (item: { type: string; id: string }) => {
|
||||
if (item.type === "local") selectLocal()
|
||||
else if (item.type === "wt") selectWorktree(item.id)
|
||||
else {
|
||||
saveTabMemory()
|
||||
setSelection(null)
|
||||
setReviewActive(false)
|
||||
session.selectSession(item.id)
|
||||
}
|
||||
else selectUnassigned(item.id)
|
||||
const el = document.querySelector(`[data-sidebar-id="${item.id}"]`)
|
||||
if (el instanceof HTMLElement) scrollIntoView(el)
|
||||
}
|
||||
@@ -1100,11 +1106,12 @@ const AgentManagerContent: Component = () => {
|
||||
} else setSidePanel((prev) => (prev === "diff" ? null : "diff"))
|
||||
} else if (msg.action === "newTab") handleNewTabForCurrentSelection()
|
||||
else if (msg.action === "closeTab") closeActiveTab()
|
||||
else if (msg.action === "newWorktree") handleNewWorktreeOrPromote()
|
||||
else if (msg.action === "newWorktree") showNewWorktreeDialog()
|
||||
else if (msg.action === "quickWorktree") handleCreateWorktree()
|
||||
else if (msg.action === "openWorktree") openWorktreeDirectory()
|
||||
else if (msg.action === "openPR") openSelectedPR()
|
||||
else if (msg.action === "runScript") runSelected()
|
||||
else if (msg.action === "advancedWorktree") showAdvancedWorktreeDialog()
|
||||
else if (msg.action === "advancedWorktree") showNewWorktreeDialog()
|
||||
else if (msg.action === "closeWorktree") closeSelectedWorktree()
|
||||
else if (msg.action === "showShortcuts") handleShowKeyboardShortcuts()
|
||||
else if (msg.action === "focusInput") window.dispatchEvent(new Event("focusPrompt"))
|
||||
@@ -1215,8 +1222,8 @@ const AgentManagerContent: Component = () => {
|
||||
// Mark sessions loaded as soon as the session context receives data (even if empty)
|
||||
const unsubSessions = vscode.onMessage((msg) => {
|
||||
if (msg.type === "sessionsLoaded" && !sessionsLoaded()) setSessionsLoaded(true)
|
||||
if (msg.type === "agentManager.sessionClosed") handleCloseTab(msg.sessionId, false)
|
||||
})
|
||||
|
||||
const unsubRun = vscode.onMessage((msg) => {
|
||||
if (msg.type !== "agentManager.runStatus") return
|
||||
const ev = msg as RunStatus
|
||||
@@ -1844,8 +1851,7 @@ const AgentManagerContent: Component = () => {
|
||||
}
|
||||
const createWorktree = metrics.click("new_worktree", "worktrees", handleCreateWorktree)
|
||||
|
||||
// Advanced worktree dialog — opens a full dialog with prompt, versions, model, mode
|
||||
const showAdvancedWorktreeDialog = () => {
|
||||
const showNewWorktreeDialog = () => {
|
||||
if (!loaded()) return
|
||||
expandSidebar()
|
||||
dialog.show(() => <NewWorktreeDialog onClose={() => dialog.close()} defaultBaseBranch={repoDefaultBranch()} />)
|
||||
@@ -1934,8 +1940,7 @@ const AgentManagerContent: Component = () => {
|
||||
confirmDeleteWorktree(worktreeId)
|
||||
}
|
||||
|
||||
const handlePromote = (sessionId: string, e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
const promoteSession = (sessionId: string) => {
|
||||
if (!loaded()) return
|
||||
metrics.track("promote_session", "unassigned_session")
|
||||
vscode.postMessage({ type: "agentManager.promoteSession", sessionId })
|
||||
@@ -1953,6 +1958,11 @@ const AgentManagerContent: Component = () => {
|
||||
vscode.postMessage({ type: "agentManager.openLocally", sessionId: sid })
|
||||
}
|
||||
|
||||
const openUnassigned = (id: string) => {
|
||||
metrics.track("open_session_locally", "unassigned_session_menu")
|
||||
openLocally(id)
|
||||
}
|
||||
|
||||
const handleAddSession = () => {
|
||||
const sel = selection()
|
||||
expandSidebar()
|
||||
@@ -1969,7 +1979,7 @@ const AgentManagerContent: Component = () => {
|
||||
if (!sel || sel === LOCAL) return vscode.postMessage(msg)
|
||||
vscode.postMessage({ ...msg, worktreeId: sel })
|
||||
}
|
||||
const handleCloseTab = (sessionId: string) => {
|
||||
const handleCloseTab = (sessionId: string, notify = true) => {
|
||||
freezeTabs()
|
||||
const pending = isPending(sessionId)
|
||||
const isActive = pending ? sessionId === activePendingId() : session.currentSessionID() === sessionId
|
||||
@@ -1999,7 +2009,7 @@ const AgentManagerContent: Component = () => {
|
||||
if (session.isSubmitting(sessionId) || isPendingSend(sessionId)) discardPendingDraft(sessionId)
|
||||
queueMicrotask(() => deletePendingDraft(sessionId))
|
||||
}
|
||||
vscode.postMessage({ type: "agentManager.closeSession", sessionId })
|
||||
if (notify) vscode.postMessage({ type: "agentManager.closeSession", sessionId })
|
||||
tabFocus.restore()
|
||||
}
|
||||
|
||||
@@ -2174,18 +2184,6 @@ const AgentManagerContent: Component = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Cmd+N: if an unassigned session is selected, promote it; otherwise create a new worktree
|
||||
const handleNewWorktreeOrPromote = () => {
|
||||
if (!loaded()) return
|
||||
const sel = selection()
|
||||
const sid = session.currentSessionID()
|
||||
if (sel === null && sid && !worktreeSessionIds().has(sid)) {
|
||||
vscode.postMessage({ type: "agentManager.promoteSession", sessionId: sid })
|
||||
return
|
||||
}
|
||||
handleCreateWorktree()
|
||||
}
|
||||
|
||||
// Close the currently selected worktree with a confirmation dialog
|
||||
const closeSelectedWorktree = () => {
|
||||
const sel = selection()
|
||||
@@ -2312,7 +2310,7 @@ const AgentManagerContent: Component = () => {
|
||||
onRef={(value) => (sidebarSearchMenu = value)}
|
||||
onSelect={focusSidebarSearchItem}
|
||||
onCreate={createWorktree}
|
||||
onAdvanced={showAdvancedWorktreeDialog}
|
||||
onNew={showNewWorktreeDialog}
|
||||
onSection={newSection}
|
||||
onShortcuts={metrics.click("keyboard_shortcuts", "worktrees_header", handleShowKeyboardShortcuts)}
|
||||
onSetup={setupScript}
|
||||
@@ -2549,7 +2547,7 @@ const AgentManagerContent: Component = () => {
|
||||
)
|
||||
})()}
|
||||
<Show when={worktrees().length === 0}>
|
||||
<button class="am-worktree-create" onClick={createWorktree}>
|
||||
<button class="am-worktree-create" onClick={showNewWorktreeDialog}>
|
||||
<Icon name="plus" size="small" />
|
||||
<span>{t("agentManager.worktree.new")}</span>
|
||||
</button>
|
||||
@@ -2559,102 +2557,16 @@ const AgentManagerContent: Component = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* SESSIONS section (unassigned) — collapsible */}
|
||||
<div class={`am-section ${sessionsCollapsed() ? "" : "am-section-grow"}`}>
|
||||
<button
|
||||
class="am-section-header am-section-toggle"
|
||||
onClick={() => {
|
||||
const next = !sessionsCollapsed()
|
||||
setSessionsCollapsed(next)
|
||||
vscode.postMessage({ type: "agentManager.setSessionsCollapsed", collapsed: next })
|
||||
}}
|
||||
>
|
||||
<span class="am-section-label">
|
||||
<Icon
|
||||
name={sessionsCollapsed() ? "chevron-right" : "chevron-down"}
|
||||
size="small"
|
||||
class="am-section-chevron"
|
||||
/>
|
||||
{t("agentManager.section.sessions")}
|
||||
</span>
|
||||
</button>
|
||||
<Show when={!sessionsCollapsed()}>
|
||||
<div class="am-list">
|
||||
<Show
|
||||
when={sessionsLoaded()}
|
||||
fallback={
|
||||
<div class="am-skeleton-list">
|
||||
<div class="am-skeleton-session">
|
||||
<div class="am-skeleton-session-title" style={{ width: "70%" }} />
|
||||
<div class="am-skeleton-session-time" />
|
||||
</div>
|
||||
<div class="am-skeleton-session">
|
||||
<div class="am-skeleton-session-title" style={{ width: "55%" }} />
|
||||
<div class="am-skeleton-session-time" />
|
||||
</div>
|
||||
<div class="am-skeleton-session">
|
||||
<div class="am-skeleton-session-title" style={{ width: "65%" }} />
|
||||
<div class="am-skeleton-session-time" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<For each={unassignedSessions()}>
|
||||
{(s) => (
|
||||
<ContextMenu>
|
||||
<ContextMenu.Trigger as="div" style={{ display: "contents" }}>
|
||||
<button
|
||||
class={`am-item ${s.id === session.currentSessionID() && selection() === null ? "am-item-active" : ""}`}
|
||||
data-sidebar-id={s.id}
|
||||
onClick={() => {
|
||||
saveTabMemory()
|
||||
setSelection(null)
|
||||
setReviewActive(false)
|
||||
session.selectSession(s.id)
|
||||
}}
|
||||
>
|
||||
<span class="am-item-title">{s.title || t("agentManager.session.untitled")}</span>
|
||||
<span class="am-item-time">{formatRelativeDate(s.updatedAt)}</span>
|
||||
<div class="am-item-promote">
|
||||
<TooltipKeybind
|
||||
title={t("agentManager.session.openInWorktree")}
|
||||
keybind={kb().newWorktree ?? ""}
|
||||
placement="right"
|
||||
>
|
||||
<IconButton
|
||||
icon="branch"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
label={t("agentManager.session.openInWorktree")}
|
||||
onClick={(e: MouseEvent) => handlePromote(s.id, e)}
|
||||
/>
|
||||
</TooltipKeybind>
|
||||
</div>
|
||||
</button>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content class="am-ctx-menu">
|
||||
<ContextMenu.Item onSelect={() => handlePromote(s.id, new MouseEvent("click"))}>
|
||||
<Icon name="branch" size="small" />
|
||||
<ContextMenu.ItemLabel>{t("agentManager.session.openInWorktree")}</ContextMenu.ItemLabel>
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item
|
||||
onSelect={metrics.click("open_session_locally", "unassigned_session_menu", () =>
|
||||
openLocally(s.id),
|
||||
)}
|
||||
>
|
||||
<Icon name="folder" size="small" />
|
||||
<ContextMenu.ItemLabel>{t("agentManager.session.openLocally")}</ContextMenu.ItemLabel>
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
<UnassignedSessionsSection
|
||||
sessions={unassignedSessions}
|
||||
loaded={sessionsLoaded}
|
||||
collapsed={sessionsCollapsed}
|
||||
active={() => (selection() === null ? session.currentSessionID() : undefined)}
|
||||
onToggle={toggleSessions}
|
||||
onSelect={selectUnassigned}
|
||||
onPromote={promoteSession}
|
||||
onOpen={openUnassigned}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="am-detail">
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import { For, Show, type Accessor, type Component } from "solid-js"
|
||||
import { ContextMenu } from "@kilocode/kilo-ui/context-menu"
|
||||
import { Icon } from "@kilocode/kilo-ui/icon"
|
||||
import { IconButton } from "@kilocode/kilo-ui/icon-button"
|
||||
import { Tooltip } from "@kilocode/kilo-ui/tooltip"
|
||||
import type { SessionInfo } from "../src/types/messages"
|
||||
import { useLanguage } from "../src/context/language"
|
||||
import { formatRelativeDate } from "../src/utils/date"
|
||||
|
||||
interface Props {
|
||||
sessions: Accessor<SessionInfo[]>
|
||||
loaded: Accessor<boolean>
|
||||
collapsed: Accessor<boolean>
|
||||
active: Accessor<string | undefined>
|
||||
onToggle: () => void
|
||||
onSelect: (id: string) => void
|
||||
onPromote: (id: string) => void
|
||||
onOpen: (id: string) => void
|
||||
}
|
||||
|
||||
export const UnassignedSessionsSection: Component<Props> = (props) => {
|
||||
const { t } = useLanguage()
|
||||
|
||||
const promote = (id: string, event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
props.onPromote(id)
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={`am-section ${props.collapsed() ? "" : "am-section-grow"}`}>
|
||||
<button class="am-section-header am-section-toggle" onClick={props.onToggle}>
|
||||
<span class="am-section-label">
|
||||
<Icon name={props.collapsed() ? "chevron-right" : "chevron-down"} size="small" class="am-section-chevron" />
|
||||
{t("agentManager.section.sessions")}
|
||||
</span>
|
||||
</button>
|
||||
<Show when={!props.collapsed()}>
|
||||
<div class="am-list">
|
||||
<Show
|
||||
when={props.loaded()}
|
||||
fallback={
|
||||
<div class="am-skeleton-list">
|
||||
<div class="am-skeleton-session">
|
||||
<div class="am-skeleton-session-title" style={{ width: "70%" }} />
|
||||
<div class="am-skeleton-session-time" />
|
||||
</div>
|
||||
<div class="am-skeleton-session">
|
||||
<div class="am-skeleton-session-title" style={{ width: "55%" }} />
|
||||
<div class="am-skeleton-session-time" />
|
||||
</div>
|
||||
<div class="am-skeleton-session">
|
||||
<div class="am-skeleton-session-title" style={{ width: "65%" }} />
|
||||
<div class="am-skeleton-session-time" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<For each={props.sessions()}>
|
||||
{(session) => (
|
||||
<ContextMenu>
|
||||
<ContextMenu.Trigger as="div" style={{ display: "contents" }}>
|
||||
<button
|
||||
class={`am-item ${session.id === props.active() ? "am-item-active" : ""}`}
|
||||
data-sidebar-id={session.id}
|
||||
onClick={() => props.onSelect(session.id)}
|
||||
>
|
||||
<span class="am-item-title" dir="auto">
|
||||
{session.title || t("agentManager.session.untitled")}
|
||||
</span>
|
||||
<span class="am-item-time">{formatRelativeDate(session.updatedAt)}</span>
|
||||
<div class="am-item-promote">
|
||||
<Tooltip value={t("agentManager.session.openInWorktree")} placement="right">
|
||||
<IconButton
|
||||
icon="branch"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
label={t("agentManager.session.openInWorktree")}
|
||||
onClick={(event: MouseEvent) => promote(session.id, event)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</button>
|
||||
</ContextMenu.Trigger>
|
||||
<ContextMenu.Portal>
|
||||
<ContextMenu.Content class="am-ctx-menu">
|
||||
<ContextMenu.Item onSelect={() => props.onPromote(session.id)}>
|
||||
<Icon name="branch" size="small" />
|
||||
<ContextMenu.ItemLabel>{t("agentManager.session.openInWorktree")}</ContextMenu.ItemLabel>
|
||||
</ContextMenu.Item>
|
||||
<ContextMenu.Item onSelect={() => props.onOpen(session.id)}>
|
||||
<Icon name="folder" size="small" />
|
||||
<ContextMenu.ItemLabel>{t("agentManager.session.openLocally")}</ContextMenu.ItemLabel>
|
||||
</ContextMenu.Item>
|
||||
</ContextMenu.Content>
|
||||
</ContextMenu.Portal>
|
||||
</ContextMenu>
|
||||
)}
|
||||
</For>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -22,7 +22,7 @@ interface WorktreeSectionActionsProps {
|
||||
onRef: (ref: SidebarSearchMenuRef) => void
|
||||
onSelect: (item: SidebarSearchItem) => void
|
||||
onCreate: () => void
|
||||
onAdvanced: () => void
|
||||
onNew: () => void
|
||||
onSection: () => void
|
||||
onShortcuts: () => void
|
||||
onSetup: () => void
|
||||
@@ -48,14 +48,19 @@ export const WorktreeSectionActions: Component<WorktreeSectionActionsProps> = (p
|
||||
/>
|
||||
<Show when={props.git}>
|
||||
<div class="am-split-button">
|
||||
<IconButton
|
||||
icon="plus"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
label={props.t("agentManager.worktree.new")}
|
||||
onClick={props.onCreate}
|
||||
disabled={!props.loaded}
|
||||
/>
|
||||
<TooltipKeybind
|
||||
title={props.t("agentManager.shortcuts.advancedWorktree")}
|
||||
keybind={props.bindings.newWorktree ?? ""}
|
||||
>
|
||||
<IconButton
|
||||
icon="plus"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
label={props.t("agentManager.worktree.new")}
|
||||
onClick={props.onNew}
|
||||
disabled={!props.loaded}
|
||||
/>
|
||||
</TooltipKeybind>
|
||||
<DropdownMenu gutter={4} placement="bottom-end">
|
||||
<DropdownMenu.Trigger
|
||||
class="am-split-arrow"
|
||||
@@ -76,16 +81,7 @@ export const WorktreeSectionActions: Component<WorktreeSectionActionsProps> = (p
|
||||
</span>
|
||||
</DropdownMenu.ItemLabel>
|
||||
<span class="am-menu-shortcut">
|
||||
{parseBindingTokens(props.bindings.newWorktree ?? "").map((token) => (
|
||||
<kbd class="am-menu-key">{token}</kbd>
|
||||
))}
|
||||
</span>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onSelect={props.onAdvanced}>
|
||||
<Icon name="settings-gear" size="small" />
|
||||
<DropdownMenu.ItemLabel>{props.t("agentManager.dialog.configureWorktree")}</DropdownMenu.ItemLabel>
|
||||
<span class="am-menu-shortcut">
|
||||
{parseBindingTokens(props.bindings.advancedWorktree ?? "").map((token) => (
|
||||
{parseBindingTokens(props.bindings.quickWorktree ?? "").map((token) => (
|
||||
<kbd class="am-menu-key">{token}</kbd>
|
||||
))}
|
||||
</span>
|
||||
|
||||
+1
-1
@@ -78,9 +78,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "العنصر السابق",
|
||||
"agentManager.shortcuts.nextItem": "العنصر التالي",
|
||||
"agentManager.shortcuts.newWorktree": "Worktree جديد",
|
||||
"agentManager.shortcuts.advancedWorktree": "تكوين worktree جديد",
|
||||
"agentManager.shortcuts.openWorktree": "فتح Worktree",
|
||||
"agentManager.shortcuts.openPR": "فتح طلب السحب",
|
||||
"agentManager.shortcuts.advancedWorktree": "تكوين worktree جديد",
|
||||
"agentManager.shortcuts.deleteWorktree": "حذف Worktree",
|
||||
"agentManager.shortcuts.previousTab": "علامة التبويب السابقة",
|
||||
"agentManager.shortcuts.nextTab": "علامة التبويب التالية",
|
||||
|
||||
+1
-1
@@ -80,9 +80,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Item anterior",
|
||||
"agentManager.shortcuts.nextItem": "Próximo item",
|
||||
"agentManager.shortcuts.newWorktree": "Novo Worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Configurar novo worktree",
|
||||
"agentManager.shortcuts.openWorktree": "Abrir Worktree",
|
||||
"agentManager.shortcuts.openPR": "Abrir PR",
|
||||
"agentManager.shortcuts.advancedWorktree": "Configurar novo worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "Excluir Worktree",
|
||||
"agentManager.shortcuts.previousTab": "Aba anterior",
|
||||
"agentManager.shortcuts.nextTab": "Próxima aba",
|
||||
|
||||
+1
-1
@@ -80,9 +80,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Prethodni element",
|
||||
"agentManager.shortcuts.nextItem": "Sljedeći element",
|
||||
"agentManager.shortcuts.newWorktree": "Novi Worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Konfiguriši novi worktree",
|
||||
"agentManager.shortcuts.openWorktree": "Otvori worktree",
|
||||
"agentManager.shortcuts.openPR": "Otvori PR",
|
||||
"agentManager.shortcuts.advancedWorktree": "Konfiguriši novi worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "Obriši Worktree",
|
||||
"agentManager.shortcuts.previousTab": "Prethodna kartica",
|
||||
"agentManager.shortcuts.nextTab": "Sljedeća kartica",
|
||||
|
||||
+1
-1
@@ -81,9 +81,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Forrige element",
|
||||
"agentManager.shortcuts.nextItem": "Næste element",
|
||||
"agentManager.shortcuts.newWorktree": "Nyt Worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Konfigurer nyt worktree",
|
||||
"agentManager.shortcuts.openWorktree": "Åbn Worktree",
|
||||
"agentManager.shortcuts.openPR": "Åbn PR",
|
||||
"agentManager.shortcuts.advancedWorktree": "Konfigurer nyt worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "Slet Worktree",
|
||||
"agentManager.shortcuts.previousTab": "Forrige fane",
|
||||
"agentManager.shortcuts.nextTab": "Næste fane",
|
||||
|
||||
@@ -81,9 +81,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Vorheriges Element",
|
||||
"agentManager.shortcuts.nextItem": "Nächstes Element",
|
||||
"agentManager.shortcuts.newWorktree": "Neuer Worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Neuen Worktree konfigurieren",
|
||||
"agentManager.shortcuts.openWorktree": "Worktree öffnen",
|
||||
"agentManager.shortcuts.openPR": "Pull Request öffnen",
|
||||
"agentManager.shortcuts.advancedWorktree": "Neuen Worktree konfigurieren",
|
||||
"agentManager.shortcuts.deleteWorktree": "Worktree löschen",
|
||||
"agentManager.shortcuts.previousTab": "Vorheriger Tab",
|
||||
"agentManager.shortcuts.nextTab": "Nächster Tab",
|
||||
|
||||
@@ -84,9 +84,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Previous item",
|
||||
"agentManager.shortcuts.nextItem": "Next item",
|
||||
"agentManager.shortcuts.newWorktree": "New worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Configure new worktree",
|
||||
"agentManager.shortcuts.openWorktree": "Open worktree",
|
||||
"agentManager.shortcuts.openPR": "Open pull request",
|
||||
"agentManager.shortcuts.advancedWorktree": "Configure new worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "Delete worktree",
|
||||
"agentManager.shortcuts.previousTab": "Previous tab",
|
||||
"agentManager.shortcuts.nextTab": "Next tab",
|
||||
|
||||
+1
-1
@@ -80,9 +80,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Elemento anterior",
|
||||
"agentManager.shortcuts.nextItem": "Siguiente elemento",
|
||||
"agentManager.shortcuts.newWorktree": "Nuevo Worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Configurar nuevo worktree",
|
||||
"agentManager.shortcuts.openWorktree": "Abrir Worktree",
|
||||
"agentManager.shortcuts.openPR": "Abrir pull request",
|
||||
"agentManager.shortcuts.advancedWorktree": "Configurar nuevo worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "Eliminar Worktree",
|
||||
"agentManager.shortcuts.previousTab": "Pestaña anterior",
|
||||
"agentManager.shortcuts.nextTab": "Siguiente pestaña",
|
||||
|
||||
+1
-1
@@ -80,9 +80,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Élément précédent",
|
||||
"agentManager.shortcuts.nextItem": "Élément suivant",
|
||||
"agentManager.shortcuts.newWorktree": "Nouveau Worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Configurer un nouveau worktree",
|
||||
"agentManager.shortcuts.openWorktree": "Ouvrir le worktree",
|
||||
"agentManager.shortcuts.openPR": "Ouvrir la pull request",
|
||||
"agentManager.shortcuts.advancedWorktree": "Configurer un nouveau worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "Supprimer le Worktree",
|
||||
"agentManager.shortcuts.previousTab": "Onglet précédent",
|
||||
"agentManager.shortcuts.nextTab": "Onglet suivant",
|
||||
|
||||
+1
-1
@@ -85,9 +85,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Elemento precedente",
|
||||
"agentManager.shortcuts.nextItem": "Elemento successivo",
|
||||
"agentManager.shortcuts.newWorktree": "Nuovo worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Configura nuovo worktree",
|
||||
"agentManager.shortcuts.openWorktree": "Apri worktree",
|
||||
"agentManager.shortcuts.openPR": "Apri pull request",
|
||||
"agentManager.shortcuts.advancedWorktree": "Configura nuovo worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "Elimina worktree",
|
||||
"agentManager.shortcuts.previousTab": "Scheda precedente",
|
||||
"agentManager.shortcuts.nextTab": "Scheda successiva",
|
||||
|
||||
+1
-1
@@ -80,9 +80,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "前の項目",
|
||||
"agentManager.shortcuts.nextItem": "次の項目",
|
||||
"agentManager.shortcuts.newWorktree": "新しいWorktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "新規 worktree の構成",
|
||||
"agentManager.shortcuts.openWorktree": "Worktreeを開く",
|
||||
"agentManager.shortcuts.openPR": "PRを開く",
|
||||
"agentManager.shortcuts.advancedWorktree": "新規 worktree の構成",
|
||||
"agentManager.shortcuts.deleteWorktree": "Worktreeを削除",
|
||||
"agentManager.shortcuts.previousTab": "前のタブ",
|
||||
"agentManager.shortcuts.nextTab": "次のタブ",
|
||||
|
||||
+1
-1
@@ -79,9 +79,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "이전 항목",
|
||||
"agentManager.shortcuts.nextItem": "다음 항목",
|
||||
"agentManager.shortcuts.newWorktree": "새 Worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "새 worktree 구성",
|
||||
"agentManager.shortcuts.openWorktree": "Worktree 열기",
|
||||
"agentManager.shortcuts.openPR": "PR 열기",
|
||||
"agentManager.shortcuts.advancedWorktree": "새 worktree 구성",
|
||||
"agentManager.shortcuts.deleteWorktree": "Worktree 삭제",
|
||||
"agentManager.shortcuts.previousTab": "이전 탭",
|
||||
"agentManager.shortcuts.nextTab": "다음 탭",
|
||||
|
||||
+1
-1
@@ -84,9 +84,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Vorig item",
|
||||
"agentManager.shortcuts.nextItem": "Volgend item",
|
||||
"agentManager.shortcuts.newWorktree": "Nieuwe worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Nieuwe worktree configureren",
|
||||
"agentManager.shortcuts.openWorktree": "Worktree openen",
|
||||
"agentManager.shortcuts.openPR": "Pull request openen",
|
||||
"agentManager.shortcuts.advancedWorktree": "Nieuwe worktree configureren",
|
||||
"agentManager.shortcuts.deleteWorktree": "Worktree verwijderen",
|
||||
"agentManager.shortcuts.previousTab": "Vorig tabblad",
|
||||
"agentManager.shortcuts.nextTab": "Volgend tabblad",
|
||||
|
||||
+1
-1
@@ -79,9 +79,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Forrige element",
|
||||
"agentManager.shortcuts.nextItem": "Neste element",
|
||||
"agentManager.shortcuts.newWorktree": "Nytt Worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Konfigurer nytt worktree",
|
||||
"agentManager.shortcuts.openWorktree": "Åpne Worktree",
|
||||
"agentManager.shortcuts.openPR": "Åpne pull request",
|
||||
"agentManager.shortcuts.advancedWorktree": "Konfigurer nytt worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "Slett Worktree",
|
||||
"agentManager.shortcuts.previousTab": "Forrige fane",
|
||||
"agentManager.shortcuts.nextTab": "Neste fane",
|
||||
|
||||
+1
-1
@@ -80,9 +80,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Poprzedni element",
|
||||
"agentManager.shortcuts.nextItem": "Następny element",
|
||||
"agentManager.shortcuts.newWorktree": "Nowy Worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Skonfiguruj nowe worktree",
|
||||
"agentManager.shortcuts.openWorktree": "Otwórz Worktree",
|
||||
"agentManager.shortcuts.openPR": "Otwórz pull request",
|
||||
"agentManager.shortcuts.advancedWorktree": "Skonfiguruj nowe worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "Usuń Worktree",
|
||||
"agentManager.shortcuts.previousTab": "Poprzednia karta",
|
||||
"agentManager.shortcuts.nextTab": "Następna karta",
|
||||
|
||||
+1
-1
@@ -80,9 +80,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Предыдущий элемент",
|
||||
"agentManager.shortcuts.nextItem": "Следующий элемент",
|
||||
"agentManager.shortcuts.newWorktree": "Новый Worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Настроить новое worktree",
|
||||
"agentManager.shortcuts.openWorktree": "Открыть Worktree",
|
||||
"agentManager.shortcuts.openPR": "Открыть PR",
|
||||
"agentManager.shortcuts.advancedWorktree": "Настроить новое worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "Удалить Worktree",
|
||||
"agentManager.shortcuts.previousTab": "Предыдущая вкладка",
|
||||
"agentManager.shortcuts.nextTab": "Следующая вкладка",
|
||||
|
||||
+1
-1
@@ -76,9 +76,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "รายการก่อนหน้า",
|
||||
"agentManager.shortcuts.nextItem": "รายการถัดไป",
|
||||
"agentManager.shortcuts.newWorktree": "Worktree ใหม่",
|
||||
"agentManager.shortcuts.advancedWorktree": "กำหนดค่า worktree ใหม่",
|
||||
"agentManager.shortcuts.openWorktree": "เปิด Worktree",
|
||||
"agentManager.shortcuts.openPR": "เปิด Pull Request",
|
||||
"agentManager.shortcuts.advancedWorktree": "กำหนดค่า worktree ใหม่",
|
||||
"agentManager.shortcuts.deleteWorktree": "ลบ Worktree",
|
||||
"agentManager.shortcuts.previousTab": "แท็บก่อนหน้า",
|
||||
"agentManager.shortcuts.nextTab": "แท็บถัดไป",
|
||||
|
||||
+1
-1
@@ -85,9 +85,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Önceki öğe",
|
||||
"agentManager.shortcuts.nextItem": "Sonraki öğe",
|
||||
"agentManager.shortcuts.newWorktree": "Yeni worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "Yeni worktree yapılandır",
|
||||
"agentManager.shortcuts.openWorktree": "Worktree aç",
|
||||
"agentManager.shortcuts.openPR": "Pull request'i aç",
|
||||
"agentManager.shortcuts.advancedWorktree": "Yeni worktree yapılandır",
|
||||
"agentManager.shortcuts.deleteWorktree": "Worktree'yi sil",
|
||||
"agentManager.shortcuts.previousTab": "Önceki sekme",
|
||||
"agentManager.shortcuts.nextTab": "Sonraki sekme",
|
||||
|
||||
+1
-1
@@ -86,9 +86,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "Попередній елемент",
|
||||
"agentManager.shortcuts.nextItem": "Наступний елемент",
|
||||
"agentManager.shortcuts.newWorktree": "Нове робоче дерево",
|
||||
"agentManager.shortcuts.advancedWorktree": "Налаштувати нове worktree",
|
||||
"agentManager.shortcuts.openWorktree": "Відкрити робоче дерево",
|
||||
"agentManager.shortcuts.openPR": "Відкрити PR",
|
||||
"agentManager.shortcuts.advancedWorktree": "Налаштувати нове worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "Видалити робоче дерево",
|
||||
"agentManager.shortcuts.previousTab": "Попередня вкладка",
|
||||
"agentManager.shortcuts.nextTab": "Наступна вкладка",
|
||||
|
||||
+1
-1
@@ -76,9 +76,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "上一项",
|
||||
"agentManager.shortcuts.nextItem": "下一项",
|
||||
"agentManager.shortcuts.newWorktree": "新建 Worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "配置新 worktree",
|
||||
"agentManager.shortcuts.openWorktree": "打开 Worktree",
|
||||
"agentManager.shortcuts.openPR": "打开 PR",
|
||||
"agentManager.shortcuts.advancedWorktree": "配置新 worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "删除 Worktree",
|
||||
"agentManager.shortcuts.previousTab": "上一个标签页",
|
||||
"agentManager.shortcuts.nextTab": "下一个标签页",
|
||||
|
||||
+1
-1
@@ -76,9 +76,9 @@ export const dict = {
|
||||
"agentManager.shortcuts.previousItem": "上一個項目",
|
||||
"agentManager.shortcuts.nextItem": "下一個項目",
|
||||
"agentManager.shortcuts.newWorktree": "新建 Worktree",
|
||||
"agentManager.shortcuts.advancedWorktree": "配置新 worktree",
|
||||
"agentManager.shortcuts.openWorktree": "開啟 Worktree",
|
||||
"agentManager.shortcuts.openPR": "開啟 PR",
|
||||
"agentManager.shortcuts.advancedWorktree": "配置新 worktree",
|
||||
"agentManager.shortcuts.deleteWorktree": "刪除 Worktree",
|
||||
"agentManager.shortcuts.previousTab": "上一個分頁",
|
||||
"agentManager.shortcuts.nextTab": "下一個分頁",
|
||||
|
||||
@@ -32,8 +32,8 @@ export function buildShortcutCategories(
|
||||
shortcuts: [
|
||||
{ label: t("agentManager.shortcuts.previousItem"), binding: bindings.previousSession ?? "" },
|
||||
{ label: t("agentManager.shortcuts.nextItem"), binding: bindings.nextSession ?? "" },
|
||||
{ label: t("agentManager.shortcuts.newWorktree"), binding: bindings.newWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.advancedWorktree"), binding: bindings.advancedWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.advancedWorktree"), binding: bindings.newWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.newWorktree"), binding: bindings.quickWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.deleteWorktree"), binding: bindings.closeWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.openWorktree"), binding: bindings.openWorktree ?? "" },
|
||||
{ label: t("agentManager.shortcuts.openPR"), binding: bindings.openPR ?? "" },
|
||||
|
||||
@@ -33,14 +33,7 @@ import type { Part as TimelinePart } from "../../types/messages"
|
||||
import type { TimelineHighlight } from "../../utils/timeline/highlight"
|
||||
import { QuestionDock } from "./QuestionDock"
|
||||
import { SuggestBar } from "./SuggestBar"
|
||||
|
||||
const EDIT_TOOLS = new Set(["edit", "write", "apply_patch"])
|
||||
|
||||
function editOpen(part: SDKPart, open: boolean) {
|
||||
if (part.type !== "tool") return undefined
|
||||
const tool = (part as unknown as ToolPart).tool
|
||||
return EDIT_TOOLS.has(tool) ? open : undefined
|
||||
}
|
||||
import { toolDefaultOpen } from "./tool-default-open"
|
||||
|
||||
/** Extract plan path from a completed plan_exit tool part. */
|
||||
function planExitInfo(part: SDKPart): { plan: string } | undefined {
|
||||
@@ -296,7 +289,7 @@ export const AssistantMessage: Component<AssistantMessageProps> = (props) => {
|
||||
part={part}
|
||||
message={props.message as SDKMessage}
|
||||
showAssistantCopyPartID={props.showAssistantCopyPartID}
|
||||
defaultOpen={editOpen(part, edit())}
|
||||
defaultOpen={toolDefaultOpen(part, open(), edit())}
|
||||
forceOpen={forceOpen()}
|
||||
forceOpenFile={forceOpen() ? props.forceOpenFile : undefined}
|
||||
reasoningAutoCollapse={display.reasoningAutoCollapse()}
|
||||
|
||||
@@ -26,6 +26,7 @@ import { isEnterKeyCommitNotIme } from "../../utils/ime-enter"
|
||||
export const QuestionDock: Component<{ request: QuestionRequest }> = (props) => {
|
||||
const session = useSession()
|
||||
const language = useLanguage()
|
||||
const id = props.request.id
|
||||
|
||||
const questions = createMemo(() => props.request.questions)
|
||||
const single = createMemo(() => questions().length === 1 && questions()[0]?.multiple !== true)
|
||||
@@ -57,8 +58,8 @@ export const QuestionDock: Component<{ request: QuestionRequest }> = (props) =>
|
||||
// Chat search indexes only the mounted page's options, and there's no
|
||||
// other signal exposing which page that is — publish it here so search
|
||||
// stays in sync as the user navigates instead of always assuming page 0.
|
||||
createEffect(() => setActiveQuestionTab(props.request.id, store.tab))
|
||||
onCleanup(() => clearActiveQuestionTab(props.request.id))
|
||||
createEffect(() => setActiveQuestionTab(id, store.tab))
|
||||
onCleanup(() => clearActiveQuestionTab(id))
|
||||
|
||||
const question = createMemo(() => questions()[store.tab])
|
||||
const confirm = createMemo(() => !single() && store.tab === questions().length)
|
||||
|
||||
@@ -122,13 +122,6 @@ export const TaskHeader: Component<TaskHeaderProps> = (props) => {
|
||||
: []),
|
||||
]
|
||||
})
|
||||
const activitySummary = createMemo(() => {
|
||||
if (memory.error()) return memory.error()!
|
||||
if (!memory.enabled()) return language.t("chat.memory.project.disabled")
|
||||
const lines = activityLines()
|
||||
if (lines.length === 0) return language.t("chat.memory.activity.idle")
|
||||
return [language.t("chat.memory.status.active"), ...lines].join(" · ")
|
||||
})
|
||||
const activityItems = createMemo(() =>
|
||||
activity()
|
||||
.flatMap((item) => {
|
||||
@@ -157,6 +150,8 @@ export const TaskHeader: Component<TaskHeaderProps> = (props) => {
|
||||
)
|
||||
const activityTooltip = () => (
|
||||
<>
|
||||
<div data-slot="task-header-context-tooltip-title">{language.t("settings.context.title")}</div>
|
||||
<div data-slot="task-header-context-tooltip-status">{memoryStatus()}</div>
|
||||
{activitySummaryView()}
|
||||
<Show when={memoryVerbose() && activityItems().length > 0}>
|
||||
<div data-slot="task-header-memory-activity-list">
|
||||
@@ -292,7 +287,9 @@ export const TaskHeader: Component<TaskHeaderProps> = (props) => {
|
||||
startRename()
|
||||
}}
|
||||
>
|
||||
<span data-slot="task-header-title-label">{title()}</span>
|
||||
<span data-slot="task-header-title-label" dir="auto">
|
||||
{title()}
|
||||
</span>
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
@@ -318,107 +315,117 @@ export const TaskHeader: Component<TaskHeaderProps> = (props) => {
|
||||
<DeferredPopover
|
||||
placement="bottom-end"
|
||||
portal={false}
|
||||
class="task-header-memory-popover"
|
||||
class="task-header-context-popover"
|
||||
triggerAs="button"
|
||||
triggerProps={{
|
||||
type: "button",
|
||||
get class() {
|
||||
return `task-header-memory-trigger${memory.enabled() ? " task-header-memory-trigger--enabled" : ""}`
|
||||
},
|
||||
class: "task-header-context-trigger",
|
||||
get ["aria-label"]() {
|
||||
return activitySummary()
|
||||
return language.t("settings.context.title")
|
||||
},
|
||||
}}
|
||||
trigger={
|
||||
<>
|
||||
<Icon name="memory" size="small" />
|
||||
<Icon name="server" size="small" />
|
||||
<Show when={memoryActive()}>
|
||||
<span data-slot="task-header-memory-dot" />
|
||||
</Show>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div data-slot="task-header-memory-menu">
|
||||
<div data-slot="task-header-memory-status">{memoryStatus()}</div>
|
||||
{activitySummaryView()}
|
||||
<Show
|
||||
when={memory.enabled()}
|
||||
fallback={
|
||||
<>
|
||||
<div data-slot="task-header-context-menu">
|
||||
<div data-slot="task-header-context-title">{language.t("settings.context.title")}</div>
|
||||
<Show when={!props.readonly}>
|
||||
<section data-slot="task-header-context-section">
|
||||
<div data-slot="task-header-context-section-title">
|
||||
<Icon name="compress" size="small" />
|
||||
<span>{language.t("settings.context.compaction.title")}</span>
|
||||
</div>
|
||||
<div data-slot="task-header-context-actions">
|
||||
<button
|
||||
data-slot="task-header-memory-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.enable()}
|
||||
data-slot="task-header-context-action"
|
||||
disabled={!canCompact()}
|
||||
onClick={() => session.compact()}
|
||||
>
|
||||
{language.t("chat.memory.enable")}
|
||||
{language.t("command.session.compact")}
|
||||
</button>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div data-slot="task-header-memory-actions">
|
||||
<button
|
||||
data-slot="task-header-memory-action"
|
||||
disabled={memory.loading() || memory.pending()}
|
||||
onClick={() => memory.showMemory()}
|
||||
>
|
||||
{language.t("chat.memory.inspect")}
|
||||
</button>
|
||||
<button
|
||||
data-slot="task-header-memory-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.remember()}
|
||||
>
|
||||
{language.t("chat.memory.remember")}
|
||||
</button>
|
||||
<button
|
||||
data-slot="task-header-memory-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.forget()}
|
||||
>
|
||||
{language.t("chat.memory.forget")}
|
||||
</button>
|
||||
<button
|
||||
data-slot="task-header-memory-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.rebuild()}
|
||||
>
|
||||
{language.t("chat.memory.rebuild")}
|
||||
</button>
|
||||
<button
|
||||
data-slot="task-header-memory-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.disable()}
|
||||
>
|
||||
{language.t("chat.memory.disable")}
|
||||
</button>
|
||||
</div>
|
||||
<div data-slot="task-header-memory-verbose">
|
||||
<span>{language.t("chat.memory.verbose")}</span>
|
||||
<Switch
|
||||
checked={memoryVerbose()}
|
||||
disabled={memory.pending()}
|
||||
hideLabel
|
||||
onChange={(next) => memory.verbose(next ? "on" : "off")}
|
||||
>
|
||||
{language.t("chat.memory.verbose")}
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
<section data-slot="task-header-context-section">
|
||||
<div data-slot="task-header-context-section-title">
|
||||
<Icon name="memory" size="small" />
|
||||
<span>{language.t("settings.context.memory.title")}</span>
|
||||
</div>
|
||||
<div data-slot="task-header-memory-status">{memoryStatus()}</div>
|
||||
{activitySummaryView()}
|
||||
<div data-slot="task-header-context-actions">
|
||||
<Show
|
||||
when={memory.enabled()}
|
||||
fallback={
|
||||
<button
|
||||
data-slot="task-header-context-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.enable()}
|
||||
>
|
||||
{language.t("chat.memory.enable")}
|
||||
</button>
|
||||
}
|
||||
>
|
||||
<button
|
||||
data-slot="task-header-context-action"
|
||||
disabled={memory.loading() || memory.pending()}
|
||||
onClick={() => memory.showMemory()}
|
||||
>
|
||||
{language.t("chat.memory.inspect")}
|
||||
</button>
|
||||
<button
|
||||
data-slot="task-header-context-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.remember()}
|
||||
>
|
||||
{language.t("chat.memory.remember")}
|
||||
</button>
|
||||
<button
|
||||
data-slot="task-header-context-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.forget()}
|
||||
>
|
||||
{language.t("chat.memory.forget")}
|
||||
</button>
|
||||
<button
|
||||
data-slot="task-header-context-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.rebuild()}
|
||||
>
|
||||
{language.t("chat.memory.rebuild")}
|
||||
</button>
|
||||
<button
|
||||
data-slot="task-header-context-action"
|
||||
disabled={memory.pending()}
|
||||
onClick={() => memory.disable()}
|
||||
>
|
||||
{language.t("chat.memory.disable")}
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={memory.enabled()}>
|
||||
<div data-slot="task-header-memory-verbose">
|
||||
<span>{language.t("chat.memory.verbose")}</span>
|
||||
<Switch
|
||||
checked={memoryVerbose()}
|
||||
disabled={memory.pending()}
|
||||
hideLabel
|
||||
onChange={(next) => memory.verbose(next ? "on" : "off")}
|
||||
>
|
||||
{language.t("chat.memory.verbose")}
|
||||
</Switch>
|
||||
</div>
|
||||
</Show>
|
||||
</section>
|
||||
</div>
|
||||
</DeferredPopover>
|
||||
</Tooltip>
|
||||
<Show when={!props.readonly}>
|
||||
<Tooltip value={language.t("command.session.compact")} placement="bottom">
|
||||
<IconButton
|
||||
icon="compress"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
disabled={!canCompact()}
|
||||
onClick={() => session.compact()}
|
||||
aria-label={language.t("command.session.compact")}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
<Show when={hasMessages()}>
|
||||
<Tooltip value={language.t("chat.search.toggle")} placement="bottom">
|
||||
<IconButton
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user