mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 11:05:31 +08:00
chore: merge main and preserve worktree deletion guards
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep Agent Manager worktree spinners active while background agents are running.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Place the prompt navigator on the outer sidebar edge and delay hover previews to avoid accidental popups. Keep the navigator on the right in Agent Manager and editor tabs.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Clear a failed turn that produced no output from the conversation when the next message is sent, so an "An error occurred" placeholder no longer lingers in history. A turn that wrote text or ran a tool before failing is kept, since its record explains changes already made.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Update DOMPurify to 3.4.13 to fix XSS vulnerabilities flagged by Dependabot in markdown sanitization.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep chat scroll position during streaming, including small upward gestures, direction changes, and scrolling over message controls.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Prevent intermittent server connection failures during VS Code startup.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Use the Git executable configured in VS Code when creating worktrees on Windows.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/sdk": patch
|
||||
---
|
||||
|
||||
Fix a prototype pollution vulnerability in the generated SDK client request parameters (CVE-2026-48819).
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Stop showing a running badge for a session that was just deleted.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": minor
|
||||
---
|
||||
|
||||
Stop treating a manually stopped session as a failure, and add a Retry action to failed turns. Pressing Stop now shows a short "Stopped" note instead of an error badge and attention dot. A failed turn keeps the error badge and card and can be retried in place, using the model and effort selected at that moment — so switching away from an unavailable provider and pressing Retry continues the conversation. This includes failures that never produced a reply, such as missing provider credentials.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Detect a worktree's pull request reliably in Agent Manager. Imported PRs — including PRs from forks — hand-made worktrees, and locally renamed branches now show their PR badge, the current repository row gets one too, and a freshly imported PR no longer waits out the status poll. Imported PR branches also get proper git tracking, so `git push` and `git pull` work in the new worktree.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-jetbrains": patch
|
||||
---
|
||||
|
||||
Stop the New Worktree dialog from flashing the previous tab's content when switching between New, From PR, and From Branch.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
"@opencode-ai/ui": patch
|
||||
"@kilocode/kilo-docs": patch
|
||||
---
|
||||
|
||||
Update Mermaid to 11.17.2 to resolve Dependabot security advisories.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"@opencode-ai/core": patch
|
||||
"@kilocode/kilo-indexing": patch
|
||||
---
|
||||
|
||||
Fix ReDoS vulnerabilities in glob matching by upgrading minimatch to 10.2.6.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/kilo-docs": patch
|
||||
---
|
||||
|
||||
Update nanoid to 3.3.18 to fix the infinite loop vulnerability flagged by Dependabot.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Reduce Agent Manager background Git and GitHub activity without adding file watchers.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Prevent the CLI from opening to a blank terminal on startup.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Restore the session title and branch subtitle immediately when opening an existing session in a worktree.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@kilocode/cli": minor
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Review all committed and uncommitted Agent Manager worktree changes with `/review worktree`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Recognize sessions discovered in managed Agent Manager worktrees during orchestration actions.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Fix the task tool intermittently returning an empty result. Subagents that ran with memory context had a synthetic marker part appended after their answer, which was picked up as the final text part and surfaced as an empty `<task_result>` to the parent agent. The task tool now ignores synthetic, ignored, and empty text parts, and background jobs no longer let an empty run overwrite an earlier successful result, so resumed tasks keep their real output.
|
||||
@@ -87,6 +87,7 @@ Examples: `fix(tui): simplify thinking toggle styling`, `docs: update contributi
|
||||
|
||||
- Keep things in one function unless composable or reusable
|
||||
- Avoid unnecessary destructuring. Instead of `const { a, b } = obj`, use `obj.a` and `obj.b` to preserve context
|
||||
- Avoid possibly out-of-bounds array access. Instead of `array[index] ?? {}`, use `array.at(index) ?? {}`. Instead of `array[array.length - 1]`, use `array.at(-1)`
|
||||
- Avoid `try`/`catch` where possible
|
||||
- Avoid using the `any` type
|
||||
- Prefer single word variable names where possible
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ The Kilo Community is [on Discord](https://kilo.ai/discord).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Bun 1.4.0+** — required for all packages.
|
||||
- **Bun 1.3.14+** — required for all packages.
|
||||
- **Java 21** — required by the JetBrains plugin. The root `bun turbo typecheck` and `bun turbo test:ci` commands include `@kilocode/kilo-jetbrains` and will fail without Java 21.
|
||||
|
||||
The preferred way to install Java is via [SDKMAN](https://sdkman.io/install):
|
||||
@@ -41,7 +41,7 @@ The Kilo Community is [on Discord](https://kilo.ai/discord).
|
||||
|
||||
## Developing Kilo CLI
|
||||
|
||||
- **Requirements:** Bun 1.4.0+, Java 21 (see [Prerequisites](#prerequisites) above)
|
||||
- **Requirements:** Bun 1.3.14+, Java 21 (see [Prerequisites](#prerequisites) above)
|
||||
- Install dependencies and start the CLI from the repo root:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -21,5 +21,5 @@
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"typescript": "^5.8.2"
|
||||
},
|
||||
"version": "7.5.0"
|
||||
"version": "7.5.5"
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
},
|
||||
"packages/client": {
|
||||
"name": "@opencode-ai/client",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@opencode-ai/protocol": "workspace:*",
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
@@ -56,7 +56,7 @@
|
||||
},
|
||||
"packages/codemode": {
|
||||
"name": "@opencode-ai/codemode",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"acorn": "8.15.0",
|
||||
"effect": "catalog:",
|
||||
@@ -70,7 +70,7 @@
|
||||
},
|
||||
"packages/core": {
|
||||
"name": "@opencode-ai/core",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"bin": {
|
||||
"opencode": "./bin/opencode",
|
||||
},
|
||||
@@ -134,7 +134,7 @@
|
||||
"immer": "11.1.4",
|
||||
"jsonc-parser": "3.3.1",
|
||||
"mime-types": "3.0.2",
|
||||
"minimatch": "10.2.5",
|
||||
"minimatch": "10.2.6",
|
||||
"npm-package-arg": "13.0.2",
|
||||
"rotating-file-stream": "3.2.9",
|
||||
"semver": "^7.6.3",
|
||||
@@ -168,7 +168,7 @@
|
||||
},
|
||||
"packages/effect-drizzle-sqlite": {
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"drizzle-orm": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -182,7 +182,7 @@
|
||||
},
|
||||
"packages/effect-sqlite-node": {
|
||||
"name": "@opencode-ai/effect-sqlite-node",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
},
|
||||
@@ -194,7 +194,7 @@
|
||||
},
|
||||
"packages/http-recorder": {
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@effect/platform-node": "4.0.0-beta.83",
|
||||
"@effect/platform-node-shared": "4.0.0-beta.83",
|
||||
@@ -215,7 +215,7 @@
|
||||
},
|
||||
"packages/httpapi-codegen": {
|
||||
"name": "@opencode-ai/httpapi-codegen",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
"prettier": "3.6.2",
|
||||
@@ -228,7 +228,7 @@
|
||||
},
|
||||
"packages/kilo-console": {
|
||||
"name": "@kilocode/kilo-console",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@kilocode/kilo-indexing": "workspace:*",
|
||||
"@kilocode/kilo-web-ui": "workspace:*",
|
||||
@@ -251,7 +251,7 @@
|
||||
},
|
||||
"packages/kilo-docs": {
|
||||
"name": "@kilocode/kilo-docs",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@docsearch/css": "^4",
|
||||
"@docsearch/js": "^4",
|
||||
@@ -281,7 +281,7 @@
|
||||
},
|
||||
"packages/kilo-gateway": {
|
||||
"name": "@kilocode/kilo-gateway",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "3.0.82",
|
||||
"@ai-sdk/openai": "3.0.88",
|
||||
@@ -315,7 +315,7 @@
|
||||
},
|
||||
"packages/kilo-i18n": {
|
||||
"name": "@kilocode/kilo-i18n",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
@@ -325,7 +325,7 @@
|
||||
},
|
||||
"packages/kilo-indexing": {
|
||||
"name": "@kilocode/kilo-indexing",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-bedrock-runtime": "3.1005.0",
|
||||
"@aws-sdk/credential-provider-ini": "3.972.31",
|
||||
@@ -340,7 +340,7 @@
|
||||
"hono": "catalog:",
|
||||
"hono-openapi": "catalog:",
|
||||
"ignore": "7.0.5",
|
||||
"minimatch": "10.2.5",
|
||||
"minimatch": "10.2.6",
|
||||
"openai": "6.27.0",
|
||||
"p-limit": "7.3.0",
|
||||
"tree-sitter-wasms": "0.1.13",
|
||||
@@ -361,7 +361,7 @@
|
||||
},
|
||||
"packages/kilo-memory": {
|
||||
"name": "@kilocode/kilo-memory",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
"zod": "catalog:",
|
||||
@@ -375,7 +375,7 @@
|
||||
},
|
||||
"packages/kilo-sandbox": {
|
||||
"name": "@kilocode/sandbox",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sandbox-runtime": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -390,7 +390,7 @@
|
||||
},
|
||||
"packages/kilo-telemetry": {
|
||||
"name": "@kilocode/kilo-telemetry",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
"posthog-node": "4.4.0",
|
||||
@@ -404,7 +404,7 @@
|
||||
},
|
||||
"packages/kilo-ui": {
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@kobalte/core": "0.13.11",
|
||||
@@ -442,7 +442,7 @@
|
||||
},
|
||||
"packages/kilo-vscode": {
|
||||
"name": "kilo-code",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
@@ -515,7 +515,7 @@
|
||||
},
|
||||
"packages/kilo-web-ui": {
|
||||
"name": "@kilocode/kilo-web-ui",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@kilocode/kilo-ui": "workspace:*",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -532,7 +532,7 @@
|
||||
},
|
||||
"packages/llm": {
|
||||
"name": "@opencode-ai/llm",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"@smithy/eventstream-codec": "4.2.14",
|
||||
@@ -551,7 +551,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "@kilocode/cli",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"bin": {
|
||||
"kilo": "./bin/kilo",
|
||||
"kilocode": "./bin/kilo",
|
||||
@@ -653,7 +653,7 @@
|
||||
"jsonc-parser": "3.3.1",
|
||||
"mammoth": "1.12.0",
|
||||
"mime-types": "3.0.2",
|
||||
"minimatch": "10.0.3",
|
||||
"minimatch": "10.2.6",
|
||||
"npm-package-arg": "13.0.2",
|
||||
"open": "10.1.2",
|
||||
"opencode-gitlab-auth": "2.1.0",
|
||||
@@ -719,7 +719,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@kilocode/plugin",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@ai-sdk/provider": "3.0.8",
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
@@ -748,7 +748,7 @@
|
||||
},
|
||||
"packages/plugin-atomic-chat": {
|
||||
"name": "@kilocode/plugin-atomic-chat",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@kilocode/plugin": "workspace:*",
|
||||
},
|
||||
@@ -762,7 +762,7 @@
|
||||
},
|
||||
"packages/protocol": {
|
||||
"name": "@opencode-ai/protocol",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@opencode-ai/schema": "workspace:*",
|
||||
"effect": "catalog:",
|
||||
@@ -775,7 +775,7 @@
|
||||
},
|
||||
"packages/schema": {
|
||||
"name": "@opencode-ai/schema",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"effect": "catalog:",
|
||||
},
|
||||
@@ -787,7 +787,7 @@
|
||||
},
|
||||
"packages/script": {
|
||||
"name": "@opencode-ai/script",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"semver": "^7.6.3",
|
||||
},
|
||||
@@ -798,7 +798,7 @@
|
||||
},
|
||||
"packages/sdk-next": {
|
||||
"name": "@opencode-ai/sdk-next",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@opencode-ai/client": "workspace:*",
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
@@ -813,12 +813,12 @@
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@kilocode/sdk",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"cross-spawn": "catalog:",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.90.10",
|
||||
"@hey-api/openapi-ts": "0.97.3",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@types/cross-spawn": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
@@ -828,7 +828,7 @@
|
||||
},
|
||||
"packages/server": {
|
||||
"name": "@opencode-ai/server",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@opencode-ai/core": "workspace:*",
|
||||
"@opencode-ai/protocol": "workspace:*",
|
||||
@@ -843,7 +843,7 @@
|
||||
},
|
||||
"packages/session-ui": {
|
||||
"name": "@opencode-ai/session-ui",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -888,7 +888,7 @@
|
||||
},
|
||||
"packages/storybook": {
|
||||
"name": "@opencode-ai/storybook",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"devDependencies": {
|
||||
"@opencode-ai/session-ui": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -913,7 +913,7 @@
|
||||
},
|
||||
"packages/tui": {
|
||||
"name": "@opencode-ai/tui",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@kilocode/plugin": "workspace:*",
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
@@ -939,7 +939,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -1069,7 +1069,7 @@
|
||||
"@tanstack/solid-virtual": "3.13.32",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@types/bun": "1.4.0",
|
||||
"@types/bun": "1.3.14",
|
||||
"@types/cross-spawn": "6.0.6",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "24.12.4",
|
||||
@@ -1572,13 +1572,17 @@
|
||||
|
||||
"@graphql-typed-document-node/core": ["@graphql-typed-document-node/core@3.2.0", "", { "peerDependencies": { "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" } }, "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ=="],
|
||||
|
||||
"@hey-api/codegen-core": ["@hey-api/codegen-core@0.5.5", "", { "dependencies": { "@hey-api/types": "0.1.2", "ansi-colors": "4.1.3", "c12": "3.3.3", "color-support": "1.1.3" }, "peerDependencies": { "typescript": ">=5.5.3" } }, "sha512-f2ZHucnA2wBGAY8ipB4wn/mrEYW+WUxU2huJmUvfDO6AE2vfILSHeF3wCO39Pz4wUYPoAWZByaauftLrOfC12Q=="],
|
||||
"@hey-api/codegen-core": ["@hey-api/codegen-core@0.8.2", "", { "dependencies": { "@hey-api/types": "0.1.4", "ansi-colors": "4.1.3", "c12": "3.3.4", "color-support": "1.1.3" } }, "sha512-R2NMf3wq97rh1mjz33WJQU8svz3F0RYUjvx/QzXucjpSqQ3O5huTdDjErG4fMxSr1X+X56NuDrqtGfHmo1TRUQ=="],
|
||||
|
||||
"@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.2.2", "", { "dependencies": { "@jsdevtools/ono": "^7.1.3", "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.1", "lodash": "^4.17.21" } }, "sha512-oS+5yAdwnK20lSeFO1d53Ku+yaGCsY8PcrmSq2GtSs3bsBfRnHAbpPKSVzQcaxAOrzj5NB+f34WhZglVrNayBA=="],
|
||||
"@hey-api/json-schema-ref-parser": ["@hey-api/json-schema-ref-parser@1.4.2", "", { "dependencies": { "@jsdevtools/ono": "7.1.3", "@types/json-schema": "7.0.15", "js-yaml": "4.1.1" } }, "sha512-ZhCFSKI2ipZHEbgmtUHdyddvRU3wJ4elgCfYUC7T7hZa4EivSrVflTQf2w+v3TuaYxR1Y2V2kq3otqTttrrK8Q=="],
|
||||
|
||||
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.90.10", "", { "dependencies": { "@hey-api/codegen-core": "^0.5.5", "@hey-api/json-schema-ref-parser": "1.2.2", "@hey-api/types": "0.1.2", "ansi-colors": "4.1.3", "color-support": "1.1.3", "commander": "14.0.2", "open": "11.0.0", "semver": "7.7.3" }, "peerDependencies": { "typescript": ">=5.5.3" }, "bin": { "openapi-ts": "bin/run.js" } }, "sha512-o0wlFxuLt1bcyIV/ZH8DQ1wrgODTnUYj/VfCHOOYgXUQlLp9Dm2PjihOz+WYrZLowhqUhSKeJRArOGzvLuOTsg=="],
|
||||
"@hey-api/openapi-ts": ["@hey-api/openapi-ts@0.97.3", "", { "dependencies": { "@hey-api/codegen-core": "0.8.2", "@hey-api/json-schema-ref-parser": "1.4.2", "@hey-api/shared": "0.4.5", "@hey-api/spec-types": "0.2.0", "@hey-api/types": "0.1.4", "@lukeed/ms": "2.0.2", "ansi-colors": "4.1.3", "color-support": "1.1.3", "commander": "14.0.3", "get-tsconfig": "4.14.0" }, "peerDependencies": { "typescript": ">=5.5.3 || >=6.0.0 || 6.0.1-rc" }, "bin": { "openapi-ts": "./bin/run.js" } }, "sha512-4sR6/E/POuy7aPZW9DDjhObzZCq7eSJWiW0+epXeKNczoTWEwdOyWFy9Ca/CnXYlZ3oJsrv0ZD0OO+YuczT7CA=="],
|
||||
|
||||
"@hey-api/types": ["@hey-api/types@0.1.2", "", {}, "sha512-uNNtiVAWL7XNrV/tFXx7GLY9lwaaDazx1173cGW3+UEaw4RUPsHEmiB4DSpcjNxMIcrctfz2sGKLnVx5PBG2RA=="],
|
||||
"@hey-api/shared": ["@hey-api/shared@0.4.5", "", { "dependencies": { "@hey-api/codegen-core": "0.8.2", "@hey-api/json-schema-ref-parser": "1.4.2", "@hey-api/spec-types": "0.2.0", "@hey-api/types": "0.1.4", "ansi-colors": "4.1.3", "cross-spawn": "7.0.6", "open": "11.0.0", "semver": "7.7.4" } }, "sha512-au4eHpBXAe1du0iMp6ESYuEaMS2jsoEyrbcT246btRhI9rMeQFEs7ZjtcMGXGsxhpaR38A8cPGNHx7QOrWAdMw=="],
|
||||
|
||||
"@hey-api/spec-types": ["@hey-api/spec-types@0.2.0", "", { "dependencies": { "@hey-api/types": "0.1.4" } }, "sha512-ibQ8Is7evMavzr8GNyJCcTg975d8DpaMUyLmOrQ85UBdy1l6t1KuRAwgChAbesJsIlNV6gjmlXruWyegDX18Fg=="],
|
||||
|
||||
"@hey-api/types": ["@hey-api/types@0.1.4", "", {}, "sha512-thWfawrDIP7wSI9ioT13I5soaaqB5vAPIiZmgD8PbeEVKNrkonc0N/Sjj97ezl7oQgusZmaNphGdMKipPO6IBg=="],
|
||||
|
||||
"@hono/node-server": ["@hono/node-server@1.19.17", "", { "peerDependencies": { "hono": "^4" } }, "sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ=="],
|
||||
|
||||
@@ -1658,10 +1662,6 @@
|
||||
|
||||
"@ioredis/commands": ["@ioredis/commands@1.10.0", "", {}, "sha512-UmeW7z4LfctwoQ5wkhVzgq8tXkreED2xZGpX+Bg+zA+WJFZCT6c062AfCK/Dfk81xZnnwdhJCUMkitihRaoC2Q=="],
|
||||
|
||||
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
|
||||
|
||||
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.1", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ=="],
|
||||
|
||||
"@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="],
|
||||
|
||||
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
||||
@@ -2536,7 +2536,7 @@
|
||||
|
||||
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.4.0", "", { "dependencies": { "bun-types": "1.4.0" } }, "sha512-K+lZULY23vRgK/CfTjFIV+tyifaNdSMlPh9j+6mQ/cLfpOznLyAuzgV/JQysyECpkBQLVMSyvjlr2fBUSA9wFQ=="],
|
||||
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
||||
|
||||
"@types/cacache": ["@types/cacache@20.0.1", "", { "dependencies": { "@types/node": "*", "minipass": "*" } }, "sha512-QlKW3AFoFr/hvPHwFHMIVUH/ZCYeetBNou3PCmxu5LaNDvrtBlPJtIA6uhmU9JRt9oxj7IYoqoLcpxtzpPiTcw=="],
|
||||
|
||||
@@ -2972,13 +2972,13 @@
|
||||
|
||||
"bun-pty": ["bun-pty@0.4.8", "", {}, "sha512-rO70Mrbr13+jxHHHu2YBkk2pNqrJE5cJn29WE++PUr+GFA0hq/VgtQPZANJ8dJo6d7XImvBk37Innt8GM7O28w=="],
|
||||
|
||||
"bun-types": ["bun-types@1.4.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-iIKw23BspnQQYd3prITOBxeUsxBHnwzX6YJfGMuNOZzeNcMmVqzIIVGRm1l69ogaPQmb4wB6BN8mA5bE9YuC5Q=="],
|
||||
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
||||
|
||||
"bundle-name": ["bundle-name@4.1.0", "", { "dependencies": { "run-applescript": "^7.0.0" } }, "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q=="],
|
||||
|
||||
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||
|
||||
"c12": ["c12@3.3.3", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.3", "exsolve": "^1.0.8", "giget": "^2.0.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q=="],
|
||||
"c12": ["c12@3.3.4", "", { "dependencies": { "chokidar": "^5.0.0", "confbox": "^0.2.4", "defu": "^6.1.6", "dotenv": "^17.3.1", "exsolve": "^1.0.8", "giget": "^3.2.0", "jiti": "^2.6.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.1.0", "pkg-types": "^2.3.0", "rc9": "^3.0.1" }, "peerDependencies": { "magicast": "*" }, "optionalPeers": ["magicast"] }, "sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA=="],
|
||||
|
||||
"c8": ["c8@10.1.3", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.1", "@istanbuljs/schema": "^0.1.3", "find-up": "^5.0.0", "foreground-child": "^3.1.1", "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", "test-exclude": "^7.0.1", "v8-to-istanbul": "^9.0.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1" }, "peerDependencies": { "monocart-coverage-reports": "^2" }, "optionalPeers": ["monocart-coverage-reports"], "bin": { "c8": "bin/c8.js" } }, "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA=="],
|
||||
|
||||
@@ -3030,8 +3030,6 @@
|
||||
|
||||
"ci-parallel-vars": ["ci-parallel-vars@1.0.1", "", {}, "sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg=="],
|
||||
|
||||
"citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
|
||||
|
||||
"classcat": ["classcat@5.0.5", "", {}, "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w=="],
|
||||
|
||||
"clean-stack": ["clean-stack@4.2.0", "", { "dependencies": { "escape-string-regexp": "5.0.0" } }, "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg=="],
|
||||
@@ -3090,8 +3088,6 @@
|
||||
|
||||
"confbox": ["confbox@0.2.4", "", {}, "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ=="],
|
||||
|
||||
"consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="],
|
||||
|
||||
"content-disposition": ["content-disposition@1.1.0", "", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="],
|
||||
|
||||
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||
@@ -3530,7 +3526,7 @@
|
||||
|
||||
"ghostty-web": ["ghostty-web@0.4.0", "", {}, "sha512-0puDBik2qapbD/QQBW9o5ZHfXnZBqZWx/ctBiVtKZ6ZLds4NYb+wZuw1cRLXZk9zYovIQ908z3rvFhexAvc5Hg=="],
|
||||
|
||||
"giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
|
||||
"giget": ["giget@3.3.1", "", { "bin": { "giget": "dist/cli.mjs" } }, "sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg=="],
|
||||
|
||||
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
|
||||
|
||||
@@ -3952,7 +3948,7 @@
|
||||
|
||||
"min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
|
||||
|
||||
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
|
||||
"minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="],
|
||||
|
||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||
|
||||
@@ -4000,7 +3996,7 @@
|
||||
|
||||
"nanoevents": ["nanoevents@7.0.1", "", {}, "sha512-o6lpKiCxLeijK4hgsqfR6CNToPyRU3keKyyI6uwuHRvpRTbZ0wXw51WRgyldVugZqoJfkGFrjrIenYH3bfEO3Q=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.17", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g=="],
|
||||
"nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="],
|
||||
|
||||
"napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
|
||||
|
||||
@@ -4018,8 +4014,6 @@
|
||||
|
||||
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
|
||||
|
||||
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
|
||||
|
||||
"node-forge": ["node-forge@1.4.0", "", {}, "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ=="],
|
||||
|
||||
"node-gyp": ["node-gyp@12.4.0", "", { "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", "tar": "^7.5.4", "tinyglobby": "^0.2.12", "undici": "^6.25.0", "which": "^6.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw=="],
|
||||
@@ -4060,8 +4054,6 @@
|
||||
|
||||
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||
|
||||
"nypm": ["nypm@0.6.9", "", { "dependencies": { "citty": "^0.2.2", "pathe": "^2.0.3", "tinyexec": "^1.2.4" }, "bin": { "nypm": "./dist/cli.mjs" } }, "sha512-zxlE2yvSWZWmHcNdT3+5zV2lrCogeE9YOklHrR3dFjqutq5wO7GFDYLFDRXLsYnJzwvy/im9fYoxePvS0VTW0w=="],
|
||||
|
||||
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||
|
||||
"object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="],
|
||||
@@ -4302,7 +4294,7 @@
|
||||
|
||||
"rc-config-loader": ["rc-config-loader@4.1.4", "", { "dependencies": { "debug": "^4.4.3", "js-yaml": "^4.1.1", "json5": "^2.2.3", "require-from-string": "^2.0.2" } }, "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ=="],
|
||||
|
||||
"rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
|
||||
"rc9": ["rc9@3.0.1", "", { "dependencies": { "defu": "^6.1.6", "destr": "^2.0.5" } }, "sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ=="],
|
||||
|
||||
"react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="],
|
||||
|
||||
@@ -5126,11 +5118,15 @@
|
||||
|
||||
"@gitlab/gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@hey-api/openapi-ts/commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="],
|
||||
"@hey-api/json-schema-ref-parser/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
|
||||
"@hey-api/openapi-ts/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
|
||||
"@hey-api/openapi-ts/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
||||
|
||||
"@hey-api/openapi-ts/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
||||
"@hey-api/openapi-ts/get-tsconfig": ["get-tsconfig@4.14.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="],
|
||||
|
||||
"@hey-api/shared/open": ["open@11.0.0", "", { "dependencies": { "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", "powershell-utils": "^0.1.0", "wsl-utils": "^0.3.0" } }, "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw=="],
|
||||
|
||||
"@hey-api/shared/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
|
||||
|
||||
@@ -5140,8 +5136,6 @@
|
||||
|
||||
"@kilocode/kilo-indexing/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
||||
|
||||
"@kilocode/kilo-indexing/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"@kilocode/plugin/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"@manypkg/find-root/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||
@@ -5154,16 +5148,12 @@
|
||||
|
||||
"@modelcontextprotocol/sdk/jose": ["jose@6.2.8", "", {}, "sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ=="],
|
||||
|
||||
"@npmcli/arborist/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"@npmcli/config/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="],
|
||||
|
||||
"@npmcli/git/ini": ["ini@6.0.0", "", {}, "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ=="],
|
||||
|
||||
"@npmcli/map-workspaces/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
||||
|
||||
"@npmcli/map-workspaces/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"@npmcli/package-json/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
|
||||
|
||||
"@octokit/core/@octokit/graphql": ["@octokit/graphql@7.1.1", "", { "dependencies": { "@octokit/request": "^8.4.1", "@octokit/types": "^13.0.0", "universal-user-agent": "^6.0.0" } }, "sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g=="],
|
||||
@@ -5206,8 +5196,6 @@
|
||||
|
||||
"@opencode-ai/core/@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
|
||||
|
||||
"@opencode-ai/core/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"@opencode-ai/plugin/@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="],
|
||||
|
||||
"@opencode-ai/session-ui/@solid-primitives/resize-observer": ["@solid-primitives/resize-observer@2.1.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ=="],
|
||||
@@ -5298,12 +5286,6 @@
|
||||
|
||||
"@textlint/linter-formatter/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"@ts-morph/common/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"@tufjs/models/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||
|
||||
"@vitest/expect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
@@ -5324,8 +5306,6 @@
|
||||
|
||||
"@vscode/vsce/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
|
||||
|
||||
"@vscode/vsce/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"archiver-utils/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"archiver-utils/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
|
||||
@@ -5448,16 +5428,12 @@
|
||||
|
||||
"gitlab-ai-provider/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"globby/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"gray-matter/js-yaml": ["js-yaml@3.15.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag=="],
|
||||
|
||||
"htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"ignore-walk/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"istanbul-lib-report/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
@@ -5532,8 +5508,6 @@
|
||||
|
||||
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
||||
|
||||
"nypm/citty": ["citty@0.2.2", "", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="],
|
||||
|
||||
"openid-client/jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
|
||||
|
||||
"openid-client/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
|
||||
@@ -5632,8 +5606,6 @@
|
||||
|
||||
"test-exclude/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="],
|
||||
|
||||
"test-exclude/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="],
|
||||
|
||||
"tree-sitter-bash/node-addon-api": ["node-addon-api@8.9.1", "", {}, "sha512-4eUQWVPCUUUiBjLnHS3cXWeC6ryoPUc0U3rP7IuzapoGbzMqd/r6KKO0clr0b+snQhsrueFEhCZDdK+LK7hxKg=="],
|
||||
@@ -5782,14 +5754,12 @@
|
||||
|
||||
"@eslint/eslintrc/minimatch/brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="],
|
||||
|
||||
"@hey-api/openapi-ts/open/wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
|
||||
"@hey-api/json-schema-ref-parser/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"@joshwooding/vite-plugin-react-docgen-typescript/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
"@hey-api/shared/open/wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
|
||||
|
||||
"@manypkg/find-root/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||
|
||||
"@npmcli/package-json/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"@octokit/core/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
||||
|
||||
"@octokit/endpoint/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@24.2.0", "", {}, "sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg=="],
|
||||
@@ -5982,8 +5952,6 @@
|
||||
|
||||
"c8/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"cacache/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"chalk-template/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"cli-truncate/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
@@ -6166,8 +6134,6 @@
|
||||
|
||||
"venice-ai-sdk-provider/@ai-sdk/openai-compatible/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.35", "", { "dependencies": { "@ai-sdk/provider": "3.0.13", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bjYld/2KGPLt78kpqbya+fD4LYS7BqVQJyUjE3qAHrYB0FR2Q90BaWEVIBZaguTWXf/A8L6uG1zO1v9TxVlGWg=="],
|
||||
|
||||
"vite-plugin-icons-spritesheet/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
exact = true
|
||||
# Keep Kilo's longer supply-chain quarantine while allowing packages that must track coordinated releases.
|
||||
minimumReleaseAge = 410520 # seconds (~4.75 days / ~114 hours)
|
||||
minimumReleaseAgeExcludes = ["mermaid", "@mermaid-js/parser", "@ai-sdk/amazon-bedrock", "@ai-sdk/anthropic", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "opentui-spinner", "gitlab-ai-provider", "opencode-gitlab-auth", "@ff-labs/fff-node", "@ff-labs/fff-bun", "@ff-labs/fff-bin-darwin-arm64", "@ff-labs/fff-bin-darwin-x64", "@ff-labs/fff-bin-linux-arm64-gnu", "@ff-labs/fff-bin-linux-arm64-musl", "@ff-labs/fff-bin-linux-x64-gnu", "@ff-labs/fff-bin-linux-x64-musl", "@ff-labs/fff-bin-win32-arm64", "@ff-labs/fff-bin-win32-x64", "@pierre/diffs", "@pierre/theming", "app-builder-lib", "dmg-builder", "electron-builder", "electron-publish", "@types/bun", "bun-types"] # kilocode_change
|
||||
minimumReleaseAgeExcludes = ["mermaid", "@mermaid-js/parser", "@ai-sdk/amazon-bedrock", "@ai-sdk/anthropic", "@opentui/core", "@opentui/core-darwin-arm64", "@opentui/core-darwin-x64", "@opentui/core-linux-arm64", "@opentui/core-linux-arm64-musl", "@opentui/core-linux-x64", "@opentui/core-linux-x64-musl", "@opentui/core-win32-arm64", "@opentui/core-win32-x64", "@opentui/keymap", "@opentui/solid", "opentui-spinner", "gitlab-ai-provider", "opencode-gitlab-auth", "@ff-labs/fff-node", "@ff-labs/fff-bun", "@ff-labs/fff-bin-darwin-arm64", "@ff-labs/fff-bin-darwin-x64", "@ff-labs/fff-bin-linux-arm64-gnu", "@ff-labs/fff-bin-linux-arm64-musl", "@ff-labs/fff-bin-linux-x64-gnu", "@ff-labs/fff-bin-linux-x64-musl", "@ff-labs/fff-bin-win32-arm64", "@ff-labs/fff-bin-win32-x64", "@pierre/diffs", "@pierre/theming", "app-builder-lib", "dmg-builder", "electron-builder", "electron-publish"]
|
||||
|
||||
[test]
|
||||
root = "./do-not-run-tests-from-root"
|
||||
|
||||
+4
-4
@@ -14,19 +14,19 @@ let
|
||||
sources = {
|
||||
"aarch64-linux" = {
|
||||
name = "bun-linux-aarch64";
|
||||
hash = "sha256-SxozLuhhmD65O8/m93D/+U4+MbLDiL2uo8jtNeWO7Q4=";
|
||||
hash = "sha256-on/7Y6gxA3WDbg1vZorhf6jY0YuIw3yCHGUzGXOhmjs=";
|
||||
};
|
||||
"x86_64-linux" = {
|
||||
name = "bun-linux-x64";
|
||||
hash = "sha256-LQP7X7g6yLVnrKCigbLOGhoZ1Ij1bClo2Iw/Jekv5FI=";
|
||||
hash = "sha256-lR7iruhV8IWVruxiJSJqKY0/6oOj3NZGXAnLzN9+hI8=";
|
||||
};
|
||||
"aarch64-darwin" = {
|
||||
name = "bun-darwin-aarch64";
|
||||
hash = "sha256-xmnpf2Fk4cluBwF0jbmN+ndJKQjL2DlMdVcTSnNd44E=";
|
||||
hash = "sha256-2LliIYKK1vl6x6wKt+lYcjQa92MAHogD6CZ2UsJlJiA=";
|
||||
};
|
||||
"x86_64-darwin" = {
|
||||
name = "bun-darwin-x64";
|
||||
hash = "sha256-HQIRuPHcmRGCNEaHrRXnLuhvFUhFpff6R3mUzTQd2bA=";
|
||||
hash = "sha256-QYPfM3RiPlurMVxUfPoJdFM81FfYa3O2OfeoeXTNZjM=";
|
||||
};
|
||||
};
|
||||
source =
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-gSexsvn9ZqrKapvVaH9O2byY5WMHzKPNqiAzfOrR9Fw=",
|
||||
"aarch64-linux": "sha256-KOX1YC8tMRyzx1paVavNzlORXCTDZSQMOfBuPKxL6TU=",
|
||||
"aarch64-darwin": "sha256-IKS43B5STRInn8XEtBn5VjHhjEWeXZv5NwTR9CQSijE=",
|
||||
"x86_64-darwin": "sha256-G18diPUH/4A3WoOdrbkLofMS56DYYSkAoJCPGroxu00="
|
||||
"x86_64-linux": "sha256-chyF6ZLYzGsNcswGsiyCWhZb6vvfGQOT8vcP+KpxvzU=",
|
||||
"aarch64-linux": "sha256-boS1OAxk95Q1a0wxg+u2ImWERLsYRaiPMMtqskd5lWk=",
|
||||
"aarch64-darwin": "sha256-tUebDKzxBnr1fKAhwJGtJEE/pB1945BzcgRomsjvYnk=",
|
||||
"x86_64-darwin": "sha256-ZoL5ChqVZSZe8ikLUqaN5K1iBMV6GvKOnuq310qDJEA="
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@
|
||||
"description": "AI-powered development tool",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.4.0",
|
||||
"packageManager": "bun@1.3.14",
|
||||
"scripts": {
|
||||
"dev": "KILO_CLIENT=cli bun run --cwd packages/opencode --conditions=node src/index.ts",
|
||||
"dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev",
|
||||
@@ -35,7 +35,7 @@
|
||||
"@effect/platform-node": "4.0.0-beta.83",
|
||||
"@anthropic-ai/sandbox-runtime": "0.0.63",
|
||||
"@npmcli/arborist": "9.4.0",
|
||||
"@types/bun": "1.4.0",
|
||||
"@types/bun": "1.3.14",
|
||||
"@types/cross-spawn": "6.0.6",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@opentui/core": "0.4.5",
|
||||
@@ -177,6 +177,6 @@
|
||||
"@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch",
|
||||
"solid-js@1.9.12": "patches/solid-js@1.9.12.patch"
|
||||
},
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
@@ -38,5 +38,5 @@
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"effect": "catalog:"
|
||||
},
|
||||
"version": "7.5.0"
|
||||
"version": "7.5.5"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@opencode-ai/codemode",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"description": "Effect-native confined code execution over schema-described tools",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
||||
@@ -6,7 +6,7 @@ FROM ${REGISTRY}/build/base:24.04
|
||||
SHELL ["/bin/bash", "-lc"]
|
||||
|
||||
ARG NODE_VERSION=24.4.0
|
||||
ARG BUN_VERSION=1.4.0
|
||||
ARG BUN_VERSION=1.3.14
|
||||
|
||||
ENV BUN_INSTALL=/opt/bun
|
||||
ENV PATH=/opt/bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"name": "@opencode-ai/core",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
@@ -77,7 +77,7 @@
|
||||
"cross-spawn": "catalog:",
|
||||
"glob": "13.0.5",
|
||||
"mime-types": "3.0.2",
|
||||
"minimatch": "10.2.5",
|
||||
"minimatch": "10.2.6",
|
||||
"npm-package-arg": "13.0.2",
|
||||
"rotating-file-stream": "3.2.9",
|
||||
"semver": "^7.6.3",
|
||||
|
||||
@@ -137,7 +137,7 @@ export const make = Effect.gen(function* () {
|
||||
if (job.info.status !== "running") return [{ info: snapshot(job) }, jobs]
|
||||
const pending = job.pending - 1
|
||||
const output =
|
||||
Exit.isSuccess(exit) && (!job.output || sequence > job.output.sequence)
|
||||
Exit.isSuccess(exit) && exit.value && sequence > (job.output?.sequence ?? -1) // kilocode_change - empty outputs never clobber; only the latest non-empty result wins (#13469)
|
||||
? { sequence, text: exit.value }
|
||||
: job.output
|
||||
if (Exit.isSuccess(exit) && pending > 0) {
|
||||
|
||||
@@ -1,25 +1,7 @@
|
||||
import { statSync } from "fs"
|
||||
import path from "path"
|
||||
import { which } from "../util/which"
|
||||
|
||||
export function args(command: string) {
|
||||
return ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", script(command)]
|
||||
}
|
||||
|
||||
export const locations = (env: NodeJS.ProcessEnv = process.env) =>
|
||||
[
|
||||
env["ProgramFiles"] && path.join(env["ProgramFiles"], "PowerShell", "7"),
|
||||
env["ProgramFiles(x86)"] && path.join(env["ProgramFiles(x86)"], "PowerShell", "7"),
|
||||
env["LOCALAPPDATA"] && path.join(env["LOCALAPPDATA"], "Microsoft", "WindowsApps"),
|
||||
]
|
||||
.filter((item): item is string => Boolean(item))
|
||||
.map((root) => path.join(root, "pwsh.exe"))
|
||||
|
||||
export const probe = (env: NodeJS.ProcessEnv = process.env) =>
|
||||
locations(env).filter((file) => statSync(file, { throwIfNoEntry: false })?.isFile())
|
||||
|
||||
export const pwsh = (env: NodeJS.ProcessEnv = process.env) => which("pwsh", env) ?? probe(env)[0]
|
||||
|
||||
const setup = `[Console]::InputEncoding = [System.Text.UTF8Encoding]::new($false);
|
||||
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false);
|
||||
$OutputEncoding = [Console]::OutputEncoding;
|
||||
@@ -141,4 +123,4 @@ function block(command: string, start: number, open: string, close: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export const PowerShell = { args, locations, probe, pwsh }
|
||||
export const PowerShell = { args }
|
||||
|
||||
@@ -4,56 +4,6 @@ import { spawn } from "#pty"
|
||||
|
||||
const TIMEOUT = 15_000
|
||||
|
||||
async function render() {
|
||||
const proc = spawn(process.execPath, ["--pure"], {
|
||||
name: "xterm-256color",
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
TERM: "xterm-256color",
|
||||
KILO_TERMINAL: "1",
|
||||
KILO_NO_DAEMON: "1",
|
||||
KILO_DISABLE_AUTOUPDATE: "1",
|
||||
KILO_DISABLE_MODELS_FETCH: "1",
|
||||
KILO_DISABLE_PROJECT_CONFIG: "1",
|
||||
KILO_DISABLE_DEFAULT_PLUGINS: "1",
|
||||
KILO_DISABLE_TERMINAL_TITLE: "0",
|
||||
KILO_CONFIG_CONTENT: "{}",
|
||||
KILO_AUTH_CONTENT: "{}",
|
||||
} as Record<string, string>,
|
||||
cols: 100,
|
||||
rows: 40,
|
||||
})
|
||||
const state = { output: "", exited: false }
|
||||
const ready = Promise.withResolvers<void>()
|
||||
const data = proc.onData((chunk) => {
|
||||
state.output = (state.output + chunk).slice(-20_000)
|
||||
if (state.output.includes("Ask anything...")) ready.resolve()
|
||||
})
|
||||
const exit = proc.onExit((event) => {
|
||||
state.exited = true
|
||||
ready.reject(new Error(`TUI exited before rendering (code ${event.exitCode}): ${JSON.stringify(state.output)}`))
|
||||
})
|
||||
const timeout = AbortSignal.timeout(TIMEOUT)
|
||||
|
||||
try {
|
||||
await Promise.race([
|
||||
ready.promise,
|
||||
new Promise<never>((_, reject) =>
|
||||
timeout.addEventListener(
|
||||
"abort",
|
||||
() => reject(new Error(`TUI produced no rendered frame within ${TIMEOUT}ms: ${JSON.stringify(state.output)}`)),
|
||||
{ once: true },
|
||||
),
|
||||
),
|
||||
])
|
||||
} finally {
|
||||
data.dispose()
|
||||
exit.dispose()
|
||||
if (!state.exited) await KiloPtyTermination.terminate(proc)
|
||||
}
|
||||
}
|
||||
|
||||
export async function smoke() {
|
||||
const proc = spawn(Shell.preferred(), [], {
|
||||
name: "xterm-256color",
|
||||
@@ -67,7 +17,8 @@ export async function smoke() {
|
||||
const exited = Promise.withResolvers<number>()
|
||||
const data = proc.onData((chunk) => {
|
||||
state.output += chunk
|
||||
if (/(?:^|[\r\n])KILO_PTY_READY(?:\r?\n|$)/.test(state.output)) output.resolve()
|
||||
const lines = state.output.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").split(/\r?\n/)
|
||||
if (lines.some((line) => line.trim() === "KILO_PTY_READY")) output.resolve()
|
||||
})
|
||||
const exit = proc.onExit((event) => {
|
||||
state.exited = true
|
||||
@@ -116,8 +67,6 @@ export async function smoke() {
|
||||
} finally {
|
||||
if (!stopped) active.kill()
|
||||
}
|
||||
|
||||
await render()
|
||||
}
|
||||
|
||||
export * as PtySmoke from "./smoke"
|
||||
|
||||
@@ -99,8 +99,7 @@ function resolve(file: string) {
|
||||
function win() {
|
||||
return Array.from(
|
||||
new Set(
|
||||
// kilocode_change - probe known PowerShell 7 install locations so legacy 5.1 is not picked when pwsh is off PATH
|
||||
[PowerShell.pwsh(), which("powershell"), gitbash(), process.env.COMSPEC || "cmd.exe"] // kilocode_change
|
||||
[which("pwsh"), which("powershell"), gitbash(), process.env.COMSPEC || "cmd.exe"]
|
||||
.filter((item): item is string => Boolean(item))
|
||||
.map(full),
|
||||
),
|
||||
|
||||
@@ -86,6 +86,27 @@ describe("BackgroundJob", () => {
|
||||
}).pipe(Effect.provide(jobsLayer)),
|
||||
)
|
||||
|
||||
// kilocode_change start - regression for #13469: an empty extended run must not clobber an earlier non-empty result
|
||||
it.live("keeps the earlier non-empty output when an extended run returns empty", () =>
|
||||
Effect.gen(function* () {
|
||||
const jobs = yield* BackgroundJob.Service
|
||||
const first = yield* Deferred.make<void>()
|
||||
const job = yield* jobs.start({
|
||||
type: "test",
|
||||
run: Deferred.await(first).pipe(Effect.as("real answer")),
|
||||
})
|
||||
|
||||
expect(yield* jobs.extend({ id: job.id, run: Effect.succeed("") })).toBe(true)
|
||||
|
||||
yield* Deferred.succeed(first, undefined)
|
||||
expect(yield* jobs.wait({ id: job.id })).toMatchObject({
|
||||
timedOut: false,
|
||||
info: { status: "completed", output: "real answer" },
|
||||
})
|
||||
}).pipe(Effect.provide(jobsLayer)),
|
||||
)
|
||||
// kilocode_change end
|
||||
|
||||
it.live("interrupts live work without promising settlement after the owning process-local scope closes", () =>
|
||||
Effect.gen(function* () {
|
||||
const scope = yield* Scope.make()
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "fs"
|
||||
import { tmpdir } from "os"
|
||||
import path from "path"
|
||||
import { Shell } from "@opencode-ai/core/shell"
|
||||
import { PowerShell } from "@opencode-ai/core/kilocode/powershell"
|
||||
import { which } from "@opencode-ai/core/util/which"
|
||||
|
||||
const LEGACY = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe"
|
||||
|
||||
const knownLocations = () => {
|
||||
const roots = [
|
||||
process.env.ProgramFiles && path.join(process.env.ProgramFiles, "PowerShell", "7"),
|
||||
process.env["ProgramFiles(x86)"] && path.join(process.env["ProgramFiles(x86)"], "PowerShell", "7"),
|
||||
process.env.LOCALAPPDATA && path.join(process.env.LOCALAPPDATA, "Microsoft", "WindowsApps"),
|
||||
].filter((item): item is string => Boolean(item))
|
||||
return roots.map((root) => path.join(root, "pwsh.exe")).filter((file) => existsSync(file))
|
||||
}
|
||||
|
||||
const pwshInstalled = () => Boolean(which("pwsh")) || knownLocations().length > 0
|
||||
|
||||
// Remove every PATH directory that can resolve pwsh or powershell so detection
|
||||
// cannot fall back to PATH lookup and must find installs on its own.
|
||||
const withoutPowershellDirs = () =>
|
||||
(process.env.PATH ?? "")
|
||||
.split(path.delimiter)
|
||||
.filter(Boolean)
|
||||
.filter((dir) => !/powershell/i.test(dir) && !existsSync(path.join(dir, "pwsh.exe")))
|
||||
.join(path.delimiter)
|
||||
|
||||
function withEnv(env: { PATH?: string; SHELL?: string }, fn: () => void) {
|
||||
const prevPath = process.env.PATH
|
||||
const prevShell = process.env.SHELL
|
||||
if (env.PATH === undefined) delete process.env.PATH
|
||||
else process.env.PATH = env.PATH
|
||||
if (env.SHELL === undefined) delete process.env.SHELL
|
||||
else process.env.SHELL = env.SHELL
|
||||
Shell.preferred.reset()
|
||||
Shell.acceptable.reset()
|
||||
try {
|
||||
fn()
|
||||
} finally {
|
||||
if (prevPath === undefined) delete process.env.PATH
|
||||
else process.env.PATH = prevPath
|
||||
if (prevShell === undefined) delete process.env.SHELL
|
||||
else process.env.SHELL = prevShell
|
||||
Shell.preferred.reset()
|
||||
Shell.acceptable.reset()
|
||||
}
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
describe("windows powershell selection", () => {
|
||||
test("prefers an installed powershell 7 when pwsh is absent from PATH", () => {
|
||||
if (!pwshInstalled()) return
|
||||
withEnv({ PATH: withoutPowershellDirs(), SHELL: undefined }, () => {
|
||||
expect(Shell.name(Shell.preferred())).toBe("pwsh")
|
||||
expect(Shell.name(Shell.acceptable())).toBe("pwsh")
|
||||
})
|
||||
})
|
||||
|
||||
test("prefers pwsh over legacy 5.1 on the unmodified PATH", () => {
|
||||
if (!pwshInstalled()) return
|
||||
withEnv({ SHELL: undefined }, () => {
|
||||
expect(Shell.name(Shell.preferred())).toBe("pwsh")
|
||||
})
|
||||
})
|
||||
|
||||
test("explicit shell config still overrides detection", () => {
|
||||
if (!existsSync(LEGACY)) return
|
||||
expect(Shell.preferred(LEGACY)).toBe(LEGACY)
|
||||
expect(Shell.acceptable(LEGACY)).toBe(LEGACY)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
describe("powershell install probing", () => {
|
||||
test("lists known locations in priority order", () => {
|
||||
expect(
|
||||
PowerShell.locations({
|
||||
ProgramFiles: "C:\\Program Files",
|
||||
"ProgramFiles(x86)": "C:\\Program Files (x86)",
|
||||
LOCALAPPDATA: "C:\\Users\\u\\AppData\\Local",
|
||||
}),
|
||||
).toEqual([
|
||||
path.join("C:\\Program Files", "PowerShell", "7", "pwsh.exe"),
|
||||
path.join("C:\\Program Files (x86)", "PowerShell", "7", "pwsh.exe"),
|
||||
path.join("C:\\Users\\u\\AppData\\Local", "Microsoft", "WindowsApps", "pwsh.exe"),
|
||||
])
|
||||
})
|
||||
|
||||
test("skips unset environment roots", () => {
|
||||
expect(PowerShell.locations({})).toEqual([])
|
||||
})
|
||||
|
||||
test("probe and pwsh resolve an installed pwsh outside PATH", () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "pwsh-probe-"))
|
||||
try {
|
||||
const dir = path.join(root, "PowerShell", "7")
|
||||
mkdirSync(dir, { recursive: true })
|
||||
const file = path.join(dir, "pwsh.exe")
|
||||
writeFileSync(file, "")
|
||||
expect(PowerShell.probe({ ProgramFiles: root })).toEqual([file])
|
||||
expect(PowerShell.pwsh({ PATH: "", ProgramFiles: root })).toBe(file)
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
test("probe ignores location roots without pwsh", () => {
|
||||
const root = mkdtempSync(path.join(tmpdir(), "pwsh-probe-empty-"))
|
||||
try {
|
||||
mkdirSync(path.join(root, "PowerShell", "7"), { recursive: true })
|
||||
expect(PowerShell.probe({ ProgramFiles: root })).toEqual([])
|
||||
expect(PowerShell.pwsh({ PATH: "", ProgramFiles: root })).toBeUndefined()
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"name": "@opencode-ai/effect-drizzle-sqlite",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"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.5.0"
|
||||
version = "7.5.5"
|
||||
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.5.0/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.5.5/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.5.0/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.5.5/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.5.0/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.5.5/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.5.0/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.5.5/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.5.0/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.5.5/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"name": "@opencode-ai/http-recorder",
|
||||
"description": "Record and replay Effect HTTP client traffic with deterministic cassettes",
|
||||
"type": "module",
|
||||
|
||||
@@ -20,5 +20,5 @@
|
||||
"@types/bun": "catalog:",
|
||||
"@typescript/native-preview": "catalog:"
|
||||
},
|
||||
"version": "7.5.0"
|
||||
"version": "7.5.5"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-console",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-docs",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --webpack --port 3002",
|
||||
|
||||
@@ -23,9 +23,17 @@ Before using Cloud Agents:
|
||||
|
||||
## Cost
|
||||
|
||||
- **Compute is free during limited beta**
|
||||
- Please provide any feedback in our Cloud Agents beta Discord channel: [Kilo Discord](https://kilo.ai/discord)
|
||||
- **Kilo Code credits are still used** when the agent performs work (model usage, operations, etc.).
|
||||
Cloud Agent compute is billed per second while the container is awake. Compute and model inference draw from the same Kilo credit balance, but they are charged separately.
|
||||
|
||||
| Cloud Agent size | Hourly rate |
|
||||
|---|---|
|
||||
| Docker | $0.60 |
|
||||
| Small | $0.60 |
|
||||
| Standard | $1.20 |
|
||||
|
||||
Usage is measured in whole seconds, with no rounding up to a longer billing interval and no minimum usage charge. Your balance must contain at least $5 to launch a container, but this is not an extra charge or minimum spend. BYOK users still pay for cloud compute because their provider keys cover inference only.
|
||||
|
||||
See [Kilo Code pricing](https://kilo.ai/pricing) for current rates and pricing for other cloud products.
|
||||
|
||||
## How to Use
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ Switch to **Kilo Teams** or **Kilo Enterprise** from other AI coding tools and e
|
||||
|
||||
**Other AI coding vendors** hide their true costs behind opaque subscription models, leaving you wondering what you're actually paying for.
|
||||
|
||||
**Kilo Teams** and **Kilo Enterprise** show you exactly what each AI request costs - no markup, no hidden fees, complete transparency.
|
||||
**Kilo Teams** and **Kilo Enterprise** show you what each AI request costs. Model inference is charged at provider rates with no markup, and credit purchases have a disclosed 5% payment-processing fee.
|
||||
|
||||
### No Rate Limiting
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ Sessions are your platform-agnostic interaction with Kilo. They remember your re
|
||||
Kilo Code's paid plans provide powerful team management features:
|
||||
|
||||
- [**About Plans**](/docs/collaborate/teams/about-plans) — Compare Teams and Enterprise plans
|
||||
- **Teams ($15/user/month)** — Zero markup on AI costs, centralized billing, team analytics
|
||||
- **Teams ($15/user/month)** — Inference at provider rates with no markup, centralized billing, team analytics; credit purchases have a 5% processing fee
|
||||
- **Enterprise ([Contact Sales](https://kilo.ai/contact-sales))** — Model controls, audit logs, SSO, dedicated support
|
||||
|
||||
### Team Management
|
||||
|
||||
@@ -21,7 +21,7 @@ No credits are included with a Teams or Enterprise plan purchase.
|
||||
|
||||
## What You Get from Kilo Teams
|
||||
|
||||
- **Zero markup** on AI provider costs - pay exactly what providers charge
|
||||
- **No inference markup** - model usage is charged at provider rates; credit purchases have a separate 5% payment-processing fee
|
||||
- **No rate limiting** or quality degradation during peak usage
|
||||
- **Centralized billing** - one invoice for your whole team
|
||||
- **Complete transparency** - see every request, cost, and usage pattern
|
||||
|
||||
@@ -5,7 +5,7 @@ description: "Manage billing and subscriptions for your team"
|
||||
|
||||
# Billing
|
||||
|
||||
Kilo seats uses a transparent, two-part billing system: a monthly subscription per seat, plus pay-as-you-go Kilo credits with zero markup.
|
||||
Kilo seats use a transparent, two-part billing system: a monthly subscription per seat, plus pay-as-you-go Kilo credits. Model inference is charged at provider rates with no markup. A separate 5% payment-processing fee applies when you purchase credits.
|
||||
|
||||
{% callout type="note" %}
|
||||
|
||||
@@ -13,6 +13,8 @@ Kilo Code seats purchases of Teams or Enterprise are separate from Kilo credits.
|
||||
|
||||
No Kilo credits are included with a Teams or Enterprise purchase.
|
||||
|
||||
$1 of purchased credits funds $1 of usage. The 5% processing fee is charged separately and does not increase the organization's credit balance.
|
||||
|
||||
{% /callout %}
|
||||
|
||||
## Organization Credits
|
||||
|
||||
@@ -5,7 +5,7 @@ description: "Set up your Kilo Code team account"
|
||||
|
||||
# Get Started with Kilo Seats in 10 Minutes
|
||||
|
||||
seats for Kilo in the Teams or Enterprise subscription brings transparent AI coding to your entire engineering organization. No markup on AI costs, no vendor lock-in, complete usage visibility.
|
||||
Seats for Kilo in the Teams or Enterprise subscription bring transparent AI coding to your entire engineering organization. Model inference is charged at provider rates with no markup, while credit purchases have a separate 5% payment-processing fee.
|
||||
|
||||
## Before You Begin
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ This document will help you set up your development environment and understand h
|
||||
Before you begin, make sure you have the following installed:
|
||||
|
||||
1. **Git** - For version control
|
||||
2. **Bun 1.4.0+** - Required for installing dependencies and running scripts
|
||||
2. **Bun 1.3.14+** - Required for installing dependencies and running scripts
|
||||
3. **Visual Studio Code** - Our recommended IDE for development
|
||||
4. **Java 21** - Required only when running JetBrains plugin checks or repo-level checks that include `@kilocode/kilo-jetbrains`
|
||||
|
||||
|
||||
@@ -32,6 +32,8 @@ Costs are determined by the upstream provider's pricing based on token usage:
|
||||
|
||||
## Balance management
|
||||
|
||||
Model inference is deducted from your balance at the upstream provider's rate with no markup. A 5% payment-processing fee applies when you purchase Kilo credits; the fee is charged separately and does not increase your balance. For example, $1 of purchased credits funds $1 of usage.
|
||||
|
||||
### Individual accounts
|
||||
|
||||
Your account balance is the difference between total credits purchased and total usage. Check your balance in the [Kilo dashboard](https://app.kilo.ai).
|
||||
|
||||
@@ -19,11 +19,12 @@ You can also use subscriptions or credits you may have purchased directly with a
|
||||
|
||||
At Kilo Code, we believe in complete pricing transparency:
|
||||
|
||||
- Our pricing matches the model provider's API rates exactly
|
||||
- We don't take any commission or markup.
|
||||
- $1 you give us becomes $1 of Kilo credits
|
||||
- We debit your Kilo credits exactly what the provider charges us in dollars
|
||||
- You only pay for what you use with no hidden fees
|
||||
- Model inference through Kilo Gateway matches the provider's API rates with no markup.
|
||||
- We debit your Kilo credits by the amount charged for inference or other metered Kilo services, such as cloud compute.
|
||||
- $1 of purchased credits funds $1 of usage.
|
||||
- A 5% payment-processing fee applies when you purchase credits. This fee is charged separately and does not increase your credit balance.
|
||||
|
||||
For current platform, inference, credit purchase, and cloud compute pricing, see [Kilo Code pricing](https://kilo.ai/pricing).
|
||||
|
||||
## Future Plans
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ Kilo automatically applies prompt caching on supported providers. Repeated conte
|
||||
|
||||
## How Costs Are Calculated
|
||||
|
||||
- Costs are a pass-through of provider pricing with no general markup.
|
||||
- Inference costs are a pass-through of provider pricing with no markup. A separate 5% payment-processing fee applies when you purchase Kilo credits.
|
||||
- Kilo calculates an estimated cost for each request based on configured pricing. This estimate is shown per-request in the chat history.
|
||||
- Cache hits are billed at a discounted rate compared to regular input tokens.
|
||||
- Requests using **Auto Free** models are billed at $0 on Kilo's side.
|
||||
|
||||
Generated
+5
-5
@@ -1215,8 +1215,8 @@ packages:
|
||||
mermaid@11.17.2:
|
||||
resolution: {integrity: sha512-V6K3C8EBdEsPFZXSKMJe6ppQOENxuHARr9GvHX4hh47lAbhMRD9qf4oEK7LoaRQxULMa80/qt5gHO73aCleBBg==}
|
||||
|
||||
nanoid@3.3.17:
|
||||
resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==}
|
||||
nanoid@3.3.18:
|
||||
resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
@@ -2529,7 +2529,7 @@ snapshots:
|
||||
ts-dedent: 2.3.0
|
||||
uuid: 14.0.1
|
||||
|
||||
nanoid@3.3.17: {}
|
||||
nanoid@3.3.18: {}
|
||||
|
||||
next@16.3.0(@types/node@25.9.5)(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
|
||||
dependencies:
|
||||
@@ -2581,13 +2581,13 @@ snapshots:
|
||||
|
||||
postcss@8.5.23:
|
||||
dependencies:
|
||||
nanoid: 3.3.17
|
||||
nanoid: 3.3.18
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
postcss@8.5.26:
|
||||
dependencies:
|
||||
nanoid: 3.3.17
|
||||
nanoid: 3.3.18
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2f39a67f1abfc391d9784d0c0dec6abb33fc8940ce6c6bed07cc47f2ebcff0f5
|
||||
size 11599
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ee3282e45fbd929ec6f39c56dd9db08d6b5952b44a77245173d4bcf0eb58791a
|
||||
size 11601
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-gateway",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-i18n",
|
||||
"version": "7.5.0",
|
||||
"version": "7.5.5",
|
||||
"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.5.0",
|
||||
"version": "7.5.5",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Standalone indexing engine and host helpers for Kilo Code",
|
||||
@@ -47,7 +47,7 @@
|
||||
"hono": "catalog:",
|
||||
"hono-openapi": "catalog:",
|
||||
"ignore": "7.0.5",
|
||||
"minimatch": "10.2.5",
|
||||
"minimatch": "10.2.6",
|
||||
"openai": "6.27.0",
|
||||
"p-limit": "7.3.0",
|
||||
"tree-sitter-wasms": "0.1.13",
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## 7.4.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#13470](https://github.com/Kilo-Org/kilocode/pull/13470) [`17bef75`](https://github.com/Kilo-Org/kilocode/commit/17bef75509e6fc0b8199fb19bba0ebdafb21c223) - Stop showing a running badge for a session that was just deleted.
|
||||
|
||||
## 7.5.0
|
||||
|
||||
### Minor Changes
|
||||
@@ -310,6 +316,61 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [7.1.0] - 2026-08-26
|
||||
|
||||
### Added
|
||||
|
||||
- Add the JetBrains Agent Manager beta for creating, opening, organizing, renaming, deleting, and tracking worktree-based tasks and their sessions from the IDE.
|
||||
- Show Agent Manager worktree activity, changes, ahead/behind, pull request, failure, and attention badges with clearer row actions, menus, tooltips, and drag-and-drop reordering.
|
||||
- Add New, From PR, and From Branch tabs to the New Worktree dialog for clearer worktree creation and import flows.
|
||||
- Add JetBrains logging settings with log reveal and backend log download actions for easier diagnostics.
|
||||
- Add JetBrains workflow settings so workflows can be reviewed and managed from the plugin.
|
||||
- Show permission-prompt diffs and approval reasons on JetBrains tool cards before acting on tool requests.
|
||||
- Open sub-agent sessions in JetBrains editor tabs with live collapsed task previews.
|
||||
- Add Retry to failed JetBrains chat turns so the original request can be rerun without retyping.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Improve Agent Manager reliability by anchoring worktrees to the main repository storage, pruning stale metadata, hiding dead managed worktrees, refusing unmanaged paths, and preserving session history by worktree.
|
||||
- Keep failed, stopped, deleted, resumed, and recovered JetBrains sessions represented correctly in chat, session lists, worktree rows, activity badges, and Agents-tab attention dots.
|
||||
- Detect unsupported JetBrains remote workspaces and missing or moved worktree folders with clearer in-session states instead of ambiguous failures.
|
||||
- Keep slash completion open and responsive while typing quickly.
|
||||
- Stabilize JetBrains chat and Agent Manager layout, including header popups, PR badges, row spacing, hover popups, overlays, worktree tab painting, and dialog branch pickers.
|
||||
- Preserve project-level snapshot disabling across restarts after choosing to disable snapshots from the slow-repo prompt.
|
||||
- Keep Ask and Plan modes read-only even when broad permission rules are configured.
|
||||
- Improve Kilo Core reliability for JetBrains by preserving output budgets, recovering reasoning-only incomplete responses, preserving Cerebras completion limits, restoring terminal startup, and removing duplicate skill catalog content from prompts.
|
||||
- Fix Agent Manager session creation on strict providers and OpenAI Responses API models by allowing nullable tool fields and explicit provider selection.
|
||||
- Clear empty failed assistant responses when sending a normal follow-up after a provider failure.
|
||||
|
||||
### Changed
|
||||
|
||||
- Update the pinned Kilo Core CLI used by JetBrains from 7.4.22 to 7.4.23.
|
||||
- Improve Kilo Core cold and warm startup speed for JetBrains and other clients.
|
||||
- Show failed-turn details in a clearer error card with the error kind and retry action, while manually stopped turns render as a muted "Stopped" note.
|
||||
- Put new, imported, or moved Agent Manager worktrees at the top of the list unless manually reordered.
|
||||
- Make Agent Manager rows visually quieter with regular-weight labels, subdued idle icons, and less stale deleted-session status.
|
||||
- Remove the experimental agent requirements and task-aware output pruning features from the bundled Kilo Core runtime.
|
||||
- Remove an unused JetBrains Compose compiler plugin dependency.
|
||||
|
||||
## [7.1.0-rc.5] - 2026-08-26
|
||||
|
||||
### Added
|
||||
|
||||
- Add separate New, From PR, and From Branch tabs to the JetBrains New Worktree dialog, replacing the old import radio buttons with clearer workflows.
|
||||
- Add Retry to failed JetBrains chat turns so you can roll back the failed response and rerun the original request without retyping it.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Stop showing error badges and Agents-tab attention dots after you manually stop a turn.
|
||||
- Keep deleted sessions from briefly reappearing as running, failed, or waiting in session lists and Agent Manager activity badges.
|
||||
- Prevent the New Worktree dialog from crashing when IntelliJ drops an editable branch picker editor during layout.
|
||||
- Detect pull requests for imported worktrees more reliably and avoid worktree tab paint artifacts.
|
||||
- Clear empty failed assistant responses when you send a normal follow-up after a provider failure.
|
||||
|
||||
### Changed
|
||||
|
||||
- Show failed-turn details in a clearer error card with the error kind and retry action, while stopped turns now render as a simple muted "Stopped" note.
|
||||
|
||||
## [7.1.0-rc.4] - 2026-08-25
|
||||
|
||||
### Added
|
||||
|
||||
+5
-4
@@ -86,14 +86,15 @@ class KiloBackendActivityManager(
|
||||
is ChatEventDto.QuestionAsked -> questions.getOrPut(event.sessionID) { mutableMapOf() }[event.request.id] = plan(event)
|
||||
is ChatEventDto.QuestionReplied -> removeMap(questions, event.sessionID, event.requestID)
|
||||
is ChatEventDto.QuestionRejected -> removeMap(questions, event.sessionID, event.requestID)
|
||||
is ChatEventDto.Error -> event.sessionID?.let { errors.add(it) }
|
||||
// A Stop publishes MessageAbortedError. That is a deliberate user action, not a failure, so
|
||||
// it must not badge the session list, worktree rows, or the Agents tab attention dot.
|
||||
is ChatEventDto.Error -> if (event.error?.aborted != true) event.sessionID?.let { errors.add(it) }
|
||||
is ChatEventDto.TurnOpen -> errors.remove(event.sessionID)
|
||||
is ChatEventDto.SessionIdle -> clear(event.sessionID)
|
||||
is ChatEventDto.SessionStatusChanged -> when (event.status.type) {
|
||||
"idle" -> clear(event.sessionID)
|
||||
// Work restarted, so whatever ended the previous turn (a Stop publishes
|
||||
// MessageAbortedError) is stale. Not every resume path publishes a turn event, so
|
||||
// busy has to clear the error itself.
|
||||
// Work restarted, so whatever ended the previous turn is stale. Not every resume
|
||||
// path publishes a turn event, so busy has to clear the error itself.
|
||||
"busy" -> errors.remove(event.sessionID)
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
+137
-57
@@ -3,6 +3,7 @@ package ai.kilocode.backend.rpc
|
||||
import ai.kilocode.backend.app.KiloBackendAppService
|
||||
import ai.kilocode.log.KiloLog
|
||||
import ai.kilocode.rpc.KiloWorktreeRpcApi
|
||||
import ai.kilocode.rpc.parsePrUrl
|
||||
import ai.kilocode.rpc.dto.BranchStatusDto
|
||||
import ai.kilocode.rpc.dto.CreateWorktreeRequestDto
|
||||
import ai.kilocode.rpc.dto.CreateWorktreeResultDto
|
||||
@@ -72,6 +73,7 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
|
||||
private val bases = ConcurrentHashMap<String, Timed<String>>()
|
||||
private val prs = ConcurrentHashMap<String, Timed<WorktreePrListDto>>()
|
||||
private val branches = ConcurrentHashMap<String, Timed<BranchStatusDto>>()
|
||||
private val resolver = PrResolver(gh = ::runGh, git = ::runGit)
|
||||
private val ghLock = Any()
|
||||
@Volatile
|
||||
private var ghProbe: Timed<GhAvailability>? = null
|
||||
@@ -153,8 +155,7 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
|
||||
val res = runGit(root, "worktree", "list", "--porcelain")
|
||||
if (!res.ok) return@withContext WorktreeStatsListDto()
|
||||
val items = managedWorktrees(parseWorktreeList(res.stdout))
|
||||
val main = items.firstOrNull { it.main }
|
||||
val fallback = main?.branch?.takeIf { it.isNotBlank() && it != "(detached)" } ?: "HEAD"
|
||||
val fallback = baseBranch(items) ?: "HEAD"
|
||||
WorktreeStatsListDto(parallel(items.filter { !it.main }) { item -> stats(item, fallback) })
|
||||
}
|
||||
|
||||
@@ -170,18 +171,17 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
|
||||
if (available != GhAvailability.OK) return@withContext WorktreePrListDto(available).also { prs[directory] = Timed(now, it) }
|
||||
val res = runGit(root, "worktree", "list", "--porcelain")
|
||||
if (!res.ok) return@withContext WorktreePrListDto().also { prs[directory] = Timed(now, it) }
|
||||
val items = managedWorktrees(parseWorktreeList(res.stdout)).filter { !it.main && it.branch != "(detached)" }
|
||||
val all = managedWorktrees(parseWorktreeList(res.stdout))
|
||||
val items = prTargets(all)
|
||||
val base = baseBranch(all)
|
||||
var status = GhAvailability.OK
|
||||
val data = parallel(items) { item ->
|
||||
if (status != GhAvailability.OK) return@parallel null
|
||||
val out = runGh(Path.of(item.path).normalize(), "pr", "view", item.branch, "--json", "number,state,isDraft,url,title")
|
||||
if (!out.ok) {
|
||||
// prError only ever returns UNAUTH or OK; a missing gh/git binary is already caught
|
||||
// by the upfront ghAvailable() check before this loop runs.
|
||||
if (prError(out.stderr) == GhAvailability.UNAUTH) status = GhAvailability.UNAUTH
|
||||
return@parallel null
|
||||
}
|
||||
parsePr(item.path, out.stdout)
|
||||
val lookup = resolver.resolve(item.path, item.branch, base)
|
||||
// The resolver only ever reports UNAUTH or OK; a missing gh/git binary is already
|
||||
// caught by the upfront ghAvailable() check before this loop runs.
|
||||
if (lookup.availability != GhAvailability.OK) status = lookup.availability
|
||||
lookup.pr
|
||||
}.filterNotNull()
|
||||
val dto = WorktreePrListDto(status, if (status == GhAvailability.OK) data else emptyList())
|
||||
prs[directory] = Timed(System.currentTimeMillis(), dto)
|
||||
@@ -195,15 +195,18 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
|
||||
val branch = runGit(root, "branch", "--show-current").stdout.trim()
|
||||
val worktree = isLinkedWorktree(root)
|
||||
val availability = ghAvailable(root)
|
||||
val pr = if (availability == GhAvailability.OK && branch.isNotBlank()) {
|
||||
val out = runGh(root, "pr", "view", branch, "--json", "number,state,isDraft,url,title,headRefName")
|
||||
// Only accept a PR whose head branch matches the current branch. Guards against gh
|
||||
// resolving a PR via upstream/remote configuration that isn't for this branch.
|
||||
if (out.ok && parsePrHeadRef(out.stdout) == branch) parsePr(directory, out.stdout) else null
|
||||
val lookup = if (availability == GhAvailability.OK && branch.isNotBlank()) {
|
||||
resolver.resolve(directory, branch, baseBranch(root))
|
||||
} else {
|
||||
null
|
||||
PrLookup()
|
||||
}
|
||||
val dto = BranchStatusDto(branch = branch, worktree = worktree, availability = availability, pr = pr)
|
||||
val dto = BranchStatusDto(
|
||||
branch = branch,
|
||||
worktree = worktree,
|
||||
// A PR lookup that hits an auth failure must not be reported as a branch without a PR.
|
||||
availability = if (availability == GhAvailability.OK) lookup.availability else availability,
|
||||
pr = lookup.pr,
|
||||
)
|
||||
branches[directory] = Timed(System.currentTimeMillis(), dto)
|
||||
dto
|
||||
}
|
||||
@@ -285,6 +288,23 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
|
||||
return Path.of(main.path).normalize()
|
||||
}
|
||||
|
||||
/** Branch checked out in the main working tree of the repo containing [root]. */
|
||||
private fun baseBranch(root: Path): String? {
|
||||
val res = runGit(root, "worktree", "list", "--porcelain")
|
||||
if (!res.ok) return null
|
||||
return baseBranch(parseWorktreeList(res.stdout))
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops the PR and branch caches so the next poll reflects a mutation immediately. Entries are
|
||||
* keyed by the requesting directory and a mutation can change any repository the backend has
|
||||
* answered for, so clear wholesale rather than by key.
|
||||
*/
|
||||
private fun invalidate() {
|
||||
prs.clear()
|
||||
branches.clear()
|
||||
}
|
||||
|
||||
override suspend fun create(directory: String, request: CreateWorktreeRequestDto): CreateWorktreeResultDto =
|
||||
withContext(Dispatchers.IO) {
|
||||
val base = Path.of(directory).normalize()
|
||||
@@ -303,18 +323,18 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
|
||||
GhAvailability.UNAUTH -> return@withContext CreateWorktreeResultDto(error = "GitHub CLI (gh) is not authorized")
|
||||
GhAvailability.OK -> Unit
|
||||
}
|
||||
val view = runGh(base, "pr", "view", ref.number.toString(), "--repo", "${ref.owner}/${ref.repo}", "--json", "headRefName,title")
|
||||
val fields = "headRefName,title,isCrossRepository,headRepositoryOwner"
|
||||
val view = runGh(base, "pr", "view", ref.number.toString(), "--repo", "${ref.owner}/${ref.repo}", "--json", fields)
|
||||
if (!view.ok) {
|
||||
LOG.warn("pr import view failed: url=$url exit=${view.exit} stderr=${view.stderr.trim()}")
|
||||
return@withContext CreateWorktreeResultDto(error = view.stderr.ifBlank { "gh pr view failed" })
|
||||
}
|
||||
val branch = parsePrHeadRef(view.stdout).ifBlank { "pr-${ref.number}" }
|
||||
// The pull ref works for both same-repo and fork PRs without adding a fork remote; the
|
||||
// leading '+' force-updates a stale local branch from a previous import attempt.
|
||||
val fetch = runGit(base, "fetch", "origin", "+refs/pull/${ref.number}/head:$branch")
|
||||
if (!fetch.ok) {
|
||||
LOG.warn("pr import fetch failed: url=$url exit=${fetch.exit} stderr=${fetch.stderr.trim()}")
|
||||
return@withContext CreateWorktreeResultDto(error = fetch.stderr.ifBlank { "git fetch failed" })
|
||||
val head = parsePrHead(view.stdout)
|
||||
val branch = prBranchName(head, ref.number)
|
||||
val failure = fetchPrBranch({ args -> runGit(base, args) }, ref.number, head, branch)
|
||||
if (failure != null) {
|
||||
LOG.warn("pr import fetch failed: url=$url exit=${failure.exit} stderr=${failure.stderr.trim()}")
|
||||
return@withContext CreateWorktreeResultDto(error = failure.stderr.ifBlank { "Failed to check out the pull request branch" })
|
||||
}
|
||||
addWorktree(base, branch, existing = true, baseRef = null)
|
||||
}
|
||||
@@ -347,6 +367,7 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
|
||||
return CreateWorktreeResultDto(error = res.stderr.ifBlank { "git worktree add failed" })
|
||||
}
|
||||
LOG.info("worktree created: branch=$branch dir=$dir")
|
||||
invalidate()
|
||||
val path = dir.toRealPath().toString()
|
||||
val list = runGit(base, "worktree", "list", "--porcelain")
|
||||
val items = if (list.ok) managedWorktrees(parseWorktreeList(list.stdout)) else emptyList()
|
||||
@@ -393,7 +414,7 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
|
||||
// worktree prunable when its admin metadata is stale while the files remain; those must still
|
||||
// be deleted so a later create of the same slug is not blocked by leftovers.
|
||||
val res = if (!Files.isDirectory(Path.of(target.path))) {
|
||||
GitResult(0, "", "")
|
||||
CmdOut(0, "", "")
|
||||
} else {
|
||||
runGit(base, "worktree", "remove", "--force", target.path)
|
||||
}
|
||||
@@ -411,6 +432,7 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
|
||||
if (!del.ok) LOG.warn("worktree branch delete failed: branch=$it exit=${del.exit} stderr=${del.stderr.trim()}")
|
||||
}
|
||||
LOG.info("worktree removed: path=$path branch=${branch ?: "(none)"}")
|
||||
invalidate()
|
||||
removeWorktreeState(store, target.path)
|
||||
val prune = runGit(base, "worktree", "prune")
|
||||
if (!prune.ok) LOG.warn("worktree prune failed: exit=${prune.exit} stderr=${prune.stderr.trim()}")
|
||||
@@ -488,35 +510,35 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
|
||||
}
|
||||
}
|
||||
|
||||
private data class GitResult(val exit: Int, val stdout: String, val stderr: String) {
|
||||
val ok get() = exit == 0
|
||||
}
|
||||
|
||||
private data class Timed<T>(val time: Long, val value: T)
|
||||
|
||||
private fun runGit(base: Path, vararg args: String): GitResult {
|
||||
private fun runGit(base: Path, vararg args: String): CmdOut = runGit(base, args.toList())
|
||||
|
||||
private fun runGit(base: Path, args: List<String>): CmdOut {
|
||||
return try {
|
||||
val cmd = GeneralCommandLine(listOf("git") + args).withWorkDirectory(base.toFile())
|
||||
val out = CapturingProcessHandler(cmd).runProcess(30_000)
|
||||
GitResult(if (out.isTimeout) -1 else out.exitCode, out.stdout, out.stderr)
|
||||
CmdOut(if (out.isTimeout) -1 else out.exitCode, out.stdout, out.stderr)
|
||||
} catch (e: Exception) {
|
||||
GitResult(-1, "", e.message ?: "git failed")
|
||||
CmdOut(-1, "", e.message ?: "git failed")
|
||||
}
|
||||
}
|
||||
|
||||
private fun runGh(base: Path, vararg args: String): GitResult {
|
||||
private fun runGh(base: Path, vararg args: String): CmdOut = runGh(base, args.toList())
|
||||
|
||||
private fun runGh(base: Path, args: List<String>): CmdOut {
|
||||
return try {
|
||||
val cmd = GeneralCommandLine(listOf("gh") + args)
|
||||
.withWorkDirectory(base.toFile())
|
||||
.withParentEnvironmentType(ParentEnvironmentType.CONSOLE)
|
||||
val out = CapturingProcessHandler(cmd).runProcess(30_000)
|
||||
GitResult(if (out.isTimeout) -1 else out.exitCode, out.stdout, out.stderr)
|
||||
CmdOut(if (out.isTimeout) -1 else out.exitCode, out.stdout, out.stderr)
|
||||
} catch (e: Exception) {
|
||||
GitResult(-1, "", e.message ?: "gh failed")
|
||||
CmdOut(-1, "", e.message ?: "gh failed")
|
||||
}
|
||||
}
|
||||
|
||||
private fun add(base: Path, args: List<String>): GitResult {
|
||||
private fun add(base: Path, args: List<String>): CmdOut {
|
||||
val first = runGit(base, *args.toTypedArray())
|
||||
if (first.ok || !stale(first.stderr)) return first
|
||||
val prune = runGit(base, "worktree", "prune")
|
||||
@@ -613,13 +635,6 @@ class KiloWorktreeRpcApiImpl : KiloWorktreeRpcApi {
|
||||
value
|
||||
}
|
||||
|
||||
private fun prError(stderr: String): GhAvailability {
|
||||
val text = stderr.lowercase()
|
||||
if (text.contains("not logged") || text.contains("gh auth login") || text.contains("authentication")) return GhAvailability.UNAUTH
|
||||
if (text.contains("not found") || text.contains("no pull requests found")) return GhAvailability.OK
|
||||
return GhAvailability.OK
|
||||
}
|
||||
|
||||
private fun snippet(text: String): String {
|
||||
return text.trim().replace(Regex("\\s+"), " ").take(180)
|
||||
}
|
||||
@@ -650,21 +665,72 @@ internal fun parsePr(path: String, raw: String): WorktreePrDto? {
|
||||
return WorktreePrDto(path, number, state, url, title)
|
||||
}
|
||||
|
||||
internal data class PrRef(val owner: String, val repo: String, val number: Int)
|
||||
/** Head of a pull request being imported. */
|
||||
internal data class PrHead(val ref: String = "", val cross: Boolean = false, val owner: String = "")
|
||||
|
||||
private val PR_URL = Regex("github\\.com[/:]([^/]+)/([^/]+?)(?:\\.git)?/pull/(\\d+)")
|
||||
|
||||
/** Parses `https://github.com/<owner>/<repo>/pull/<n>` (and ssh-style hosts) into its parts. */
|
||||
internal fun parsePrUrl(url: String): PrRef? {
|
||||
val match = PR_URL.find(url.trim()) ?: return null
|
||||
val number = match.groupValues[3].toIntOrNull() ?: return null
|
||||
return PrRef(match.groupValues[1], match.groupValues[2], number)
|
||||
/** Reads the head branch and its repository out of a `gh pr view --json` payload. */
|
||||
internal fun parsePrHead(raw: String): PrHead {
|
||||
val obj = runCatching { json.parseToJsonElement(raw) as? JsonObject }.getOrNull() ?: return PrHead()
|
||||
val ref = obj["headRefName"]?.jsonPrimitive?.content?.trim().orEmpty()
|
||||
val cross = obj["isCrossRepository"]?.jsonPrimitive?.booleanOrNull == true
|
||||
val owner = (obj["headRepositoryOwner"] as? JsonObject)?.get("login")?.jsonPrimitive?.content?.trim().orEmpty()
|
||||
return PrHead(ref, cross, owner)
|
||||
}
|
||||
|
||||
/** Reads `headRefName` out of a `gh pr view --json` payload. */
|
||||
internal fun parsePrHeadRef(raw: String): String {
|
||||
val obj = runCatching { json.parseToJsonElement(raw) as? JsonObject }.getOrNull() ?: return ""
|
||||
return obj["headRefName"]?.jsonPrimitive?.content?.trim().orEmpty()
|
||||
/**
|
||||
* Local branch name for an imported PR. Fork PRs are prefixed with their owner so two PRs sharing a
|
||||
* head branch name — `patch-1` is common — can be imported side by side.
|
||||
*/
|
||||
internal fun prBranchName(head: PrHead, number: Int): String {
|
||||
if (head.ref.isBlank()) return "pr-$number"
|
||||
val owner = head.owner.lowercase()
|
||||
return if (head.cross && owner.isNotEmpty()) "$owner/${head.ref}" else head.ref
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the PR head into [branch] and records which PR it belongs to, mirroring `gh pr checkout`:
|
||||
* a same-repo PR gets an ordinary upstream (so `git push`/`git pull` work in the imported worktree),
|
||||
* while a fork PR is tracked through `refs/pull/<number>/head`, which `gh` resolves back to the PR
|
||||
* by number. [run] executes git in the repository. Returns the failing command, or null on success.
|
||||
*/
|
||||
internal fun fetchPrBranch(run: (List<String>) -> CmdOut, number: Int, head: PrHead, branch: String): CmdOut? {
|
||||
val pull = "refs/pull/$number/head"
|
||||
// A fork head lives in a repository we may have no remote for. The pull ref reaches it without
|
||||
// adding one, and '+' force-updates a stale branch left by an earlier import attempt.
|
||||
if (head.cross || head.ref.isBlank()) {
|
||||
val fetch = run(listOf("fetch", "origin", "+$pull:$branch"))
|
||||
if (!fetch.ok) return fetch
|
||||
recordPrBranch(run, branch, pull)
|
||||
return null
|
||||
}
|
||||
val tracking = "refs/remotes/origin/${head.ref}"
|
||||
val direct = run(listOf("fetch", "origin", "+refs/heads/${head.ref}:$tracking"))
|
||||
if (!direct.ok) {
|
||||
// The head branch is gone — merged PR, or the author deleted it — but the pull ref survives.
|
||||
val fallback = run(listOf("fetch", "origin", "+$pull:$tracking"))
|
||||
if (!fallback.ok) return fallback
|
||||
}
|
||||
val point = run(listOf("branch", "--force", branch, tracking))
|
||||
if (!point.ok) return point
|
||||
recordPrBranch(run, branch, if (direct.ok) "refs/heads/${head.ref}" else pull)
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Records the branch's remote and merge ref. This is what lets a PR be recognised later without
|
||||
* guessing from the branch name, so a failure only degrades PR detection to slower lookups and must
|
||||
* never fail the import.
|
||||
*/
|
||||
private fun recordPrBranch(run: (List<String>) -> CmdOut, branch: String, merge: String) {
|
||||
listOf(
|
||||
listOf("config", "branch.$branch.remote", "origin"),
|
||||
listOf("config", "branch.$branch.merge", merge),
|
||||
).forEach { args ->
|
||||
val res = run(args)
|
||||
if (!res.ok) {
|
||||
KiloWorktreeRpcApiImpl.LOG.warn("pr import config failed: args=$args exit=${res.exit} stderr=${res.stderr.trim()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val json = Json { prettyPrint = true; ignoreUnknownKeys = true }
|
||||
@@ -737,6 +803,20 @@ internal fun managedWorktrees(items: List<WorktreeDto>): List<WorktreeDto> {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Worktrees eligible for a PR lookup. The main working tree is included — it can sit on a PR branch
|
||||
* just like a linked worktree — while detached heads have no branch to resolve and prunable entries
|
||||
* have no checkout left.
|
||||
*/
|
||||
internal fun prTargets(items: List<WorktreeDto>): List<WorktreeDto> {
|
||||
return items.filter { !it.prunable && it.branch != "(detached)" }
|
||||
}
|
||||
|
||||
/** Branch checked out in the main working tree, or null when it is missing or detached. */
|
||||
internal fun baseBranch(items: List<WorktreeDto>): String? {
|
||||
return items.firstOrNull { it.main }?.branch?.takeIf { it.isNotBlank() && it != "(detached)" }
|
||||
}
|
||||
|
||||
internal fun overlayWorktreeNames(items: List<WorktreeDto>, names: Map<String, String>): List<WorktreeDto> {
|
||||
if (names.isEmpty()) return items
|
||||
return items.map { item ->
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package ai.kilocode.backend.rpc
|
||||
|
||||
import ai.kilocode.rpc.dto.GhAvailability
|
||||
import ai.kilocode.rpc.dto.WorktreePrDto
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import java.nio.file.Path
|
||||
|
||||
/** Result of running a `git`/`gh` command. */
|
||||
internal data class CmdOut(val exit: Int, val stdout: String, val stderr: String) {
|
||||
val ok get() = exit == 0
|
||||
}
|
||||
|
||||
/** PR for one checkout, plus the gh availability observed while resolving it. */
|
||||
internal data class PrLookup(val pr: WorktreePrDto? = null, val availability: GhAvailability = GhAvailability.OK)
|
||||
|
||||
internal const val PR_FIELDS = "number,state,isDraft,url,title"
|
||||
|
||||
/**
|
||||
* Resolves the pull request a checkout belongs to. A worktree can reach a PR in several ways —
|
||||
* Kilo's PR import, `gh pr checkout`, a hand-made `git worktree add`, a branch renamed locally, a
|
||||
* fork PR — so identity is resolved by branch config or head commit rather than by branch name
|
||||
* alone, in increasing order of cost:
|
||||
*
|
||||
* 1. `gh pr view` with no selector. The only form that honours `branch.<name>.merge`, so it
|
||||
* resolves `refs/pull/N/head` branches by PR number and fork PRs through the push remote.
|
||||
* 2. `gh pr view <branch>`. Matches same-repo branches pushed to origin, no branch config needed.
|
||||
* Cannot match a fork PR: gh compares against `owner:branch` for cross-repository heads.
|
||||
* 3. `gh pr list --search "<HEAD sha>"`, accepting only an exact `headRefOid` match.
|
||||
*
|
||||
* Commands are injected so the strategy ladder is testable without `gh` or network access.
|
||||
*/
|
||||
internal class PrResolver(
|
||||
private val gh: (Path, List<String>) -> CmdOut,
|
||||
private val git: (Path, List<String>) -> CmdOut,
|
||||
) {
|
||||
/**
|
||||
* Resolves the PR for the checkout at [path] on [branch]. [base] is the repository's base
|
||||
* branch; a PR headed by it is not worth a search query, so strategy 3 is skipped there.
|
||||
*/
|
||||
fun resolve(path: String, branch: String, base: String?): PrLookup {
|
||||
val dir = Path.of(path).normalize()
|
||||
view(dir, path, null)?.let { return it }
|
||||
view(dir, path, branch)?.let { return it }
|
||||
if (branch == base) return PrLookup()
|
||||
return search(dir, path) ?: PrLookup()
|
||||
}
|
||||
|
||||
/** Null means "no PR here, keep looking"; a value is terminal (a PR, or gh being unusable). */
|
||||
private fun view(dir: Path, path: String, branch: String?): PrLookup? {
|
||||
val args = buildList {
|
||||
add("pr")
|
||||
add("view")
|
||||
branch?.let { add(it) }
|
||||
add("--json")
|
||||
add(PR_FIELDS)
|
||||
}
|
||||
val out = gh(dir, args)
|
||||
if (!out.ok) return unusable(out.stderr)
|
||||
return parsePr(path, out.stdout)?.let { PrLookup(it) }
|
||||
}
|
||||
|
||||
private fun search(dir: Path, path: String): PrLookup? {
|
||||
val head = git(dir, listOf("rev-parse", "HEAD")).stdout.trim()
|
||||
if (head.isEmpty()) return null
|
||||
val out = gh(
|
||||
dir,
|
||||
listOf("pr", "list", "--state", "all", "--search", "$head is:pr", "--limit", "5", "--json", "$PR_FIELDS,headRefOid"),
|
||||
)
|
||||
if (!out.ok) return unusable(out.stderr)
|
||||
val items = runCatching { json.parseToJsonElement(out.stdout) as? JsonArray }.getOrNull() ?: return null
|
||||
for (item in items) {
|
||||
val obj = item as? JsonObject ?: continue
|
||||
// The search matches commit mentions too, so only an exact head match is our PR.
|
||||
if (obj["headRefOid"]?.jsonPrimitive?.content != head) continue
|
||||
parsePr(path, obj.toString())?.let { return PrLookup(it) }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun unusable(stderr: String): PrLookup? {
|
||||
val status = prError(stderr)
|
||||
return if (status == GhAvailability.OK) null else PrLookup(availability = status)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifies a failing `gh pr` command. A missing PR is the normal case, so anything that is not a
|
||||
* recognised authorization failure counts as OK — a missing `gh` binary is caught by the upfront
|
||||
* availability probe instead.
|
||||
*/
|
||||
internal fun prError(stderr: String): GhAvailability {
|
||||
val text = stderr.lowercase()
|
||||
if (text.contains("not logged") || text.contains("gh auth login") || text.contains("authentication")) {
|
||||
return GhAvailability.UNAUTH
|
||||
}
|
||||
return GhAvailability.OK
|
||||
}
|
||||
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
+20
-5
@@ -2,6 +2,7 @@ package ai.kilocode.backend.app
|
||||
|
||||
import ai.kilocode.backend.testing.TestLog
|
||||
import ai.kilocode.rpc.dto.ChatEventDto
|
||||
import ai.kilocode.rpc.dto.MessageErrorDto
|
||||
import ai.kilocode.rpc.dto.PermissionRequestDto
|
||||
import ai.kilocode.rpc.dto.QuestionInfoDto
|
||||
import ai.kilocode.rpc.dto.QuestionRequestDto
|
||||
@@ -55,7 +56,7 @@ class KiloBackendActivityManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `permission asked overlays running and reply reverts`() = runBlocking {
|
||||
fun `permission asked overlays running and reply reverts`() = runBlocking<Unit> {
|
||||
directories["ses_1"] = "/repo/wt"
|
||||
statuses.value = mapOf("ses_1" to SessionStatusDto("busy"))
|
||||
start()
|
||||
@@ -69,7 +70,7 @@ class KiloBackendActivityManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `question kinds distinguish plain and plan followup`() = runBlocking {
|
||||
fun `question kinds distinguish plain and plan followup`() = runBlocking<Unit> {
|
||||
directories["ses_plain"] = "/repo/a"
|
||||
directories["ses_plan"] = "/repo/b"
|
||||
start()
|
||||
@@ -115,12 +116,26 @@ class KiloBackendActivityManagerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `busy outranks a pending error so a resumed session runs`() = runBlocking {
|
||||
fun `aborted error does not badge the session`() = runBlocking {
|
||||
directories["ses_1"] = "/repo/wt"
|
||||
statuses.value = mapOf("ses_1" to SessionStatusDto("busy"))
|
||||
start()
|
||||
await("ses_1", SessionActivityKindDto.RUNNING)
|
||||
|
||||
events.emit(ChatEventDto.Error("ses_1", MessageErrorDto(MessageErrorDto.ABORTED, "aborted")))
|
||||
statuses.value = mapOf("ses_1" to SessionStatusDto("idle"))
|
||||
events.emit(ChatEventDto.SessionIdle("ses_1"))
|
||||
|
||||
withTimeout(5_000) { manager.activity.first { "ses_1" !in it } }
|
||||
assertFalse("ses_1" in manager.activity.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `busy outranks a pending provider error so a resumed session runs`() = runBlocking<Unit> {
|
||||
directories["ses_1"] = "/repo/wt"
|
||||
start()
|
||||
|
||||
// A Stop leaves the session errored and idle.
|
||||
events.emit(ChatEventDto.Error("ses_1"))
|
||||
events.emit(ChatEventDto.Error("ses_1", MessageErrorDto("APIError", "Provider failed")))
|
||||
await("ses_1", SessionActivityKindDto.ERROR)
|
||||
|
||||
// Resumed: busy arrives before anything clears the error.
|
||||
|
||||
+148
-3
@@ -1,5 +1,6 @@
|
||||
package ai.kilocode.backend.rpc
|
||||
|
||||
import ai.kilocode.rpc.parsePrUrl
|
||||
import ai.kilocode.rpc.dto.CreateWorktreeRequestDto
|
||||
import ai.kilocode.rpc.dto.GhAvailability
|
||||
import ai.kilocode.rpc.dto.GhState
|
||||
@@ -22,11 +23,13 @@ import kotlin.test.assertTrue
|
||||
|
||||
class KiloWorktreeRpcApiImplTest {
|
||||
private val repo: Path = Files.createTempDirectory("kilo-worktree")
|
||||
private val remote: Path = Files.createTempDirectory("kilo-origin")
|
||||
private val api = KiloWorktreeRpcApiImpl()
|
||||
|
||||
@AfterTest
|
||||
fun tearDown() {
|
||||
delete(repo)
|
||||
delete(remote)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -593,9 +596,118 @@ class KiloWorktreeRpcApiImplTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `parsePrHeadRef reads headRefName`() {
|
||||
assertEquals("feature/login", parsePrHeadRef("""{"headRefName":"feature/login","title":"x"}"""))
|
||||
assertEquals("", parsePrHeadRef("not json"))
|
||||
fun `parsePrHead reads head branch and repository`() {
|
||||
val same = parsePrHead("""{"headRefName":"feature/login","title":"x","isCrossRepository":false}""")
|
||||
assertEquals("feature/login", same.ref)
|
||||
assertFalse(same.cross)
|
||||
|
||||
val fork = parsePrHead(
|
||||
"""{"headRefName":"patch-1","isCrossRepository":true,"headRepositoryOwner":{"login":"Contributor"}}""",
|
||||
)
|
||||
assertEquals("patch-1", fork.ref)
|
||||
assertTrue(fork.cross)
|
||||
assertEquals("Contributor", fork.owner)
|
||||
|
||||
assertEquals(PrHead(), parsePrHead("not json"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `prBranchName prefixes fork heads and falls back to the pr number`() {
|
||||
assertEquals("feature/login", prBranchName(PrHead("feature/login"), 7))
|
||||
assertEquals("contributor/patch-1", prBranchName(PrHead("patch-1", cross = true, owner = "Contributor"), 7))
|
||||
// A cross-repo PR whose owner gh did not report still needs a usable branch name.
|
||||
assertEquals("patch-1", prBranchName(PrHead("patch-1", cross = true), 7))
|
||||
assertEquals("pr-7", prBranchName(PrHead(), 7))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `prTargets keeps the main tree and drops detached and prunable entries`() {
|
||||
val items = listOf(
|
||||
WorktreeDto("/repo", "repo", "main", "/repo", main = true),
|
||||
WorktreeDto("/repo/.kilo/worktrees/a", "a", "feature/a", "/repo/.kilo/worktrees/a"),
|
||||
WorktreeDto("/repo/.kilo/worktrees/detached", "detached", "(detached)", "/repo/.kilo/worktrees/detached"),
|
||||
WorktreeDto("/repo/.kilo/worktrees/gone", "gone", "feature/gone", "/repo/.kilo/worktrees/gone", prunable = true),
|
||||
)
|
||||
|
||||
assertEquals(listOf("/repo", "/repo/.kilo/worktrees/a"), prTargets(items).map { it.path })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `baseBranch reads the main tree branch and ignores a detached one`() {
|
||||
val main = WorktreeDto("/repo", "repo", "main", "/repo", main = true)
|
||||
val linked = WorktreeDto("/repo/.kilo/worktrees/a", "a", "feature/a", "/repo/.kilo/worktrees/a")
|
||||
|
||||
assertEquals("main", baseBranch(listOf(main, linked)))
|
||||
assertNull(baseBranch(listOf(main.copy(branch = "(detached)"), linked)))
|
||||
assertNull(baseBranch(listOf(linked)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetchPrBranch tracks the head branch for a same-repo pull request`() {
|
||||
initRepo()
|
||||
val origin = originWith(pull = 7, head = "feature/login")
|
||||
|
||||
val failure = fetchPrBranch(runner(repo), 7, PrHead("feature/login"), "feature/login")
|
||||
|
||||
assertNull(failure, "same-repo import should succeed")
|
||||
assertEquals("origin", config("branch.feature/login.remote"))
|
||||
assertEquals("refs/heads/feature/login", config("branch.feature/login.merge"))
|
||||
assertEquals(
|
||||
head(origin, "refs/heads/feature/login"),
|
||||
head(repo, "refs/heads/feature/login"),
|
||||
"local branch should point at the fetched head",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetchPrBranch falls back to the pull ref when the head branch is gone`() {
|
||||
initRepo()
|
||||
val origin = originWith(pull = 7, head = "feature/login")
|
||||
git(origin, "update-ref", "-d", "refs/heads/feature/login")
|
||||
|
||||
val failure = fetchPrBranch(runner(repo), 7, PrHead("feature/login"), "feature/login")
|
||||
|
||||
assertNull(failure, "import should fall back to the pull ref")
|
||||
assertEquals("refs/pull/7/head", config("branch.feature/login.merge"))
|
||||
assertEquals(head(origin, "refs/pull/7/head"), head(repo, "refs/heads/feature/login"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetchPrBranch tracks the pull ref for a fork pull request`() {
|
||||
initRepo()
|
||||
val origin = originWith(pull = 7, head = "patch-1")
|
||||
// A fork head is not on origin at all; only the pull ref can reach it.
|
||||
git(origin, "update-ref", "-d", "refs/heads/patch-1")
|
||||
val fork = PrHead("patch-1", cross = true, owner = "contributor")
|
||||
|
||||
val failure = fetchPrBranch(runner(repo), 7, fork, prBranchName(fork, 7))
|
||||
|
||||
assertNull(failure, "fork import should succeed")
|
||||
assertEquals("origin", config("branch.contributor/patch-1.remote"))
|
||||
assertEquals("refs/pull/7/head", config("branch.contributor/patch-1.merge"))
|
||||
assertEquals(head(origin, "refs/pull/7/head"), head(repo, "refs/heads/contributor/patch-1"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetchPrBranch force updates a branch left by an earlier import`() {
|
||||
initRepo()
|
||||
val origin = originWith(pull = 7, head = "feature/login")
|
||||
git(repo, "branch", "feature/login")
|
||||
|
||||
val failure = fetchPrBranch(runner(repo), 7, PrHead("feature/login"), "feature/login")
|
||||
|
||||
assertNull(failure, "re-import should refresh the stale branch")
|
||||
assertEquals(head(origin, "refs/heads/feature/login"), head(repo, "refs/heads/feature/login"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetchPrBranch reports the failing command`() {
|
||||
initRepo()
|
||||
|
||||
val failure = fetchPrBranch(runner(repo), 7, PrHead("feature/login"), "feature/login")
|
||||
|
||||
assertNotNull(failure, "a repo without origin cannot fetch a pull request")
|
||||
assertFalse(failure.ok)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -752,6 +864,39 @@ class KiloWorktreeRpcApiImplTest {
|
||||
git(repo, "commit", "-m", "init")
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an "origin" repository holding [head] plus a `refs/pull/<pull>/head` ref pointing at it,
|
||||
* the shape GitHub exposes for a pull request, and registers it as [repo]'s origin.
|
||||
*/
|
||||
private fun originWith(pull: Int, head: String): Path {
|
||||
git(remote, "init")
|
||||
git(remote, "config", "user.email", "test@kilo.ai")
|
||||
git(remote, "config", "user.name", "Kilo Test")
|
||||
Files.writeString(remote.resolve("README.md"), "origin")
|
||||
git(remote, "add", "README.md")
|
||||
git(remote, "commit", "-m", "init")
|
||||
val base = output(remote, "branch", "--show-current").trim()
|
||||
git(remote, "checkout", "-b", head)
|
||||
Files.writeString(remote.resolve("pr.txt"), "pr work\n")
|
||||
git(remote, "add", "pr.txt")
|
||||
git(remote, "commit", "-m", "pr work")
|
||||
git(remote, "update-ref", "refs/pull/$pull/head", "refs/heads/$head")
|
||||
// Leave the PR head unchecked out so tests can delete it to emulate a deleted branch.
|
||||
git(remote, "checkout", base)
|
||||
git(repo, "remote", "add", "origin", remote.toString())
|
||||
return remote
|
||||
}
|
||||
|
||||
private fun runner(dir: Path): (List<String>) -> CmdOut = { args ->
|
||||
val cmd = GeneralCommandLine(listOf("git") + args).withWorkDirectory(dir.toFile())
|
||||
val out = CapturingProcessHandler(cmd).runProcess(30_000)
|
||||
CmdOut(if (out.isTimeout) -1 else out.exitCode, out.stdout, out.stderr)
|
||||
}
|
||||
|
||||
private fun config(key: String): String = output(repo, "config", "--get", key).trim()
|
||||
|
||||
private fun head(dir: Path, ref: String): String = output(dir, "rev-parse", ref).trim()
|
||||
|
||||
private fun git(dir: Path, vararg args: String) {
|
||||
val cmd = GeneralCommandLine(listOf("git") + args).withWorkDirectory(dir.toFile())
|
||||
val out = CapturingProcessHandler(cmd).runProcess(30_000)
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package ai.kilocode.backend.rpc
|
||||
|
||||
import ai.kilocode.rpc.dto.GhAvailability
|
||||
import ai.kilocode.rpc.dto.GhState
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class PrResolverTest {
|
||||
private val path = "/repo/.kilo/worktrees/feature-x"
|
||||
private val calls = mutableListOf<List<String>>()
|
||||
|
||||
@Test
|
||||
fun `resolves through branch config without falling back`() {
|
||||
val resolver = resolver(view = { pr(7, "OPEN") })
|
||||
|
||||
val lookup = resolver.resolve(path, "feature/x", base = "main")
|
||||
|
||||
val pull = assertNotNull(lookup.pr)
|
||||
assertEquals(7, pull.number)
|
||||
assertEquals(path, pull.path)
|
||||
assertEquals(GhState.OPEN, pull.state)
|
||||
// The config-driven form answered, so the branch selector and the search never run.
|
||||
assertEquals(listOf(listOf("pr", "view", "--json", PR_FIELDS)), calls)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `falls back to the branch selector when config resolves nothing`() {
|
||||
val resolver = resolver(view = { args -> if (args.contains("feature/x")) pr(8, "DRAFT") else missing() })
|
||||
|
||||
val lookup = resolver.resolve(path, "feature/x", base = "main")
|
||||
|
||||
assertEquals(8, assertNotNull(lookup.pr).number)
|
||||
assertEquals(GhState.DRAFT, lookup.pr?.state)
|
||||
assertEquals(2, calls.size, "the head search should not run once the branch selector answered")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `falls back to searching the head commit`() {
|
||||
val resolver = resolver(
|
||||
view = { missing() },
|
||||
list = { ok("""[{"number":9,"state":"MERGED","isDraft":false,"url":"https://pr/9","title":"Fork work","headRefOid":"$SHA"}]""") },
|
||||
)
|
||||
|
||||
val lookup = resolver.resolve(path, "renamed-locally", base = "main")
|
||||
|
||||
val pull = assertNotNull(lookup.pr, "an exact head match should resolve the PR")
|
||||
assertEquals(9, pull.number)
|
||||
assertEquals(GhState.MERGED, pull.state)
|
||||
assertTrue(calls.any { it.contains("$SHA is:pr") }, "the search should use the head sha")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rejects a search hit whose head commit differs`() {
|
||||
val resolver = resolver(
|
||||
view = { missing() },
|
||||
// The GitHub search also matches PRs that merely mention the commit.
|
||||
list = { ok("""[{"number":9,"state":"OPEN","isDraft":false,"url":"https://pr/9","headRefOid":"deadbeef"}]""") },
|
||||
)
|
||||
|
||||
assertNull(resolver.resolve(path, "renamed-locally", base = "main").pr)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `skips the head search for the base branch`() {
|
||||
val resolver = resolver(view = { missing() }, list = { throw IllegalStateException("must not search") })
|
||||
|
||||
assertNull(resolver.resolve("/repo", "main", base = "main").pr)
|
||||
assertEquals(2, calls.size, "only the two view forms should run for the base branch")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reports an authorization failure instead of a missing pull request`() {
|
||||
val resolver = resolver(view = { CmdOut(1, "", "gh auth login required") })
|
||||
|
||||
val lookup = resolver.resolve(path, "feature/x", base = "main")
|
||||
|
||||
assertNull(lookup.pr)
|
||||
assertEquals(GhAvailability.UNAUTH, lookup.availability)
|
||||
assertEquals(1, calls.size, "an unusable gh must stop the ladder immediately")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `treats a missing pull request as a clean result`() {
|
||||
val resolver = resolver(view = { missing() }, list = { ok("[]") })
|
||||
|
||||
val lookup = resolver.resolve(path, "feature/x", base = "main")
|
||||
|
||||
assertNull(lookup.pr)
|
||||
assertEquals(GhAvailability.OK, lookup.availability)
|
||||
}
|
||||
|
||||
private fun resolver(
|
||||
view: (List<String>) -> CmdOut,
|
||||
list: (List<String>) -> CmdOut = { ok("[]") },
|
||||
): PrResolver = PrResolver(
|
||||
gh = { _, args ->
|
||||
calls.add(args)
|
||||
if (args.getOrNull(1) == "list") list(args) else view(args)
|
||||
},
|
||||
git = { _, args ->
|
||||
calls.add(args)
|
||||
assertEquals(listOf("rev-parse", "HEAD"), args)
|
||||
ok("$SHA\n")
|
||||
},
|
||||
)
|
||||
|
||||
private fun pr(number: Int, state: String): CmdOut =
|
||||
ok("""{"number":$number,"state":"$state","isDraft":${state == "DRAFT"},"url":"https://pr/$number","title":"Work"}""")
|
||||
|
||||
private fun ok(stdout: String) = CmdOut(0, stdout, "")
|
||||
|
||||
private fun missing() = CmdOut(1, "", "no pull requests found for branch \"feature/x\"")
|
||||
|
||||
private companion object {
|
||||
const val SHA = "1111111111111111111111111111111111111111"
|
||||
}
|
||||
}
|
||||
+34
-5
@@ -1,8 +1,11 @@
|
||||
package ai.kilocode.client.agentManager
|
||||
|
||||
import ai.kilocode.client.KiloNotifications
|
||||
import ai.kilocode.client.agentManager.worktree.CreateFailure
|
||||
import ai.kilocode.client.agentManager.worktree.CreateKind
|
||||
import ai.kilocode.client.agentManager.worktree.NewWorktreeDialog
|
||||
import ai.kilocode.client.agentManager.worktree.NewWorktreeHandle
|
||||
import ai.kilocode.client.agentManager.worktree.NewWorktreePlan
|
||||
import ai.kilocode.client.agentManager.worktree.GhBanner
|
||||
import ai.kilocode.client.agentManager.worktree.WorktreeController
|
||||
import ai.kilocode.client.agentManager.worktree.WorktreeDataKeys
|
||||
@@ -24,6 +27,7 @@ import ai.kilocode.client.diff.diffParams
|
||||
import ai.kilocode.client.diff.ensureDiffEditorKind
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.SessionActivityKind
|
||||
import ai.kilocode.client.telemetry.Telemetry
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.client.ui.list.ActiveList
|
||||
import ai.kilocode.client.ui.list.ActiveListBadge
|
||||
@@ -133,6 +137,13 @@ class AgentManagerPanel(
|
||||
if (list.select(key)) list.focusList()
|
||||
item(key)?.takeIf { controller.progress(it.id) == null }?.let { open(it, focus = false) }
|
||||
}
|
||||
// A fresh worktree changes what git reports, so bypass the refresh throttle instead of
|
||||
// leaving the new row without its stats and PR badge until the next poll.
|
||||
controller.onCreated = {
|
||||
project?.service<WorktreeStatusService>()?.refreshStats()
|
||||
project?.service<WorktreeStatusService>()?.refreshPr(force = true)
|
||||
}
|
||||
controller.onReload = { sync() }
|
||||
controller.onCreateFailure = { err -> notifyCreateFailed(err) }
|
||||
controller.onMoveFailure = { err -> notifyMoveFailed(err) }
|
||||
controller.onRemoveSuccess = { item, index -> onRemoved(item, index) }
|
||||
@@ -183,7 +194,17 @@ class AgentManagerPanel(
|
||||
if (!handle.showAndGet()) return
|
||||
val plan = handle.result() ?: return
|
||||
onCreate()
|
||||
controller.create(plan.branch, plan.base, prompt = plan.prompt)
|
||||
when (plan) {
|
||||
is NewWorktreePlan.Create -> controller.create(plan.branch, plan.base, prompt = plan.prompt)
|
||||
is NewWorktreePlan.Branch -> {
|
||||
Telemetry.send("Worktree Import Submitted", mapOf("kind" to "branch"))
|
||||
controller.importBranch(plan.branch)
|
||||
}
|
||||
is NewWorktreePlan.Pr -> {
|
||||
Telemetry.send("Worktree Import Submitted", mapOf("kind" to "pr"))
|
||||
controller.importPr(plan.url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun move(sessionId: String?, directory: String) = controller.move(sessionId, directory)
|
||||
@@ -241,7 +262,7 @@ class AgentManagerPanel(
|
||||
|
||||
/** The PR URL for [item], or null when it has none or is not in a stable, openable state. */
|
||||
private fun prUrl(item: WorktreeDto?): String? {
|
||||
if (item == null || item.main) return null
|
||||
if (item == null) return null
|
||||
if (controller.progress(item.id) != null) return null
|
||||
return prs[normalizeWorktreePath(item.path)]?.url
|
||||
}
|
||||
@@ -322,8 +343,13 @@ class AgentManagerPanel(
|
||||
return controller.model.getElementAt(index.coerceIn(0, size - 1))
|
||||
}
|
||||
|
||||
private fun notifyCreateFailed(err: String?) {
|
||||
KiloNotifications.error(project, KiloBundle.message("worktree.create.failed.title"), err)
|
||||
private fun notifyCreateFailed(failure: CreateFailure) {
|
||||
val title = when (failure.kind) {
|
||||
CreateKind.CREATE -> KiloBundle.message("worktree.create.failed.title")
|
||||
CreateKind.BRANCH -> KiloBundle.message("worktree.import.branch.failed.title", failure.branch)
|
||||
CreateKind.PR -> KiloBundle.message("worktree.import.pr.failed.title")
|
||||
}
|
||||
KiloNotifications.error(project, title, failure.error)
|
||||
}
|
||||
|
||||
private fun notifyMoveFailed(err: String?) {
|
||||
@@ -374,7 +400,8 @@ class AgentManagerPanel(
|
||||
progress = null,
|
||||
kind = controller.kind(item.path),
|
||||
stats = null,
|
||||
pr = null,
|
||||
// The main checkout can sit on a PR branch just like a worktree can.
|
||||
pr = prs[normalizeWorktreePath(item.path)],
|
||||
current = true,
|
||||
)
|
||||
}
|
||||
@@ -434,6 +461,8 @@ class AgentManagerPanel(
|
||||
|
||||
override fun dispose() {
|
||||
controller.onSelect = null
|
||||
controller.onCreated = null
|
||||
controller.onReload = null
|
||||
controller.onCreateFailure = null
|
||||
controller.onMoveFailure = null
|
||||
controller.onRemoveSuccess = null
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package ai.kilocode.client.agentManager.worktree
|
||||
|
||||
import ai.kilocode.client.session.ui.prompt.PromptFuzzyRanker
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.ui.DocumentAdapter
|
||||
import java.awt.Dimension
|
||||
import java.awt.event.FocusAdapter
|
||||
import java.awt.event.FocusEvent
|
||||
import javax.swing.ComboBoxModel
|
||||
import javax.swing.DefaultComboBoxModel
|
||||
import javax.swing.JTextField
|
||||
import javax.swing.event.DocumentEvent
|
||||
import javax.swing.plaf.basic.BasicComboBoxUI
|
||||
import javax.swing.plaf.basic.BasicComboPopup
|
||||
|
||||
internal class BranchPicker(branches: List<String>, private val default: String = "") :
|
||||
ComboBox<String>(model(branches, default)) {
|
||||
private val branches = ordered(branches, default)
|
||||
private val set = this.branches.toSet()
|
||||
private var syncing = false
|
||||
|
||||
val empty: Boolean get() = branches.isEmpty()
|
||||
|
||||
init {
|
||||
isEditable = true
|
||||
if (default.isNotBlank()) selectedItem = default
|
||||
wire()
|
||||
}
|
||||
|
||||
override fun getPreferredSize(): Dimension {
|
||||
ensureEditor()
|
||||
return super.getPreferredSize()
|
||||
}
|
||||
|
||||
fun resolve(): String? {
|
||||
val value = text()
|
||||
if (value.isEmpty()) {
|
||||
val fallback = default.trim()
|
||||
if (fallback.isNotEmpty()) set(fallback)
|
||||
return fallback.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
if (value in set) return value
|
||||
val idx = match(value) ?: return value
|
||||
val target = branches[idx]
|
||||
set(target)
|
||||
return target
|
||||
}
|
||||
|
||||
fun known(value: String?): Boolean = value == null || value in set
|
||||
|
||||
fun focusText() {
|
||||
field()?.apply {
|
||||
requestFocusInWindow()
|
||||
selectAll()
|
||||
}
|
||||
}
|
||||
|
||||
private fun wire() {
|
||||
val field = field() ?: return
|
||||
field.document.addDocumentListener(object : DocumentAdapter() {
|
||||
override fun textChanged(e: DocumentEvent) {
|
||||
if (!syncing) sync(field.text, popup = true)
|
||||
}
|
||||
})
|
||||
field.addFocusListener(object : FocusAdapter() {
|
||||
override fun focusLost(e: FocusEvent) {
|
||||
restore()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun restore() {
|
||||
if (default.isBlank() || text().isNotEmpty()) return
|
||||
set(default)
|
||||
}
|
||||
|
||||
private fun sync(text: String, popup: Boolean) {
|
||||
val value = text.trim()
|
||||
if (value.isEmpty()) return
|
||||
if (popup && isShowing && !isPopupVisible) isPopupVisible = true
|
||||
val idx = match(value) ?: return
|
||||
val list = popupList() ?: return
|
||||
if (list.selectedIndex != idx) list.selectedIndex = idx
|
||||
list.ensureIndexIsVisible(idx)
|
||||
}
|
||||
|
||||
private fun match(text: String): Int? {
|
||||
val rank = PromptFuzzyRanker(text)
|
||||
return branches.withIndex().mapNotNull { item ->
|
||||
rank.score(item.value, emptyList())?.let { score -> item.index to score }
|
||||
}.maxByOrNull { it.second }?.first
|
||||
}
|
||||
|
||||
private fun popupList() = (getAccessibleContext()?.getAccessibleChild(0) as? BasicComboPopup)?.list
|
||||
|
||||
private fun field() = editor.editorComponent as? JTextField
|
||||
|
||||
private fun text() = field()?.text?.trim() ?: editor.item?.toString()?.trim().orEmpty()
|
||||
|
||||
private fun ensureEditor() {
|
||||
if (!isEditable) return
|
||||
val ui = ui as? BasicComboBoxUI ?: return
|
||||
val comp = editor.editorComponent ?: return
|
||||
if (components.none { it === comp }) ui.addEditor()
|
||||
}
|
||||
|
||||
private fun set(value: String) {
|
||||
syncing = true
|
||||
try {
|
||||
selectedItem = value
|
||||
field()?.text = value
|
||||
} finally {
|
||||
syncing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ordered(branches: List<String>, default: String): List<String> {
|
||||
val ordered = LinkedHashSet<String>()
|
||||
if (default.isNotBlank()) ordered.add(default)
|
||||
ordered.addAll(branches)
|
||||
return ordered.toList()
|
||||
}
|
||||
|
||||
private fun model(branches: List<String>, default: String): ComboBoxModel<String> {
|
||||
return DefaultComboBoxModel(ordered(branches, default).toTypedArray())
|
||||
}
|
||||
+141
-116
@@ -10,22 +10,29 @@ import ai.kilocode.client.session.ui.model.ModelPicker
|
||||
import ai.kilocode.client.session.ui.model.modelItems
|
||||
import ai.kilocode.client.session.ui.prompt.KiloPromptCompletionProvider
|
||||
import ai.kilocode.client.session.ui.prompt.MentionAction
|
||||
import ai.kilocode.client.session.ui.prompt.PromptFuzzyRanker
|
||||
import ai.kilocode.client.session.ui.prompt.PromptPanel
|
||||
import ai.kilocode.client.session.ui.prompt.SlashAction
|
||||
import ai.kilocode.client.settings.base.BaseContentPanel
|
||||
import ai.kilocode.client.settings.base.SettingsRows
|
||||
import ai.kilocode.client.settings.base.SettingsStackedRow
|
||||
import ai.kilocode.client.ui.UiStyle
|
||||
import ai.kilocode.client.ui.layout.Stack
|
||||
import ai.kilocode.rpc.dto.ModelsWorkspaceDto
|
||||
import ai.kilocode.rpc.parsePrUrl
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
import com.intellij.ui.DocumentAdapter
|
||||
import com.intellij.ui.components.JBTextField
|
||||
import com.intellij.ui.tabs.JBTabs
|
||||
import com.intellij.ui.tabs.JBTabsFactory
|
||||
import com.intellij.ui.tabs.JBTabsPosition
|
||||
import com.intellij.ui.tabs.TabInfo
|
||||
import com.intellij.ui.tabs.TabsListener
|
||||
import com.intellij.util.ui.FormBuilder
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.components.BorderLayoutPanel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
@@ -33,19 +40,16 @@ import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import java.awt.Component
|
||||
import java.awt.GridBagConstraints
|
||||
import java.awt.event.FocusAdapter
|
||||
import java.awt.event.FocusEvent
|
||||
import javax.swing.ComboBoxModel
|
||||
import javax.swing.DefaultComboBoxModel
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JTextField
|
||||
import javax.swing.event.DocumentEvent
|
||||
import javax.swing.plaf.basic.BasicComboPopup
|
||||
|
||||
private const val NAME_COLUMNS = 67
|
||||
|
||||
/** What the user confirmed in the New Worktree dialog. */
|
||||
data class NewWorktreePlan(val branch: String, val base: String?, val prompt: PendingPrompt?)
|
||||
sealed interface NewWorktreePlan {
|
||||
data class Create(val branch: String, val base: String?, val prompt: PendingPrompt?) : NewWorktreePlan
|
||||
data class Branch(val branch: String) : NewWorktreePlan
|
||||
data class Pr(val url: String) : NewWorktreePlan
|
||||
}
|
||||
|
||||
/**
|
||||
* The New Worktree dialog as seen by its caller: show it, then read what the user confirmed.
|
||||
@@ -57,10 +61,16 @@ interface NewWorktreeHandle {
|
||||
}
|
||||
|
||||
/**
|
||||
* New Worktree dialog with parity to the VS Code Agent Manager dialog: a worktree name (top), an
|
||||
* initial prompt with the same mode / model / reasoning pickers as the chat prompt (center), and the
|
||||
* branch name + base branch (bottom). Creating a worktree starts a session automatically with the
|
||||
* prompt.
|
||||
* New Worktree dialog with parity to the VS Code Agent Manager dialog, split into three tabs:
|
||||
*
|
||||
* - **New** creates a branch: a worktree name (top), an initial prompt with the same mode / model /
|
||||
* reasoning pickers as the chat prompt (center), and the branch name + base branch (bottom).
|
||||
* Creating a worktree starts a session automatically with the prompt.
|
||||
* - **From PR** checks out a GitHub pull request by URL.
|
||||
* - **From Branch** checks out a local branch that no worktree holds yet.
|
||||
*
|
||||
* Both import tabs carry no initial prompt, so the worktree opens with an empty session. The
|
||||
* selected tab alone decides which input the OK button acts on.
|
||||
*
|
||||
* The dialog performs no worktree work itself — it records the confirmed [result] and closes; the
|
||||
* panel then drives the controller, so no view switch or worktree work runs while the modal dialog
|
||||
@@ -103,13 +113,12 @@ internal class NewWorktreeDialog(
|
||||
showEnhance = false,
|
||||
)
|
||||
private val branch = JBTextField(suggestedName)
|
||||
private val bases = baseBranches(branches, defaultBase)
|
||||
private val baseSet = bases.toSet()
|
||||
private val base = ComboBox(baseModel(bases)).apply {
|
||||
isEditable = true
|
||||
selectedItem = defaultBase
|
||||
private val base = BranchPicker(branches, defaultBase)
|
||||
private val url = JBTextField().apply {
|
||||
emptyText.text = KiloBundle.message("worktree.import.pr.placeholder")
|
||||
}
|
||||
private var syncing = false
|
||||
private val pick = BranchPicker(branches)
|
||||
private var tab = DialogTab.NEW
|
||||
|
||||
private var plan: NewWorktreePlan? = null
|
||||
|
||||
@@ -128,25 +137,25 @@ internal class NewWorktreeDialog(
|
||||
private var center: JComponent? = null
|
||||
|
||||
init {
|
||||
wireBase()
|
||||
if (pick.empty) pick.isEnabled = false
|
||||
title = KiloBundle.message("worktree.configure.title")
|
||||
init()
|
||||
setOKButtonText(KiloBundle.message("worktree.dialog.create"))
|
||||
}
|
||||
|
||||
override fun createCenterPanel(): JComponent = content().also { center = it }
|
||||
override fun createCenterPanel(): JComponent = tabs().also { center = it }
|
||||
|
||||
/** The built content, so tests can drive the real Swing tree before the dialog is shown. */
|
||||
internal fun centerComponent(): JComponent = center ?: error("center panel not built")
|
||||
|
||||
override fun result(): NewWorktreePlan? = plan
|
||||
|
||||
override fun getPreferredFocusedComponent(): JComponent = prompt.defaultFocusedComponent
|
||||
override fun getPreferredFocusedComponent(): JComponent = focus()
|
||||
|
||||
// Versioned: DialogWrapper persists the size per key, so a stale entry would keep the old width.
|
||||
override fun getDimensionServiceKey(): String = "ai.kilocode.NewWorktreeDialog.v2"
|
||||
override fun getDimensionServiceKey(): String = "ai.kilocode.NewWorktreeDialog.v3"
|
||||
|
||||
override fun doOKAction() = submitCreate()
|
||||
override fun doOKAction() = submit()
|
||||
|
||||
override fun dispose() {
|
||||
disposed = true
|
||||
@@ -154,7 +163,50 @@ internal class NewWorktreeDialog(
|
||||
super.dispose()
|
||||
}
|
||||
|
||||
private fun content(): JComponent {
|
||||
internal fun submit() {
|
||||
setErrorText(null)
|
||||
when (tab) {
|
||||
DialogTab.PR -> submitPr()
|
||||
DialogTab.BRANCH -> submitBranch()
|
||||
DialogTab.NEW -> submitCreate()
|
||||
}
|
||||
}
|
||||
|
||||
private fun tabs(): JComponent {
|
||||
val fresh = TabInfo(newContent()).setText(KiloBundle.message("worktree.dialog.tab.new"))
|
||||
val pr = TabInfo(prContent()).setText(KiloBundle.message("worktree.dialog.tab.pr"))
|
||||
val local = TabInfo(branchContent()).setText(KiloBundle.message("worktree.dialog.tab.branch"))
|
||||
val tabs: JBTabs = JBTabsFactory.createTabs(project, disposable).apply {
|
||||
presentation.setSingleRow(true)
|
||||
presentation.setTabsPosition(JBTabsPosition.top)
|
||||
presentation.showBorder = false
|
||||
addTab(fresh).setPreferredFocusableComponent(prompt.defaultFocusedComponent)
|
||||
addTab(pr).setPreferredFocusableComponent(url)
|
||||
addTab(local).setPreferredFocusableComponent(pick)
|
||||
addListener(object : TabsListener {
|
||||
override fun beforeSelectionChanged(oldSelection: TabInfo?, newSelection: TabInfo?) {
|
||||
// JBTabs defers removing the old body while focus settles, and that body keeps
|
||||
// its previous bounds. Hide it before layout so stale content cannot paint over
|
||||
// the newly selected tab.
|
||||
newSelection?.component?.isVisible = true
|
||||
oldSelection?.component?.isVisible = false
|
||||
}
|
||||
|
||||
override fun selectionChanged(oldSelection: TabInfo?, newSelection: TabInfo?) {
|
||||
tab = when {
|
||||
newSelection === pr -> DialogTab.PR
|
||||
newSelection === local -> DialogTab.BRANCH
|
||||
else -> DialogTab.NEW
|
||||
}
|
||||
setOKButtonText(KiloBundle.message(if (tab == DialogTab.NEW) "worktree.dialog.create" else "worktree.dialog.import"))
|
||||
ui { focus().requestFocusInWindow() }
|
||||
}
|
||||
}, disposable)
|
||||
}
|
||||
return tabs.component
|
||||
}
|
||||
|
||||
private fun newContent(): JComponent {
|
||||
wirePickers()
|
||||
loadModels()
|
||||
return Stack.vertical(gap = UiStyle.Gap.pad())
|
||||
@@ -164,6 +216,27 @@ internal class NewWorktreeDialog(
|
||||
.apply { border = JBUI.Borders.empty(UiStyle.Gap.sm()) }
|
||||
}
|
||||
|
||||
private fun prContent(): JComponent = importContent(SettingsStackedRow(
|
||||
KiloBundle.message("worktree.import.pr.section"),
|
||||
description = KiloBundle.message("worktree.import.pr.description"),
|
||||
value = url,
|
||||
))
|
||||
|
||||
private fun branchContent(): JComponent = importContent(SettingsStackedRow(
|
||||
KiloBundle.message("worktree.import.branch.section"),
|
||||
description = KiloBundle.message(if (pick.empty) "worktree.import.branch.empty" else "worktree.import.branch.description"),
|
||||
value = pick,
|
||||
))
|
||||
|
||||
private fun importContent(row: JComponent): JComponent {
|
||||
val body = BaseContentPanel().apply {
|
||||
border = JBUI.Borders.empty(UiStyle.Gap.pad(), UiStyle.Gap.sm(), UiStyle.Gap.pad(), UiStyle.Gap.sm())
|
||||
}
|
||||
body.next(SettingsRows().row(row))
|
||||
// Pinned to the top: the import forms are shorter than the New tab, which sizes the dialog.
|
||||
return BorderLayoutPanel().apply { addToTop(body) }
|
||||
}
|
||||
|
||||
// A FormBuilder that stretches every field to the full width, so the base-branch combo matches
|
||||
// the name field and prompt above it.
|
||||
private fun fields(): JComponent = object : FormBuilder() {
|
||||
@@ -229,99 +302,60 @@ internal class NewWorktreeDialog(
|
||||
)
|
||||
}
|
||||
|
||||
private fun wireBase() {
|
||||
val field = baseField() ?: return
|
||||
field.document.addDocumentListener(object : DocumentAdapter() {
|
||||
override fun textChanged(e: DocumentEvent) {
|
||||
if (!syncing) syncBase(field.text, popup = true)
|
||||
}
|
||||
})
|
||||
field.addFocusListener(object : FocusAdapter() {
|
||||
override fun focusLost(e: FocusEvent) {
|
||||
restoreBase()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun restoreBase() {
|
||||
if (baseText().isNotEmpty() || defaultBase.isBlank()) return
|
||||
setBase(defaultBase)
|
||||
}
|
||||
|
||||
private fun syncBase(text: String, popup: Boolean) {
|
||||
val value = text.trim()
|
||||
if (value.isEmpty()) return
|
||||
if (popup && base.isShowing && !base.isPopupVisible) {
|
||||
base.isPopupVisible = true
|
||||
}
|
||||
val idx = matchBase(value) ?: return
|
||||
val list = popupList() ?: return
|
||||
if (list.selectedIndex != idx) list.selectedIndex = idx
|
||||
list.ensureIndexIsVisible(idx)
|
||||
}
|
||||
|
||||
private fun matchBase(text: String): Int? {
|
||||
val rank = PromptFuzzyRanker(text)
|
||||
return bases.withIndex().mapNotNull { item ->
|
||||
rank.score(item.value, emptyList())?.let { score -> item.index to score }
|
||||
}.maxByOrNull { it.second }?.first
|
||||
}
|
||||
|
||||
private fun popupList() = (base.accessibleContext?.getAccessibleChild(0) as? BasicComboPopup)?.list
|
||||
|
||||
private fun baseField() = base.editor.editorComponent as? JTextField
|
||||
|
||||
private fun baseText() = baseField()?.text?.trim()
|
||||
?: base.editor.item?.toString()?.trim().orEmpty()
|
||||
|
||||
private fun setBase(value: String) {
|
||||
syncing = true
|
||||
try {
|
||||
base.selectedItem = value
|
||||
baseField()?.text = value
|
||||
} finally {
|
||||
syncing = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolvedBase(): String? {
|
||||
val value = baseText()
|
||||
if (value.isEmpty()) {
|
||||
val fallback = defaultBase.trim()
|
||||
if (fallback.isNotEmpty()) setBase(fallback)
|
||||
return fallback.takeIf { it.isNotEmpty() }
|
||||
}
|
||||
if (value in baseSet) return value
|
||||
val idx = matchBase(value) ?: return value
|
||||
val target = bases[idx]
|
||||
setBase(target)
|
||||
return target
|
||||
}
|
||||
|
||||
private fun validBase(value: String?): Boolean {
|
||||
if (value == null || value in baseSet) return true
|
||||
if (base.known(value)) return true
|
||||
KiloNotifications.error(
|
||||
project,
|
||||
KiloBundle.message("worktree.configure.base.invalid.title"),
|
||||
KiloBundle.message("worktree.configure.base.invalid.content", value),
|
||||
KiloBundle.message("worktree.configure.base.invalid.content", value.orEmpty()),
|
||||
)
|
||||
baseField()?.apply {
|
||||
requestFocusInWindow()
|
||||
selectAll()
|
||||
}
|
||||
syncBase(value, popup = true)
|
||||
base.focusText()
|
||||
return false
|
||||
}
|
||||
|
||||
private fun submitCreate(text: String = prompt.text()) {
|
||||
val explicit = branch.text.trim()
|
||||
val resolved = explicit.ifEmpty { name.text.trim() }.ifEmpty { suggestedName }
|
||||
val target = resolvedBase()
|
||||
val target = base.resolve()
|
||||
if (!validBase(target)) return
|
||||
plan = NewWorktreePlan(resolved, target, pending(text))
|
||||
plan = NewWorktreePlan.Create(resolved, target, pending(text))
|
||||
close(OK_EXIT_CODE)
|
||||
}
|
||||
|
||||
private fun submitPr() {
|
||||
val value = url.text.trim()
|
||||
if (value.isEmpty()) {
|
||||
setErrorText(KiloBundle.message("worktree.import.pr.required"), url)
|
||||
url.requestFocusInWindow()
|
||||
return
|
||||
}
|
||||
if (parsePrUrl(value) == null) {
|
||||
setErrorText(KiloBundle.message("worktree.import.pr.invalid"), url)
|
||||
url.requestFocusInWindow()
|
||||
url.selectAll()
|
||||
return
|
||||
}
|
||||
plan = NewWorktreePlan.Pr(value)
|
||||
close(OK_EXIT_CODE)
|
||||
}
|
||||
|
||||
private fun submitBranch() {
|
||||
val target = pick.resolve()
|
||||
if (target == null || !pick.known(target)) {
|
||||
setErrorText(KiloBundle.message("worktree.import.branch.invalid"), pick)
|
||||
pick.focusText()
|
||||
return
|
||||
}
|
||||
plan = NewWorktreePlan.Branch(target)
|
||||
close(OK_EXIT_CODE)
|
||||
}
|
||||
|
||||
private fun focus(): JComponent = when (tab) {
|
||||
DialogTab.PR -> url
|
||||
DialogTab.BRANCH -> pick
|
||||
DialogTab.NEW -> prompt.defaultFocusedComponent
|
||||
}
|
||||
|
||||
/** Bundles the typed prompt with the picked mode / model / reasoning, or null when empty. */
|
||||
private fun pending(text: String): PendingPrompt? {
|
||||
val body = text.trim()
|
||||
@@ -361,16 +395,7 @@ internal class NewWorktreeDialog(
|
||||
spec.available,
|
||||
)
|
||||
|
||||
private fun baseBranches(branches: List<String>, default: String): List<String> {
|
||||
val ordered = LinkedHashSet<String>()
|
||||
if (default.isNotBlank()) ordered.add(default)
|
||||
ordered.addAll(branches)
|
||||
return ordered.toList()
|
||||
}
|
||||
|
||||
private fun baseModel(branches: List<String>): ComboBoxModel<String> {
|
||||
return DefaultComboBoxModel(branches.toTypedArray())
|
||||
}
|
||||
|
||||
private fun variantTitle(value: String): String = value.replaceFirstChar { it.titlecase() }
|
||||
|
||||
private enum class DialogTab { NEW, PR, BRANCH }
|
||||
}
|
||||
|
||||
+30
-6
@@ -20,6 +20,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
enum class CreateKind { CREATE, BRANCH, PR }
|
||||
|
||||
data class CreateFailure(val error: String?, val kind: CreateKind, val branch: String)
|
||||
|
||||
/**
|
||||
* Owns the worktree list model and drives the [KiloWorktreeService] off the EDT. Model mutations
|
||||
* are marshalled back onto the EDT via [edt]. Mirrors the History stack's controller shape.
|
||||
@@ -37,7 +41,17 @@ class WorktreeController(
|
||||
private val tasks = LinkedHashMap<String, String>()
|
||||
private val moves = LinkedHashSet<String>()
|
||||
var onSelect: ((String) -> Unit)? = null
|
||||
var onCreateFailure: ((String?) -> Unit)? = null
|
||||
|
||||
/** Fired on the EDT once a worktree exists, so callers can refresh state git just changed. */
|
||||
var onCreated: ((WorktreeDto) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* Fired on the EDT after a reload settled. Replacing the model only notifies list listeners when
|
||||
* the rows actually changed, so a repo whose sole worktree is the main one would otherwise never
|
||||
* render its [current] row.
|
||||
*/
|
||||
var onReload: (() -> Unit)? = null
|
||||
var onCreateFailure: ((CreateFailure) -> Unit)? = null
|
||||
var onMoveFailure: ((String?) -> Unit)? = null
|
||||
var onRemoveSuccess: ((WorktreeDto, Int) -> Unit)? = null
|
||||
var onActivityChanged: (() -> Unit)? = null
|
||||
@@ -97,6 +111,7 @@ class WorktreeController(
|
||||
val worktreeBranches = rows.mapTo(HashSet()) { it.branch }
|
||||
branches = branchInfo.branches.filter { it !in worktreeBranches }
|
||||
known = branchInfo.branches.toMutableSet().apply { addAll(rows.map { it.branch }) }
|
||||
onReload?.invoke()
|
||||
telemetry("Worktree List Loaded", mapOf("count" to extra.size.toString()))
|
||||
}
|
||||
}
|
||||
@@ -109,13 +124,19 @@ class WorktreeController(
|
||||
fun quickCreate() = create(suggestName(), defaultBranch)
|
||||
|
||||
/** Imports a worktree that checks out an existing local branch. */
|
||||
fun importBranch(branch: String) = create(branch, base = null, existingBranch = true)
|
||||
fun importBranch(branch: String) = create(branch, base = null, existingBranch = true, kind = CreateKind.BRANCH)
|
||||
|
||||
/**
|
||||
* Creates a worktree. When [prompt] is set, it is stashed for the worktree's first session so the
|
||||
* editor auto-sends it once it opens with its picked mode/model (see [PendingWorktreePrompt]).
|
||||
*/
|
||||
fun create(branch: String, base: String?, existingBranch: Boolean = false, prompt: PendingPrompt? = null) {
|
||||
fun create(
|
||||
branch: String,
|
||||
base: String?,
|
||||
existingBranch: Boolean = false,
|
||||
prompt: PendingPrompt? = null,
|
||||
kind: CreateKind = CreateKind.CREATE,
|
||||
) {
|
||||
val id = "pending:$branch:${System.nanoTime()}"
|
||||
val temp = WorktreeDto(id, branch, branch, id)
|
||||
edt {
|
||||
@@ -126,7 +147,7 @@ class WorktreeController(
|
||||
}
|
||||
cs.launch {
|
||||
val result = service.create(directory, CreateWorktreeRequestDto(branch, base, existingBranch))
|
||||
finishCreate(temp, branch, prompt, result)
|
||||
finishCreate(temp, branch, prompt, result, kind)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +162,7 @@ class WorktreeController(
|
||||
}
|
||||
cs.launch {
|
||||
val result = service.importPr(directory, url)
|
||||
finishCreate(temp, "pr", null, result)
|
||||
finishCreate(temp, "pr", null, result, CreateKind.PR)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +171,7 @@ class WorktreeController(
|
||||
branch: String,
|
||||
prompt: PendingPrompt?,
|
||||
result: CreateWorktreeResultDto,
|
||||
kind: CreateKind,
|
||||
) {
|
||||
val created = result.worktree
|
||||
edt {
|
||||
@@ -161,12 +183,13 @@ class WorktreeController(
|
||||
cache().put(created)
|
||||
prompt?.let { service<PendingWorktreePrompt>().put(created.path, it) }
|
||||
onSelect?.invoke(created.id)
|
||||
onCreated?.invoke(created)
|
||||
telemetry("Worktree Created", mapOf("branch" to branch))
|
||||
return@edt
|
||||
}
|
||||
if (idx >= 0) model.remove(temp)
|
||||
telemetry("Worktree Create Failed", mapOf("branch" to branch))
|
||||
onCreateFailure?.invoke(result.error)
|
||||
onCreateFailure?.invoke(CreateFailure(result.error, kind, branch))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,6 +272,7 @@ class WorktreeController(
|
||||
// open; the tab's identity stays the worktree path alone.
|
||||
event.session?.let { service<PendingWorktreeSession>().put(worktree.path, it) }
|
||||
onSelect?.invoke(worktree.id)
|
||||
onCreated?.invoke(worktree)
|
||||
telemetry(
|
||||
"Continue in Worktree",
|
||||
mapOf("surface" to "sidebar", "session" to (sessionId != null).toString()),
|
||||
|
||||
+2
@@ -400,6 +400,8 @@ class SessionUi(
|
||||
outcome = SessionOutcomeView(
|
||||
selection = selection,
|
||||
focus = focus,
|
||||
retry = if (readonly) null else controller::retry,
|
||||
retryable = controller::canRetry,
|
||||
)
|
||||
messageBody = SessionMessageListPanel(
|
||||
controller.model,
|
||||
|
||||
+121
-7
@@ -24,7 +24,6 @@ import ai.kilocode.client.session.ui.mode.agentTitle
|
||||
import ai.kilocode.client.session.model.ToolCallRef
|
||||
import ai.kilocode.client.session.model.Text
|
||||
import ai.kilocode.client.session.model.Outcome
|
||||
import ai.kilocode.client.session.model.OutcomeTone
|
||||
import ai.kilocode.client.session.model.TurnOutcome
|
||||
import ai.kilocode.client.plugin.KiloPluginSettings
|
||||
import ai.kilocode.client.session.SessionRef
|
||||
@@ -126,7 +125,6 @@ class SessionController(
|
||||
|
||||
companion object {
|
||||
private val LOG = KiloLog.create(SessionController::class.java)
|
||||
private const val ABORT_ERROR = "MessageAbortedError"
|
||||
internal const val RECENT_LIMIT = 5
|
||||
internal const val DISPLAY_DELAY_MS = 1_000L
|
||||
internal const val REVERT_TIMEOUT_MS = 30_000L
|
||||
@@ -489,6 +487,98 @@ class SessionController(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-runs the last user turn after it failed, discarding the failed assistant turn first.
|
||||
*
|
||||
* Reverting to the failed assistant message restores the workspace when that turn already edited
|
||||
* files (a no-op server-side when it edited nothing), and the prompt that follows is what actually
|
||||
* removes the message: `SessionRevert.cleanup` drops everything at or after the revert target on the
|
||||
* next prompt. The replay reuses the original user message id, so no synthetic message is appended.
|
||||
*
|
||||
* A turn that failed before the assistant message existed (model resolution, missing provider
|
||||
* credentials) has nothing to roll back, so that path skips the revert and only replays.
|
||||
*/
|
||||
fun retry() {
|
||||
assertEdt()
|
||||
val id = sid ?: return
|
||||
val target = retryTarget() ?: return
|
||||
LOG.info("${ChatLogSummary.sid(id)} kind=retry clicked=true message=${target.assistant ?: "none"}")
|
||||
val op = beginReverting(
|
||||
KiloBundle.message("session.status.retrying"),
|
||||
// No rollback marker: the transcript should not paint the failed turn as a revert target,
|
||||
// it is about to be replaced. SessionMessageListPanel only marks when message != null.
|
||||
SessionState.Reverting.Kind.ROLLBACK,
|
||||
message = null,
|
||||
) ?: return
|
||||
revertJob = cs.launch {
|
||||
try {
|
||||
target.assistant?.let {
|
||||
sessions.revert(id, directory, it, null)
|
||||
synchronizeFromDisk(id, "retry")
|
||||
}
|
||||
capture("Session Retry", sessionProps(id) + mapOf("rolledBack" to (target.assistant != null).toString()))
|
||||
edt {
|
||||
if (disposed) return@edt
|
||||
clearReverting(op)
|
||||
model.setState(SessionState.Busy(KiloBundle.message("session.status.considering")))
|
||||
}
|
||||
sessions.prompt(id, directory, target.prompt)
|
||||
LOG.info("${ChatLogSummary.sid(id)} kind=retry ok=true")
|
||||
} catch (e: CancellationException) {
|
||||
edt { cancelReverting(op) }
|
||||
} catch (e: Exception) {
|
||||
capture("Session Error", sessionProps(id) + mapOf("context" to "retry", "errorClass" to e::class.java.name))
|
||||
LOG.warn("${ChatLogSummary.sid(id)} kind=retry dir=${ChatLogSummary.dir(directory)} failed message=${e.message}", e)
|
||||
edt {
|
||||
if (disposed) return@edt
|
||||
// The revert may already have landed. Leave it applied and surface the failure so the
|
||||
// user can retry again or redo, rather than silently dropping back to idle.
|
||||
if (revertOp?.key == op.key) failReverting(op, e)
|
||||
else model.setState(SessionState.Error(e.message ?: KiloBundle.message("session.error.prompt")))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Whether the error card should offer Retry. Gates the action so it is never painted as a no-op. */
|
||||
@RequiresEdt
|
||||
fun canRetry(): Boolean = retryTarget() != null
|
||||
|
||||
/**
|
||||
* The failed tail turn to replay, or null when retry does not apply: no session, an operation already
|
||||
* in flight, a busy session, a turn that did not fail, or a tail that is neither the last user message
|
||||
* nor the assistant that failed answering it.
|
||||
*/
|
||||
private fun retryTarget(): RetryTarget? {
|
||||
assertEdt()
|
||||
if (sid == null) return null
|
||||
if (revertOp != null) return null
|
||||
if (model.state.isBusy()) return null
|
||||
val tail = model.messages().lastOrNull() ?: return null
|
||||
val err = tail.info.error
|
||||
val state = model.state
|
||||
val failed = when {
|
||||
// A user stop also lands an errored tail (MessageAbortedError), and it is not a failure.
|
||||
err != null -> !err.aborted
|
||||
// A turn that completed cleanly is not retryable even when a session-level error arrives
|
||||
// afterwards: replaying it would revert work the model actually delivered.
|
||||
tail.info.role == "assistant" && tail.info.time.completed != null -> false
|
||||
else -> state is SessionState.Error ||
|
||||
(state is SessionState.TurnEnded && state.outcome == Outcome.FAILED)
|
||||
}
|
||||
if (!failed) return null
|
||||
val prompt = retryPromptCurrent() ?: return null
|
||||
// The failure hit before the assistant message existed — model resolution and provider
|
||||
// credentials are checked ahead of it — so the user turn is the tail and nothing needs rolling
|
||||
// back.
|
||||
if (tail.info.id == prompt.messageID) return RetryTarget(null, prompt)
|
||||
if (tail.info.role != "assistant") return null
|
||||
if (tail.info.parentID != prompt.messageID) return null
|
||||
return RetryTarget(tail.info.id, prompt)
|
||||
}
|
||||
|
||||
private data class RetryTarget(val assistant: String?, val prompt: PromptDto)
|
||||
|
||||
fun deleteQueuedMessage(message: String) {
|
||||
assertEdt()
|
||||
val id = sid ?: return
|
||||
@@ -1422,8 +1512,8 @@ class SessionController(
|
||||
|
||||
private fun seedOutcome() {
|
||||
val err = model.messages().lastOrNull { it.info.role == "assistant" }?.info?.error ?: return
|
||||
if (err.type == ABORT_ERROR) {
|
||||
model.setState(SessionState.TurnEnded(Outcome.INTERRUPTED, OutcomeTone.WARNING))
|
||||
if (err.aborted) {
|
||||
model.setState(SessionState.TurnEnded(Outcome.INTERRUPTED))
|
||||
return
|
||||
}
|
||||
model.setState(SessionState.Error(err.message ?: err.type, err.type))
|
||||
@@ -1510,7 +1600,7 @@ class SessionController(
|
||||
if (current is SessionState.Error && event.reason != "completed") return
|
||||
val ended = TurnOutcome.classify(event.reason)
|
||||
when {
|
||||
ended != null -> model.setState(SessionState.TurnEnded(ended.first, ended.second))
|
||||
ended != null -> model.setState(SessionState.TurnEnded(ended))
|
||||
event.reason == "completed" -> {
|
||||
capture("Task Completed", sessionProps(event.sessionID))
|
||||
model.setState(SessionState.Idle)
|
||||
@@ -1522,7 +1612,7 @@ class SessionController(
|
||||
is ChatEventDto.SessionCreated -> adoptFollowup(event.info)
|
||||
|
||||
is ChatEventDto.Error -> {
|
||||
if (event.error?.type != ABORT_ERROR) {
|
||||
if (event.error?.aborted != true) {
|
||||
capture("Session Error", sessionProps(event.sessionID) + mapOf("context" to "event", "errorClass" to (event.error?.type ?: "unknown")))
|
||||
}
|
||||
error(event, true)
|
||||
@@ -1645,7 +1735,7 @@ class SessionController(
|
||||
model.setState(SessionState.LoginRequired(KiloBundle.message("session.login.required.description")))
|
||||
return
|
||||
}
|
||||
if (event.error?.type == ABORT_ERROR) return
|
||||
if (event.error?.aborted == true) return
|
||||
val msg = event.error?.message ?: event.error?.type ?: KiloBundle.message("session.error.unknown")
|
||||
model.setState(SessionState.Error(msg, event.error?.type))
|
||||
}
|
||||
@@ -1877,6 +1967,12 @@ class SessionController(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replays the last user message with the agent/model recorded on it.
|
||||
*
|
||||
* Login resume needs exactly this: the user authenticated for the model that demanded it, so
|
||||
* resuming must use that model rather than whatever is selected now.
|
||||
*/
|
||||
private fun retryPrompt(): PromptDto? {
|
||||
val msg = model.messages().lastOrNull { it.info.role == "user" } ?: return null
|
||||
return PromptDto(
|
||||
@@ -1890,6 +1986,24 @@ class SessionController(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Like [retryPrompt], but honours the *current* model/agent/effort selection.
|
||||
*
|
||||
* A turn usually fails because of the model it ran with — missing credentials, provider overload,
|
||||
* context limit — so switching model or effort and pressing Retry has to pick that change up.
|
||||
* Resolution mirrors [promptDto]; the recorded values are only a fallback for when no selection has
|
||||
* resolved yet.
|
||||
*/
|
||||
private fun retryPromptCurrent(): PromptDto? {
|
||||
val base = retryPrompt() ?: return null
|
||||
val sel = model.model?.let(::parseModel)
|
||||
return base.copy(
|
||||
providerID = sel?.first ?: base.providerID,
|
||||
modelID = sel?.second ?: base.modelID,
|
||||
agent = model.agent ?: base.agent,
|
||||
)
|
||||
}
|
||||
|
||||
private fun resumeAfterLogin() {
|
||||
assertEdt()
|
||||
val retry = loginRetry
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ sealed class SessionState {
|
||||
|
||||
data class Error(val message: String, val kind: String? = null) : SessionState()
|
||||
|
||||
data class TurnEnded(val outcome: Outcome, val tone: OutcomeTone) : SessionState()
|
||||
data class TurnEnded(val outcome: Outcome) : SessionState()
|
||||
|
||||
data class LoginRequired(val message: String) : SessionState()
|
||||
|
||||
|
||||
+7
-5
@@ -2,12 +2,14 @@ package ai.kilocode.client.session.model
|
||||
|
||||
enum class Outcome { INTERRUPTED, FAILED }
|
||||
|
||||
enum class OutcomeTone { WARNING, CRITICAL }
|
||||
|
||||
object TurnOutcome {
|
||||
fun classify(reason: String): Pair<Outcome, OutcomeTone>? = when (reason) {
|
||||
"interrupted" -> Outcome.INTERRUPTED to OutcomeTone.WARNING
|
||||
"error" -> Outcome.FAILED to OutcomeTone.CRITICAL
|
||||
/**
|
||||
* Maps a `session.turn.close` reason to the outcome the transcript should show. `completed` and
|
||||
* `superseded` are normal endings and return null so the session simply falls back to idle.
|
||||
*/
|
||||
fun classify(reason: String): Outcome? = when (reason) {
|
||||
"interrupted" -> Outcome.INTERRUPTED
|
||||
"error" -> Outcome.FAILED
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -470,7 +470,7 @@ class SessionMessageListPanel(
|
||||
question?.hideView()
|
||||
permission?.hideView()
|
||||
login?.hideView()
|
||||
outcome?.showOutcome(state.outcome, state.tone)
|
||||
outcome?.showOutcome(state.outcome)
|
||||
}
|
||||
else -> {
|
||||
setHiddenQuestionTool(null)
|
||||
|
||||
+54
-16
@@ -2,7 +2,6 @@ package ai.kilocode.client.session.views
|
||||
|
||||
import ai.kilocode.client.plugin.KiloBundle
|
||||
import ai.kilocode.client.session.model.Outcome
|
||||
import ai.kilocode.client.session.model.OutcomeTone
|
||||
import ai.kilocode.client.session.ui.SessionView
|
||||
import ai.kilocode.client.session.ui.selection.SessionSelection
|
||||
import ai.kilocode.client.session.ui.style.SessionEditorStyle
|
||||
@@ -21,6 +20,8 @@ import javax.swing.ScrollPaneConstants
|
||||
class SessionOutcomeView(
|
||||
selection: SessionSelection? = null,
|
||||
focus: (() -> Unit)? = null,
|
||||
private val retry: (() -> Unit)? = null,
|
||||
private val retryable: (() -> Boolean)? = null,
|
||||
) : DialogView(selection, focus), SessionView {
|
||||
|
||||
override val sessionViewKind = SessionView.Kind.Default
|
||||
@@ -35,37 +36,70 @@ class SessionOutcomeView(
|
||||
|
||||
@RequiresEdt
|
||||
fun showError(message: String, kind: String?) {
|
||||
setOutlined(true)
|
||||
setHeaderIcon(AllIcons.General.Error, kind ?: KiloBundle.message("session.error.title"))
|
||||
setHeader(KiloBundle.message("session.error.title"))
|
||||
setHeader(KiloBundle.message("session.error.title"), kind)
|
||||
error.text = message
|
||||
setContentPadding(left = false, right = false)
|
||||
setContent(error.scroll)
|
||||
syncRetry(true)
|
||||
isVisible = true
|
||||
refresh()
|
||||
}
|
||||
|
||||
/**
|
||||
* A user-initiated stop is not a failure: it renders as one muted line with no icon and no card
|
||||
* outline. Only a model/provider failure gets the error card treatment.
|
||||
*/
|
||||
@RequiresEdt
|
||||
fun showOutcome(outcome: Outcome, tone: OutcomeTone) {
|
||||
val title = when (outcome) {
|
||||
Outcome.INTERRUPTED -> KiloBundle.message("session.outcome.interrupted.title")
|
||||
Outcome.FAILED -> KiloBundle.message("session.outcome.failed.title")
|
||||
fun showOutcome(outcome: Outcome) {
|
||||
when (outcome) {
|
||||
Outcome.INTERRUPTED -> {
|
||||
setOutlined(false)
|
||||
setHeaderIcon(null)
|
||||
setHeader("", KiloBundle.message("session.outcome.interrupted.note"))
|
||||
syncRetry(false)
|
||||
}
|
||||
|
||||
Outcome.FAILED -> {
|
||||
val title = KiloBundle.message("session.outcome.failed.title")
|
||||
setOutlined(true)
|
||||
setHeaderIcon(AllIcons.General.Error, title)
|
||||
setHeader(title, KiloBundle.message("session.outcome.failed.description"))
|
||||
syncRetry(true)
|
||||
}
|
||||
}
|
||||
val desc = when (outcome) {
|
||||
Outcome.INTERRUPTED -> KiloBundle.message("session.outcome.interrupted.description")
|
||||
Outcome.FAILED -> KiloBundle.message("session.outcome.failed.description")
|
||||
}
|
||||
val icon = when (tone) {
|
||||
OutcomeTone.WARNING -> AllIcons.General.Warning
|
||||
OutcomeTone.CRITICAL -> AllIcons.General.Error
|
||||
}
|
||||
setHeaderIcon(icon, title)
|
||||
setHeader(title, desc)
|
||||
setContentPadding()
|
||||
setContent(null)
|
||||
isVisible = true
|
||||
refresh()
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry belongs to failures only; a user-initiated stop stays a plain note with no controls.
|
||||
*
|
||||
* [retryable] is asked on every show because the answer depends on the transcript tail, not on the
|
||||
* outcome alone: a session-level error that arrived after a completed turn has nothing to replay.
|
||||
*/
|
||||
@RequiresEdt
|
||||
private fun syncRetry(show: Boolean) {
|
||||
val run = retry
|
||||
if (run == null || !show || retryable?.invoke() == false) {
|
||||
setActions(emptyList())
|
||||
return
|
||||
}
|
||||
setActions(
|
||||
listOf(
|
||||
Action(
|
||||
id = RETRY_ACTION,
|
||||
text = KiloBundle.message("session.outcome.retry"),
|
||||
primary = true,
|
||||
handler = run,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
fun hideView() {
|
||||
if (!isVisible) return
|
||||
@@ -78,6 +112,10 @@ class SessionOutcomeView(
|
||||
super.applyStyle(style)
|
||||
error.applyStyle(style)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val RETRY_ACTION = "retry"
|
||||
}
|
||||
}
|
||||
|
||||
private class ErrorBody {
|
||||
|
||||
+15
-3
@@ -87,7 +87,10 @@ open class DialogView(
|
||||
isVisible = false
|
||||
}
|
||||
|
||||
private val headerText: JBTextArea = makeText("", SessionUiStyle.Colors.foreground(), bold = true)
|
||||
// Both rows start blank, so both start hidden; setHeader/setDescription drive visibility from text.
|
||||
private val headerText: JBTextArea = makeText("", SessionUiStyle.Colors.foreground(), bold = true).apply {
|
||||
isVisible = false
|
||||
}
|
||||
private val descriptionText: JBTextArea = makeText("", SessionUiStyle.Text.Secondary.foreground(), bold = false).apply {
|
||||
isVisible = false
|
||||
}
|
||||
@@ -106,6 +109,7 @@ open class DialogView(
|
||||
private var padLeft = true
|
||||
private var padRight = true
|
||||
private var padBottom = true
|
||||
private var outlined = true
|
||||
|
||||
// action buttons keyed by id for retained updates
|
||||
private val actionButtons = mutableMapOf<String, JButton>()
|
||||
@@ -137,6 +141,7 @@ open class DialogView(
|
||||
@RequiresEdt
|
||||
fun setHeader(text: String, description: String? = null) {
|
||||
headerText.text = text
|
||||
headerText.isVisible = text.isNotBlank()
|
||||
setDescription(description)
|
||||
syncNorth()
|
||||
}
|
||||
@@ -303,6 +308,13 @@ open class DialogView(
|
||||
btn.text = text
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
fun setOutlined(value: Boolean) {
|
||||
if (outlined == value) return
|
||||
outlined = value
|
||||
repaint()
|
||||
}
|
||||
|
||||
/** Returns the retained action component for focus management, or this card when absent. */
|
||||
@RequiresEdt
|
||||
fun preferredActionComponent(id: String): JComponent = actionButtons[id] ?: this
|
||||
@@ -327,7 +339,7 @@ open class DialogView(
|
||||
|
||||
override fun contentColor(): Color = SessionUiStyle.View.Surface.bgColor()
|
||||
|
||||
override fun outlineColor(): Color = SessionUiStyle.View.Outline.brightColor()
|
||||
override fun outlineColor(): Color? = if (outlined) SessionUiStyle.View.Outline.brightColor() else null
|
||||
|
||||
// ---- private helpers ----
|
||||
|
||||
@@ -340,7 +352,7 @@ open class DialogView(
|
||||
north.repaint()
|
||||
}
|
||||
|
||||
private fun hasHeader() = icon.icon != null || headerText.text.isNotBlank() || descriptionText.isVisible
|
||||
private fun hasHeader() = icon.icon != null || headerText.isVisible || descriptionText.isVisible
|
||||
|
||||
private fun syncInsets() {
|
||||
val side = UiStyle.Gap.pad()
|
||||
|
||||
@@ -60,6 +60,7 @@ revert.banner.filesNotRestored=Snapshots are off - only the conversation was rev
|
||||
revert.banner.openDiff.title=Rolled back changes
|
||||
revert.message.rollback=Rollback to this message
|
||||
session.status.rollingback=Rolling back\u2026
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=Redoing\u2026
|
||||
session.status.operation.finishing=Waiting for the operation to finish\u2026
|
||||
session.error.revert.timeout=Operation timed out. Waiting for it to finish before continuing.
|
||||
@@ -214,8 +215,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=Unknown error
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.login.required.title=You need to sign in to use this model
|
||||
session.login.required.description=Go to User Profile settings to sign in, then continue this session.
|
||||
@@ -415,13 +416,25 @@ worktree.configure.base.invalid.content=Select an existing base branch before cr
|
||||
worktree.dialog.name.placeholder=Worktree name (optional)
|
||||
worktree.dialog.prompt.placeholder=Describe what you want to start working on ({0} to create)
|
||||
worktree.dialog.create=Create Worktree
|
||||
worktree.dialog.import=Import Worktree
|
||||
worktree.dialog.tab.new=New
|
||||
worktree.dialog.tab.pr=From PR
|
||||
worktree.dialog.tab.branch=From Branch
|
||||
worktree.progress.creating=Creating worktree…
|
||||
worktree.progress.capturing=Capturing changes…
|
||||
worktree.progress.transferring=Transferring changes…
|
||||
worktree.progress.starting=Starting session…
|
||||
worktree.move.failed.title=Failed to move to worktree
|
||||
worktree.import.pr.section=Pull Request
|
||||
worktree.import.pr.description=Paste a GitHub pull request URL. Kilo will fetch the PR head and open it in a worktree.
|
||||
worktree.import.pr.placeholder=https://github.com/owner/repo/pull/123
|
||||
worktree.import.pr.required=Enter a pull request URL.
|
||||
worktree.import.pr.invalid=Enter a valid GitHub pull request URL.
|
||||
worktree.import.pr.failed.title=Couldn''t import pull request
|
||||
worktree.import.branch.section=Branch
|
||||
worktree.import.branch.description=Choose an existing local branch that is not already checked out in another worktree.
|
||||
worktree.import.branch.invalid=Select an existing branch before importing.
|
||||
worktree.import.branch.empty=No local branches are available to import.
|
||||
worktree.import.branch.failed.title=Couldn''t import branch "{0}"
|
||||
worktree.stats.diff.tooltip={0} additions, {1} deletions
|
||||
worktree.stats.ahead.tooltip=Commits ahead of base branch
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=جار البحث على الويب…
|
||||
session.status.editing=جاري التحرير…
|
||||
session.status.commands=جاري تنفيذ الأوامر…
|
||||
session.status.rollingback=جار التراجع…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=جار الإعادة…
|
||||
session.status.operation.finishing=في انتظار انتهاء العملية…
|
||||
session.error.revert.timeout=انتهت مهلة العملية. جار انتظار انتهائها قبل المتابعة.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=خطأ غير معروف
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=الرموز
|
||||
session.header.tokens.description=الرموز المستخدمة في آخر رد للمساعد: الإدخال، الإخراج، كتابات المخزن المؤقت وقراءاته.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=Pretraživanje weba…
|
||||
session.status.editing=Uređivanje…
|
||||
session.status.commands=Pokretanje komandi…
|
||||
session.status.rollingback=Vraćanje unazad…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=Ponovno izvršavanje…
|
||||
session.status.operation.finishing=Čeka se završetak operacije…
|
||||
session.error.revert.timeout=Operacija je istekla. Čeka se da završi prije nastavka.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=Nepoznata greška
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=Tokeni
|
||||
session.header.tokens.description=Tokeni korišteni u posljednjem odgovoru asistenta: ulaz, izlaz, pisanja u keš i čitanja iz keša.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=Søger på nettet…
|
||||
session.status.editing=Foretager redigeringer…
|
||||
session.status.commands=Kører kommandoer…
|
||||
session.status.rollingback=Ruller tilbage…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=Gentager…
|
||||
session.status.operation.finishing=Venter på, at handlingen afsluttes…
|
||||
session.error.revert.timeout=Handlingen fik timeout. Venter på, at den afsluttes, før der fortsættes.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=Ukendt fejl
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=Tokens
|
||||
session.header.tokens.description=Tokens brugt af det seneste assistentsvar: input, output, cache-skrivninger og cache-læsninger.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=Web durchsuchen…
|
||||
session.status.editing=Änderungen vornehmen…
|
||||
session.status.commands=Befehle ausführen…
|
||||
session.status.rollingback=Rollback wird ausgeführt…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=Wird wiederholt…
|
||||
session.status.operation.finishing=Warten, bis der Vorgang abgeschlossen ist…
|
||||
session.error.revert.timeout=Zeitüberschreitung beim Vorgang. Es wird gewartet, bis er abgeschlossen ist, bevor fortgefahren wird.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=Unbekannter Fehler
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=Token
|
||||
session.header.tokens.description=Von der letzten Assistentenantwort verwendete Token: Eingabe, Ausgabe, Cache-Schreibvorgänge und Cache-Lesevorgänge.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=Buscando en la web…
|
||||
session.status.editing=Realizando ediciones…
|
||||
session.status.commands=Ejecutando comandos…
|
||||
session.status.rollingback=Revirtiendo…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=Rehaciendo…
|
||||
session.status.operation.finishing=Esperando a que finalice la operación…
|
||||
session.error.revert.timeout=La operación ha agotado el tiempo. Esperando a que finalice antes de continuar.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=Error desconocido
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=Tokens
|
||||
session.header.tokens.description=Tokens utilizados por la última respuesta del asistente: entrada, salida, escrituras en caché y lecturas en caché.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=Recherche sur le web…
|
||||
session.status.editing=Modifications en cours…
|
||||
session.status.commands=Exécution des commandes…
|
||||
session.status.rollingback=Retour en arrière…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=Rétablissement…
|
||||
session.status.operation.finishing=En attente de la fin de l’opération…
|
||||
session.error.revert.timeout=L’opération a expiré. Attente de sa fin avant de continuer.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=Erreur inconnue
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=Tokens
|
||||
session.header.tokens.description=Tokens utilisés par la dernière réponse de l'assistant : entrée, sortie, écritures en cache et lectures en cache.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=ウェブを検索中…
|
||||
session.status.editing=編集中…
|
||||
session.status.commands=コマンドを実行中…
|
||||
session.status.rollingback=ロールバック中…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=やり直し中…
|
||||
session.status.operation.finishing=操作の完了を待機しています…
|
||||
session.error.revert.timeout=操作がタイムアウトしました。続行する前に完了を待機しています。
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=不明なエラー
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=トークン
|
||||
session.header.tokens.description=最新のアシスタントの回答で使用されたトークン:入力、出力、キャッシュ書き込み、キャッシュ読み取り。
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=웹 검색 중…
|
||||
session.status.editing=편집 중…
|
||||
session.status.commands=명령 실행 중…
|
||||
session.status.rollingback=롤백 중…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=다시 실행 중…
|
||||
session.status.operation.finishing=작업이 완료될 때까지 기다리는 중…
|
||||
session.error.revert.timeout=작업 시간이 초과되었습니다. 계속하기 전에 완료될 때까지 기다리는 중입니다.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=알 수 없는 오류
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=토큰
|
||||
session.header.tokens.description=마지막 어시스턴트 응답에서 사용된 토큰: 입력, 출력, 캐시 쓰기, 캐시 읽기.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=Web doorzoeken…
|
||||
session.status.editing=Bewerkingen uitvoeren…
|
||||
session.status.commands=Opdrachten uitvoeren…
|
||||
session.status.rollingback=Terugdraaien…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=Opnieuw uitvoeren…
|
||||
session.status.operation.finishing=Wachten tot de bewerking is voltooid…
|
||||
session.error.revert.timeout=Time-out van bewerking. Wachten tot deze is voltooid voordat wordt doorgegaan.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=Onbekende fout
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=Tokens
|
||||
session.header.tokens.description=Tokens gebruikt door de laatste assistent-reactie: invoer, uitvoer, cache-schrijfacties en cache-leesacties.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=Søker på nettet…
|
||||
session.status.editing=Gjør redigeringer…
|
||||
session.status.commands=Kjører kommandoer…
|
||||
session.status.rollingback=Ruller tilbake…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=Gjør om…
|
||||
session.status.operation.finishing=Venter på at operasjonen skal fullføres…
|
||||
session.error.revert.timeout=Operasjonen tidsavbrøt. Venter på at den skal fullføres før vi fortsetter.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=Ukjent feil
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=Tokens
|
||||
session.header.tokens.description=Tokens brukt av siste assistentsvar: inndata, utdata, cache-skrivninger og cache-lesninger.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=Przeszukiwanie sieci…
|
||||
session.status.editing=Dokonywanie edycji…
|
||||
session.status.commands=Uruchamianie poleceń…
|
||||
session.status.rollingback=Wycofywanie…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=Ponawianie…
|
||||
session.status.operation.finishing=Oczekiwanie na zakończenie operacji…
|
||||
session.error.revert.timeout=Upłynął limit czasu operacji. Oczekiwanie na jej zakończenie przed kontynuacją.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=Nieznany błąd
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=Tokeny
|
||||
session.header.tokens.description=Tokeny użyte przez ostatnią odpowiedź asystenta: wejściowe, wyjściowe, zapisy do bufora i odczyty z bufora.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=Pesquisando na web…
|
||||
session.status.editing=Realizando edições…
|
||||
session.status.commands=Executando comandos…
|
||||
session.status.rollingback=Revertendo…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=Refazendo…
|
||||
session.status.operation.finishing=Aguardando a operação terminar…
|
||||
session.error.revert.timeout=A operação atingiu o tempo limite. Aguardando sua conclusão antes de continuar.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=Erro desconhecido
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=Tokens
|
||||
session.header.tokens.description=Tokens usados pela última resposta do assistente: entrada, saída, escritas em cache e leituras em cache.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=Поиск в интернете…
|
||||
session.status.editing=Вношу изменения…
|
||||
session.status.commands=Выполняю команды…
|
||||
session.status.rollingback=Выполняется откат…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=Повторное применение…
|
||||
session.status.operation.finishing=Ожидание завершения операции…
|
||||
session.error.revert.timeout=Время ожидания операции истекло. Ждем ее завершения перед продолжением.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=Неизвестная ошибка
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=Токены
|
||||
session.header.tokens.description=Токены, использованные последним ответом ассистента: входные, исходные, запись в кэш и чтение из кэша.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=กำลังค้นหาบนเว็บ…
|
||||
session.status.editing=กำลังแก้ไข…
|
||||
session.status.commands=กำลังเรียกใช้คำสั่ง…
|
||||
session.status.rollingback=กำลังย้อนกลับ…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=กำลังทำซ้ำ…
|
||||
session.status.operation.finishing=กำลังรอให้การดำเนินการเสร็จสิ้น…
|
||||
session.error.revert.timeout=การดำเนินการหมดเวลา กำลังรอให้เสร็จสิ้นก่อนดำเนินการต่อ
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=ข้อผิดพลาดที่ไม่ทราบ
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=โทเคน
|
||||
session.header.tokens.description=โทเคนที่ใช้โดยคำตอบล่าสุดของผู้ช่วย: อินพุต เอาต์พุต การเขียนแคช และการอ่านแคช
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=Web aranıyor…
|
||||
session.status.editing=Düzenleme yapılıyor…
|
||||
session.status.commands=Komutlar çalıştırılıyor…
|
||||
session.status.rollingback=Geri alınıyor…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=Yeniden uygulanıyor…
|
||||
session.status.operation.finishing=İşlemin tamamlanması bekleniyor…
|
||||
session.error.revert.timeout=İşlem zaman aşımına uğradı. Devam etmeden önce tamamlanması bekleniyor.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=Bilinmeyen hata
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=Jeton
|
||||
session.header.tokens.description=Son asistan yanıtında kullanılan jetonlar: giriş, çıkış, önbelleğe yazma ve önbellekten okuma.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=Шукаю в інтернеті…
|
||||
session.status.editing=Вношу зміни…
|
||||
session.status.commands=Виконую команди…
|
||||
session.status.rollingback=Виконується відкат…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=Повторне застосування…
|
||||
session.status.operation.finishing=Очікування завершення операції…
|
||||
session.error.revert.timeout=Час очікування операції минув. Очікуємо її завершення перед продовженням.
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=Невідома помилка
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=Токени
|
||||
session.header.tokens.description=Токени, використані останньою відповіддю асистента: вхід, вихід, запис у кеш і читання з кешу.
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=搜索网页…
|
||||
session.status.editing=正在编辑…
|
||||
session.status.commands=运行命令…
|
||||
session.status.rollingback=正在回滚…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=正在重做…
|
||||
session.status.operation.finishing=正在等待操作完成…
|
||||
session.error.revert.timeout=操作超时。继续前正在等待其完成。
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=未知错误
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=令牌
|
||||
session.header.tokens.description=最近一次助手回复中使用的令牌:输入、输出、缓存写入和缓存读取。
|
||||
|
||||
+3
-2
@@ -56,6 +56,7 @@ session.status.searching.web=搜尋網頁…
|
||||
session.status.editing=進行編輯…
|
||||
session.status.commands=執行指令…
|
||||
session.status.rollingback=正在復原…
|
||||
session.status.retrying=Retrying\u2026
|
||||
session.status.redoing=正在重做…
|
||||
session.status.operation.finishing=正在等待操作完成…
|
||||
session.error.revert.timeout=操作逾時。繼續前正在等待其完成。
|
||||
@@ -77,8 +78,8 @@ session.error.title=Request failed
|
||||
session.error.unknown=未知錯誤
|
||||
session.outcome.failed.description=The model stopped this turn with an error.
|
||||
session.outcome.failed.title=Response failed
|
||||
session.outcome.interrupted.description=This turn was interrupted before it finished.
|
||||
session.outcome.interrupted.title=Response stopped
|
||||
session.outcome.retry=Retry
|
||||
session.outcome.interrupted.note=Stopped
|
||||
|
||||
session.header.tokens=記號
|
||||
session.header.tokens.description=最新助手回覆使用的記號:輸入、輸出、快取寫入和快取讀取。
|
||||
|
||||
+73
-1
@@ -134,7 +134,7 @@ class AgentManagerPanelTest : BasePlatformTestCase() {
|
||||
|
||||
fun `test configure creates the worktree only after the dialog closes`() {
|
||||
val order = mutableListOf<String>()
|
||||
val plan = NewWorktreePlan("feature/y", "main", PendingPrompt("build it"))
|
||||
val plan = NewWorktreePlan.Create("feature/y", "main", PendingPrompt("build it"))
|
||||
val controller = WorktreeController(service, "/test", coroutines.scope)
|
||||
val panel = edt {
|
||||
AgentManagerPanel(testRootDisposable, controller, project, dialog = { _, _ -> FakeWorktreeDialog(plan, order) })
|
||||
@@ -166,6 +166,36 @@ class AgentManagerPanelTest : BasePlatformTestCase() {
|
||||
assertTrue(rpc.creates.isEmpty())
|
||||
}
|
||||
|
||||
fun `test configure imports an existing branch`() {
|
||||
val order = mutableListOf<String>()
|
||||
val plan = NewWorktreePlan.Branch("feature/x")
|
||||
val controller = WorktreeController(service, "/test", coroutines.scope)
|
||||
val panel = edt {
|
||||
AgentManagerPanel(testRootDisposable, controller, project, dialog = { _, _ -> FakeWorktreeDialog(plan, order) })
|
||||
}
|
||||
|
||||
edt { panel.configure() }
|
||||
flush()
|
||||
|
||||
val req = rpc.creates.single()
|
||||
assertEquals("feature/x", req.branch)
|
||||
assertTrue("branch import checks out an existing branch", req.existingBranch)
|
||||
}
|
||||
|
||||
fun `test configure imports a pull request`() {
|
||||
val order = mutableListOf<String>()
|
||||
val plan = NewWorktreePlan.Pr("https://github.com/o/r/pull/7")
|
||||
val controller = WorktreeController(service, "/test", coroutines.scope)
|
||||
val panel = edt {
|
||||
AgentManagerPanel(testRootDisposable, controller, project, dialog = { _, _ -> FakeWorktreeDialog(plan, order) })
|
||||
}
|
||||
|
||||
edt { panel.configure() }
|
||||
flush()
|
||||
|
||||
assertEquals(listOf("https://github.com/o/r/pull/7"), rpc.prImports.toList())
|
||||
}
|
||||
|
||||
fun `test panel hides worktree search field`() {
|
||||
val controller = WorktreeController(service, "/test", coroutines.scope)
|
||||
val panel = edt { AgentManagerPanel(testRootDisposable, controller) }
|
||||
@@ -649,6 +679,43 @@ class AgentManagerPanelTest : BasePlatformTestCase() {
|
||||
assertTrue(edt { panel.canShowRename(item) })
|
||||
}
|
||||
|
||||
fun `test current row renders without any linked worktrees`() {
|
||||
rpc.listed += main()
|
||||
val controller = WorktreeController(service, project.basePath!!, coroutines.scope)
|
||||
val panel = edt { AgentManagerPanel(testRootDisposable, controller, project) }
|
||||
|
||||
edt { controller.reload() }
|
||||
flush()
|
||||
|
||||
// Replacing an empty model with an empty list notifies nobody, so the row has to come from
|
||||
// the reload itself.
|
||||
assertEquals(1, rows(panel))
|
||||
assertEquals("main", row(panel, 0).title)
|
||||
}
|
||||
|
||||
fun `test current row shows the pr badge for the main checkout`() {
|
||||
val main = main()
|
||||
rpc.listed += main
|
||||
rpc.prResult = WorktreePrListDto(
|
||||
GhAvailability.OK,
|
||||
listOf(WorktreePrDto(main.path, 12, GhState.OPEN, "https://example.test/pr/12", "Main work")),
|
||||
)
|
||||
val timers = TestUiTimers()
|
||||
ApplicationManager.getApplication().replaceService(KiloWorktreeService::class.java, service, testRootDisposable)
|
||||
project.replaceService(WorktreeStatusService::class.java, WorktreeStatusService(project, coroutines.scope, timers), testRootDisposable)
|
||||
val controller = WorktreeController(service, project.basePath!!, coroutines.scope)
|
||||
val panel = edt { AgentManagerPanel(testRootDisposable, controller, project) }
|
||||
edt { controller.reload() }
|
||||
timers.advanceBy(300)
|
||||
waitUntil { rows(panel) > 0 && row(panel, 0).metrics != null }
|
||||
|
||||
// The current row keeps the branch as its title; the PR arrives as a badge beside it.
|
||||
val current = row(panel, 0)
|
||||
assertEquals("main", current.title)
|
||||
assertEquals("#12", current.metrics?.pr?.text)
|
||||
assertTrue(edt { panel.canOpenPr(main) })
|
||||
}
|
||||
|
||||
fun `test pr title replaces row name and tooltip reveals custom name`() {
|
||||
val path = "${project.basePath!!}/.kilo/worktrees/feature-x"
|
||||
val item = WorktreeDto(path, "Feature Label", "feature/x", path)
|
||||
@@ -875,6 +942,11 @@ class AgentManagerPanelTest : BasePlatformTestCase() {
|
||||
return edt { list.model.getElementAt(idx) as ActiveListItem }
|
||||
}
|
||||
|
||||
private fun rows(panel: AgentManagerPanel): Int {
|
||||
val list = edt { UIUtil.findComponentOfType(panel, JBList::class.java)!! }
|
||||
return edt { list.model.size }
|
||||
}
|
||||
|
||||
private fun components(root: Component): List<Component> {
|
||||
val out = mutableListOf<Component>()
|
||||
fun visit(item: Component) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user