mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-30 17:14:40 +08:00
Merge remote-tracking branch 'origin/main' into johnnyeric/kilo-opencode-v1.18.0
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Update the active model, mode, and thinking selectors when executing a slash command with configured overrides.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Fix the Agent Manager terminal toolbar button to toggle panel visibility directly.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Route Agent Manager tool-launched sessions to the project that owns the tool event directory, keep sandboxed worktree sessions inside their active worktree, and wait for busy managed sessions before prompting them.
|
||||
@@ -2,4 +2,4 @@
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Fix the sidebar navigation bar (New Task, History, Agent Manager, KiloClaw, Marketplace, Profile, Settings) disappearing when the Kilo Code view is docked in the Secondary Side Bar. The navigation is now rendered inside the webview itself, so it stays visible regardless of where the view is docked.
|
||||
Fix the sidebar navigation bar (New Task, History, Agent Manager, KiloClaw, Marketplace, Profile, Settings) disappearing in Cursor when the Kilo Code view is docked in the Secondary Side Bar. Cursor now renders the navigation inside the webview itself so it stays visible regardless of dock location. VS Code is unaffected — it continues to use its native title bar toolbar, which already worked correctly everywhere.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
---
|
||||
|
||||
Move the "why was this tool call approved" line to after the tool output instead of between the header and body, add an icon to it, and add a Display setting to hide it.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep Agent Manager terminals aligned and correctly wrapped when the terminal panel is narrow.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Keep recent sessions and the Show History action inset and usable in narrow VS Code sidebars.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"kilo-code": minor
|
||||
"@kilocode/cli": minor
|
||||
---
|
||||
|
||||
Add nested slash command suggestions for `/review` in VS Code and support `staged`, `unpushed`, and `quick` review modes.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Apply saved sandbox settings to existing sessions and use the latest settings when enabling sandboxing
|
||||
@@ -41,6 +41,9 @@ jobs:
|
||||
- name: Check Effect Promise facade allowlist
|
||||
run: bun run script/check-opencode-promise-facades.ts
|
||||
|
||||
- name: Check domain architecture boundaries and ratchets
|
||||
run: bun run script/check-architecture.ts
|
||||
|
||||
- name: Check model tool network boundary
|
||||
run: bun run script/check-model-tool-network.ts
|
||||
|
||||
|
||||
@@ -484,6 +484,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "4.11.3",
|
||||
"@babel/core": "^7.28.4",
|
||||
"@babel/preset-typescript": "^7.27.1",
|
||||
"@playwright/test": "1.57.0",
|
||||
"@storybook/addon-a11y": "10.2.10",
|
||||
"@storybook/addon-docs": "10.2.10",
|
||||
@@ -495,6 +497,7 @@
|
||||
"@vscode/test-cli": "^0.0.12",
|
||||
"@vscode/test-electron": "^2.5.2",
|
||||
"@vscode/vsce": "^3.7.1",
|
||||
"babel-preset-solid": "^1.9.9",
|
||||
"esbuild": "^0.27.2",
|
||||
"esbuild-plugin-solid": "^0.6.0",
|
||||
"eslint": "^9.39.2",
|
||||
@@ -997,23 +1000,23 @@
|
||||
},
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"esbuild",
|
||||
"protobufjs",
|
||||
"web-tree-sitter",
|
||||
"esbuild",
|
||||
"tree-sitter-bash",
|
||||
"protobufjs",
|
||||
],
|
||||
"patchedDependencies": {
|
||||
"@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch",
|
||||
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
|
||||
"virtua@0.49.1": "patches/virtua@0.49.1.patch",
|
||||
"mammoth@1.12.0": "patches/mammoth@1.12.0.patch",
|
||||
"@ff-labs/fff-bun@0.9.4": "patches/@ff-labs%2Ffff-bun@0.9.4.patch",
|
||||
"pacote@21.5.1": "patches/pacote@21.5.1.patch",
|
||||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
|
||||
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
||||
"pacote@21.5.1": "patches/pacote@21.5.1.patch",
|
||||
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
|
||||
"effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch",
|
||||
"@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch",
|
||||
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
|
||||
"virtua@0.49.1": "patches/virtua@0.49.1.patch",
|
||||
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
||||
"mammoth@1.12.0": "patches/mammoth@1.12.0.patch",
|
||||
"@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch",
|
||||
},
|
||||
"overrides": {
|
||||
"@effect/platform-node-shared": "4.0.0-beta.74",
|
||||
@@ -5460,10 +5463,6 @@
|
||||
|
||||
"@solid-primitives/resize-observer/@solid-primitives/rootless": ["@solid-primitives/rootless@1.5.3", "", { "dependencies": { "@solid-primitives/utils": "^6.4.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-N8cIDAHbWcLahNRLr0knAAQvXyEdEMoAZvIMZKmhNb1mlx9e2UOv9BRD5YNwQUJwbNoYVhhLwFOEOcVXFx0HqA=="],
|
||||
|
||||
"@standard-community/standard-json/effect": ["effect@4.0.0-beta.74", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-Yx+Kh12U+i2FmjwEfKs+ePFmpMd43RPD1oGqc/VraSS9bYzvF0Ff3PojwEFEVEewp8xc92Uxu28gTspU4qyvHA=="],
|
||||
|
||||
"@standard-community/standard-openapi/effect": ["effect@4.0.0-beta.74", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.8.0", "find-my-way-ts": "^0.1.6", "ini": "^7.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^2.0.1", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^14.0.0", "yaml": "^2.9.0" } }, "sha512-Yx+Kh12U+i2FmjwEfKs+ePFmpMd43RPD1oGqc/VraSS9bYzvF0Ff3PojwEFEVEewp8xc92Uxu28gTspU4qyvHA=="],
|
||||
|
||||
"@storybook/addon-links/storybook": ["storybook@10.4.4", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "oxc-parser": "^0.127.0", "oxc-resolver": "^11.19.1", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "prettier": "^2 || ^3", "vite-plus": "^0.1.15" }, "optionalPeers": ["@types/react", "prettier", "vite-plus"], "bin": "./dist/bin/dispatcher.js" }, "sha512-Nn0qFRxU5fyABa6dGRftfL3lz0Y+HkKOaAkfytF8S4Q2K6Szwwq7TwPAEs3Wsj8hBQbYhsobrKADcPsyXQpJaA=="],
|
||||
|
||||
"@storybook/addon-onboarding/storybook": ["storybook@10.4.4", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.2", "@testing-library/jest-dom": "^6.9.1", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "@webcontainer/env": "^1.1.1", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "oxc-parser": "^0.127.0", "oxc-resolver": "^11.19.1", "recast": "^0.23.5", "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "prettier": "^2 || ^3", "vite-plus": "^0.1.15" }, "optionalPeers": ["@types/react", "prettier", "vite-plus"], "bin": "./dist/bin/dispatcher.js" }, "sha512-Nn0qFRxU5fyABa6dGRftfL3lz0Y+HkKOaAkfytF8S4Q2K6Szwwq7TwPAEs3Wsj8hBQbYhsobrKADcPsyXQpJaA=="],
|
||||
@@ -6182,14 +6181,6 @@
|
||||
|
||||
"@smithy/util-stream/@smithy/core/@smithy/types": ["@smithy/types@4.14.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ=="],
|
||||
|
||||
"@standard-community/standard-json/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@standard-community/standard-json/effect/yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
|
||||
|
||||
"@standard-community/standard-openapi/effect/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
|
||||
|
||||
"@standard-community/standard-openapi/effect/yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
|
||||
|
||||
"@storybook/addon-links/storybook/@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="],
|
||||
|
||||
"@storybook/addon-links/storybook/@vitest/spy": ["@vitest/spy@3.2.4", "", { "dependencies": { "tinyspy": "^4.0.3" } }, "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw=="],
|
||||
|
||||
+4
-4
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"nodeModules": {
|
||||
"x86_64-linux": "sha256-epitmtKUd9fAucKdH/sDdqv5WmwfpiPH+h/PNt55gd4=",
|
||||
"aarch64-linux": "sha256-FHVsi2iho+U5aj6Z9lg2GTIu1ViUuqI2c/nZuwjmPH8=",
|
||||
"aarch64-darwin": "sha256-QDPVLcbTyaZRZvSnqvWNR/mIkS1TrZLX0HjUlg6xwwI=",
|
||||
"x86_64-darwin": "sha256-SSPc9b3WwcYCnuywcyMRJjVlugoXuo0UoPJ+qzPGajk="
|
||||
"x86_64-linux": "sha256-y6PZR6BsVZcsK/qJ0XvJBevukYzVJ8NRLHRbNSuSzb8=",
|
||||
"aarch64-linux": "sha256-0ozGLgGTHajlHqofixtjJv/dggq25MhWYIqmfmb+6Z0=",
|
||||
"aarch64-darwin": "sha256-IEYJothLBDT20BmLamBqM+pciFMQtWy8Um1YjFjbKz4=",
|
||||
"x86_64-darwin": "sha256-k2bQGKTkvIZmB2+I2Fy3TPm/NZ0BbGjxp9esxtGuUbI="
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev",
|
||||
"dev:storybook": "bun --cwd packages/storybook storybook",
|
||||
"lint": "oxlint",
|
||||
"check:architecture": "bun run script/check-architecture.ts",
|
||||
"typecheck": "bun turbo typecheck",
|
||||
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
|
||||
"postinstall": "bun run --cwd packages/core fix-node-pty && bun run script/setup-git.ts",
|
||||
|
||||
@@ -531,6 +531,7 @@ type Endpoint14_1Input = {
|
||||
readonly cwd?: Endpoint14_1Request["payload"]["cwd"]
|
||||
readonly title?: Endpoint14_1Request["payload"]["title"]
|
||||
readonly env?: Endpoint14_1Request["payload"]["env"]
|
||||
readonly size?: Endpoint14_1Request["payload"]["size"]
|
||||
}
|
||||
const Endpoint14_1 = (raw: RawClient["server.pty"]) => (input?: Endpoint14_1Input) =>
|
||||
raw["pty.create"]({
|
||||
@@ -541,6 +542,7 @@ const Endpoint14_1 = (raw: RawClient["server.pty"]) => (input?: Endpoint14_1Inpu
|
||||
cwd: input?.["cwd"],
|
||||
title: input?.["title"],
|
||||
env: input?.["env"],
|
||||
size: input?.["size"],
|
||||
},
|
||||
}).pipe(Effect.mapError(mapClientError))
|
||||
|
||||
|
||||
@@ -840,6 +840,7 @@ export function make(options: ClientOptions) {
|
||||
cwd: input?.["cwd"],
|
||||
title: input?.["title"],
|
||||
env: input?.["env"],
|
||||
size: input?.["size"],
|
||||
},
|
||||
successStatus: 200,
|
||||
declaredStatuses: [400, 401],
|
||||
|
||||
@@ -2632,6 +2632,7 @@ export type PtysCreateInput = {
|
||||
readonly cwd?: string
|
||||
readonly title?: string
|
||||
readonly env?: { readonly [x: string]: string }
|
||||
readonly size?: { readonly rows: number; readonly cols: number }
|
||||
}["command"]
|
||||
readonly args?: {
|
||||
readonly command?: string
|
||||
@@ -2639,6 +2640,7 @@ export type PtysCreateInput = {
|
||||
readonly cwd?: string
|
||||
readonly title?: string
|
||||
readonly env?: { readonly [x: string]: string }
|
||||
readonly size?: { readonly rows: number; readonly cols: number }
|
||||
}["args"]
|
||||
readonly cwd?: {
|
||||
readonly command?: string
|
||||
@@ -2646,6 +2648,7 @@ export type PtysCreateInput = {
|
||||
readonly cwd?: string
|
||||
readonly title?: string
|
||||
readonly env?: { readonly [x: string]: string }
|
||||
readonly size?: { readonly rows: number; readonly cols: number }
|
||||
}["cwd"]
|
||||
readonly title?: {
|
||||
readonly command?: string
|
||||
@@ -2653,6 +2656,7 @@ export type PtysCreateInput = {
|
||||
readonly cwd?: string
|
||||
readonly title?: string
|
||||
readonly env?: { readonly [x: string]: string }
|
||||
readonly size?: { readonly rows: number; readonly cols: number }
|
||||
}["title"]
|
||||
readonly env?: {
|
||||
readonly command?: string
|
||||
@@ -2660,7 +2664,16 @@ export type PtysCreateInput = {
|
||||
readonly cwd?: string
|
||||
readonly title?: string
|
||||
readonly env?: { readonly [x: string]: string }
|
||||
readonly size?: { readonly rows: number; readonly cols: number }
|
||||
}["env"]
|
||||
readonly size?: {
|
||||
readonly command?: string
|
||||
readonly args?: ReadonlyArray<string>
|
||||
readonly cwd?: string
|
||||
readonly title?: string
|
||||
readonly env?: { readonly [x: string]: string }
|
||||
readonly size?: { readonly rows: number; readonly cols: number }
|
||||
}["size"]
|
||||
}
|
||||
|
||||
export type PtysCreateOutput = {
|
||||
|
||||
@@ -214,7 +214,17 @@ const layer = Layer.effect(
|
||||
}
|
||||
yield* Effect.logInfo("creating session", { id, cmd: command, args, cwd })
|
||||
const { spawn } = yield* Effect.promise(() => pty())
|
||||
const proc = yield* Effect.sync(() => spawn(command, args, { name: "xterm-256color", cwd, env }))
|
||||
// kilocode_change start - spawn with initial terminal dimensions
|
||||
const proc = yield* Effect.sync(() =>
|
||||
spawn(command, args, {
|
||||
name: "xterm-256color",
|
||||
cwd,
|
||||
env,
|
||||
cols: input.size?.cols,
|
||||
rows: input.size?.rows,
|
||||
}),
|
||||
)
|
||||
// kilocode_change end
|
||||
const info: Info = {
|
||||
id,
|
||||
title: input.title || `Terminal ${id.slice(-4)}`,
|
||||
|
||||
@@ -161,9 +161,9 @@ A configured destination is an egress route, not tenant, organization, repositor
|
||||
|
||||
The config setting supplies the initial default for new sessions that do not have a saved preference. Use the lock button in the VS Code prompt or `/sandbox` in the CLI to change the current session. Your latest choice is saved as the default for future sessions in that project, takes precedence over the config default, and persists across restarts.
|
||||
|
||||
Each initialized session snapshots its network mode, allowed destinations, and additional writable paths. Changing config affects new sessions. The prompt control or `/sandbox` can change the current session's enabled state, but it cannot change these authority lists, and they never expand during an active session.
|
||||
Each session preserves its enabled or disabled choice. Saving changes through Kilo settings to network mode, allowed destinations, or additional writable paths refreshes existing session policies before their next tool execution. Enabling sandboxing also reads the latest settings. A tool that is already running keeps the policy it started with.
|
||||
|
||||
Forked sessions retain the source session's confinement. Subagents inherit the stricter combination of parent and child settings: sandboxing remains enabled if either requires it, deny-all wins over destination exceptions, destination lists intersect, and additional writable paths intersect.
|
||||
At creation, forked sessions retain the source session's confinement and subagents inherit the stricter combination of parent and child settings: sandboxing remains enabled if either requires it, deny-all wins over destination exceptions, destination lists intersect, and additional writable paths intersect. Later trusted sandbox settings replace those network and writable-path limits before the affected session's next tool execution.
|
||||
|
||||
Cloud sessions do not expose the local sandbox control because their tools do not run in your local sandbox.
|
||||
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1bc2a28b80d8dc5aea4c03609efc13a23a2c88103e56df741155d9e794fc96f7
|
||||
size 2422
|
||||
oid sha256:d49870048a42ea9c0166143fbb364b0d9ee45d786e3b1346fcd0eda76c533bdb
|
||||
size 2414
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e3f86dabc39700b3a86508c1e6499a546eeabf720832b62c9ce12f2d97378ebd
|
||||
size 34649
|
||||
oid sha256:00092414080e2f70fb9b3166e8d06a1692af93e33717d0ab8fea5fe96e6a6982
|
||||
size 34468
|
||||
|
||||
@@ -72,6 +72,8 @@
|
||||
<!-- packages/kilo-vscode/src/agent-manager/WorktreeManager.ts -->
|
||||
- <https://github.com>
|
||||
<!-- packages/opencode/src/kilocode/security/github.ts -->
|
||||
- <https://github.com/anthropics/claude-code/issues/31375>
|
||||
<!-- packages/kilo-vscode/src/utils.ts -->
|
||||
- <https://github.com/apps/kiloconnect>
|
||||
<!-- packages/opencode/src/cli/cmd/github.handler.ts -->
|
||||
- <https://github.com/cline/cline/blob/main/evals/diff-edits/diff-apply/diff-06-23-25.ts>
|
||||
|
||||
@@ -507,21 +507,25 @@ html[data-theme="kilo-vscode"] [data-component="tool-part-wrapper"][data-part-ty
|
||||
}
|
||||
}
|
||||
|
||||
/* "why was this allowed" line inside a tool's expanded body */
|
||||
/* "why was this allowed" line inside a tool's expanded body. Styled like
|
||||
[data-component="tool-hint"] (muted, italic) so it reads as ambient
|
||||
context rather than a call to action, and recedes the way reasoning text does. */
|
||||
[data-slot="tool-approval-line"] {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 0 6px;
|
||||
font-family: var(--font-family-sans);
|
||||
font-size: var(--font-size-small);
|
||||
font-style: italic;
|
||||
line-height: var(--line-height-normal);
|
||||
color: var(--text-weak);
|
||||
opacity: 0.9;
|
||||
|
||||
[data-slot="tool-approval-decision"] {
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--text-strong);
|
||||
svg {
|
||||
flex-shrink: 0;
|
||||
color: var(--text-weak);
|
||||
}
|
||||
|
||||
[data-slot="tool-approval-rule"] {
|
||||
|
||||
@@ -44,18 +44,24 @@ export function BasicTool(props: BasicToolProps) {
|
||||
writeToolOpen(key(), open)
|
||||
props.onOpenChange?.(open)
|
||||
}
|
||||
// Renders after the body/tool list, not before — it's context about what
|
||||
// happened, not part of the header.
|
||||
const details = () => (
|
||||
<div data-slot="basic-tool-details">
|
||||
<Show when={inBody() && approval()}>{(value) => <ToolApprovalLine display={value()} />}</Show>
|
||||
{props.children}
|
||||
<Show when={inBody() && approval()}>{(value) => <ToolApprovalLine display={value()} />}</Show>
|
||||
</div>
|
||||
)
|
||||
if (!("children" in props) && !inBody()) {
|
||||
return <Base {...props} defaultOpen={initial()} retainDetails={props.defer} onOpenChange={change} />
|
||||
}
|
||||
// A <Show>, not a plain `if`: inBody() tracks the visibility toggle, which can
|
||||
// flip after mount (Settings), so the branch must stay reactive.
|
||||
return (
|
||||
<Base {...props} defaultOpen={initial()} retainDetails={props.defer} onOpenChange={change} hasDetails={inBody()}>
|
||||
{details()}
|
||||
</Base>
|
||||
<Show
|
||||
when={"children" in props || inBody()}
|
||||
fallback={<Base {...props} defaultOpen={initial()} retainDetails={props.defer} onOpenChange={change} />}
|
||||
>
|
||||
<Base {...props} defaultOpen={initial()} retainDetails={props.defer} onOpenChange={change} hasDetails={inBody()}>
|
||||
{details()}
|
||||
</Base>
|
||||
</Show>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ import { Tooltip } from "./tooltip"
|
||||
import { IconButton } from "./icon-button"
|
||||
import { TextShimmer } from "@opencode-ai/ui/text-shimmer"
|
||||
import { ToolApprovalProvider, resolveToolApproval } from "./tool-approval"
|
||||
export { ToolApprovalProvider, resolveToolApproval } from "./tool-approval"
|
||||
export { ToolApprovalProvider, resolveToolApproval, ToolApprovalVisibilityProvider } from "./tool-approval"
|
||||
import { GrowBox } from "./grow-box"
|
||||
import { COLLAPSIBLE_SPRING } from "./motion"
|
||||
import { busy, createThrottledValue, useToolFade, useContextToolPending } from "./tool-utils"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createContext, useContext, Show, type Accessor, type ParentProps } from "solid-js"
|
||||
import { Icon } from "./icon"
|
||||
|
||||
/**
|
||||
* Explains why a tool call was auto-approved, inside the expanded tool row.
|
||||
@@ -30,8 +31,23 @@ export function ToolApprovalProvider(props: ParentProps<{ value: Accessor<ToolAp
|
||||
return <Context.Provider value={props.value}>{props.children}</Context.Provider>
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the approval line should render at all. Hosts that expose a "hide
|
||||
* auto-approval reason" display setting wrap their tree in
|
||||
* `ToolApprovalVisibilityProvider`; without one, the line stays visible.
|
||||
*/
|
||||
const VisibilityContext = createContext<Accessor<boolean>>(() => true)
|
||||
|
||||
export function ToolApprovalVisibilityProvider(props: ParentProps<{ value: Accessor<boolean> }>) {
|
||||
return <VisibilityContext.Provider value={props.value}>{props.children}</VisibilityContext.Provider>
|
||||
}
|
||||
|
||||
/** Read the approval for the tool row below, gated by the visibility toggle
|
||||
* here (not per call site) so a new `ToolApprovalProvider` usage can't forget it. */
|
||||
export function useToolApproval() {
|
||||
return useContext(Context)
|
||||
const value = useContext(Context)
|
||||
const visible = useContext(VisibilityContext)
|
||||
return () => (visible() ? value() : undefined)
|
||||
}
|
||||
|
||||
/** Read the raw approval payload off a tool part's metadata, if present. */
|
||||
@@ -79,6 +95,7 @@ export function ToolApprovalLine(props: { display: ToolApprovalDisplay }) {
|
||||
const manual = () => props.display.approval.source === "manual"
|
||||
return (
|
||||
<div data-slot="tool-approval-line" data-source={props.display.approval.source}>
|
||||
<Icon name="shield" size="small" />
|
||||
<span data-slot="tool-approval-decision">{props.display.decision}</span>
|
||||
<Show when={!manual()}>
|
||||
<Show when={props.display.source}>{(text) => <span data-slot="tool-approval-source">{text()}</span>}</Show>
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
* Only add icons here that are actually used — esbuild/Vite will
|
||||
* tree-shake unused exports but explicit re-exports keep the API small.
|
||||
*/
|
||||
export { WandSparkles } from "lucide-solid"
|
||||
export { default as WandSparkles } from "lucide-solid/icons/wand-sparkles"
|
||||
|
||||
+172
-114
@@ -1,10 +1,96 @@
|
||||
const esbuild = require("esbuild")
|
||||
const path = require("path")
|
||||
const { solidPlugin } = require("esbuild-plugin-solid")
|
||||
const fs = require("fs")
|
||||
const crypto = require("crypto")
|
||||
const core = require("@babel/core")
|
||||
const solid = require("babel-preset-solid")
|
||||
const ts = require("@babel/preset-typescript")
|
||||
|
||||
const production = process.argv.includes("--production")
|
||||
const watch = process.argv.includes("--watch")
|
||||
|
||||
/**
|
||||
* Cache transformed Solid JSX files in memory and on disk to avoid
|
||||
* re-parsing and re-transforming unchanged files across builds and webviews.
|
||||
*/
|
||||
const solidCacheDir = path.join(__dirname, "node_modules", ".cache", "esbuild-solid")
|
||||
const solidMemCache = new Map()
|
||||
const buildScriptHash = crypto
|
||||
.createHash("sha256")
|
||||
.update(fs.readFileSync(__filename, "utf8"))
|
||||
.update(require("babel-preset-solid/package.json").version || "")
|
||||
.update(require("@babel/preset-typescript/package.json").version || "")
|
||||
.digest("hex")
|
||||
.slice(0, 8)
|
||||
|
||||
if (!fs.existsSync(solidCacheDir)) {
|
||||
try {
|
||||
fs.mkdirSync(solidCacheDir, { recursive: true })
|
||||
} catch (err) {
|
||||
console.warn("[esbuild] could not create solid cache directory", err)
|
||||
}
|
||||
}
|
||||
|
||||
const cachedSolidPlugin = {
|
||||
name: "esbuild:solid-cached",
|
||||
setup(build) {
|
||||
build.onLoad({ filter: /\.(t|j)sx$/ }, async (args) => {
|
||||
let mtime = 0
|
||||
let size = 0
|
||||
try {
|
||||
const st = fs.statSync(args.path)
|
||||
mtime = st.mtimeMs
|
||||
size = st.size
|
||||
} catch (err) {
|
||||
console.warn("[esbuild] could not stat source file for cache key", args.path, err)
|
||||
}
|
||||
|
||||
const cacheKey = `${args.path}:${mtime}:${size}:${buildScriptHash}`
|
||||
const memHit = solidMemCache.get(cacheKey)
|
||||
if (memHit) return { contents: memHit, loader: "js" }
|
||||
|
||||
const diskKey = crypto.createHash("sha256").update(cacheKey).digest("hex") + ".js"
|
||||
const diskPath = path.join(solidCacheDir, diskKey)
|
||||
|
||||
if (fs.existsSync(diskPath)) {
|
||||
try {
|
||||
const diskCode = fs.readFileSync(diskPath, "utf8")
|
||||
solidMemCache.set(cacheKey, diskCode)
|
||||
return { contents: diskCode, loader: "js" }
|
||||
} catch (err) {
|
||||
console.warn("[esbuild] cache read failed, rebuilding", diskPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
const source = fs.readFileSync(args.path, "utf8")
|
||||
const { name, ext } = path.parse(args.path)
|
||||
const filename = name + ext
|
||||
const result = await core.transformAsync(source, {
|
||||
presets: [
|
||||
[solid, {}],
|
||||
[ts, {}],
|
||||
],
|
||||
filename,
|
||||
sourceMaps: "inline",
|
||||
})
|
||||
|
||||
if (result?.code === void 0 || result.code === null) {
|
||||
throw new Error("No result was provided from Babel")
|
||||
}
|
||||
|
||||
if (solidMemCache.size > 2000) solidMemCache.clear()
|
||||
solidMemCache.set(cacheKey, result.code)
|
||||
try {
|
||||
fs.writeFileSync(diskPath, result.code)
|
||||
} catch (err) {
|
||||
console.warn("[esbuild] cache write failed", diskPath, err)
|
||||
}
|
||||
|
||||
return { contents: result.code, loader: "js" }
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Force all solid-js imports (from kilo-ui and the webview) to resolve to
|
||||
* the **same** copy so SolidJS contexts are shared across packages.
|
||||
@@ -123,7 +209,7 @@ const svgSpritePlugin = {
|
||||
name: "svg-sprite-inline",
|
||||
setup(build) {
|
||||
build.onLoad({ filter: /sprite\.svg$/ }, (args) => {
|
||||
const content = require("fs").readFileSync(args.path, "utf8")
|
||||
const content = fs.readFileSync(args.path, "utf8")
|
||||
return {
|
||||
contents: `
|
||||
const svg = ${JSON.stringify(content)};
|
||||
@@ -160,69 +246,8 @@ const cssPackageResolvePlugin = {
|
||||
},
|
||||
}
|
||||
|
||||
function createBrowserWebviewContext(entryPoint, outfile) {
|
||||
return esbuild.context({
|
||||
entryPoints: [entryPoint],
|
||||
bundle: true,
|
||||
format: "iife",
|
||||
minify: production,
|
||||
sourcemap: !production,
|
||||
sourcesContent: false,
|
||||
platform: "browser",
|
||||
outfile,
|
||||
logLevel: "silent",
|
||||
loader: {
|
||||
".woff": "file",
|
||||
".woff2": "file",
|
||||
".ttf": "file",
|
||||
},
|
||||
plugins: [
|
||||
solidDedupePlugin,
|
||||
pierreWorkerAliasPlugin,
|
||||
markdownWorkerUrlPlugin,
|
||||
svgSpritePlugin,
|
||||
cssPackageResolvePlugin,
|
||||
solidPlugin(),
|
||||
esbuildProblemMatcherPlugin,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// Bundle Pierre's Shiki worker into a single self-contained asset that the
|
||||
// webviews load off the main thread for syntax highlighting.
|
||||
function createShikiWorkerContext() {
|
||||
return esbuild.context({
|
||||
entryPoints: ["kilo-shiki-worker"],
|
||||
bundle: true,
|
||||
format: "iife",
|
||||
minify: production,
|
||||
sourcemap: !production,
|
||||
sourcesContent: false,
|
||||
platform: "browser",
|
||||
outfile: "dist/shiki-worker.js",
|
||||
logLevel: "silent",
|
||||
plugins: [shikiWorkerEntryPlugin, esbuildProblemMatcherPlugin],
|
||||
})
|
||||
}
|
||||
|
||||
function createMarkdownShikiWorkerContext() {
|
||||
return esbuild.context({
|
||||
entryPoints: [path.join(__dirname, "..", "ui", "src", "components", "markdown-shiki.worker.ts")],
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
minify: production,
|
||||
sourcemap: !production,
|
||||
sourcesContent: false,
|
||||
platform: "browser",
|
||||
outfile: "dist/markdown-shiki-worker.js",
|
||||
logLevel: "silent",
|
||||
plugins: [esbuildProblemMatcherPlugin],
|
||||
})
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Build extension
|
||||
const extensionCtx = await esbuild.context({
|
||||
function getExtensionConfig() {
|
||||
return {
|
||||
entryPoints: ["src/extension.ts"],
|
||||
bundle: true,
|
||||
format: "cjs",
|
||||
@@ -239,68 +264,101 @@ async function main() {
|
||||
outfile: "dist/extension.js",
|
||||
external: ["vscode"],
|
||||
logLevel: "silent",
|
||||
plugins: [esbuildProblemMatcherPlugin],
|
||||
})
|
||||
plugins: watch ? [esbuildProblemMatcherPlugin] : [],
|
||||
}
|
||||
}
|
||||
|
||||
// Build Agent Manager webview (SolidJS, shares components with sidebar)
|
||||
const agentManagerCtx = await createBrowserWebviewContext(
|
||||
"webview-ui/agent-manager/index.tsx",
|
||||
"dist/agent-manager.js",
|
||||
)
|
||||
function getWebviewsConfig() {
|
||||
return {
|
||||
entryPoints: {
|
||||
"agent-manager": "webview-ui/agent-manager/index.tsx",
|
||||
kiloclaw: "webview-ui/kiloclaw/index.tsx",
|
||||
marketplace: "webview-ui/marketplace/index.tsx",
|
||||
"diff-viewer": "webview-ui/diff-viewer/index.tsx",
|
||||
"diff-virtual": "webview-ui/diff-virtual/index.tsx",
|
||||
webview: "webview-ui/src/index.tsx",
|
||||
},
|
||||
outdir: "dist",
|
||||
bundle: true,
|
||||
format: "iife",
|
||||
minify: production,
|
||||
sourcemap: !production,
|
||||
sourcesContent: false,
|
||||
platform: "browser",
|
||||
logLevel: "silent",
|
||||
loader: {
|
||||
".woff": "file",
|
||||
".woff2": "file",
|
||||
".ttf": "file",
|
||||
},
|
||||
plugins: [
|
||||
solidDedupePlugin,
|
||||
pierreWorkerAliasPlugin,
|
||||
markdownWorkerUrlPlugin,
|
||||
svgSpritePlugin,
|
||||
cssPackageResolvePlugin,
|
||||
cachedSolidPlugin,
|
||||
...(watch ? [esbuildProblemMatcherPlugin] : []),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
// Build KiloClaw webview (SolidJS, standalone chat panel)
|
||||
const kiloClawCtx = await createBrowserWebviewContext("webview-ui/kiloclaw/index.tsx", "dist/kiloclaw.js")
|
||||
function getShikiWorkerConfig() {
|
||||
return {
|
||||
entryPoints: ["kilo-shiki-worker"],
|
||||
bundle: true,
|
||||
format: "iife",
|
||||
minify: production,
|
||||
sourcemap: !production,
|
||||
sourcesContent: false,
|
||||
platform: "browser",
|
||||
outfile: "dist/shiki-worker.js",
|
||||
logLevel: "silent",
|
||||
plugins: [shikiWorkerEntryPlugin, ...(watch ? [esbuildProblemMatcherPlugin] : [])],
|
||||
}
|
||||
}
|
||||
|
||||
// Build Marketplace webview (SolidJS, standalone catalog panel)
|
||||
const marketplaceCtx = await createBrowserWebviewContext("webview-ui/marketplace/index.tsx", "dist/marketplace.js")
|
||||
function getMarkdownShikiWorkerConfig() {
|
||||
return {
|
||||
entryPoints: [path.join(__dirname, "..", "ui", "src", "components", "markdown-shiki.worker.ts")],
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
minify: production,
|
||||
sourcemap: !production,
|
||||
sourcesContent: false,
|
||||
platform: "browser",
|
||||
outfile: "dist/markdown-shiki-worker.js",
|
||||
logLevel: "silent",
|
||||
plugins: watch ? [esbuildProblemMatcherPlugin] : [],
|
||||
}
|
||||
}
|
||||
|
||||
// Build Diff Viewer webview (SolidJS, reuses Agent Manager diff components)
|
||||
const diffViewerCtx = await createBrowserWebviewContext("webview-ui/diff-viewer/index.tsx", "dist/diff-viewer.js")
|
||||
|
||||
// Build Diff Virtual webview (lightweight single-file diff for permission approval)
|
||||
const diffVirtualCtx = await createBrowserWebviewContext("webview-ui/diff-virtual/index.tsx", "dist/diff-virtual.js")
|
||||
|
||||
// Build webview
|
||||
const webviewCtx = await createBrowserWebviewContext("webview-ui/src/index.tsx", "dist/webview.js")
|
||||
|
||||
// Build the shared Shiki highlighting worker asset
|
||||
const shikiWorkerCtx = await createShikiWorkerContext()
|
||||
const markdownShikiWorkerCtx = await createMarkdownShikiWorkerContext()
|
||||
async function main() {
|
||||
const extensionConfig = getExtensionConfig()
|
||||
const webviewsConfig = getWebviewsConfig()
|
||||
const shikiWorkerConfig = getShikiWorkerConfig()
|
||||
const markdownShikiWorkerConfig = getMarkdownShikiWorkerConfig()
|
||||
|
||||
if (watch) {
|
||||
const [extensionCtx, webviewsCtx, shikiWorkerCtx, markdownShikiWorkerCtx] = await Promise.all([
|
||||
esbuild.context(extensionConfig),
|
||||
esbuild.context(webviewsConfig),
|
||||
esbuild.context(shikiWorkerConfig),
|
||||
esbuild.context(markdownShikiWorkerConfig),
|
||||
])
|
||||
|
||||
await Promise.all([
|
||||
extensionCtx.watch(),
|
||||
webviewCtx.watch(),
|
||||
agentManagerCtx.watch(),
|
||||
diffViewerCtx.watch(),
|
||||
diffVirtualCtx.watch(),
|
||||
kiloClawCtx.watch(),
|
||||
marketplaceCtx.watch(),
|
||||
webviewsCtx.watch(),
|
||||
shikiWorkerCtx.watch(),
|
||||
markdownShikiWorkerCtx.watch(),
|
||||
])
|
||||
} else {
|
||||
await Promise.all([
|
||||
extensionCtx.rebuild(),
|
||||
webviewCtx.rebuild(),
|
||||
agentManagerCtx.rebuild(),
|
||||
kiloClawCtx.rebuild(),
|
||||
marketplaceCtx.rebuild(),
|
||||
diffViewerCtx.rebuild(),
|
||||
diffVirtualCtx.rebuild(),
|
||||
shikiWorkerCtx.rebuild(),
|
||||
markdownShikiWorkerCtx.rebuild(),
|
||||
])
|
||||
await Promise.all([
|
||||
extensionCtx.dispose(),
|
||||
webviewCtx.dispose(),
|
||||
agentManagerCtx.dispose(),
|
||||
diffViewerCtx.dispose(),
|
||||
diffVirtualCtx.dispose(),
|
||||
kiloClawCtx.dispose(),
|
||||
marketplaceCtx.dispose(),
|
||||
shikiWorkerCtx.dispose(),
|
||||
markdownShikiWorkerCtx.dispose(),
|
||||
esbuild.build(extensionConfig),
|
||||
esbuild.build(webviewsConfig),
|
||||
esbuild.build(shikiWorkerConfig),
|
||||
esbuild.build(markdownShikiWorkerConfig),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +147,41 @@
|
||||
"category": "Kilo Code",
|
||||
"icon": "$(settings-gear)"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.plusButtonClicked",
|
||||
"title": "New Task",
|
||||
"icon": "$(add)"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.agentManagerOpen",
|
||||
"title": "Agent Manager",
|
||||
"icon": "$(organization)"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.kiloClawOpen",
|
||||
"title": "KiloClaw",
|
||||
"icon": "$(comment-discussion)"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.marketplaceButtonClicked",
|
||||
"title": "Marketplace",
|
||||
"icon": "$(extensions)"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.historyButtonClicked",
|
||||
"title": "History",
|
||||
"icon": "$(history)"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.profileButtonClicked",
|
||||
"title": "Profile",
|
||||
"icon": "$(account)"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.settingsButtonClicked",
|
||||
"title": "Settings",
|
||||
"icon": "$(settings-gear)"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.openInTab",
|
||||
"title": "Open in Tab",
|
||||
@@ -442,12 +477,76 @@
|
||||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.plusButtonClicked",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.historyButtonClicked",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.agentManagerOpen",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.kiloClawOpen",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.agentManager.advancedWorktree",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.marketplaceButtonClicked",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.profileButtonClicked",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.settingsButtonClicked",
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"view/title": [
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.plusButtonClicked",
|
||||
"group": "navigation@0",
|
||||
"when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.historyButtonClicked",
|
||||
"group": "navigation@1",
|
||||
"when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.agentManagerOpen",
|
||||
"group": "navigation@2",
|
||||
"when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.kiloClawOpen",
|
||||
"group": "navigation@3",
|
||||
"when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.marketplaceButtonClicked",
|
||||
"group": "navigation@4",
|
||||
"when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.profileButtonClicked",
|
||||
"group": "navigation@5",
|
||||
"when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.sidebarTitle.settingsButtonClicked",
|
||||
"group": "navigation@6",
|
||||
"when": "view == kilo-code.SidebarProvider && !kilo-code.new.isCursor"
|
||||
}
|
||||
],
|
||||
"view/title": [],
|
||||
"scm/title": [
|
||||
{
|
||||
"command": "kilo-code.new.generateCommitMessage",
|
||||
@@ -467,6 +566,26 @@
|
||||
"command": "kilo-code.new.openInTab",
|
||||
"group": "navigation",
|
||||
"when": "true"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.plusButtonClicked",
|
||||
"group": "navigation@0",
|
||||
"when": "activeWebviewPanelId == kilo-code.new.TabPanel && !kilo-code.new.isCursor"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.historyButtonClicked",
|
||||
"group": "navigation@1",
|
||||
"when": "activeWebviewPanelId == kilo-code.new.TabPanel && !kilo-code.new.isCursor"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.profileButtonClicked",
|
||||
"group": "navigation@2",
|
||||
"when": "activeWebviewPanelId == kilo-code.new.TabPanel && !kilo-code.new.isCursor"
|
||||
},
|
||||
{
|
||||
"command": "kilo-code.new.settingsButtonClicked",
|
||||
"group": "navigation@3",
|
||||
"when": "activeWebviewPanelId == kilo-code.new.TabPanel && !kilo-code.new.isCursor"
|
||||
}
|
||||
],
|
||||
"editor/context": [
|
||||
@@ -1072,6 +1191,11 @@
|
||||
"default": false,
|
||||
"description": "Show tokens-per-second (prompt-processing / text-generation) badges on assistant messages and the task header"
|
||||
},
|
||||
"kilo-code.new.showAutoApprovalReason": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Show a line on tool calls explaining why they were auto-approved (matched rule, agent default, YOLO mode, etc.)"
|
||||
},
|
||||
"kilo-code.new.chat.shiftTabCyclesVariant": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
@@ -1107,12 +1231,12 @@
|
||||
"prepare:cli-binary": "bun script/local-bin.ts",
|
||||
"prepare:sdk": "bun script/prepare-sdk.ts",
|
||||
"build:launch": "bun run prepare:cli-binary && bun run prepare:sdk && bun run build:check:production",
|
||||
"compile": "bun run prepare:cli-binary -- --force && bun run rebuild-sdk && bun run build:check",
|
||||
"watch": "bun run rebuild-sdk && bun run --parallel watch:esbuild watch:tsc",
|
||||
"compile": "bun run prepare:cli-binary && bun run prepare:sdk && bun run build:check",
|
||||
"watch": "bun run prepare:sdk && bun run --parallel watch:esbuild watch:tsc",
|
||||
"watch:esbuild": "bun run prepare:cli-binary && node esbuild.js --watch",
|
||||
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
|
||||
"watch:cli": "bun script/watch-cli.ts",
|
||||
"package": "bun run prepare:cli-binary && bun run rebuild-sdk && bun run build:check:production",
|
||||
"package": "bun run prepare:cli-binary && bun run prepare:sdk && bun run build:check:production",
|
||||
"build:check": "bun run --parallel check-types check-types:webview lint bundle",
|
||||
"build:check:production": "bun run --parallel check-types check-types:webview lint bundle:production",
|
||||
"bundle": "bun esbuild.js",
|
||||
@@ -1144,6 +1268,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@axe-core/playwright": "4.11.3",
|
||||
"@babel/core": "^7.28.4",
|
||||
"@babel/preset-typescript": "^7.27.1",
|
||||
"@playwright/test": "1.57.0",
|
||||
"@storybook/addon-a11y": "10.2.10",
|
||||
"@storybook/addon-docs": "10.2.10",
|
||||
@@ -1155,6 +1281,7 @@
|
||||
"@vscode/test-cli": "^0.0.12",
|
||||
"@vscode/test-electron": "^2.5.2",
|
||||
"@vscode/vsce": "^3.7.1",
|
||||
"babel-preset-solid": "^1.9.9",
|
||||
"esbuild": "^0.27.2",
|
||||
"esbuild-plugin-solid": "^0.6.0",
|
||||
"eslint": "^9.39.2",
|
||||
|
||||
@@ -252,13 +252,15 @@ async function ensureBuiltBinary(): Promise<string> {
|
||||
)
|
||||
}
|
||||
|
||||
// Use the repository-pinned Bun version throughout. Newer canaries can fail compilation
|
||||
// and must not cause packaged snapshots to fall back to the browser-mode source wrapper.
|
||||
const pkg = await Bun.file(join(repoDir, "package.json")).json()
|
||||
const bun = String(pkg.packageManager)
|
||||
log("Installing dependencies in opencode package...")
|
||||
await $`bunx ${bun} install --frozen-lockfile`.cwd(opencodeDir)
|
||||
await $`bunx ${bun} run build --single --skip-install`.cwd(opencodeDir)
|
||||
log("Building CLI binary...")
|
||||
try {
|
||||
await $`bunx ${bun} run build --single --skip-install`.cwd(opencodeDir)
|
||||
} catch (err) {
|
||||
log(`Pinned bunx build failed (${err}), running via active bun runtime...`)
|
||||
await $`bun run script/build.ts --single --skip-install`.cwd(opencodeDir)
|
||||
}
|
||||
|
||||
const built = await findKiloBinaryInOpencodeDist()
|
||||
if (!built) {
|
||||
|
||||
@@ -17,7 +17,7 @@ import type { EditorContext, IndexingStatus } from "./services/cli-backend/types
|
||||
import { FileIgnoreController } from "./services/autocomplete/shims/FileIgnoreController"
|
||||
import { ChatTextAreaAutocomplete } from "./services/autocomplete/chat-autocomplete/ChatTextAreaAutocomplete"
|
||||
import { notebookUri } from "./services/autocomplete/continuedev/core/autocomplete/notebook"
|
||||
import { buildWebviewHtml, getWebviewFontSize } from "./utils"
|
||||
import { buildWebviewHtml, getWebviewFontSize, isCursorHost } from "./utils"
|
||||
import { saveImage } from "./kilo-provider/save-image"
|
||||
import { handleEditorAction } from "./kilo-provider/editor-actions"
|
||||
import { exportTranscript } from "./kilo-provider/export-transcript"
|
||||
@@ -186,6 +186,10 @@ import {
|
||||
import { canonicalizePath, projectIdFor, samePath } from "./agent-manager/project/paths"
|
||||
import { validChatSetting, watchChatConfig } from "./kilo-provider/chat-settings"
|
||||
import { buildThroughputSettingMessage, watchThroughputConfig } from "./kilo-provider/throughput-settings"
|
||||
import {
|
||||
buildAutoApprovalReasonSettingMessage,
|
||||
watchAutoApprovalReasonConfig,
|
||||
} from "./kilo-provider/auto-approval-reason-settings"
|
||||
|
||||
let maxCost = 0
|
||||
|
||||
@@ -419,6 +423,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
private indexingConfigDisposable: vscode.Disposable | null = null
|
||||
private chatConfigDisposable: vscode.Disposable | null = null
|
||||
private throughputConfigDisposable: vscode.Disposable | null = null
|
||||
private autoApprovalReasonConfigDisposable: vscode.Disposable | null = null
|
||||
private telemetryStateDisposable: vscode.Disposable | null = null
|
||||
private viewStateDisposable: vscode.Disposable | null = null
|
||||
private visibilityDisposable: vscode.Disposable | null = null
|
||||
@@ -1002,6 +1007,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.chatConfigDisposable = watchChatConfig((msg) => this.postMessage(msg))
|
||||
this.throughputConfigDisposable?.dispose()
|
||||
this.throughputConfigDisposable = watchThroughputConfig((msg) => this.postMessage(msg))
|
||||
this.autoApprovalReasonConfigDisposable?.dispose()
|
||||
this.autoApprovalReasonConfigDisposable = watchAutoApprovalReasonConfig((msg) => this.postMessage(msg))
|
||||
this.telemetryStateDisposable?.dispose()
|
||||
this.telemetryStateDisposable = watchTelemetryState((msg) => this.postMessage(msg))
|
||||
this.webviewMessageDisposable = webview.onDidReceiveMessage(async (message) => {
|
||||
@@ -1832,6 +1839,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.sendNotificationSettings()
|
||||
this.sendTimelineSetting()
|
||||
this.postMessage(buildThroughputSettingMessage())
|
||||
this.postMessage(buildAutoApprovalReasonSettingMessage())
|
||||
this.postMessage({ type: "extensionDataReady" })
|
||||
|
||||
console.log("[Kilo New] KiloProvider: ✅ initializeConnection completed successfully")
|
||||
@@ -4072,6 +4080,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.sendNotificationSettings()
|
||||
this.sendTimelineSetting()
|
||||
this.postMessage(buildThroughputSettingMessage())
|
||||
this.postMessage(buildAutoApprovalReasonSettingMessage())
|
||||
this.sendWorkStyle()
|
||||
await ModelState.reset(this.client, (msg) => this.postMessage(msg))
|
||||
|
||||
@@ -4909,7 +4918,10 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
title: "Kilo Code",
|
||||
port: this.connectionService.getServerInfo()?.port,
|
||||
extraStyles: `.container { height: 100vh; }`,
|
||||
topBar: this.opts.hideTopBar !== true,
|
||||
// Dedicated single-purpose panels (Settings, Profile, Sub-Agent Viewer)
|
||||
// never show the bar. Sidebar and "Open in Tab" only need it in Cursor —
|
||||
// VS Code's native toolbar (restored in package.json) works everywhere.
|
||||
topBar: this.opts.hideTopBar !== true && isCursorHost(),
|
||||
topBarSurface: this.opts.topBarSurface === "tab" ? "tab_title" : "sidebar_title",
|
||||
})
|
||||
}
|
||||
@@ -4998,6 +5010,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.indexingConfigDisposable?.dispose()
|
||||
this.chatConfigDisposable?.dispose()
|
||||
this.throughputConfigDisposable?.dispose()
|
||||
this.autoApprovalReasonConfigDisposable?.dispose()
|
||||
this.telemetryStateDisposable?.dispose()
|
||||
this.autoApproveBridge?.dispose()
|
||||
this.visibleTaskStreams.clear()
|
||||
|
||||
@@ -53,8 +53,10 @@ import {
|
||||
import { initContextState, pushProjectSessions, reactivateProject, registerProjectSessions } from "./project/init"
|
||||
import { createLocalDiff } from "./local-diff"
|
||||
import { parseToolRequest, startFromTool, type ToolRequest } from "./tool-start"
|
||||
import { handleToolEvent } from "./tool-project"
|
||||
import { sandboxSessionMetadata } from "../shared/sandbox-session"
|
||||
import { AgentManagerOrchestrationBridge } from "./orchestration-bridge"
|
||||
import { createOrchestrationBridge } from "./orchestration-setup"
|
||||
import type { AgentManagerOrchestrationBridge } from "./orchestration-bridge"
|
||||
import { pruneSubagents } from "./prune-subagents"
|
||||
import { startSession } from "./mcp-warmup"
|
||||
import { readTerminalFont, watchTerminalFont } from "./terminal-font"
|
||||
@@ -253,22 +255,20 @@ export class AgentManagerProvider implements Disposable {
|
||||
this.statsPoller = pollers.stats
|
||||
this.prBridge = pollers.pr
|
||||
this.projectPollers = pollers.projects
|
||||
this.orchestration = new AgentManagerOrchestrationBridge(this.connectionService, {
|
||||
root: () => this.getRoot(),
|
||||
state: () => this.state,
|
||||
ready: async () => {
|
||||
this.stateReady ??= this.initializeState()
|
||||
await this.stateReady
|
||||
return this.state
|
||||
},
|
||||
stats: () => this.statsPoller.snapshot(),
|
||||
prs: () => this.prBridge.snapshot(),
|
||||
push: () => this.pushState(),
|
||||
managed: (id) => this.panelSessions.has(id) || !!this.state?.getSession(id),
|
||||
close: async (id) => {
|
||||
await this.onCloseSession(id)
|
||||
this.postToWebview({ type: "agentManager.sessionClosed", sessionId: id })
|
||||
},
|
||||
this.orchestration = createOrchestrationBridge({
|
||||
connectionService: this.connectionService,
|
||||
contexts: this.contexts,
|
||||
projectScope: this.projectScope,
|
||||
getRoot: () => this.getRoot(),
|
||||
getState: () => this.state,
|
||||
getStateReady: () => this.stateReady,
|
||||
initStateReady: () => (this.stateReady = this.initializeState()),
|
||||
getStats: () => this.statsPoller.snapshot(),
|
||||
getPrs: () => this.prBridge.snapshot(),
|
||||
pushState: (ctx) => this.pushState(ctx),
|
||||
hasPanelSession: (id) => this.panelSessions.has(id),
|
||||
closeSession: (id) => this.onCloseSession(id),
|
||||
postSessionClosed: (id) => this.postToWebview({ type: "agentManager.sessionClosed", sessionId: id }),
|
||||
log: (...args) => this.log(...args),
|
||||
})
|
||||
this.unsubTool = this.connectionService.onEventFiltered(
|
||||
@@ -1081,14 +1081,16 @@ export class AgentManagerProvider implements Disposable {
|
||||
}
|
||||
|
||||
private onToolEvent(event: unknown, directory?: string): void {
|
||||
const properties = (event as { properties?: unknown }).properties
|
||||
const req = parseToolRequest(properties)
|
||||
if (!req) return
|
||||
if (directory) {
|
||||
req.directory = directory
|
||||
req.projectId ??= this.contexts.byDirectory(directory)?.id
|
||||
}
|
||||
void this.startToolRequest(req)
|
||||
handleToolEvent(
|
||||
event,
|
||||
directory,
|
||||
{
|
||||
byDirectory: (value) => this.contexts.byDirectory(value),
|
||||
usable: (id) => this.contexts.usable(id),
|
||||
},
|
||||
this.projectScope,
|
||||
(req) => this.startToolRequest(req),
|
||||
)
|
||||
}
|
||||
|
||||
private async startToolRequest(req: ToolRequest): Promise<void> {
|
||||
@@ -1826,19 +1828,15 @@ export class AgentManagerProvider implements Disposable {
|
||||
|
||||
public async createFromSidebar(baseBranch?: string, branchName?: string): Promise<void> {
|
||||
this.openPanel()
|
||||
const panel = this.panel
|
||||
if (!panel) return
|
||||
if (!(await this.waitForPanelReady(panel))) return
|
||||
if (!this.panel || !(await this.waitForPanelReady(this.panel))) return
|
||||
await this.waitForStateReady("createFromSidebar")
|
||||
await this.onCreateWorktree(baseBranch, branchName)
|
||||
}
|
||||
|
||||
public async openAdvancedWorktree(): Promise<void> {
|
||||
this.openPanel()
|
||||
const panel = this.panel
|
||||
if (!panel) return
|
||||
if (!(await this.waitForPanelActive(panel))) return
|
||||
if (!(await this.waitForPanelReady(panel))) return
|
||||
if (!this.panel || !(await this.waitForPanelActive(this.panel)) || !(await this.waitForPanelReady(this.panel)))
|
||||
return
|
||||
await this.waitForStateReady("openAdvancedWorktree")
|
||||
queueMicrotask(() => this.postToWebview({ type: "action", action: "advancedWorktree" }))
|
||||
}
|
||||
|
||||
@@ -41,14 +41,15 @@ interface Failure {
|
||||
}
|
||||
|
||||
interface Options {
|
||||
root(): string | undefined
|
||||
ready(): Promise<WorktreeStateManager | undefined>
|
||||
state(): WorktreeStateManager | undefined
|
||||
stats(): Promise<{ worktrees: WorktreeStats[]; local?: LocalStats }>
|
||||
prs(): Map<string, PRStatus>
|
||||
push(): void
|
||||
managed(sessionID: string): boolean
|
||||
close(sessionID: string): Promise<void>
|
||||
root(directory?: string): string | undefined
|
||||
ready(directory?: string): Promise<WorktreeStateManager | undefined>
|
||||
state(directory?: string): WorktreeStateManager | undefined
|
||||
stats(directory?: string): Promise<{ worktrees: WorktreeStats[]; local?: LocalStats }>
|
||||
prs(directory?: string): Map<string, PRStatus>
|
||||
push(directory?: string): void
|
||||
managed(sessionID: string, directory?: string): boolean
|
||||
close(sessionID: string, directory?: string): Promise<void>
|
||||
directories?(): string[]
|
||||
log(...args: unknown[]): void
|
||||
}
|
||||
|
||||
@@ -108,6 +109,7 @@ export class AgentManagerOrchestrationBridge {
|
||||
})
|
||||
})
|
||||
this.unsubscribeDirectories = connection.registerDirectoryProvider(() => {
|
||||
if (this.options.directories) return this.options.directories()
|
||||
const root = this.options.root()
|
||||
const dirs =
|
||||
this.options
|
||||
@@ -181,8 +183,8 @@ export class AgentManagerOrchestrationBridge {
|
||||
}
|
||||
|
||||
private async admit(request: Request, directory: string): Promise<void> {
|
||||
const state = await this.options.ready()
|
||||
const root = this.options.root()
|
||||
const state = await this.options.ready(directory)
|
||||
const root = this.options.root(directory)
|
||||
if (this.disposed || this.settled.has(request.id)) return
|
||||
if (!state || !root) {
|
||||
const accepted = await this.reject(request.id, directory, {
|
||||
@@ -232,7 +234,7 @@ export class AgentManagerOrchestrationBridge {
|
||||
|
||||
private async run(request: Request, origin: Origin, active: Active): Promise<void> {
|
||||
try {
|
||||
const outcome = this.outcomes.get(request.id) ?? (await this.execute(request, active))
|
||||
const outcome = this.outcomes.get(request.id) ?? (await this.execute(request, origin, active))
|
||||
if (!outcome || this.disposed || active.cancelled) return
|
||||
this.rememberOutcome(request.id, outcome)
|
||||
const accepted =
|
||||
@@ -248,10 +250,10 @@ export class AgentManagerOrchestrationBridge {
|
||||
}
|
||||
}
|
||||
|
||||
private async execute(request: Request, active: Active): Promise<Outcome | undefined> {
|
||||
private async execute(request: Request, origin: Origin, active: Active): Promise<Outcome | undefined> {
|
||||
try {
|
||||
const state = await this.options.ready()
|
||||
const root = this.options.root()
|
||||
const state = await this.options.ready(origin.directory)
|
||||
const root = this.options.root(origin.directory)
|
||||
if (!state || !root)
|
||||
throw new OrchestrationError("workspace_unavailable", "Agent Manager requires an open workspace")
|
||||
if (this.disposed || active.cancelled) return
|
||||
@@ -260,7 +262,7 @@ export class AgentManagerOrchestrationBridge {
|
||||
// Git stats are refreshed by the poller independently. A forced refresh
|
||||
// here can spawn one diff/ahead-behind pair per worktree and exceed the
|
||||
// host request timeout before the overview can return its IDs.
|
||||
const stats = await this.options.stats()
|
||||
const stats = await this.options.stats(origin.directory)
|
||||
if (this.disposed || active.cancelled) return
|
||||
const result = await overview({
|
||||
client,
|
||||
@@ -269,7 +271,7 @@ export class AgentManagerOrchestrationBridge {
|
||||
titles: this.titles,
|
||||
filter: request.filter,
|
||||
stats,
|
||||
prs: this.options.prs(),
|
||||
prs: this.options.prs(origin.directory),
|
||||
})
|
||||
return { result: { operation: "overview", overview: result } }
|
||||
}
|
||||
@@ -288,7 +290,7 @@ export class AgentManagerOrchestrationBridge {
|
||||
}
|
||||
if (request.operation === "move") {
|
||||
move({ state, sessionID: request.targetSessionID, sectionID: request.sectionID })
|
||||
this.options.push()
|
||||
this.options.push(origin.directory)
|
||||
if (this.disposed || active.cancelled) return
|
||||
return {
|
||||
result: {
|
||||
@@ -299,10 +301,10 @@ export class AgentManagerOrchestrationBridge {
|
||||
},
|
||||
}
|
||||
}
|
||||
if (!this.options.managed(request.targetSessionID)) {
|
||||
if (!this.options.managed(request.targetSessionID, origin.directory)) {
|
||||
throw new OrchestrationError("unknown_session", "The session is not managed by this Agent Manager workspace")
|
||||
}
|
||||
await this.options.close(request.targetSessionID)
|
||||
await this.options.close(request.targetSessionID, origin.directory)
|
||||
if (this.disposed || active.cancelled) return
|
||||
return { result: { operation: "stop", sessionID: request.targetSessionID, stopped: true } }
|
||||
} catch (error) {
|
||||
|
||||
@@ -319,6 +319,7 @@ export async function prompt(input: {
|
||||
text: string
|
||||
messageID: string
|
||||
signal?: AbortSignal
|
||||
idleTimeoutMs?: number
|
||||
}): Promise<void> {
|
||||
if (input.signal?.aborted) return
|
||||
const managed = input.state.getSession(input.sessionID)
|
||||
@@ -342,15 +343,7 @@ export async function prompt(input: {
|
||||
if (!(await sameManagedDirectory(response.data.directory, dir))) {
|
||||
throw new OrchestrationError("cross_workspace", "The managed session belongs to a different workspace directory")
|
||||
}
|
||||
const status = await input.client.session.status({ directory: dir })
|
||||
if (status.error) throw new OrchestrationError("host_error", "The managed session status could not be read")
|
||||
const activity = status.data?.[input.sessionID]?.type ?? "idle"
|
||||
if (activity !== "idle") {
|
||||
throw new OrchestrationError(
|
||||
"unavailable_session",
|
||||
`The managed session is ${activity}; only idle sessions can be prompted`,
|
||||
)
|
||||
}
|
||||
await waitForIdle(input.client, dir, input.sessionID, input.signal, input.idleTimeoutMs ?? 30_000)
|
||||
if (input.signal?.aborted) return
|
||||
await input.client.session.promptAsync(
|
||||
{
|
||||
@@ -364,6 +357,29 @@ export async function prompt(input: {
|
||||
)
|
||||
}
|
||||
|
||||
async function waitForIdle(
|
||||
client: KiloClient,
|
||||
directory: string,
|
||||
sessionID: string,
|
||||
signal: AbortSignal | undefined,
|
||||
timeout: number,
|
||||
start = Date.now(),
|
||||
): Promise<void> {
|
||||
if (signal?.aborted) return
|
||||
const status = await client.session.status({ directory })
|
||||
if (status.error) throw new OrchestrationError("host_error", "The managed session status could not be read")
|
||||
const activity = status.data?.[sessionID]?.type ?? "idle"
|
||||
if (activity === "idle") return
|
||||
if (Date.now() - start >= timeout) {
|
||||
throw new OrchestrationError(
|
||||
"unavailable_session",
|
||||
`The managed session is still ${activity}; only idle sessions can be prompted`,
|
||||
)
|
||||
}
|
||||
await new Promise<void>((resolve) => setTimeout(resolve, 250))
|
||||
return waitForIdle(client, directory, sessionID, signal, timeout, start)
|
||||
}
|
||||
|
||||
export function move(input: { state: WorktreeStateManager; sessionID: string; sectionID: string | null }): void {
|
||||
const session = input.state.getSession(input.sessionID)
|
||||
if (!session)
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import type { KiloConnectionService } from "../services/cli-backend/connection-service"
|
||||
import { AgentManagerOrchestrationBridge } from "./orchestration-bridge"
|
||||
import type { ProjectContexts } from "./project/contexts"
|
||||
import type { ProjectContext } from "./project/context"
|
||||
import type { ProjectScope } from "./project/scope"
|
||||
import type { WorktreeStateManager } from "./WorktreeStateManager"
|
||||
import type { WorktreeStats, LocalStats } from "./GitStatsPoller"
|
||||
import type { PRStatus } from "./types"
|
||||
import { initContextState } from "./project/init"
|
||||
|
||||
export interface OrchestrationBridgeDeps {
|
||||
connectionService: KiloConnectionService
|
||||
contexts: ProjectContexts
|
||||
projectScope: ProjectScope
|
||||
getRoot: () => string | undefined
|
||||
getState: () => WorktreeStateManager | undefined
|
||||
getStateReady: () => Promise<void> | undefined
|
||||
initStateReady: () => Promise<void>
|
||||
getStats: () => Promise<{ worktrees: WorktreeStats[]; local?: LocalStats }>
|
||||
getPrs: () => Map<string, PRStatus>
|
||||
pushState: (ctx?: ProjectContext) => void
|
||||
hasPanelSession: (id: string) => boolean
|
||||
closeSession: (id: string) => Promise<unknown>
|
||||
postSessionClosed: (id: string) => void
|
||||
log: (...args: unknown[]) => void
|
||||
}
|
||||
|
||||
export function createOrchestrationBridge(deps: OrchestrationBridgeDeps): AgentManagerOrchestrationBridge {
|
||||
return new AgentManagerOrchestrationBridge(deps.connectionService, {
|
||||
root: (dir) => (dir ? deps.contexts.byDirectory(dir)?.root : undefined) ?? deps.getRoot(),
|
||||
state: (dir) => (dir ? deps.contexts.byDirectory(dir)?.peekState() : undefined) ?? deps.getState(),
|
||||
ready: async (dir) => {
|
||||
const ctx = dir ? deps.contexts.byDirectory(dir) : undefined
|
||||
if (ctx && ctx.id !== deps.contexts.active()?.id) {
|
||||
await initContextState(ctx, (...args) => deps.log(...args))
|
||||
return ctx.stateManager()
|
||||
}
|
||||
const ready = deps.getStateReady() ?? deps.initStateReady()
|
||||
await ready
|
||||
return deps.getState()
|
||||
},
|
||||
stats: () => deps.getStats(),
|
||||
prs: () => deps.getPrs(),
|
||||
push: (dir) => {
|
||||
const ctx = dir ? deps.contexts.byDirectory(dir) : undefined
|
||||
deps.pushState(ctx)
|
||||
},
|
||||
managed: (id, dir) => {
|
||||
const ctx = dir ? deps.contexts.byDirectory(dir) : undefined
|
||||
if (ctx) return ctx.hasLiveSession(id) || !!ctx.peekState()?.getSession(id)
|
||||
return deps.hasPanelSession(id) || !!deps.getState()?.getSession(id)
|
||||
},
|
||||
close: async (id, dir) => {
|
||||
const ctx = dir ? deps.contexts.byDirectory(dir) : undefined
|
||||
if (ctx) {
|
||||
await deps.projectScope.run(ctx, () => deps.closeSession(id))
|
||||
} else {
|
||||
await deps.closeSession(id)
|
||||
}
|
||||
deps.postSessionClosed(id)
|
||||
},
|
||||
directories: () => {
|
||||
const all: string[] = []
|
||||
for (const ctx of deps.contexts.values()) {
|
||||
all.push(ctx.root)
|
||||
for (const wt of ctx.peekState()?.getWorktrees() ?? []) {
|
||||
if (wt.path) all.push(wt.path)
|
||||
}
|
||||
}
|
||||
if (all.length === 0) {
|
||||
const root = deps.getRoot()
|
||||
if (root) all.push(root)
|
||||
}
|
||||
return all
|
||||
},
|
||||
log: (...args) => deps.log(...args),
|
||||
})
|
||||
}
|
||||
@@ -100,6 +100,10 @@ export class ProjectContexts {
|
||||
return this.contexts.get(id)
|
||||
}
|
||||
|
||||
values(): IterableIterator<ProjectContext> {
|
||||
return this.contexts.values()
|
||||
}
|
||||
|
||||
/** The context that owns a directory: its root or one of its worktree paths. */
|
||||
byDirectory(dir: string): ProjectContext | undefined {
|
||||
for (const ctx of this.contexts.values()) {
|
||||
|
||||
@@ -51,6 +51,7 @@ interface Entry {
|
||||
export class TerminalManager {
|
||||
private readonly entries = new Map<string, Entry>()
|
||||
private readonly restarts = new Map<string, Promise<void>>()
|
||||
private readonly pending = new Map<string, { cols: number; rows: number }>()
|
||||
|
||||
constructor(private readonly deps: TerminalManagerDeps) {}
|
||||
|
||||
@@ -67,7 +68,14 @@ export class TerminalManager {
|
||||
worktreeId: string | null
|
||||
cwd: string
|
||||
title: string
|
||||
cols?: number
|
||||
rows?: number
|
||||
}): Promise<{ terminalId: string; worktreeId: string | null; title: string; wsUrl: string }> {
|
||||
const initial =
|
||||
this.pending.get(params.terminalId) ??
|
||||
(params.cols !== undefined && params.rows !== undefined ? { cols: params.cols, rows: params.rows } : undefined)
|
||||
this.pending.delete(params.terminalId)
|
||||
|
||||
const client = this.deps.getClient()
|
||||
const { data, error } = await client.pty.create({
|
||||
directory: params.cwd,
|
||||
@@ -76,6 +84,7 @@ export class TerminalManager {
|
||||
// xterm's DOM renderer cannot draw the Unicode sextant glyphs used by
|
||||
// Kilo's modern wordmark, so use the compatible logo in embedded tabs.
|
||||
env,
|
||||
size: initial,
|
||||
})
|
||||
if (error || !data) {
|
||||
const err = error instanceof Error ? error.message : String(error ?? "unknown error")
|
||||
@@ -89,15 +98,39 @@ export class TerminalManager {
|
||||
title: data.title ?? params.title,
|
||||
}
|
||||
this.entries.set(params.terminalId, entry)
|
||||
// If a resize arrived while pty.create was in flight that differed from `initial`, apply it now.
|
||||
const latest = this.pending.get(params.terminalId)
|
||||
if (latest && (latest.cols !== initial?.cols || latest.rows !== initial?.rows)) {
|
||||
this.pending.delete(params.terminalId)
|
||||
const { error: resizeErr } = await client.pty.update({
|
||||
directory: entry.cwd,
|
||||
ptyID: entry.ptyID,
|
||||
size: latest,
|
||||
})
|
||||
if (resizeErr) {
|
||||
const err = resizeErr instanceof Error ? resizeErr.message : String(resizeErr)
|
||||
this.deps.log(`Initial terminal resize failed (${params.terminalId}): ${err}`)
|
||||
}
|
||||
}
|
||||
const wsUrl = this.deps.buildWsUrl(entry.ptyID, entry.cwd)
|
||||
this.deps.log(`Terminal created: ${params.terminalId} -> pty ${entry.ptyID} cwd=${entry.cwd}`)
|
||||
return { terminalId: params.terminalId, worktreeId: entry.worktreeId, title: entry.title, wsUrl }
|
||||
}
|
||||
|
||||
/** Forward a resize event to the backend PTY. Missing terminals are a no-op. */
|
||||
/**
|
||||
* Forward a resize event to the backend PTY.
|
||||
*
|
||||
* If the terminal creation is still in flight, dimensions are queued into
|
||||
* `pending` and applied during PTY initialization before the WebSocket
|
||||
* URL is returned.
|
||||
*/
|
||||
async resize(terminalId: string, cols: number, rows: number): Promise<void> {
|
||||
const entry = this.entries.get(terminalId)
|
||||
if (!entry) return
|
||||
if (!entry) {
|
||||
this.pending.set(terminalId, { cols, rows })
|
||||
return
|
||||
}
|
||||
this.pending.delete(terminalId)
|
||||
const client = this.deps.getClient()
|
||||
const { error } = await client.pty.update({
|
||||
directory: entry.cwd,
|
||||
@@ -126,6 +159,7 @@ export class TerminalManager {
|
||||
* failed delete would be silently logged as a successful close and
|
||||
* the server-side PTY would linger until `kilo serve` exits. */
|
||||
async close(terminalId: string): Promise<void> {
|
||||
this.pending.delete(terminalId)
|
||||
const entry = this.entries.get(terminalId)
|
||||
if (!entry) return
|
||||
this.entries.delete(terminalId)
|
||||
@@ -187,6 +221,7 @@ export class TerminalManager {
|
||||
* is sampled mid-shutdown.
|
||||
*/
|
||||
async dispose(): Promise<void> {
|
||||
this.pending.clear()
|
||||
const snapshot = [...this.entries.values()]
|
||||
if (snapshot.length === 0) {
|
||||
this.entries.clear()
|
||||
|
||||
@@ -88,7 +88,7 @@ export class TerminalRouter {
|
||||
handle(m: AgentManagerInMessage): boolean {
|
||||
if (!isTerminalMessage(m)) return false
|
||||
if (m.type === "agentManager.terminal.create") {
|
||||
void this.handleCreate(m.createId, m.placement, m.worktreeId)
|
||||
void this.handleCreate(m.createId, m.placement, m.worktreeId, m.cols, m.rows)
|
||||
return true
|
||||
}
|
||||
if (m.type === "agentManager.terminal.close") {
|
||||
@@ -132,7 +132,13 @@ export class TerminalRouter {
|
||||
return manager.dispose()
|
||||
}
|
||||
|
||||
private async handleCreate(createId: string, placement: TerminalPlacement, worktreeId: string | null): Promise<void> {
|
||||
private async handleCreate(
|
||||
createId: string,
|
||||
placement: TerminalPlacement,
|
||||
worktreeId: string | null,
|
||||
cols?: number,
|
||||
rows?: number,
|
||||
): Promise<void> {
|
||||
const generation = this.generation
|
||||
const manager = this.manager
|
||||
const cwd = this.resolveCwd(worktreeId)
|
||||
@@ -155,7 +161,7 @@ export class TerminalRouter {
|
||||
// Join the shared backend connection instead of racing its synchronous
|
||||
// client accessor when this is the first Kilo action in the window.
|
||||
await this.deps.getClientAsync()
|
||||
const created = await manager.create({ terminalId: createId, worktreeId, cwd, title })
|
||||
const created = await manager.create({ terminalId: createId, worktreeId, cwd, title, cols, rows })
|
||||
if (generation !== this.generation) {
|
||||
await manager.close(created.terminalId)
|
||||
return
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { ToolRequest } from "./tool-start"
|
||||
import { parseToolRequest } from "./tool-start"
|
||||
|
||||
export function routeToolRequest<T extends { projectId?: string; directory?: string }, C extends { id: string }>(
|
||||
input: T,
|
||||
directory: string | undefined,
|
||||
deps: { byDirectory: (value: string) => C | undefined; usable: (id: string) => C | undefined },
|
||||
): { request: T; owner?: C } {
|
||||
const request = directory ? { ...input, directory } : input
|
||||
const owner =
|
||||
(directory && deps.byDirectory(directory)) ?? (request.projectId ? deps.usable(request.projectId) : undefined)
|
||||
if (!owner) return { request }
|
||||
return { request: { ...request, projectId: owner.id }, owner }
|
||||
}
|
||||
|
||||
export function handleToolEvent<C extends { id: string }>(
|
||||
event: unknown,
|
||||
directory: string | undefined,
|
||||
contexts: { byDirectory: (value: string) => C | undefined; usable: (id: string) => C | undefined },
|
||||
scope: { run: <T>(owner: C, fn: () => Promise<T>) => Promise<T> },
|
||||
start: (req: ToolRequest) => Promise<void>,
|
||||
): void {
|
||||
const properties = (event as { properties?: unknown }).properties
|
||||
const req = parseToolRequest(properties)
|
||||
if (!req) return
|
||||
const routed = routeToolRequest(req, directory, contexts)
|
||||
if (routed.owner) {
|
||||
void scope.run(routed.owner, () => start(routed.request))
|
||||
return
|
||||
}
|
||||
void start(routed.request)
|
||||
}
|
||||
@@ -946,6 +946,8 @@ interface TerminalCreateIn {
|
||||
placement: TerminalPlacement
|
||||
/** null for LOCAL, worktree id otherwise */
|
||||
worktreeId: string | null
|
||||
cols?: number
|
||||
rows?: number
|
||||
}
|
||||
|
||||
interface TerminalCloseIn {
|
||||
|
||||
@@ -17,7 +17,7 @@ import { ensureBackendForAutocomplete } from "./services/autocomplete/ensure-bac
|
||||
import { AutocompleteServiceManager } from "./services/autocomplete/AutocompleteServiceManager"
|
||||
import { AttentionService } from "./services/attention"
|
||||
import { BrowserAutomationService } from "./services/browser-automation"
|
||||
import { TelemetryProxy } from "./services/telemetry"
|
||||
import { TelemetryEventName, TelemetryProxy } from "./services/telemetry"
|
||||
import { registerCommitMessageService } from "./services/commit-message"
|
||||
import { registerCodeActions, registerTerminalActions, KiloCodeActionProvider } from "./services/code-actions"
|
||||
import { registerToggleAutoApprove } from "./commands/toggle-auto-approve"
|
||||
@@ -26,6 +26,7 @@ import { RemoteStatusService } from "./services/RemoteStatusService"
|
||||
import { markWorkspace } from "./util/spotlight"
|
||||
import { createNotebookBridge } from "./services/notebook"
|
||||
import { createGitExecutable } from "./util/git-executable"
|
||||
import { isCursorHost } from "./utils"
|
||||
|
||||
let agentManager: AgentManagerProvider | undefined
|
||||
let shuttingDown = false
|
||||
@@ -48,6 +49,10 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
console.log("Kilo Code extension is now active")
|
||||
shuttingDown = false
|
||||
|
||||
// Drives the "!kilo-code.new.isCursor" guards on the native view/title and
|
||||
// editor/title menu contributions — see isCursorHost() for why.
|
||||
void vscode.commands.executeCommand("setContext", "kilo-code.new.isCursor", isCursorHost())
|
||||
|
||||
const telemetry = TelemetryProxy.getInstance()
|
||||
|
||||
// Create shared connection service (one server for all webviews)
|
||||
@@ -339,8 +344,39 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
}),
|
||||
)
|
||||
|
||||
// Sidebar menus use wrapper commands so this event measures real title button presses,
|
||||
// not programmatic opens, shortcuts, or editor title commands.
|
||||
const track = (button: string, command: string) => {
|
||||
TelemetryProxy.capture(TelemetryEventName.TITLE_BUTTON_CLICKED, {
|
||||
button,
|
||||
surface: "sidebar_title",
|
||||
})
|
||||
void vscode.commands.executeCommand(command)
|
||||
}
|
||||
|
||||
// Register toolbar button command handlers
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.plusButtonClicked", () => {
|
||||
track("new_task", "kilo-code.new.plusButtonClicked")
|
||||
}),
|
||||
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.historyButtonClicked", () => {
|
||||
track("history", "kilo-code.new.historyButtonClicked")
|
||||
}),
|
||||
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.agentManagerOpen", () => {
|
||||
track("agent_manager", "kilo-code.new.agentManagerOpen")
|
||||
}),
|
||||
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.kiloClawOpen", () => {
|
||||
track("kiloclaw", "kilo-code.new.kiloClawOpen")
|
||||
}),
|
||||
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.marketplaceButtonClicked", () => {
|
||||
track("marketplace", "kilo-code.new.marketplaceButtonClicked")
|
||||
}),
|
||||
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.profileButtonClicked", () => {
|
||||
track("profile", "kilo-code.new.profileButtonClicked")
|
||||
}),
|
||||
vscode.commands.registerCommand("kilo-code.new.sidebarTitle.settingsButtonClicked", () => {
|
||||
track("settings", "kilo-code.new.settingsButtonClicked")
|
||||
}),
|
||||
vscode.commands.registerCommand("kilo-code.new.plusButtonClicked", () => {
|
||||
const tab = activeTabProvider()
|
||||
if (tab) tab.postMessage({ type: "action", action: "plusButtonClicked" })
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import * as vscode from "vscode"
|
||||
|
||||
type Post = (msg: unknown) => void
|
||||
|
||||
export function buildAutoApprovalReasonSettingMessage() {
|
||||
const config = vscode.workspace.getConfiguration("kilo-code.new")
|
||||
return {
|
||||
type: "autoApprovalReasonSettingLoaded" as const,
|
||||
visible: config.get<boolean>("showAutoApprovalReason", true),
|
||||
}
|
||||
}
|
||||
|
||||
export function watchAutoApprovalReasonConfig(post: Post): vscode.Disposable {
|
||||
return vscode.workspace.onDidChangeConfiguration((event) => {
|
||||
if (event.affectsConfiguration("kilo-code.new.showAutoApprovalReason")) {
|
||||
post(buildAutoApprovalReasonSettingMessage())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -13,12 +13,18 @@ export async function loadCommands(client: KiloClient, dir: string): Promise<unk
|
||||
|
||||
const promise = retry(() => client.command.list({ directory: dir }, { throwOnError: true })).then(({ data }) => ({
|
||||
type: "commandsLoaded",
|
||||
commands: data.map((cmd) => ({
|
||||
name: cmd.name,
|
||||
description: cmd.description,
|
||||
source: cmd.source,
|
||||
hints: cmd.hints,
|
||||
})),
|
||||
commands: data.map((cmd) => {
|
||||
const item = cmd as typeof cmd & { variant?: string }
|
||||
return {
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
agent: item.agent,
|
||||
model: item.model,
|
||||
variant: item.variant,
|
||||
source: item.source,
|
||||
hints: item.hints,
|
||||
}
|
||||
}),
|
||||
}))
|
||||
|
||||
promises.set(dir, promise)
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { SuggestionContext } from "./handlers/suggestion"
|
||||
import type { KiloClient } from "@kilocode/sdk/v2/client"
|
||||
import { buildChatSettingsMessage } from "./chat-settings"
|
||||
import { buildThroughputSettingMessage } from "./throughput-settings"
|
||||
import { buildAutoApprovalReasonSettingMessage } from "./auto-approval-reason-settings"
|
||||
import { handleModelUsageMessage, type ModelUsageMessage } from "./model-usage"
|
||||
|
||||
type Ctx = {
|
||||
@@ -71,6 +72,10 @@ export async function routeEarlyMessage(
|
||||
ctx.post(buildThroughputSettingMessage())
|
||||
return true
|
||||
}
|
||||
if (message.type === "requestAutoApprovalReasonSetting") {
|
||||
ctx.post(buildAutoApprovalReasonSettingMessage())
|
||||
return true
|
||||
}
|
||||
if (message.type === "requestSpeechToTextModels") {
|
||||
await ctx.speechToTextModels()
|
||||
return true
|
||||
|
||||
@@ -46,7 +46,10 @@ function run(args) {
|
||||
const error = Ref()
|
||||
const url = $.NSURL.fileURLWithPath(args[0])
|
||||
const recorder = $.AVAudioRecorder.alloc.initWithURLSettingsError(url, settings, error)
|
||||
if (!recorder || !recorder.prepareToRecord || !recorder.record) throw new Error("Could not start recording")
|
||||
if (!recorder || !recorder.prepareToRecord || !recorder.record) {
|
||||
const description = error[0] && error[0].localizedDescription
|
||||
throw new Error(description ? description.js : "Could not start recording")
|
||||
}
|
||||
console.log("ready")
|
||||
$.NSFileHandle.fileHandleWithStandardInput.readDataToEndOfFile
|
||||
recorder.stop
|
||||
|
||||
@@ -18,6 +18,27 @@ export function getWebviewFontSize(): number {
|
||||
return clamp(raw)
|
||||
}
|
||||
|
||||
/**
|
||||
* True when running inside Cursor rather than real VS Code (or another
|
||||
* fork). Cursor's Secondary Side Bar support is known to be unreliable for
|
||||
* extension-contributed `view/title` toolbars (see
|
||||
* https://github.com/anthropics/claude-code/issues/31375 for the same class
|
||||
* of bug in a different extension), so Cursor falls back to an in-webview
|
||||
* navigation bar instead of the native toolbar that VS Code renders fine
|
||||
* everywhere.
|
||||
*
|
||||
* This is a per-host choice, not a per-dock-location one: `WebviewView` (and
|
||||
* the rest of the public API, checked against @types/vscode) exposes no way
|
||||
* to ask "is my view currently in the primary or secondary side bar", so the
|
||||
* webview fallback bar is Cursor's only option in both locations. Accepted
|
||||
* trade-off: Cursor's primary side bar loses the single-line native look it
|
||||
* had before this existed, in exchange for the Secondary Side Bar actually
|
||||
* working, with zero guessing about dock position anywhere.
|
||||
*/
|
||||
export function isCursorHost(): boolean {
|
||||
return vscode.env.appName.toLowerCase().includes("cursor")
|
||||
}
|
||||
|
||||
function fontStyle(): string {
|
||||
const base = getWebviewFontSize()
|
||||
const vars = SIZES.map((size) => `--kilo-font-size-${size}: ${(base * size) / 13}px;`).join("\n ")
|
||||
|
||||
@@ -33,7 +33,9 @@ describe("AgentManagerOrchestrationBridge", () => {
|
||||
fs.rmSync(root, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
function harness() {
|
||||
function harness(
|
||||
overrides?: Partial<Parameters<(typeof AgentManagerOrchestrationBridge.prototype)["constructor"]>[1]>,
|
||||
) {
|
||||
const replies: unknown[] = []
|
||||
const rejections: unknown[] = []
|
||||
const lists = new Map<string, AgentManagerRequest[]>()
|
||||
@@ -49,8 +51,8 @@ describe("AgentManagerOrchestrationBridge", () => {
|
||||
const push = mock(() => undefined)
|
||||
const client = {
|
||||
session: {
|
||||
get: mock(async () => ({
|
||||
data: { id: "ses_target", directory: dir, title: "Target" } as Session,
|
||||
get: mock(async ({ sessionID, directory }: { sessionID?: string; directory?: string }) => ({
|
||||
data: { id: sessionID ?? "ses_target", directory: directory ?? dir, title: "Target" } as Session,
|
||||
})),
|
||||
status: mock(async () => ({ data: {} })),
|
||||
promptAsync,
|
||||
@@ -100,17 +102,17 @@ describe("AgentManagerOrchestrationBridge", () => {
|
||||
getClient: () => client,
|
||||
}
|
||||
const bridge = new AgentManagerOrchestrationBridge(connection as never, {
|
||||
root: () => root,
|
||||
ready: async () => state,
|
||||
state: () => state,
|
||||
stats: async () => {
|
||||
root: (dir) => (overrides?.root ? overrides.root(dir) : root),
|
||||
ready: async (dir) => (overrides?.ready ? overrides.ready(dir) : state),
|
||||
state: (dir) => (overrides?.state ? overrides.state(dir) : state),
|
||||
stats: async (dir) => {
|
||||
statsCalls.push(1)
|
||||
return { worktrees: [] }
|
||||
return overrides?.stats ? overrides.stats(dir) : { worktrees: [] }
|
||||
},
|
||||
prs: () => new Map(),
|
||||
push,
|
||||
managed: (id) => managed.has(id),
|
||||
close,
|
||||
prs: (dir) => (overrides?.prs ? overrides.prs(dir) : new Map()),
|
||||
push: (dir) => (overrides?.push ? overrides.push(dir) : push()),
|
||||
managed: (id, dir) => (overrides?.managed ? overrides.managed(id, dir) : managed.has(id)),
|
||||
close: async (id, dir) => (overrides?.close ? overrides.close(id, dir) : close(id, dir)),
|
||||
log: () => undefined,
|
||||
})
|
||||
const request = (value: AgentManagerRequest, directory = root) =>
|
||||
@@ -195,7 +197,7 @@ describe("AgentManagerOrchestrationBridge", () => {
|
||||
await waitFor(() => test.replies.length === 2)
|
||||
|
||||
expect(test.close).toHaveBeenCalledTimes(1)
|
||||
expect(test.close).toHaveBeenCalledWith("ses_target")
|
||||
expect(test.close).toHaveBeenCalledWith("ses_target", root)
|
||||
expect(test.replies).toEqual([
|
||||
{
|
||||
requestID: "amr_stop",
|
||||
@@ -294,7 +296,7 @@ describe("AgentManagerOrchestrationBridge", () => {
|
||||
await waitFor(() => test.replies.length === 1)
|
||||
|
||||
expect(state.getSession("ses_live")).toBeUndefined()
|
||||
expect(test.close).toHaveBeenCalledWith("ses_live")
|
||||
expect(test.close).toHaveBeenCalledWith("ses_live", root)
|
||||
expect(test.replies[0]).toEqual({
|
||||
requestID: "amr_stop_live",
|
||||
directory: root,
|
||||
@@ -389,4 +391,39 @@ describe("AgentManagerOrchestrationBridge", () => {
|
||||
expect(test.promptAsync).toHaveBeenCalledTimes(1)
|
||||
test.bridge.dispose()
|
||||
})
|
||||
|
||||
it("handles requests for secondary project directories in multi-project mode", async () => {
|
||||
const secondaryRoot = fs.mkdtempSync(path.join(os.tmpdir(), "am-orchestration-secondary-"))
|
||||
fs.mkdirSync(path.join(secondaryRoot, ".kilo"), { recursive: true })
|
||||
const secondaryState = new WorktreeStateManager(secondaryRoot, () => undefined)
|
||||
secondaryState.addSession("ses_secondary", null)
|
||||
|
||||
const test = harness({
|
||||
root: (d) => (d === secondaryRoot ? secondaryRoot : root),
|
||||
ready: async (d) => (d === secondaryRoot ? secondaryState : state),
|
||||
state: (d) => (d === secondaryRoot ? secondaryState : state),
|
||||
})
|
||||
|
||||
test.request(
|
||||
{
|
||||
id: "amr_secondary",
|
||||
sessionID: "ses_caller",
|
||||
operation: "prompt",
|
||||
targetSessionID: "ses_secondary",
|
||||
prompt: "Hello from secondary",
|
||||
},
|
||||
secondaryRoot,
|
||||
)
|
||||
await waitFor(() => test.replies.length === 1)
|
||||
|
||||
expect(test.promptAsync).toHaveBeenCalledTimes(1)
|
||||
expect(test.replies[0]).toEqual({
|
||||
requestID: "amr_secondary",
|
||||
directory: secondaryRoot,
|
||||
result: { operation: "prompt", sessionID: "ses_secondary", delivered: true },
|
||||
})
|
||||
test.bridge.dispose()
|
||||
await secondaryState.flush()
|
||||
fs.rmSync(secondaryRoot, { recursive: true, force: true })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -202,6 +202,25 @@ describe("Agent Manager orchestration domain", () => {
|
||||
)
|
||||
})
|
||||
|
||||
it("waits for a busy managed session to become idle before prompting", async () => {
|
||||
const managed = state.addWorktree({ branch: "fix/wait", path: worktree, parentBranch: "main" })
|
||||
state.addSession("ses_wait", managed.id)
|
||||
let calls = 0
|
||||
const promptAsync = mock(async () => ({ data: undefined }))
|
||||
const client = {
|
||||
session: {
|
||||
get: mock(async () => ({ data: { id: "ses_wait", directory: worktree, title: "Wait" } as Session })),
|
||||
status: mock(async () => ({ data: calls++ === 0 ? { ses_wait: { type: "busy" } } : {} })),
|
||||
promptAsync,
|
||||
},
|
||||
} as unknown as KiloClient
|
||||
|
||||
await prompt({ client, root, state, sessionID: "ses_wait", text: "Continue", messageID: "amr_wait" })
|
||||
|
||||
expect(client.session.status).toHaveBeenCalledTimes(2)
|
||||
expect(promptAsync).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("rejects unknown, stale, cross-workspace, and busy targets", async () => {
|
||||
const managed = state.addWorktree({ branch: "fix/errors", path: worktree, parentBranch: "main" })
|
||||
state.addSession("ses_target", managed.id)
|
||||
@@ -231,7 +250,15 @@ describe("Agent Manager orchestration domain", () => {
|
||||
data: { ses_target: { type: "busy" } },
|
||||
}))
|
||||
await expect(
|
||||
prompt({ client, root, state, sessionID: "ses_target", text: "Continue", messageID: "amr_busy" }),
|
||||
prompt({
|
||||
client,
|
||||
root,
|
||||
state,
|
||||
sessionID: "ses_target",
|
||||
text: "Continue",
|
||||
messageID: "amr_busy",
|
||||
idleTimeoutMs: 0,
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: "unavailable_session",
|
||||
} satisfies Partial<OrchestrationError>)
|
||||
|
||||
@@ -58,6 +58,29 @@ test("does not refit hidden terminal buffers during resize", () => {
|
||||
expect(callback!.indexOf("if (!props.active) return")).toBeLessThan(callback!.indexOf("fit.fit()"))
|
||||
})
|
||||
|
||||
test("keeps raw PTY line endings and initializes Unicode widths before attaching", () => {
|
||||
expect(terminal).toContain("convertEol: false")
|
||||
expect(terminal).toContain('term.unicode.activeVersion = "15-graphemes"')
|
||||
expect(terminal.indexOf("term.loadAddon(new UnicodeGraphemesAddon())")).toBeLessThan(
|
||||
terminal.indexOf("open(props.wsUrl)"),
|
||||
)
|
||||
})
|
||||
|
||||
test("fits and forces the initial PTY dimensions before socket attach", () => {
|
||||
expect(terminal).toContain("const syncSize = (force = false)")
|
||||
expect(terminal).toContain("if (props.active) syncSize(true)")
|
||||
expect(terminal.indexOf("fitNow()\n open(props.wsUrl)")).toBeGreaterThan(-1)
|
||||
})
|
||||
|
||||
test("re-sends dimensions when an optimistic terminal receives its PTY", () => {
|
||||
const created = terminal.match(
|
||||
/if \(message\.terminalId === props\.terminalId && !ws\) \{([\s\S]*?)\n \}/,
|
||||
)?.[1]
|
||||
expect(created).toBeDefined()
|
||||
expect(created).toContain("fitNow()")
|
||||
expect(created!.indexOf("fitNow()")).toBeLessThan(created!.indexOf("open(message.wsUrl)"))
|
||||
})
|
||||
|
||||
test("clamps the restored inspector width to the shared layout bounds", () => {
|
||||
expect(clampPanelWidth(undefined, 1200)).toBe(600)
|
||||
expect(clampPanelWidth(500, 1200)).toBe(500)
|
||||
|
||||
@@ -343,4 +343,62 @@ describe("Agent Manager terminal routing", () => {
|
||||
})
|
||||
await router.dispose()
|
||||
})
|
||||
|
||||
it("applies initial create dimensions and queues resize messages before creation settles", async () => {
|
||||
const creates: Array<Record<string, unknown>> = []
|
||||
const updates: Array<{ ptyID: string; size?: { cols: number; rows: number } }> = []
|
||||
let createResolver: ((value: { data: { id: string; title: string } }) => void) | undefined
|
||||
const client = {
|
||||
pty: {
|
||||
create: (params: Record<string, unknown>) =>
|
||||
new Promise<{ data: { id: string; title: string } }>((resolve) => {
|
||||
creates.push(params)
|
||||
createResolver = resolve
|
||||
}),
|
||||
remove: async () => ({ data: true }),
|
||||
update: async (params: { ptyID: string; size?: { cols: number; rows: number } }) => {
|
||||
updates.push(params)
|
||||
return { data: true }
|
||||
},
|
||||
},
|
||||
} as unknown as KiloClient
|
||||
const router = new TerminalRouter({
|
||||
getClient: () => client,
|
||||
getClientAsync: async () => client,
|
||||
getServerConfig: () => ({ baseUrl: "http://127.0.0.1:4096", password: "secret" }),
|
||||
getRoot: () => "/workspace",
|
||||
getWorktreePath: () => undefined,
|
||||
getProjectId: () => "prj-1",
|
||||
log: () => undefined,
|
||||
post: () => undefined,
|
||||
getTerminalFont: () => font,
|
||||
})
|
||||
|
||||
router.handle({
|
||||
type: "agentManager.terminal.create",
|
||||
createId: "queued",
|
||||
placement: "side",
|
||||
worktreeId: null,
|
||||
cols: 60,
|
||||
rows: 20,
|
||||
})
|
||||
await wait()
|
||||
expect(creates).toHaveLength(1)
|
||||
expect(creates[0]?.size).toEqual({ cols: 60, rows: 20 })
|
||||
|
||||
// Send a resize before pty.create settles (optimistic side terminal layout)
|
||||
router.handle({
|
||||
type: "agentManager.terminal.resize",
|
||||
terminalId: "queued",
|
||||
cols: 55,
|
||||
rows: 18,
|
||||
})
|
||||
await wait()
|
||||
expect(updates).toHaveLength(0)
|
||||
|
||||
createResolver?.({ data: { id: "pty-queued", title: "Terminal 1" } })
|
||||
await wait()
|
||||
expect(updates).toEqual([{ directory: "/workspace", ptyID: "pty-queued", size: { cols: 55, rows: 18 } }])
|
||||
await router.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -83,6 +83,23 @@ describe("Agent Manager side terminal controller", () => {
|
||||
expect(hidden.calls.hide).toBe(0)
|
||||
})
|
||||
|
||||
it("toggles panel visibility from toolbar button without requiring focus", () => {
|
||||
const visibleUnfocused = scene({ destination: "agentManager", visible: true })
|
||||
visibleUnfocused.ctl.openPreferred("tab_toolbar")
|
||||
expect(visibleUnfocused.calls.hide).toBe(1)
|
||||
expect(visibleUnfocused.calls.requestSide).toBe(0)
|
||||
|
||||
const visibleFocused = scene({ destination: "agentManager", visible: true, focusedId: "terminal:side" })
|
||||
visibleFocused.ctl.openPreferred("tab_toolbar")
|
||||
expect(visibleFocused.calls.hide).toBe(1)
|
||||
expect(visibleFocused.calls.requestSide).toBe(0)
|
||||
|
||||
const hidden = scene({ destination: "agentManager", visible: false })
|
||||
hidden.ctl.openPreferred("tab_toolbar")
|
||||
expect(hidden.calls.requestSide).toBe(1)
|
||||
expect(hidden.calls.hide).toBe(0)
|
||||
})
|
||||
|
||||
it("ensures an open terminal panel has a terminal after switching contexts", async () => {
|
||||
const visible = scene({ visible: true })
|
||||
visible.ctl.syncContext("wt-2", "wt-1")
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { routeToolRequest } from "../../src/agent-manager/tool-project"
|
||||
|
||||
describe("Agent Manager tool project routing", () => {
|
||||
it("routes by the event directory before any explicit project id", () => {
|
||||
const secondary = { id: "prj-secondary" }
|
||||
const request = routeToolRequest({ requestID: "am-1", projectId: "prj-active", mode: "worktree" }, "/secondary", {
|
||||
byDirectory: (dir) => (dir === "/secondary" ? secondary : undefined),
|
||||
usable: () => ({ id: "prj-active" }),
|
||||
})
|
||||
|
||||
expect(request.owner).toBe(secondary)
|
||||
expect(request.request).toEqual({
|
||||
requestID: "am-1",
|
||||
projectId: "prj-secondary",
|
||||
mode: "worktree",
|
||||
directory: "/secondary",
|
||||
})
|
||||
})
|
||||
|
||||
it("uses an explicit usable project when no event directory is available", () => {
|
||||
const project = { id: "prj-secondary" }
|
||||
const request = routeToolRequest({ requestID: "am-2", projectId: "prj-secondary", mode: "local" }, undefined, {
|
||||
byDirectory: () => undefined,
|
||||
usable: (id) => (id === project.id ? project : undefined),
|
||||
})
|
||||
|
||||
expect(request.owner).toBe(project)
|
||||
expect(request.request.projectId).toBe("prj-secondary")
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,62 @@
|
||||
import { describe, it, expect } from "bun:test"
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
import { builtinModules } from "node:module"
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, "../..")
|
||||
const PKG_FILE = path.join(ROOT, "package.json")
|
||||
const ESBUILD_FILE = path.join(ROOT, "esbuild.js")
|
||||
|
||||
const BUILTINS = new Set([...builtinModules, ...builtinModules.map((m) => `node:${m}`)])
|
||||
|
||||
function extractPackageName(specifier: string): string | null {
|
||||
if (specifier.startsWith(".") || specifier.startsWith("/")) return null
|
||||
if (BUILTINS.has(specifier)) return null
|
||||
if (specifier.startsWith("node:")) return null
|
||||
|
||||
if (specifier.startsWith("@")) {
|
||||
const parts = specifier.split("/")
|
||||
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier
|
||||
}
|
||||
return specifier.split("/")[0]
|
||||
}
|
||||
|
||||
function findImportsAndRequires(content: string): string[] {
|
||||
const specifiers = new Set<string>()
|
||||
const requireRegex = /require\(["']([^"']+)["']\)/g
|
||||
const importRegex = /(?:import|from)\s+["']([^"']+)["']/g
|
||||
|
||||
for (const match of content.matchAll(requireRegex)) {
|
||||
specifiers.add(match[1])
|
||||
}
|
||||
for (const match of content.matchAll(importRegex)) {
|
||||
specifiers.add(match[1])
|
||||
}
|
||||
|
||||
return Array.from(specifiers)
|
||||
}
|
||||
|
||||
describe("Build Script Dependency Declarations", () => {
|
||||
it("esbuild.js must declare all imported/required packages in package.json", () => {
|
||||
const pkg = JSON.parse(fs.readFileSync(PKG_FILE, "utf8"))
|
||||
const declared = new Set([
|
||||
...Object.keys(pkg.dependencies || {}),
|
||||
...Object.keys(pkg.devDependencies || {}),
|
||||
...Object.keys(pkg.peerDependencies || {}),
|
||||
"vscode",
|
||||
])
|
||||
|
||||
const esbuildContent = fs.readFileSync(ESBUILD_FILE, "utf8")
|
||||
const specifiers = findImportsAndRequires(esbuildContent)
|
||||
|
||||
const undeclared: string[] = []
|
||||
for (const spec of specifiers) {
|
||||
const pkgName = extractPackageName(spec)
|
||||
if (pkgName && !declared.has(pkgName)) {
|
||||
undeclared.push(`${spec} (package: ${pkgName})`)
|
||||
}
|
||||
}
|
||||
|
||||
expect(undeclared).toEqual([])
|
||||
})
|
||||
})
|
||||
@@ -78,6 +78,15 @@ describe("sendCommand dismisses pending tool requests", () => {
|
||||
it("rejects questions before sending", () => {
|
||||
expect(body).toContain("dismissQuestion")
|
||||
})
|
||||
|
||||
it("applies model, agent, and variant overrides when provided by a command", () => {
|
||||
expect(body).toContain("if (overrides?.agent)")
|
||||
expect(body).toContain("selectAgent(overrides.agent, scope)")
|
||||
expect(body).toContain("if (overrides?.model)")
|
||||
expect(body).toContain("selectModel(parsed.providerID, parsed.modelID, scope)")
|
||||
expect(body).toContain("if (overrides?.variant)")
|
||||
expect(body).toContain("selectVariant(overrides.variant, scope)")
|
||||
})
|
||||
})
|
||||
|
||||
describe("static command completion contract", () => {
|
||||
|
||||
@@ -19,6 +19,7 @@ describe("macCaptureArgs", () => {
|
||||
expect(args[3]).toContain("numberWithDouble(16000), $.AVSampleRateKey")
|
||||
expect(args[3]).toContain("numberWithInt(1), $.AVNumberOfChannelsKey")
|
||||
expect(args[3]).toContain("numberWithInt(24000), $.AVEncoderBitRateKey")
|
||||
expect(args[3]).toContain("error[0] && error[0].localizedDescription")
|
||||
expect(args[3]).toContain('console.log("ready")')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -188,4 +188,76 @@ describe("useSlashCommand sandbox action", () => {
|
||||
expect(ctx.slash.results()[0]?.description).toBe("Toggle sandbox")
|
||||
ctx.dispose()
|
||||
})
|
||||
|
||||
it("opens review options from the top-level command", () => {
|
||||
const ctx = setup(() => {})
|
||||
const state = { text: "/review" }
|
||||
const textarea = {
|
||||
value: state.text,
|
||||
setSelectionRange: () => {},
|
||||
focus: () => {},
|
||||
} as unknown as HTMLTextAreaElement
|
||||
|
||||
ctx.slash.onInput("/rev", 4)
|
||||
|
||||
expect(ctx.slash.results()).toContainEqual(
|
||||
expect.objectContaining({ name: "review", description: expect.stringContaining("Review code changes") }),
|
||||
)
|
||||
ctx.slash.select(ctx.slash.results().find((c) => c.name === "review")!, textarea, (text) => (state.text = text))
|
||||
expect(state.text).toBe("/review ")
|
||||
expect(ctx.slash.results().map((command) => command.name)).toEqual([
|
||||
"review uncommitted",
|
||||
"review staged",
|
||||
"review unpushed",
|
||||
"review branch",
|
||||
"review quick",
|
||||
])
|
||||
ctx.dispose()
|
||||
})
|
||||
|
||||
it("completes nested review actions and closes for free text", () => {
|
||||
const ctx = setup(() => {})
|
||||
const state = { text: "/review unp" }
|
||||
const textarea = {
|
||||
value: state.text,
|
||||
setSelectionRange: () => {},
|
||||
focus: () => {},
|
||||
} as unknown as HTMLTextAreaElement
|
||||
|
||||
ctx.slash.onInput(state.text, state.text.length)
|
||||
expect(ctx.slash.results().map((command) => command.name)).toEqual(["review unpushed"])
|
||||
ctx.slash.select(ctx.slash.results()[0]!, textarea, (text) => (state.text = text))
|
||||
expect(state.text).toBe("/review unpushed ")
|
||||
|
||||
ctx.slash.onInput("/review focus on auth", 20)
|
||||
expect(ctx.slash.show()).toBe(false)
|
||||
ctx.dispose()
|
||||
})
|
||||
|
||||
it("preserves model, agent, and variant metadata on loaded server commands", () => {
|
||||
const ctx = setup(() => {})
|
||||
|
||||
ctx.fire({
|
||||
type: "commandsLoaded",
|
||||
commands: [
|
||||
{
|
||||
name: "ship",
|
||||
description: "Ship PR",
|
||||
agent: "code",
|
||||
model: "openai/gpt-5.6-luna-fast",
|
||||
variant: "xhigh",
|
||||
hints: ["deploy"],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
ctx.slash.onInput("/ship", 5)
|
||||
const matches = ctx.slash.results()
|
||||
expect(matches).toHaveLength(1)
|
||||
expect(matches[0]?.name).toBe("ship")
|
||||
expect(matches[0]?.agent).toBe("code")
|
||||
expect(matches[0]?.model).toBe("openai/gpt-5.6-luna-fast")
|
||||
expect(matches[0]?.variant).toBe("xhigh")
|
||||
ctx.dispose()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -24,17 +24,24 @@ afterEach(async () => {
|
||||
)
|
||||
})
|
||||
|
||||
function gitExec(args: string[]) {
|
||||
const res = Bun.spawnSync(args, { stdout: "ignore", stderr: "pipe" })
|
||||
if (res.exitCode !== 0) {
|
||||
const err = Buffer.from(res.stderr).toString("utf8")
|
||||
throw new Error(`git command failed (${args.join(" ")}): ${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a temp git repo with an initial commit (required for worktrees). */
|
||||
async function createTempRepo(): Promise<string> {
|
||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "kilo-wt-"))
|
||||
tempDirs.push(dir)
|
||||
const git = simpleGit(dir)
|
||||
await git.init()
|
||||
await git.addConfig("user.email", "test@test.com")
|
||||
await git.addConfig("user.name", "Test")
|
||||
gitExec(["git", "init", "-b", "main", dir])
|
||||
gitExec(["git", "-C", dir, "config", "user.email", "test@test.com"])
|
||||
gitExec(["git", "-C", dir, "config", "user.name", "Test"])
|
||||
await fs.writeFile(path.join(dir, "README.md"), "init")
|
||||
await git.add(".")
|
||||
await git.commit("initial commit")
|
||||
gitExec(["git", "-C", dir, "add", "."])
|
||||
gitExec(["git", "-C", dir, "commit", "-m", "initial commit"])
|
||||
return dir
|
||||
}
|
||||
|
||||
@@ -51,31 +58,18 @@ async function changedFiles(cwd: string): Promise<string[]> {
|
||||
|
||||
/** Create a temp repo with a bare origin remote so origin/<branch> refs exist. */
|
||||
async function createTempRepoWithOrigin(): Promise<{ bare: string; clone: string }> {
|
||||
// Use a non-bare seed repo to control the initial branch name, then clone bare
|
||||
const seed = await fs.mkdtemp(path.join(os.tmpdir(), "kilo-wt-seed-"))
|
||||
tempDirs.push(seed)
|
||||
const seedGit = simpleGit(seed)
|
||||
await seedGit.init()
|
||||
await seedGit.addConfig("user.email", "test@test.com")
|
||||
await seedGit.addConfig("user.name", "Test")
|
||||
await fs.writeFile(path.join(seed, "README.md"), "init")
|
||||
await seedGit.add(".")
|
||||
await seedGit.commit("initial commit")
|
||||
// Ensure branch is named "main" regardless of system default
|
||||
const seedBranch = (await seedGit.revparse(["--abbrev-ref", "HEAD"])).trim()
|
||||
if (seedBranch !== "main") await seedGit.raw(["branch", "-m", seedBranch, "main"])
|
||||
|
||||
// Clone to bare, then clone again as working copy
|
||||
const bare = await fs.mkdtemp(path.join(os.tmpdir(), "kilo-wt-bare-"))
|
||||
tempDirs.push(bare)
|
||||
await simpleGit().clone(seed, bare, ["--bare"])
|
||||
|
||||
const clone = await fs.mkdtemp(path.join(os.tmpdir(), "kilo-wt-clone-"))
|
||||
tempDirs.push(clone)
|
||||
await simpleGit().clone(bare, clone)
|
||||
const cloneGit = simpleGit(clone)
|
||||
await cloneGit.addConfig("user.email", "test@test.com")
|
||||
await cloneGit.addConfig("user.name", "Test")
|
||||
tempDirs.push(bare, clone)
|
||||
|
||||
gitExec(["git", "init", "--bare", "-b", "main", bare])
|
||||
gitExec(["git", "clone", bare, clone])
|
||||
gitExec(["git", "-C", clone, "config", "user.email", "test@test.com"])
|
||||
gitExec(["git", "-C", clone, "config", "user.name", "Test"])
|
||||
await fs.writeFile(path.join(clone, "README.md"), "init")
|
||||
gitExec(["git", "-C", clone, "add", "."])
|
||||
gitExec(["git", "-C", clone, "commit", "-m", "initial commit"])
|
||||
gitExec(["git", "-C", clone, "push", "-u", "origin", "main"])
|
||||
|
||||
return { bare, clone }
|
||||
}
|
||||
|
||||
@@ -2373,9 +2373,6 @@ const AgentManagerContent: Component = () => {
|
||||
onToggleReview={metrics.click("fullscreen_review", "tab_toolbar", toggleReviewTab)}
|
||||
terminalDestination={sideCtl.destination}
|
||||
terminalDestinationActive={() => sidePanel() === "terminal"}
|
||||
terminalDestinationFocused={() =>
|
||||
sideCtl.destination() === "agentManager" && terms.sideFocusedId() !== undefined
|
||||
}
|
||||
terminalKeybind={() => kb().showTerminal ?? ""}
|
||||
onTerminalDestinationOpen={() => {
|
||||
cancelAmbientSetup()
|
||||
|
||||
@@ -57,7 +57,6 @@ export interface TabBarProps {
|
||||
onToggleReview: () => void
|
||||
terminalDestination: () => TerminalDestination
|
||||
terminalDestinationActive: () => boolean
|
||||
terminalDestinationFocused: () => boolean
|
||||
terminalKeybind: () => string
|
||||
onTerminalDestinationOpen: () => void
|
||||
onTerminalDestinationChoose: (destination: TerminalDestination) => void
|
||||
@@ -260,7 +259,6 @@ export const TabBar: Component<TabBarProps> = (props) => (
|
||||
<TerminalDestinationButton
|
||||
destination={props.terminalDestination}
|
||||
active={props.terminalDestinationActive}
|
||||
focused={props.terminalDestinationFocused}
|
||||
keybind={props.terminalKeybind}
|
||||
onOpen={props.onTerminalDestinationOpen}
|
||||
onChoose={props.onTerminalDestinationChoose}
|
||||
|
||||
+1
-7
@@ -20,8 +20,6 @@ interface Props {
|
||||
destination: Accessor<TerminalDestination>
|
||||
/** True while the embedded terminal panel is showing. */
|
||||
active: Accessor<boolean>
|
||||
/** True while the embedded terminal owns DOM focus. */
|
||||
focused: Accessor<boolean>
|
||||
keybind: Accessor<string>
|
||||
onOpen: () => void
|
||||
onChoose: (destination: TerminalDestination) => void
|
||||
@@ -29,10 +27,6 @@ interface Props {
|
||||
|
||||
export const TerminalDestinationButton: Component<Props> = (props) => {
|
||||
const { t } = useLanguage()
|
||||
const title = () =>
|
||||
props.destination() === "agentManager" && props.focused()
|
||||
? t("agentManager.shortcuts.toggleTerminal")
|
||||
: t("agentManager.tab.openTerminal")
|
||||
const item = (destination: TerminalDestination, label: string) => (
|
||||
<DropdownMenu.Item onSelect={() => props.onChoose(destination)}>
|
||||
<span class="am-menu-check">
|
||||
@@ -45,7 +39,7 @@ export const TerminalDestinationButton: Component<Props> = (props) => {
|
||||
)
|
||||
return (
|
||||
<div class="am-split-button">
|
||||
<TooltipKeybind title={title()} keybind={props.keybind()} placement="bottom">
|
||||
<TooltipKeybind title={t("agentManager.tab.terminal")} keybind={props.keybind()} placement="bottom">
|
||||
<IconButton
|
||||
icon="console"
|
||||
size="small"
|
||||
|
||||
@@ -152,7 +152,10 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
|
||||
onMount(() => {
|
||||
const term = new Terminal({
|
||||
convertEol: true,
|
||||
// PTY output already contains terminal line endings. Converting LF to
|
||||
// CRLF here corrupts raw PTY output and is especially visible when a
|
||||
// narrow terminal wraps and redraws the prompt.
|
||||
convertEol: false,
|
||||
cursorBlink: true,
|
||||
cursorInactiveStyle: "outline",
|
||||
fontFamily: props.font.fontFamily,
|
||||
@@ -164,17 +167,11 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
const fit = new FitAddon()
|
||||
term.loadAddon(fit)
|
||||
term.open(host)
|
||||
// Fit on the next frame — `host` might still have 0px dimensions
|
||||
// during the initial layout pass otherwise.
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
fit.fit()
|
||||
} catch (err) {
|
||||
// Host still detached at mount time. ResizeObserver will retry
|
||||
// once layout kicks in. Logged so regressions don't hide.
|
||||
log("initial fit() threw", err)
|
||||
}
|
||||
})
|
||||
// Unicode width must be configured before the first PTY bytes are parsed.
|
||||
// Loading it later can leave already-wrapped graphemes with stale cell
|
||||
// widths, which moves the cursor in narrow terminals.
|
||||
term.loadAddon(new UnicodeGraphemesAddon())
|
||||
term.unicode.activeVersion = "15-graphemes"
|
||||
|
||||
// Pass Agent Manager hotkeys through to the parent key handler so
|
||||
// ⌘T / ⌘⇧T / ⌘W / terminal cycling / ⌘⌥← still work while focused.
|
||||
@@ -356,11 +353,9 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
}
|
||||
const disposeData = term.onData(send)
|
||||
const disposeBinary = term.onBinary(send)
|
||||
open(props.wsUrl)
|
||||
|
||||
// These addons are not needed to paint the initial prompt. Defer them
|
||||
// until after the first frame so their startup work, especially the
|
||||
// Unicode 15 width tables, does not delay the shell connection.
|
||||
// until after the first frame so their startup work does not delay the
|
||||
// shell connection.
|
||||
const loadAddons = () => {
|
||||
deferred = undefined
|
||||
if (closed) return
|
||||
@@ -375,16 +370,8 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
)
|
||||
// OSC 52 clipboard support for shell programs such as tmux and neovim.
|
||||
term.loadAddon(new ClipboardAddon())
|
||||
// Use grapheme-aware width tables for newer emoji and ZWJ sequences.
|
||||
term.loadAddon(new UnicodeGraphemesAddon())
|
||||
term.unicode.activeVersion = "15-graphemes"
|
||||
term.refresh(0, Math.max(0, term.rows - 1))
|
||||
}
|
||||
frame = requestAnimationFrame(() => {
|
||||
frame = undefined
|
||||
deferred = requestAnimationFrame(loadAddons)
|
||||
})
|
||||
|
||||
const restarted = (url: string) => {
|
||||
open(url)
|
||||
}
|
||||
@@ -395,10 +382,12 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
let resizeTimer: ReturnType<typeof setTimeout> | undefined
|
||||
let lastCols = term.cols
|
||||
let lastRows = term.rows
|
||||
const syncSize = () => {
|
||||
if (term.cols === lastCols && term.rows === lastRows) return
|
||||
let synced = false
|
||||
const syncSize = (force = false) => {
|
||||
if (!force && synced && term.cols === lastCols && term.rows === lastRows) return
|
||||
lastCols = term.cols
|
||||
lastRows = term.rows
|
||||
synced = true
|
||||
vscode.postMessage({
|
||||
type: "agentManager.terminal.resize",
|
||||
terminalId: props.terminalId,
|
||||
@@ -406,6 +395,16 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
rows: term.rows,
|
||||
})
|
||||
}
|
||||
const fitNow = () => {
|
||||
try {
|
||||
fit.fit()
|
||||
if (props.active) syncSize(true)
|
||||
} catch (err) {
|
||||
// Host still detached at mount time. ResizeObserver will retry
|
||||
// once layout kicks in. Logged so regressions don't hide.
|
||||
log("fit() threw", err)
|
||||
}
|
||||
}
|
||||
const ro = new ResizeObserver(() => {
|
||||
if (!props.active) return
|
||||
try {
|
||||
@@ -422,6 +421,16 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
resizeTimer = setTimeout(syncSize, RESIZE_DEBOUNCE_MS)
|
||||
})
|
||||
ro.observe(host)
|
||||
// Wait for the first committed layout before attaching the socket. This
|
||||
// prevents the shell from emitting its first prompt at xterm's default
|
||||
// 80 columns, which is most visible in a narrow side panel.
|
||||
frame = requestAnimationFrame(() => {
|
||||
frame = undefined
|
||||
if (closed) return
|
||||
fitNow()
|
||||
open(props.wsUrl)
|
||||
deferred = requestAnimationFrame(loadAddons)
|
||||
})
|
||||
|
||||
// ---- Repaint recovery ----
|
||||
//
|
||||
@@ -452,7 +461,7 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
if (!isRenderable()) return
|
||||
try {
|
||||
fit.fit()
|
||||
syncSize()
|
||||
syncSize(!synced)
|
||||
} catch (err) {
|
||||
// Layout not settled yet; ResizeObserver retries on next change.
|
||||
log("repaint fit() threw", err)
|
||||
@@ -485,6 +494,9 @@ export const TerminalTab: Component<Props> = (props) => {
|
||||
if (message.terminalId === props.terminalId && !ws) {
|
||||
term.options.fontFamily = message.font.fontFamily
|
||||
term.options.fontSize = message.font.fontSize
|
||||
// Optimistic side terminals can fit before their backend PTY exists;
|
||||
// force the first resize again once the created response arrives.
|
||||
fitNow()
|
||||
scheduleRepaint()
|
||||
open(message.wsUrl)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
*
|
||||
* Extracted from AgentManagerApp.tsx to keep that file under the
|
||||
* `max-lines` lint cap. Owns the destination preference plus the toggle
|
||||
* semantics of the toolbar button / `Cmd/Ctrl+/` shortcut, so the
|
||||
* embedded terminal behaves like the diff panel: press once to reveal,
|
||||
* press again while focused to hide, and press while visible but unfocused
|
||||
* to return focus to the shell. Hiding never kills the terminal — only the
|
||||
* explicit close action does.
|
||||
* semantics: the toolbar button toggles visibility, while `Cmd/Ctrl+/`
|
||||
* reveals, focuses when unfocused, and hides when focused.
|
||||
* Hiding never kills the terminal — only the explicit close action does.
|
||||
*
|
||||
* ## Destination state ownership
|
||||
*
|
||||
@@ -109,14 +107,15 @@ export function createSideTerminal(deps: SideTerminalDeps) {
|
||||
if (wasFocused) deps.refocus()
|
||||
}
|
||||
|
||||
const toggle = () => {
|
||||
const toggle = (trigger: "keyboard_shortcut" | "tab_toolbar" = "keyboard_shortcut") => {
|
||||
if (deps.visible()) {
|
||||
if (!deps.focusedId()) {
|
||||
if (trigger === "keyboard_shortcut" && !deps.focusedId()) {
|
||||
deps.handlers.requestSide()
|
||||
return
|
||||
}
|
||||
const was = deps.focusedId() !== undefined
|
||||
deps.hide()
|
||||
handoff(true)
|
||||
handoff(was)
|
||||
return
|
||||
}
|
||||
deps.handlers.requestSide()
|
||||
@@ -149,7 +148,7 @@ export function createSideTerminal(deps: SideTerminalDeps) {
|
||||
const target = destination()
|
||||
deps.track("terminal", trigger, { destination: target })
|
||||
if (target === "agentManager") {
|
||||
toggle()
|
||||
toggle(trigger)
|
||||
return
|
||||
}
|
||||
deps.openVscode()
|
||||
|
||||
@@ -628,10 +628,32 @@ function newId(): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the close-terminal handler the main component wires to the
|
||||
* close button. Picks the next visible tab before dropping the entry
|
||||
* so focus flows naturally; notifies the extension last.
|
||||
* Estimate initial terminal geometry from the current DOM container.
|
||||
* Provides best-effort columns and rows so PTY spawn avoids the default
|
||||
* 80-column line width before the first xterm fit pass commits.
|
||||
*/
|
||||
function measureInitialDimensions(
|
||||
placement: TerminalPlacement,
|
||||
font: TerminalFont,
|
||||
): { cols: number; rows: number } | undefined {
|
||||
if (typeof document === "undefined") return undefined
|
||||
const selector =
|
||||
placement === "side"
|
||||
? ".am-side-terminal-layer, .am-side-terminal, .am-diff-panel-wrapper"
|
||||
: ".am-terminal-layer, .am-detail-stack"
|
||||
const host = document.querySelector(selector) as HTMLElement | null
|
||||
const rect = host?.getBoundingClientRect()
|
||||
if (!rect || rect.width <= 0 || rect.height <= 0) return undefined
|
||||
const cellWidth = font.fontSize > 0 ? font.fontSize * 0.6 : 7.2
|
||||
const cellHeight = font.fontSize > 0 ? font.fontSize * 1.2 : 14.4
|
||||
const availableWidth = Math.max(0, rect.width - 30)
|
||||
const availableHeight = Math.max(0, rect.height - 16)
|
||||
return {
|
||||
cols: Math.max(10, Math.floor(availableWidth / cellWidth)),
|
||||
rows: Math.max(3, Math.floor(availableHeight / cellHeight)),
|
||||
}
|
||||
}
|
||||
|
||||
export function createTerminalHandlers(deps: TerminalHandlerDeps) {
|
||||
const activate = (id: string) => {
|
||||
deps.state.setActiveId(id)
|
||||
@@ -645,11 +667,15 @@ export function createTerminalHandlers(deps: TerminalHandlerDeps) {
|
||||
const requestNew = () => {
|
||||
const sel = deps.getSelection()
|
||||
if (sel === null) return
|
||||
const font = deps.getFont()
|
||||
const dims = measureInitialDimensions("tab", font)
|
||||
deps.postMessage({
|
||||
type: "agentManager.terminal.create",
|
||||
createId: newId(),
|
||||
placement: "tab",
|
||||
worktreeId: sel === deps.LOCAL ? null : sel,
|
||||
cols: dims?.cols,
|
||||
rows: dims?.rows,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -659,12 +685,14 @@ export function createTerminalHandlers(deps: TerminalHandlerDeps) {
|
||||
// project-namespaced state key and must not leak into the message.
|
||||
const sel = deps.getSelection()
|
||||
const id = newId()
|
||||
const font = deps.getFont()
|
||||
const dims = measureInitialDimensions("side", font)
|
||||
deps.state.beginSide(key, id)
|
||||
deps.state.add(key === deps.LOCAL ? null : key, {
|
||||
id,
|
||||
title: "Terminal",
|
||||
wsUrl: "",
|
||||
font: deps.getFont(),
|
||||
font,
|
||||
placement: "side",
|
||||
})
|
||||
deps.state.setSideActive(key, id)
|
||||
@@ -674,6 +702,8 @@ export function createTerminalHandlers(deps: TerminalHandlerDeps) {
|
||||
createId: id,
|
||||
placement: "side",
|
||||
worktreeId: sel === null || sel === deps.LOCAL ? null : sel,
|
||||
cols: dims?.cols,
|
||||
rows: dims?.rows,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -311,8 +311,11 @@ const AppContent: Component = () => {
|
||||
|
||||
// Set synchronously in the webview HTML by KiloProvider so it's available
|
||||
// before this component ever mounts (see buildWebviewHtml/_getHtmlForWebview).
|
||||
// Dedicated single-purpose panels (Settings, Profile, Sub-Agent Viewer) set
|
||||
// KILO_TOP_BAR = false since navigating away from them makes no sense.
|
||||
// False for dedicated single-purpose panels (Settings, Profile, Sub-Agent
|
||||
// Viewer) always, and for the Sidebar/"Open in Tab" outside Cursor — real
|
||||
// VS Code's native title bar toolbar already covers those. Defaults to
|
||||
// true only when unset entirely (e.g. Storybook, which doesn't render the
|
||||
// real page HTML).
|
||||
const host = window as { KILO_TOP_BAR?: boolean; KILO_TOP_BAR_SURFACE?: string }
|
||||
const showTopBar = host.KILO_TOP_BAR !== false
|
||||
const topBarSurface = host.KILO_TOP_BAR_SURFACE ?? "sidebar_title"
|
||||
|
||||
@@ -1239,6 +1239,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
pendingId,
|
||||
context,
|
||||
origin ?? null,
|
||||
{
|
||||
agent: matched.agent,
|
||||
model: matched.model,
|
||||
variant: matched.variant,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
session.sendMessage(message, sel?.providerID, sel?.modelID, attachments, pendingId, context, data, origin ?? null)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
/**
|
||||
* Renders New Task, History, Agent Manager, KiloClaw, Marketplace, Profile, and
|
||||
* Settings inside the webview. VS Code's native `view/title` toolbar renders
|
||||
* outside the webview DOM and disappears in the Secondary Side Bar with no way
|
||||
* to detect or work around that — this bar guarantees the actions stay visible.
|
||||
* Settings inside the webview, as a fallback for Cursor only (see isCursorHost()
|
||||
* in src/utils.ts). Cursor's Secondary Side Bar support is unreliable for
|
||||
* extension-contributed `view/title` toolbars, which render outside the webview
|
||||
* DOM with no API to detect or work around the failure. Real VS Code renders the
|
||||
* native toolbar fine everywhere, so it keeps using that instead of this bar.
|
||||
*/
|
||||
|
||||
import { Component, For } from "solid-js"
|
||||
@@ -45,13 +47,12 @@ export const SidebarTopBar: Component<SidebarTopBarProps> = (props) => {
|
||||
type: "openAgentManager" | "openKiloClaw" | "openMarketplacePanel" | "openProfilePanel" | "openSettingsPanel",
|
||||
) => vscode.postMessage({ type })
|
||||
|
||||
const actions: (Action | "spacer")[] = [
|
||||
const actions: Action[] = [
|
||||
{ key: "newTask", codicon: "add", button: "new_task", run: () => props.onNewTask() },
|
||||
{ key: "history", codicon: "history", button: "history", run: () => props.onHistory() },
|
||||
{ key: "agentManager", codicon: "organization", button: "agent_manager", run: () => open("openAgentManager") },
|
||||
{ key: "kiloClaw", codicon: "comment-discussion", button: "kiloclaw", run: () => open("openKiloClaw") },
|
||||
{ key: "marketplace", codicon: "extensions", button: "marketplace", run: () => open("openMarketplacePanel") },
|
||||
"spacer",
|
||||
{ key: "profile", codicon: "account", button: "profile", run: () => open("openProfilePanel") },
|
||||
{ key: "settings", codicon: "settings-gear", button: "settings", run: () => open("openSettingsPanel") },
|
||||
]
|
||||
@@ -60,7 +61,6 @@ export const SidebarTopBar: Component<SidebarTopBarProps> = (props) => {
|
||||
<div class="sidebar-top-bar" role="toolbar" aria-label={language.t("sidebar.topBar.label")}>
|
||||
<For each={actions}>
|
||||
{(action) => {
|
||||
if (action === "spacer") return <div class="sidebar-top-bar-spacer" />
|
||||
const label = language.t(`sidebar.topBar.${action.key}`)
|
||||
return (
|
||||
<Tooltip value={label} placement="bottom">
|
||||
|
||||
@@ -104,6 +104,19 @@ const DisplayTab: Component = () => {
|
||||
</Switch>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.display.autoApprovalReason.title")}
|
||||
description={language.t("settings.display.autoApprovalReason.description")}
|
||||
>
|
||||
<Switch
|
||||
checked={Boolean(settings()["showAutoApprovalReason"] ?? true)}
|
||||
onChange={(checked: boolean) => updateSetting("showAutoApprovalReason", checked)}
|
||||
hideLabel
|
||||
>
|
||||
{language.t("settings.display.autoApprovalReason.title")}
|
||||
</Switch>
|
||||
</SettingsRow>
|
||||
|
||||
<SettingsRow
|
||||
title={language.t("settings.display.terminalCommand.title")}
|
||||
description={language.t("settings.display.terminalCommand.description")}
|
||||
|
||||
@@ -77,6 +77,7 @@ function loadedSettings(message: ExtensionMessage): Record<string, unknown> | un
|
||||
return { "chat.shiftTabCyclesVariant": message.settings.shiftTabCyclesVariant }
|
||||
}
|
||||
if (message.type === "throughputSettingLoaded") return { showTokenThroughput: message.visible }
|
||||
if (message.type === "autoApprovalReasonSettingLoaded") return { showAutoApprovalReason: message.visible }
|
||||
}
|
||||
|
||||
export const ConfigProvider: ParentComponent = (props) => {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useConfig } from "./config"
|
||||
import { useVSCode } from "./vscode"
|
||||
import type { ExtensionMessage } from "../types/messages"
|
||||
import { applyFontSize, clampFontSize, readFontSize } from "../font-size"
|
||||
import { ToolApprovalVisibilityProvider } from "@kilocode/kilo-ui/message-part"
|
||||
|
||||
interface DisplayContextValue {
|
||||
reasoningAutoCollapse: Accessor<boolean>
|
||||
@@ -23,6 +24,8 @@ interface DisplayContextValue {
|
||||
// every AssistantMessage and the aggregated row in TaskHeader, so flipping
|
||||
// the setting once updates both surfaces without round-trips.
|
||||
throughputVisible: Accessor<boolean>
|
||||
// Whether the "why was this tool call approved" line renders on tool calls.
|
||||
autoApprovalReasonVisible: Accessor<boolean>
|
||||
}
|
||||
|
||||
export const DisplayContext = createContext<DisplayContextValue>()
|
||||
@@ -33,15 +36,20 @@ export const DisplayProvider: ParentComponent = (props) => {
|
||||
const reasoningAutoCollapse = createMemo(() => config().auto_collapse_reasoning ?? false)
|
||||
const [fontSize, setFontSizeSignal] = createSignal(readFontSize())
|
||||
const [throughputVisible, setThroughputVisible] = createSignal(false)
|
||||
const [autoApprovalReasonVisible, setAutoApprovalReasonVisible] = createSignal(true)
|
||||
|
||||
// Request the throughput toggle once on mount; the extension posts back
|
||||
// Request both toggles once on mount; the extension posts back
|
||||
// (and onDidChangeConfiguration forwards subsequent edits).
|
||||
onMount(() => vscode.postMessage({ type: "requestThroughputSetting" }))
|
||||
onMount(() => {
|
||||
vscode.postMessage({ type: "requestThroughputSetting" })
|
||||
vscode.postMessage({ type: "requestAutoApprovalReasonSetting" })
|
||||
})
|
||||
|
||||
const unsubscribe = vscode.onMessage((message: ExtensionMessage) => {
|
||||
if (message.type === "ready" && message.fontSize !== undefined) setFontSizeSignal(clampFontSize(message.fontSize))
|
||||
if (message.type === "fontSizeChanged") setFontSizeSignal(clampFontSize(message.fontSize))
|
||||
if (message.type === "throughputSettingLoaded") setThroughputVisible(Boolean(message.visible))
|
||||
if (message.type === "autoApprovalReasonSettingLoaded") setAutoApprovalReasonVisible(Boolean(message.visible))
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
@@ -62,9 +70,13 @@ export const DisplayProvider: ParentComponent = (props) => {
|
||||
vscode.postMessage({ type: "updateSetting", key: "fontSize", value: next })
|
||||
},
|
||||
throughputVisible,
|
||||
autoApprovalReasonVisible,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
{/* Bridges the toggle into kilo-ui's generic gate so every tool render hides the line consistently. */}
|
||||
<ToolApprovalVisibilityProvider value={autoApprovalReasonVisible}>
|
||||
{props.children}
|
||||
</ToolApprovalVisibilityProvider>
|
||||
</DisplayContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -278,6 +278,7 @@ interface SessionContextValue {
|
||||
draftID?: string,
|
||||
context?: string,
|
||||
origin?: string | null,
|
||||
overrides?: { agent?: string; model?: string; variant?: string },
|
||||
) => void
|
||||
abort: () => void
|
||||
compact: () => void
|
||||
@@ -2345,31 +2346,51 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
draftID?: string,
|
||||
context?: string,
|
||||
origin?: string | null,
|
||||
overrides?: { agent?: string; model?: string; variant?: string },
|
||||
) {
|
||||
if (!server.isConnected()) {
|
||||
console.warn("[Kilo New] Cannot send command: not connected")
|
||||
return
|
||||
}
|
||||
|
||||
// Cloud previews need import-then-command; post importAndSend with command metadata
|
||||
const sid = origin === undefined ? currentSessionID() : (origin ?? undefined)
|
||||
const selection = providerID && modelID ? { providerID, modelID } : selected(sid)
|
||||
recordModelUsage(selection?.providerID, selection?.modelID)
|
||||
const effectiveDraftID = !sid && !draftID ? crypto.randomUUID() : draftID
|
||||
const scope = effectiveDraftID ?? sid
|
||||
if (!sid && !draftID && effectiveDraftID) agentDrafts.seed(effectiveDraftID)
|
||||
|
||||
if (overrides?.agent) {
|
||||
selectAgent(overrides.agent, scope)
|
||||
}
|
||||
if (overrides?.model) {
|
||||
const parsed = parseModelString(overrides.model)
|
||||
if (parsed) {
|
||||
selectModel(parsed.providerID, parsed.modelID, scope)
|
||||
}
|
||||
}
|
||||
if (overrides?.variant) {
|
||||
selectVariant(overrides.variant, scope)
|
||||
}
|
||||
|
||||
const effectiveSelection = selected(scope)
|
||||
const effectiveProvider = effectiveSelection?.providerID ?? providerID
|
||||
const effectiveModel = effectiveSelection?.modelID ?? modelID
|
||||
recordModelUsage(effectiveProvider, effectiveModel)
|
||||
|
||||
// Cloud previews need import-then-command; post importAndSend with command metadata
|
||||
const preview = sid?.startsWith("cloud:")
|
||||
? sid.slice("cloud:".length)
|
||||
: origin === undefined
|
||||
? cloudPreviewId()
|
||||
: null
|
||||
if (preview) {
|
||||
const scope = draftID ?? sid
|
||||
const agent = promptAgent(scope)
|
||||
vscode.postMessage({
|
||||
type: "importAndSend",
|
||||
cloudSessionId: preview,
|
||||
text: `/${command} ${args}`.trim(),
|
||||
messageID: Identifier.ascending("message"),
|
||||
providerID,
|
||||
modelID,
|
||||
providerID: effectiveProvider,
|
||||
modelID: effectiveModel,
|
||||
agent,
|
||||
variant: currentVariant(scope),
|
||||
files,
|
||||
@@ -2386,9 +2407,6 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
dismissQuestion(q.id)
|
||||
}
|
||||
|
||||
const effectiveDraftID = !sid && !draftID ? crypto.randomUUID() : draftID
|
||||
const scope = effectiveDraftID ?? sid
|
||||
if (!sid && !draftID && effectiveDraftID) agentDrafts.seed(effectiveDraftID)
|
||||
if (scope) {
|
||||
clearClose(scope)
|
||||
addOptimistic(scope, messageID, `/${command} ${args}`.trim(), files)
|
||||
@@ -2407,8 +2425,8 @@ export const SessionProvider: ParentComponent = (props) => {
|
||||
messageID,
|
||||
sessionID: sid,
|
||||
draftID: effectiveDraftID,
|
||||
providerID,
|
||||
modelID,
|
||||
providerID: effectiveProvider,
|
||||
modelID: effectiveModel,
|
||||
agent,
|
||||
variant: currentVariant(scope),
|
||||
files,
|
||||
|
||||
@@ -144,6 +144,21 @@ export function useSlashCommand(
|
||||
{ name: "memory auto on", description: "Enable automatic memory saves", hints: [] },
|
||||
{ name: "memory auto off", description: "Disable automatic memory saves", hints: [] },
|
||||
{ name: "memory purge confirm", description: "Delete all project memory files", hints: [] },
|
||||
{
|
||||
name: "review",
|
||||
description: "Review code changes [uncommitted, staged, unpushed, branch, commit, pr]",
|
||||
hints: ["code-review", "diff"],
|
||||
nested: true,
|
||||
},
|
||||
{ name: "review uncommitted", description: "Review uncommitted changes (staged, unstaged, untracked)", hints: [] },
|
||||
{ name: "review staged", description: "Review staged changes only", hints: [] },
|
||||
{ name: "review unpushed", description: "Review local commits ahead of upstream", hints: [] },
|
||||
{ name: "review branch", description: "Review current branch against base branch", hints: [] },
|
||||
{
|
||||
name: "review quick",
|
||||
description: "Fast single-pass review with minimal token usage",
|
||||
hints: ["--quick", "fast"],
|
||||
},
|
||||
{
|
||||
name: "export",
|
||||
description: "Export the current session transcript as Markdown",
|
||||
@@ -240,6 +255,15 @@ export function useSlashCommand(
|
||||
lower,
|
||||
)
|
||||
}
|
||||
if (q.startsWith("review ")) {
|
||||
const matches = list.filter((cmd) => cmd.name.startsWith("review "))
|
||||
if (q === "review ") return matches
|
||||
const lower = q.toLowerCase()
|
||||
return sortByScore(
|
||||
matches.filter((cmd) => cmd.name.toLowerCase().startsWith(lower)),
|
||||
lower,
|
||||
)
|
||||
}
|
||||
const root = list.filter((cmd) => !cmd.name.includes(" "))
|
||||
if (!q) return root
|
||||
const lower = q.toLowerCase()
|
||||
@@ -275,12 +299,24 @@ export function useSlashCommand(
|
||||
return
|
||||
}
|
||||
const memory = before.match(/^\/(?:memory|mem)\s+([^\n]*)$/i)
|
||||
if (!memory) return close()
|
||||
const value = `memory ${memory[1]}`.toLowerCase()
|
||||
if (!commands().some((cmd) => cmd.name.toLowerCase().startsWith(value))) return close()
|
||||
request()
|
||||
setQuery(value)
|
||||
setIndex(0)
|
||||
if (memory) {
|
||||
const value = `memory ${memory[1]}`.toLowerCase()
|
||||
if (!commands().some((cmd) => cmd.name.toLowerCase().startsWith(value))) return close()
|
||||
request()
|
||||
setQuery(value)
|
||||
setIndex(0)
|
||||
return
|
||||
}
|
||||
const review = before.match(/^\/review\s+([^\n]*)$/i)
|
||||
if (review) {
|
||||
const value = `review ${review[1]}`.toLowerCase()
|
||||
if (!commands().some((cmd) => cmd.name.toLowerCase().startsWith(value))) return close()
|
||||
request()
|
||||
setQuery(value)
|
||||
setIndex(0)
|
||||
return
|
||||
}
|
||||
return close()
|
||||
}
|
||||
|
||||
const select = (
|
||||
|
||||
+10
-7
@@ -872,7 +872,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "وجهات الشبكة المسموح بها",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"وجهات مضيف ومنفذ DNS لحركة مرور وكيل HTTP وHTTPS المعزولة. يحتاج GitHub CLI وHTTPS Git عادةً إلى github.com:443 وapi.github.com:443. تنطبق التغييرات على الجلسات الجديدة.",
|
||||
"وجهات مضيف ومنفذ DNS لحركة مرور وكيل HTTP وHTTPS المعزولة. يحتاج GitHub CLI وHTTPS Git عادةً إلى github.com:443 وapi.github.com:443.",
|
||||
"settings.sandboxing.writablePaths.title": "مسارات قابلة للكتابة إضافية",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"مسارات نظام ملفات إضافية يسمح صندوق الرمل بالكتابة إليها (مثل /tmp، /var/log). يتم دمجها مع مسارات الكتابة الافتراضية عندما يكون صندوق الرمل نشطًا.",
|
||||
@@ -1105,19 +1105,22 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "تبديل جهد الاستدلال باستخدام Shift+Tab",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"اضغط على Shift+Tab في حقل إدخال الموجه للتبديل إلى مستوى جهد الاستدلال التالي. عطّل هذا الخيار للاحتفاظ بـ Shift+Tab للتنقل بين عناصر التركيز باستخدام لوحة المفاتيح.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "كتل أوامر الطرفية",
|
||||
"settings.display.terminalCommand.description": "اختر ما إذا كانت كتل أوامر الطرفية تبدأ موسّعة أم مطوية.",
|
||||
"settings.display.terminalCommand.expanded": "موسّعة",
|
||||
"settings.display.terminalCommand.collapsed": "مطوية",
|
||||
"settings.display.codeEdit.title": "كتل تعديلات التعليمات البرمجية",
|
||||
"settings.display.codeEdit.description":
|
||||
"اختر ما إذا كانت الكتل التي تعرض تعديلات التعليمات البرمجية والفروقات تبدأ موسّعة أم مطوية.",
|
||||
"settings.display.codeEdit.expanded": "موسّعة",
|
||||
"settings.display.codeEdit.collapsed": "مطوية",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "إظهار إنتاجية الرموز",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"عرض معدل توليد النص (رموز/ثانية) على آخر رسالة من المساعد وفي رأس المهمة. مخفي بشكل افتراضي للحفاظ على تنظيم المحادثة.",
|
||||
"settings.display.autoApprovalReason.title": "إظهار سبب الموافقة التلقائية",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"إظهار سطر عند استدعاءات الأدوات يوضح سبب الموافقة التلقائية عليها (قاعدة مطابقة، إعداد افتراضي للوكيل، وضع YOLO، إلخ).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+11
-7
@@ -903,7 +903,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "Destinos de rede permitidos",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"Destinos de host e porta DNS para o tráfego de proxy HTTP e HTTPS em sandbox. GitHub CLI e HTTPS Git geralmente precisam de github.com:443 e api.github.com:443. As alterações se aplicam a novas sessões.",
|
||||
"Destinos de host e porta DNS para o tráfego de proxy HTTP e HTTPS em sandbox. GitHub CLI e HTTPS Git geralmente precisam de github.com:443 e api.github.com:443.",
|
||||
"settings.sandboxing.writablePaths.title": "Caminhos graváveis adicionais",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Caminhos adicionais do sistema de arquivos onde o sandbox permite gravação (por exemplo, /tmp, /var/log). Eles são mesclados com os caminhos graváveis padrão quando o sandbox está ativo.",
|
||||
@@ -1148,19 +1148,23 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Alternar o esforço de raciocínio com Shift+Tab",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"Pressione Shift+Tab em um campo de entrada de prompt para alternar para o próximo nível de esforço de raciocínio. Desative para manter Shift+Tab para navegação de foco pelo teclado.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "Blocos de comando do terminal",
|
||||
"settings.display.terminalCommand.description":
|
||||
"Escolha se os blocos de comando do terminal começam expandidos ou recolhidos.",
|
||||
"settings.display.terminalCommand.expanded": "Expandidos",
|
||||
"settings.display.terminalCommand.collapsed": "Recolhidos",
|
||||
"settings.display.codeEdit.title": "Blocos de edição de código",
|
||||
"settings.display.codeEdit.description":
|
||||
"Escolha se os blocos que exibem edições de código e diferenças começam expandidos ou recolhidos.",
|
||||
"settings.display.codeEdit.expanded": "Expandidos",
|
||||
"settings.display.codeEdit.collapsed": "Recolhidos",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "Mostrar taxa de tokens",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"Exibe a taxa de geração de texto (tokens/s) na última mensagem do assistente e no cabeçalho da tarefa. Oculto por padrão para manter o chat organizado.",
|
||||
"settings.display.autoApprovalReason.title": "Mostrar motivo da aprovação automática",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Mostra uma linha nas chamadas de ferramentas explicando por que foram aprovadas automaticamente (regra correspondente, padrão do agente, modo YOLO, etc.).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+11
-7
@@ -896,7 +896,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "Dozvoljena mrežna odredišta",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"DNS odredišta hosta i porta za sandboxirani HTTP i HTTPS proxy promet. GitHub CLI i HTTPS Git obično trebaju github.com:443 i api.github.com:443. Promjene se primjenjuju na nove sesije.",
|
||||
"DNS odredišta hosta i porta za sandboxirani HTTP i HTTPS proxy promet. GitHub CLI i HTTPS Git obično trebaju github.com:443 i api.github.com:443.",
|
||||
"settings.sandboxing.writablePaths.title": "Dodatne upisive putanje",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Dodatne putanje sistema datoteka u koje sandbox dozvoljava upis (npr. /tmp, /var/log). Spajaju se sa zadanim upisivim putanjama kada je sandbox aktivan.",
|
||||
@@ -1138,19 +1138,23 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Promijeni napor razmišljanja pomoću Shift+Tab",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"Pritisnite Shift+Tab u polju za unos upita da pređete na sljedeći nivo napora razmišljanja. Onemogućite ovu opciju kako biste zadržali Shift+Tab za navigaciju fokusom putem tastature.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "Blokovi terminalskih naredbi",
|
||||
"settings.display.terminalCommand.description":
|
||||
"Odaberite da li blokovi terminalskih naredbi počinju prošireni ili sažeti.",
|
||||
"settings.display.terminalCommand.expanded": "Prošireni",
|
||||
"settings.display.terminalCommand.collapsed": "Sažeti",
|
||||
"settings.display.codeEdit.title": "Blokovi izmjena koda",
|
||||
"settings.display.codeEdit.description":
|
||||
"Odaberite da li će blokovi koji prikazuju izmjene koda i razlike u početku biti prošireni ili sažeti.",
|
||||
"settings.display.codeEdit.expanded": "Prošireni",
|
||||
"settings.display.codeEdit.collapsed": "Sažeti",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "Prikaži protok tokena",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"Prikazuje brzinu generisanja teksta (tokena/s) na najnovijoj poruci asistenta i u zaglavlju zadatka. Podrazumevano skriveno radi urednijeg chata.",
|
||||
"settings.display.autoApprovalReason.title": "Prikaži razlog automatskog odobravanja",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Prikazuje red uz pozive alata koji objašnjava zašto su automatski odobreni (odgovarajuće pravilo, podrazumevana vrijednost agenta, YOLO režim itd.).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+10
-7
@@ -895,7 +895,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "Tilladte netværksdestinationer",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"DNS-værts- og portdestinationer for sandboxet HTTP- og HTTPS-proxytrafik. GitHub CLI og HTTPS Git kræver typisk github.com:443 og api.github.com:443. Ændringer gælder for nye sessioner.",
|
||||
"DNS-værts- og portdestinationer for sandboxet HTTP- og HTTPS-proxytrafik. GitHub CLI og HTTPS Git kræver typisk github.com:443 og api.github.com:443.",
|
||||
"settings.sandboxing.writablePaths.title": "Yderligere skrivbare stier",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Yderligere filsystemstier, som sandkassen tillader skrivning til (f.eks. /tmp, /var/log). Disse flettes med de standardskrivbare stier, når sandkassen er aktiv.",
|
||||
@@ -1134,19 +1134,22 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Skift ræsonnementsindsats med Shift+Tab",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"Tryk på Shift+Tab i et promptindtastningsfelt for at skifte til næste niveau af ræsonnementsindsats. Deaktivér for at beholde Shift+Tab til tastaturnavigation af fokus.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "Terminalkommandoblokke",
|
||||
"settings.display.terminalCommand.description": "Vælg om terminalkommandoblokke starter foldet ud eller sammen.",
|
||||
"settings.display.terminalCommand.expanded": "Foldet ud",
|
||||
"settings.display.terminalCommand.collapsed": "Foldet sammen",
|
||||
"settings.display.codeEdit.title": "Koderedigeringsblokke",
|
||||
"settings.display.codeEdit.description":
|
||||
"Vælg, om blokke, der viser koderedigeringer og forskelle, starter foldet ud eller sammen.",
|
||||
"settings.display.codeEdit.expanded": "Foldet ud",
|
||||
"settings.display.codeEdit.collapsed": "Foldet sammen",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "Vis genereringshastighed",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"Viser tekstgenereringshastigheden (tokens/sek.) på den seneste assistentmeddelelse og i opgavehovedet. Skjult som standard for at holde chatten ryddig.",
|
||||
"settings.display.autoApprovalReason.title": "Vis grund til automatisk godkendelse",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Viser en linje ved værktøjskald, der forklarer, hvorfor de blev automatisk godkendt (matchende regel, agent-standard, YOLO-tilstand osv.).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
@@ -917,7 +917,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "Zulässige Netzwerkziele",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"DNS-Host- und Portziele für Sandbox-HTTP- und HTTPS-Proxy-Datenverkehr. GitHub CLI und HTTPS Git benötigen üblicherweise github.com:443 und api.github.com:443. Änderungen gelten für neue Sitzungen.",
|
||||
"DNS-Host- und Portziele für Sandbox-HTTP- und HTTPS-Proxy-Datenverkehr. GitHub CLI und HTTPS Git benötigen üblicherweise github.com:443 und api.github.com:443.",
|
||||
"settings.sandboxing.writablePaths.title": "Zusätzliche schreibbare Pfade",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Zusätzliche Dateisystempfade, in die die Sandbox Schreibvorgänge erlaubt (z. B. /tmp, /var/log). Diese werden mit den Standard-Schreibpfaden zusammengeführt, wenn die Sandbox aktiv ist.",
|
||||
@@ -1161,19 +1161,23 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Reasoning-Aufwand mit Shift+Tab durchlaufen",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"Drücken Sie Shift+Tab in einem Prompt-Eingabefeld, um zur nächsten Stufe des Reasoning-Aufwands zu wechseln. Deaktivieren Sie dies, um Shift+Tab für die Tastaturfokusnavigation beizubehalten.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "Terminalbefehlsblöcke",
|
||||
"settings.display.terminalCommand.description":
|
||||
"Wählen Sie, ob Terminalbefehlsblöcke anfangs aus- oder eingeklappt sind.",
|
||||
"settings.display.terminalCommand.expanded": "Ausgeklappt",
|
||||
"settings.display.terminalCommand.collapsed": "Eingeklappt",
|
||||
"settings.display.codeEdit.title": "Blöcke für Codebearbeitungen",
|
||||
"settings.display.codeEdit.description":
|
||||
"Wählen Sie, ob Blöcke mit Codebearbeitungen und Unterschieden anfangs aus- oder eingeklappt sind.",
|
||||
"settings.display.codeEdit.expanded": "Ausgeklappt",
|
||||
"settings.display.codeEdit.collapsed": "Eingeklappt",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "Token-Durchsatz anzeigen",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"Zeigt die Textgenerierungsrate (Tokens/Sek.) in der letzten Assistentennachricht und im Aufgabenkopf an. Standardmäßig ausgeblendet, um den Chat übersichtlich zu halten.",
|
||||
"settings.display.autoApprovalReason.title": "Grund für automatische Genehmigung anzeigen",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Zeigt bei Tool-Aufrufen eine Zeile an, die erklärt, warum sie automatisch genehmigt wurden (passende Regel, Agent-Standard, YOLO-Modus usw.).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
@@ -875,7 +875,7 @@ export const dict = {
|
||||
"Block direct outbound access from model-originated commands and HTTP tools. Local and remote MCP tools are unavailable while restricted. Provider traffic and trusted plugin hooks remain outside this restriction.",
|
||||
"settings.sandboxing.allowedHosts.title": "Allowed Network Destinations",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"DNS host and port destinations for sandboxed HTTP and HTTPS proxy traffic. GitHub CLI and HTTPS Git commonly need github.com:443 and api.github.com:443. Changes apply to new sessions.",
|
||||
"DNS host and port destinations for sandboxed HTTP and HTTPS proxy traffic. GitHub CLI and HTTPS Git commonly need github.com:443 and api.github.com:443.",
|
||||
"settings.sandboxing.writablePaths.title": "Additional Writable Paths",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Extra filesystem paths the sandbox allows writes to (e.g. /tmp, /var/log). These are merged with the default writable paths when the sandbox is active.",
|
||||
@@ -1120,6 +1120,9 @@ export const dict = {
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"settings.display.autoApprovalReason.title": "Show Auto-Approval Reason",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Show a line on tool calls explaining why they were auto-approved (matched rule, agent default, YOLO mode, etc.).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+11
-7
@@ -906,7 +906,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "Destinos de red permitidos",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"Destinos de host y puerto DNS para el tráfico de proxy HTTP y HTTPS en zona de pruebas. GitHub CLI y HTTPS Git suelen necesitar github.com:443 y api.github.com:443. Los cambios se aplican a las sesiones nuevas.",
|
||||
"Destinos de host y puerto DNS para el tráfico de proxy HTTP y HTTPS en zona de pruebas. GitHub CLI y HTTPS Git suelen necesitar github.com:443 y api.github.com:443.",
|
||||
"settings.sandboxing.writablePaths.title": "Rutas de escritura adicionales",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Rutas del sistema de archivos adicionales donde el sandbox permite escritura (por ej., /tmp, /var/log). Se combinan con las rutas de escritura predeterminadas cuando el sandbox está activo.",
|
||||
@@ -1151,19 +1151,23 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Alternar el esfuerzo de razonamiento con Shift+Tab",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"Pulsa Shift+Tab en un campo de entrada de prompt para cambiar al siguiente nivel de esfuerzo de razonamiento. Desactívalo para conservar Shift+Tab para la navegación del foco con el teclado.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "Bloques de comandos de terminal",
|
||||
"settings.display.terminalCommand.description":
|
||||
"Elige si los bloques de comandos de terminal aparecen inicialmente expandidos o contraídos.",
|
||||
"settings.display.terminalCommand.expanded": "Expandidos",
|
||||
"settings.display.terminalCommand.collapsed": "Contraídos",
|
||||
"settings.display.codeEdit.title": "Bloques de edición de código",
|
||||
"settings.display.codeEdit.description":
|
||||
"Elige si los bloques de edición de código y de diferencias aparecen inicialmente expandidos o contraídos.",
|
||||
"settings.display.codeEdit.expanded": "Expandidos",
|
||||
"settings.display.codeEdit.collapsed": "Contraídos",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "Mostrar rendimiento de tokens",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"Muestra la tasa de generación de texto (tokens/s) en el último mensaje del asistente y en el encabezado de la tarea. Oculto de forma predeterminada para mantener el chat ordenado.",
|
||||
"settings.display.autoApprovalReason.title": "Mostrar motivo de aprobación automática",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Muestra una línea en las llamadas a herramientas que explica por qué se aprobaron automáticamente (regla coincidente, valor predeterminado del agente, modo YOLO, etc.).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+3
@@ -1133,6 +1133,9 @@ export const dict = {
|
||||
"settings.display.tokenThroughput.title": "نمایش توان عملیاتی توکن",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"نرخ تولید متن (توکن/ثانیه) را در آخرین پیام دستیار و در سربرگ وظیفه نمایش میدهد. بهطور پیشفرض پنهان است تا چت شلوغ نشود.",
|
||||
"settings.display.autoApprovalReason.title": "نمایش دلیل تأیید خودکار",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"نمایش خطی در فراخوانی ابزارها که توضیح میدهد چرا بهطور خودکار تأیید شدهاند (قانون مطابق، پیشفرض عامل، حالت YOLO و غیره).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"میانگین {{speed}} توکن/ثانیه برای این نوبت. شامل توکنهای خروجی و استدلال میشود؛ زمان اجرای ابزار و انتظار را شامل نمیشود.",
|
||||
|
||||
+11
-7
@@ -918,7 +918,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "Destinations réseau autorisées",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"Destinations d’hôte et de port DNS pour le trafic proxy HTTP et HTTPS isolé. GitHub CLI et HTTPS Git nécessitent généralement github.com:443 et api.github.com:443. Les modifications s’appliquent aux nouvelles sessions.",
|
||||
"Destinations d’hôte et de port DNS pour le trafic proxy HTTP et HTTPS isolé. GitHub CLI et HTTPS Git nécessitent généralement github.com:443 et api.github.com:443.",
|
||||
"settings.sandboxing.writablePaths.title": "Chemins en écriture supplémentaires",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Chemins système supplémentaires autorisés en écriture par le bac à sable (par ex. /tmp, /var/log). Ils sont fusionnés avec les chemins en écriture par défaut lorsque le bac à sable est actif.",
|
||||
@@ -1168,19 +1168,23 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Parcourir l'effort de raisonnement avec Shift+Tab",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"Appuyez sur Shift+Tab dans un champ de saisie de prompt pour passer au niveau d'effort de raisonnement suivant. Désactivez cette option pour conserver Shift+Tab pour la navigation du focus au clavier.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "Blocs de commande de terminal",
|
||||
"settings.display.terminalCommand.description":
|
||||
"Choisissez si les blocs de commande de terminal sont initialement développés ou réduits.",
|
||||
"settings.display.terminalCommand.expanded": "Développés",
|
||||
"settings.display.terminalCommand.collapsed": "Réduits",
|
||||
"settings.display.codeEdit.title": "Blocs de modification du code",
|
||||
"settings.display.codeEdit.description":
|
||||
"Choisissez si les blocs de modification du code et de différences sont initialement développés ou réduits.",
|
||||
"settings.display.codeEdit.expanded": "Développés",
|
||||
"settings.display.codeEdit.collapsed": "Réduits",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "Afficher le débit de tokens",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"Affiche le taux de génération de texte (tokens/s) sur le dernier message de l'assistant et dans l'en-tête de la tâche. Masqué par défaut pour garder le chat épuré.",
|
||||
"settings.display.autoApprovalReason.title": "Afficher la raison de l'approbation automatique",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Affiche une ligne sur les appels d'outils expliquant pourquoi ils ont été approuvés automatiquement (règle correspondante, agent par défaut, mode YOLO, etc.).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+6
-3
@@ -743,7 +743,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "Destinazioni di rete consentite",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"Destinazioni DNS di host e porta per il traffico proxy HTTP e HTTPS in sandbox. GitHub CLI e HTTPS Git richiedono comunemente github.com:443 e api.github.com:443. Le modifiche si applicano alle nuove sessioni.",
|
||||
"Destinazioni DNS di host e porta per il traffico proxy HTTP e HTTPS in sandbox. GitHub CLI e HTTPS Git richiedono comunemente github.com:443 e api.github.com:443.",
|
||||
"settings.sandboxing.writablePaths.title": "Percorsi di scrittura aggiuntivi",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Percorsi aggiuntivi del file system in cui la sandbox consente la scrittura (es. /tmp, /var/log). Vengono uniti con i percorsi di scrittura predefiniti quando la sandbox è attiva.",
|
||||
@@ -1004,9 +1004,12 @@ export const dict = {
|
||||
"settings.display.codeEdit.expanded": "Espansi",
|
||||
"settings.display.codeEdit.collapsed": "Compressi",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "Mostra velocità di generazione dei token",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"Mostra la velocità di generazione del testo (token/sec) sull'ultimo messaggio dell'assistente e nell'intestazione dell'attività. Nascosto per impostazione predefinita per mantenere la chat ordinata.",
|
||||
"settings.display.autoApprovalReason.title": "Mostra motivo dell'approvazione automatica",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Mostra una riga sulle chiamate agli strumenti che spiega perché sono state approvate automaticamente (regola corrispondente, predefinito dell'agente, modalità YOLO, ecc.).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+11
-7
@@ -888,7 +888,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "許可されたネットワーク接続先",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"サンドボックス化された HTTP および HTTPS プロキシトラフィックの DNS ホストとポートの宛先。GitHub CLI と HTTPS Git では通常、github.com:443 と api.github.com:443 が必要です。変更は新しいセッションに適用されます。",
|
||||
"サンドボックス化された HTTP および HTTPS プロキシトラフィックの DNS ホストとポートの宛先。GitHub CLI と HTTPS Git では通常、github.com:443 と api.github.com:443 が必要です。",
|
||||
"settings.sandboxing.writablePaths.title": "追加の書き込み可能パス",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"サンドボックスでの書き込みを許可する追加のファイルシステムパス(例: /tmp、/var/log)。サンドボックス有効時、デフォルトの書き込み可能パスと統合されます。",
|
||||
@@ -1126,19 +1126,23 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Shift+Tab で推論の強度を切り替える",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"プロンプト入力欄で Shift+Tab を押すと、次の推論の強度レベルに切り替わります。Shift+Tab をキーボードフォーカスの移動に使用する場合は、無効にしてください。",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "ターミナルコマンドブロック",
|
||||
"settings.display.terminalCommand.description":
|
||||
"ターミナルコマンドブロックを最初から展開するか折りたたむかを選択します。",
|
||||
"settings.display.terminalCommand.expanded": "展開",
|
||||
"settings.display.terminalCommand.collapsed": "折りたたみ",
|
||||
"settings.display.codeEdit.title": "コード編集ブロック",
|
||||
"settings.display.codeEdit.description":
|
||||
"コード編集ブロックと差分ブロックを最初から展開するか折りたたむかを選択します。",
|
||||
"settings.display.codeEdit.expanded": "展開",
|
||||
"settings.display.codeEdit.collapsed": "折りたたみ",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "トークンスループットを表示",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"最新のアシスタントメッセージとタスクヘッダーにテキスト生成速度(トークン/秒)を表示します。チャットを整理するためデフォルトでは非表示です。",
|
||||
"settings.display.autoApprovalReason.title": "自動承認の理由を表示",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"ツール呼び出しが自動承認された理由(一致したルール、エージェントのデフォルト、YOLOモードなど)を示す行を表示します。",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+10
-7
@@ -885,7 +885,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "허용된 네트워크 대상",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"샌드박스 처리된 HTTP 및 HTTPS 프록시 트래픽의 DNS 호스트 및 포트 대상입니다. GitHub CLI 및 HTTPS Git에는 일반적으로 github.com:443 및 api.github.com:443가 필요합니다. 변경 사항은 새 세션에 적용됩니다.",
|
||||
"샌드박스 처리된 HTTP 및 HTTPS 프록시 트래픽의 DNS 호스트 및 포트 대상입니다. GitHub CLI 및 HTTPS Git에는 일반적으로 github.com:443 및 api.github.com:443가 필요합니다.",
|
||||
"settings.sandboxing.writablePaths.title": "추가 쓰기 가능 경로",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"샌드박스에서 쓰기를 허용하는 추가 파일시스템 경로(예: /tmp, /var/log). 샌드박스가 활성화되면 기본 쓰기 가능 경로와 병합됩니다.",
|
||||
@@ -1115,18 +1115,21 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Shift+Tab으로 추론 강도 전환",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"프롬프트 입력란에서 Shift+Tab을 눌러 다음 추론 강도 수준으로 전환합니다. Shift+Tab을 키보드 포커스 탐색에 사용하려면 비활성화하세요.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "터미널 명령 블록",
|
||||
"settings.display.terminalCommand.description": "터미널 명령 블록을 처음부터 펼칠지 접을지 선택합니다.",
|
||||
"settings.display.terminalCommand.expanded": "펼침",
|
||||
"settings.display.terminalCommand.collapsed": "접힘",
|
||||
"settings.display.codeEdit.title": "코드 편집 블록",
|
||||
"settings.display.codeEdit.description": "코드 편집 블록과 차이점 블록을 처음부터 펼칠지 접을지 선택합니다.",
|
||||
"settings.display.codeEdit.expanded": "펼침",
|
||||
"settings.display.codeEdit.collapsed": "접힘",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "토큰 처리량 표시",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"최신 어시스턴트 메시지와 작업 헤더에 텍스트 생성 속도(토큰/초)를 표시합니다. 채팅을 깔끔하게 유지하기 위해 기본적으로 숨겨져 있습니다.",
|
||||
"settings.display.autoApprovalReason.title": "자동 승인 이유 표시",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"도구 호출이 자동으로 승인된 이유(일치한 규칙, 에이전트 기본값, YOLO 모드 등)를 설명하는 줄을 표시합니다.",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+11
-7
@@ -896,7 +896,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "Toegestane netwerkbestemmingen",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"DNS-host- en poortbestemmingen voor HTTP- en HTTPS-proxyverkeer in een sandbox. GitHub CLI en HTTPS Git hebben doorgaans github.com:443 en api.github.com:443 nodig. Wijzigingen gelden voor nieuwe sessies.",
|
||||
"DNS-host- en poortbestemmingen voor HTTP- en HTTPS-proxyverkeer in een sandbox. GitHub CLI en HTTPS Git hebben doorgaans github.com:443 en api.github.com:443 nodig.",
|
||||
"settings.sandboxing.writablePaths.title": "Extra schrijfbare paden",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Extra bestandssysteempaden waar de sandbox schrijftoestemming voor geeft (bijv. /tmp, /var/log). Deze worden samengevoegd met de standaard schrijfbare paden wanneer de sandbox actief is.",
|
||||
@@ -1108,19 +1108,23 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Doorloop niveaus van redeneringsinspanning met Shift+Tab",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"Druk op Shift+Tab in een promptinvoerveld om naar het volgende niveau van redeneringsinspanning te gaan. Schakel dit uit om Shift+Tab te behouden voor focusnavigatie via het toetsenbord.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "Terminalopdrachtblokken",
|
||||
"settings.display.terminalCommand.description":
|
||||
"Kies of terminalopdrachtblokken standaard uitgeklapt of ingeklapt zijn.",
|
||||
"settings.display.terminalCommand.expanded": "Uitgeklapt",
|
||||
"settings.display.terminalCommand.collapsed": "Ingeklapt",
|
||||
"settings.display.codeEdit.title": "Blokken met codebewerkingen",
|
||||
"settings.display.codeEdit.description":
|
||||
"Kies of blokken met codebewerkingen en verschillen standaard uitgeklapt of ingeklapt zijn.",
|
||||
"settings.display.codeEdit.expanded": "Uitgeklapt",
|
||||
"settings.display.codeEdit.collapsed": "Ingeklapt",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "Tokendoorvoer weergeven",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"Toont de tekstgeneratiesnelheid (tokens/sec) bij het laatste assistentbericht en in de taakkop. Standaard verborgen om de chat overzichtelijk te houden.",
|
||||
"settings.display.autoApprovalReason.title": "Reden voor automatische goedkeuring weergeven",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Toont een regel bij tool-aanroepen die uitlegt waarom ze automatisch zijn goedgekeurd (overeenkomende regel, agentstandaard, YOLO-modus, enz.).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+9
-5
@@ -856,7 +856,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "Tillatte nettverksmål",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"DNS-verts- og portdestinasjoner for HTTP- og HTTPS-proxytrafikk i sandkassen. GitHub CLI og HTTPS Git trenger vanligvis github.com:443 og api.github.com:443. Endringer gjelder for nye økter.",
|
||||
"DNS-verts- og portdestinasjoner for HTTP- og HTTPS-proxytrafikk i sandkassen. GitHub CLI og HTTPS Git trenger vanligvis github.com:443 og api.github.com:443.",
|
||||
"settings.sandboxing.writablePaths.title": "Ytterligere skrivbare baner",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Ytterligere filsystembaner som sandkassen tillater skriving til (f.eks. /tmp, /var/log). Disse flettes med de standardskrivbare banene når sandkassen er aktiv.",
|
||||
@@ -1132,10 +1132,11 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Bytt resonnementsinnsats med Shift+Tab",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"Trykk Shift+Tab i et promptinndatafelt for å bytte til neste nivå for resonnementsinnsats. Deaktiver for å beholde Shift+Tab for tastaturnavigering av fokus.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "Blokker for terminalkommandoer",
|
||||
"settings.display.terminalCommand.description":
|
||||
"Velg om blokker for terminalkommandoer skal være utvidet eller skjult fra start.",
|
||||
"settings.display.terminalCommand.expanded": "Utvidet",
|
||||
"settings.display.terminalCommand.collapsed": "Skjult",
|
||||
"settings.display.codeEdit.title": "Blokker for kodeendringer",
|
||||
"settings.display.codeEdit.description":
|
||||
"Velg om blokker for kodeendringer og forskjeller skal være utvidet eller skjult fra start.",
|
||||
@@ -1145,6 +1146,9 @@ export const dict = {
|
||||
"settings.display.tokenThroughput.title": "Vis genereringshastighet",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Vis tekstgenereringshastighet (tokens/sek) på den siste assistentmeldingen og i oppgaveoverskriften. Skjult som standard for å holde chatten ryddig.",
|
||||
"settings.display.autoApprovalReason.title": "Vis årsak til automatisk godkjenning",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Viser en linje ved verktøykall som forklarer hvorfor de ble automatisk godkjent (samsvarende regel, agentstandard, YOLO-modus osv.).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+11
-7
@@ -853,7 +853,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "Dozwolone miejsca docelowe sieci",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"Docelowe hosty DNS i porty dla ruchu HTTP i HTTPS przez proxy w piaskownicy. GitHub CLI i HTTPS Git zwykle wymagają github.com:443 i api.github.com:443. Zmiany dotyczą nowych sesji.",
|
||||
"Docelowe hosty DNS i porty dla ruchu HTTP i HTTPS przez proxy w piaskownicy. GitHub CLI i HTTPS Git zwykle wymagają github.com:443 i api.github.com:443.",
|
||||
"settings.sandboxing.writablePaths.title": "Dodatkowe ścieżki zapisu",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Dodatkowe ścieżki systemu plików, do których sandbox zezwala na zapis (np. /tmp, /var/log). Są one łączone z domyślnymi ścieżkami zapisu, gdy sandbox jest aktywny.",
|
||||
@@ -1139,19 +1139,23 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Przełączaj wysiłek rozumowania za pomocą Shift+Tab",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"Naciśnij Shift+Tab w polu wprowadzania promptu, aby przełączyć się na następny poziom wysiłku rozumowania. Wyłącz tę opcję, aby zachować Shift+Tab do nawigacji fokusem za pomocą klawiatury.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "Bloki poleceń terminala",
|
||||
"settings.display.terminalCommand.description":
|
||||
"Wybierz, czy bloki poleceń terminala mają być początkowo rozwinięte czy zwinięte.",
|
||||
"settings.display.terminalCommand.expanded": "Rozwinięte",
|
||||
"settings.display.terminalCommand.collapsed": "Zwinięte",
|
||||
"settings.display.codeEdit.title": "Bloki edycji kodu",
|
||||
"settings.display.codeEdit.description":
|
||||
"Wybierz, czy bloki edycji kodu i podglądy różnic mają być początkowo rozwinięte czy zwinięte.",
|
||||
"settings.display.codeEdit.expanded": "Rozwinięte",
|
||||
"settings.display.codeEdit.collapsed": "Zwinięte",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "Pokaż przepustowość tokenów",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"Wyświetla szybkość generowania tekstu (tokeny/s) w ostatniej wiadomości asystenta i w nagłówku zadania. Domyślnie skryte, aby czat był przejrzysty.",
|
||||
"settings.display.autoApprovalReason.title": "Pokaż powód automatycznego zatwierdzenia",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Pokazuje wiersz przy wywołaniach narzędzi wyjaśniający, dlaczego zostały automatycznie zatwierdzone (dopasowana reguła, wartość domyślna agenta, tryb YOLO itp.).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+11
-7
@@ -892,7 +892,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "Разрешенные сетевые назначения",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"Целевые DNS-хосты и порты для прокси-трафика HTTP и HTTPS в песочнице. GitHub CLI и HTTPS Git обычно требуют github.com:443 и api.github.com:443. Изменения применяются к новым сеансам.",
|
||||
"Целевые DNS-хосты и порты для прокси-трафика HTTP и HTTPS в песочнице. GitHub CLI и HTTPS Git обычно требуют github.com:443 и api.github.com:443.",
|
||||
"settings.sandboxing.writablePaths.title": "Дополнительные пути для записи",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Дополнительные пути файловой системы, в которые разрешена запись в песочнице (например, /tmp, /var/log). Они объединяются с путями записи по умолчанию при активной песочнице.",
|
||||
@@ -1133,19 +1133,23 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Переключать усилие рассуждения с помощью Shift+Tab",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"Нажмите Shift+Tab в поле ввода запроса, чтобы перейти к следующему уровню усилий рассуждения. Отключите эту настройку, чтобы сохранить Shift+Tab для навигации по фокусу с помощью клавиатуры.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "Блоки команд терминала",
|
||||
"settings.display.terminalCommand.description":
|
||||
"Выберите, будут ли блоки команд терминала изначально развёрнуты или свёрнуты.",
|
||||
"settings.display.terminalCommand.expanded": "Развёрнуты",
|
||||
"settings.display.terminalCommand.collapsed": "Свёрнуты",
|
||||
"settings.display.codeEdit.title": "Блоки изменений кода",
|
||||
"settings.display.codeEdit.description":
|
||||
"Выберите, будут ли блоки изменений кода и различий изначально развёрнуты или свёрнуты.",
|
||||
"settings.display.codeEdit.expanded": "Развёрнуты",
|
||||
"settings.display.codeEdit.collapsed": "Свёрнуты",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "Показывать пропускную способность токенов",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"Отображает скорость генерации текста (токенов/с) в последнем сообщении ассистента и в заголовке задачи. По умолчанию скрыто, чтобы не загромождать чат.",
|
||||
"settings.display.autoApprovalReason.title": "Показывать причину автоодобрения",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Показывает строку у вызовов инструментов, объясняющую, почему они были одобрены автоматически (совпавшее правило, значение агента по умолчанию, режим YOLO и т. д.).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+10
-7
@@ -881,7 +881,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "ปลายทางเครือข่ายที่อนุญาต",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"โฮสต์ DNS และพอร์ตปลายทางสำหรับทราฟฟิกพร็อกซี HTTP และ HTTPS ในแซนด์บ็อกซ์ GitHub CLI และ HTTPS Git มักต้องใช้ github.com:443 และ api.github.com:443 การเปลี่ยนแปลงจะมีผลกับเซสชันใหม่",
|
||||
"โฮสต์ DNS และพอร์ตปลายทางสำหรับทราฟฟิกพร็อกซี HTTP และ HTTPS ในแซนด์บ็อกซ์ GitHub CLI และ HTTPS Git มักต้องใช้ github.com:443 และ api.github.com:443",
|
||||
"settings.sandboxing.writablePaths.title": "เส้นทางที่เขียนได้เพิ่มเติม",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"เส้นทางระบบไฟล์เพิ่มเติมที่แซนด์บ็อกซ์อนุญาตให้เขียนได้ (เช่น /tmp, /var/log) จะถูกรวมเข้ากับเส้นทางที่เขียนได้เริ่มต้นเมื่อแซนด์บ็อกซ์เปิดใช้งาน",
|
||||
@@ -1113,18 +1113,21 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "สลับระดับความพยายามในการให้เหตุผลด้วย Shift+Tab",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"กด Shift+Tab ในช่องป้อนพรอมต์เพื่อสลับไปยังระดับความพยายามในการให้เหตุผลถัดไป ปิดใช้งานเพื่อคง Shift+Tab ไว้สำหรับการนำทางโฟกัสด้วยแป้นพิมพ์",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "บล็อกคำสั่งเทอร์มินัล",
|
||||
"settings.display.terminalCommand.description": "เลือกว่าบล็อกคำสั่งเทอร์มินัลจะเริ่มต้นแบบขยายหรือยุบ",
|
||||
"settings.display.terminalCommand.expanded": "ขยาย",
|
||||
"settings.display.terminalCommand.collapsed": "ยุบ",
|
||||
"settings.display.codeEdit.title": "บล็อกการแก้ไขโค้ด",
|
||||
"settings.display.codeEdit.description": "เลือกว่าบล็อกการแก้ไขโค้ดและบล็อกแสดงความแตกต่างจะเริ่มต้นแบบขยายหรือยุบ",
|
||||
"settings.display.codeEdit.expanded": "ขยาย",
|
||||
"settings.display.codeEdit.collapsed": "ยุบ",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "แสดงอัตราการประมวลผลโทเคน",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"แสดงอัตราการสร้างข้อความ (โทเคน/วินาที) บนข้อความล่าสุดของผู้ช่วยและในส่วนหัวของงาน ซ่อนโดยค่าเริ่มต้นเพื่อให้แชทดูเรียบร้อย",
|
||||
"settings.display.autoApprovalReason.title": "แสดงเหตุผลการอนุมัติอัตโนมัติ",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"แสดงบรรทัดในการเรียกใช้เครื่องมือเพื่ออธิบายว่าเหตุใดจึงได้รับการอนุมัติอัตโนมัติ (กฎที่ตรงกัน ค่าเริ่มต้นของเอเจนต์ โหมด YOLO ฯลฯ)",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+11
-7
@@ -885,7 +885,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "İzin Verilen Ağ Hedefleri",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"Korumalı alana alınmış HTTP ve HTTPS proxy trafiği için DNS ana bilgisayar ve bağlantı noktası hedefleri. GitHub CLI ve HTTPS Git genellikle github.com:443 ve api.github.com:443 gerektirir. Değişiklikler yeni oturumlara uygulanır.",
|
||||
"Korumalı alana alınmış HTTP ve HTTPS proxy trafiği için DNS ana bilgisayar ve bağlantı noktası hedefleri. GitHub CLI ve HTTPS Git genellikle github.com:443 ve api.github.com:443 gerektirir.",
|
||||
"settings.sandboxing.writablePaths.title": "Ek Yazılabilir Yollar",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Sandığın yazılmasına izin veren ek dosya sistemi yolları (ör. /tmp, /var/log). Sandık etkinken varsayılan yazılabilir yollarla birleştirilir.",
|
||||
@@ -1094,19 +1094,23 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Shift+Tab ile akıl yürütme eforunu değiştir",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"Bir sonraki akıl yürütme eforu seviyesine geçmek için komut girişinde Shift+Tab tuşlarına basın. Shift+Tab tuşunu klavye odağında gezinmek için korumak üzere devre dışı bırakın.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "Terminal Komut Blokları",
|
||||
"settings.display.terminalCommand.description":
|
||||
"Terminal komut bloklarının başlangıçta genişletilmiş mi yoksa daraltılmış mı olacağını seçin.",
|
||||
"settings.display.terminalCommand.expanded": "Genişletilmiş",
|
||||
"settings.display.terminalCommand.collapsed": "Daraltılmış",
|
||||
"settings.display.codeEdit.title": "Kod Düzenleme Blokları",
|
||||
"settings.display.codeEdit.description":
|
||||
"Kod düzenleme ve fark bloklarının başlangıçta genişletilmiş mi yoksa daraltılmış mı olacağını seçin.",
|
||||
"settings.display.codeEdit.expanded": "Genişletilmiş",
|
||||
"settings.display.codeEdit.collapsed": "Daraltılmış",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "Token İşleme Hızını Göster",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"En son asistan mesajında ve görev başlığında metin üretim hızını (token/sn) gösterir. Sohbeti dağınık göstermemek için varsayılan olarak gizlidir.",
|
||||
"settings.display.autoApprovalReason.title": "Otomatik Onay Nedenini Göster",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Araç çağrılarının neden otomatik olarak onaylandığını açıklayan bir satır gösterir (eşleşen kural, aracı varsayılanı, YOLO modu vb.).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+11
-7
@@ -887,7 +887,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "Дозволені мережеві адреси",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"DNS-вузли та порти призначення для ізольованого proxy-трафіку HTTP і HTTPS. GitHub CLI та HTTPS Git зазвичай потребують github.com:443 і api.github.com:443. Зміни застосовуються до нових сеансів.",
|
||||
"DNS-вузли та порти призначення для ізольованого proxy-трафіку HTTP і HTTPS. GitHub CLI та HTTPS Git зазвичай потребують github.com:443 і api.github.com:443.",
|
||||
"settings.sandboxing.writablePaths.title": "Додаткові шляхи для запису",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"Додаткові шляхи файлової системи, у які дозволено запис у пісочниці (наприклад, /tmp, /var/log). Вони об'єднуються зі шляхами запису за замовчуванням, коли пісочниця активна.",
|
||||
@@ -1094,19 +1094,23 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "Перемикати зусилля міркування за допомогою Shift+Tab",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"Натисніть Shift+Tab у полі введення запиту, щоб перейти до наступного рівня зусиль міркування. Вимкніть цю опцію, щоб зберегти Shift+Tab для навігації фокусом за допомогою клавіатури.",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "Блоки команд термінала",
|
||||
"settings.display.terminalCommand.description":
|
||||
"Виберіть, чи будуть блоки команд термінала спочатку розгорнутими чи згорнутими.",
|
||||
"settings.display.terminalCommand.expanded": "Розгорнуті",
|
||||
"settings.display.terminalCommand.collapsed": "Згорнуті",
|
||||
"settings.display.codeEdit.title": "Блоки редагування коду",
|
||||
"settings.display.codeEdit.description":
|
||||
"Виберіть, чи будуть блоки редагування коду та відмінностей спочатку розгорнутими чи згорнутими.",
|
||||
"settings.display.codeEdit.expanded": "Розгорнуті",
|
||||
"settings.display.codeEdit.collapsed": "Згорнуті",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "Показувати пропускну здатність токенів",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"Показує швидкість генерації тексту (токенів/с) на останньому повідомленні асистента та в заголовку завдання. За замовчуванням приховано, щоб чат залишався охайним.",
|
||||
"settings.display.autoApprovalReason.title": "Показувати причину автосхвалення",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"Показує рядок біля викликів інструментів, що пояснює, чому їх автоматично схвалено (відповідне правило, стандартне значення агента, режим YOLO тощо).",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+10
-7
@@ -859,7 +859,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "允许的网络目标",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"用于沙盒化 HTTP 和 HTTPS 代理流量的 DNS 主机和端口目标。GitHub CLI 和 HTTPS Git 通常需要 github.com:443 和 api.github.com:443。更改将应用于新会话。",
|
||||
"用于沙盒化 HTTP 和 HTTPS 代理流量的 DNS 主机和端口目标。GitHub CLI 和 HTTPS Git 通常需要 github.com:443 和 api.github.com:443。",
|
||||
"settings.sandboxing.writablePaths.title": "额外可写路径",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"沙盒允许写入的额外文件系统路径(例如 /tmp、/var/log)。沙盒启用后,这些路径会与默认可写路径合并。",
|
||||
@@ -1073,18 +1073,21 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "使用 Shift+Tab 切换推理强度",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"在提示输入框中按 Shift+Tab 可切换到下一个推理强度等级。禁用此选项可将 Shift+Tab 用于键盘焦点导航。",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "终端命令块",
|
||||
"settings.display.terminalCommand.description": "选择终端命令块的初始状态:展开或折叠。",
|
||||
"settings.display.terminalCommand.expanded": "展开",
|
||||
"settings.display.terminalCommand.collapsed": "折叠",
|
||||
"settings.display.codeEdit.title": "代码编辑块",
|
||||
"settings.display.codeEdit.description": "选择代码编辑块和差异块的初始状态:展开或折叠。",
|
||||
"settings.display.codeEdit.expanded": "展开",
|
||||
"settings.display.codeEdit.collapsed": "折叠",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "显示令牌吞吐量",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"在最新的助手消息和任务标题中显示文本生成速率(令牌/秒)。默认隐藏以保持聊天简洁。",
|
||||
"settings.display.autoApprovalReason.title": "显示自动批准原因",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"在工具调用中显示一行说明其被自动批准的原因(匹配的规则、代理默认值、YOLO 模式等)。",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
+10
-7
@@ -819,7 +819,7 @@ export const dict = {
|
||||
|
||||
"settings.sandboxing.allowedHosts.title": "允許的網路目的地",
|
||||
"settings.sandboxing.allowedHosts.description":
|
||||
"適用於沙盒 HTTP 和 HTTPS Proxy 流量的 DNS 主機與連接埠目標。GitHub CLI 和 HTTPS Git 通常需要 github.com:443 和 api.github.com:443。變更將套用至新工作階段。",
|
||||
"適用於沙盒 HTTP 和 HTTPS Proxy 流量的 DNS 主機與連接埠目標。GitHub CLI 和 HTTPS Git 通常需要 github.com:443 和 api.github.com:443。",
|
||||
"settings.sandboxing.writablePaths.title": "額外可寫路徑",
|
||||
"settings.sandboxing.writablePaths.description":
|
||||
"沙盒允許寫入的額外檔案系統路徑(例如 /tmp、/var/log)。沙盒啟用後,這些路徑會與預設可寫路徑合併。",
|
||||
@@ -1036,18 +1036,21 @@ export const dict = {
|
||||
"settings.display.shiftTabCycle.title": "使用 Shift+Tab 切換推理強度",
|
||||
"settings.display.shiftTabCycle.description":
|
||||
"在提示輸入框中按 Shift+Tab 可切換至下一個推理強度等級。停用此選項可保留 Shift+Tab 用於鍵盤焦點導覽。",
|
||||
"settings.display.terminalCommand.title": "Terminal Command Blocks",
|
||||
"settings.display.terminalCommand.description": "Choose whether terminal command blocks start expanded or collapsed.",
|
||||
"settings.display.terminalCommand.expanded": "Expanded",
|
||||
"settings.display.terminalCommand.collapsed": "Collapsed",
|
||||
"settings.display.terminalCommand.title": "終端命令區塊",
|
||||
"settings.display.terminalCommand.description": "選擇終端命令區塊的初始狀態:展開或收合。",
|
||||
"settings.display.terminalCommand.expanded": "展開",
|
||||
"settings.display.terminalCommand.collapsed": "收合",
|
||||
"settings.display.codeEdit.title": "程式碼編輯區塊",
|
||||
"settings.display.codeEdit.description": "選擇程式碼編輯區塊與差異區塊的初始狀態:展開或收合。",
|
||||
"settings.display.codeEdit.expanded": "展開",
|
||||
"settings.display.codeEdit.collapsed": "收合",
|
||||
|
||||
"settings.display.tokenThroughput.title": "Show Token Throughput",
|
||||
"settings.display.tokenThroughput.title": "顯示權杖吞吐量",
|
||||
"settings.display.tokenThroughput.description":
|
||||
"Display the text-generation rate (tokens/sec) on the latest assistant message and in the task header. Hidden by default to keep the chat uncluttered.",
|
||||
"在最新的助理訊息與工作標題中顯示文字生成速率(權杖/秒)。預設隱藏,以保持對話簡潔。",
|
||||
"settings.display.autoApprovalReason.title": "顯示自動核准原因",
|
||||
"settings.display.autoApprovalReason.description":
|
||||
"在工具呼叫中顯示一行說明其被自動核准的原因(符合的規則、代理預設值、YOLO 模式等)。",
|
||||
|
||||
"chat.throughput.tooltip":
|
||||
"Average {{speed}} tokens/s for this turn. Includes output and reasoning tokens; excludes tool execution and waiting time.",
|
||||
|
||||
@@ -35,6 +35,9 @@
|
||||
--top-bar-border: 1px solid var(--vscode-sideBar-border, var(--vscode-widget-border));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* Matches the native view/title toolbar, which clusters its icons at the
|
||||
right edge of the row rather than the left. */
|
||||
justify-content: flex-end;
|
||||
gap: 2px;
|
||||
flex-shrink: 0;
|
||||
padding: 4px 6px;
|
||||
@@ -48,11 +51,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-top-bar-spacer {
|
||||
flex: 1;
|
||||
min-width: 4px;
|
||||
}
|
||||
|
||||
.chat-messages-wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
@@ -145,10 +143,14 @@
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
padding-inline: 12px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
|
||||
@@ -210,6 +210,7 @@
|
||||
font-family: inherit;
|
||||
font-size: var(--kilo-font-size-12);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 0.15s,
|
||||
|
||||
@@ -11,6 +11,9 @@ export interface SkillInfo {
|
||||
export interface SlashCommandInfo {
|
||||
name: string
|
||||
description?: string
|
||||
agent?: string
|
||||
model?: string
|
||||
variant?: string
|
||||
source?: "command" | "mcp" | "skill"
|
||||
hints: string[]
|
||||
}
|
||||
|
||||
@@ -660,6 +660,11 @@ export interface ThroughputSettingLoadedMessage {
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
export interface AutoApprovalReasonSettingLoadedMessage {
|
||||
type: "autoApprovalReasonSettingLoaded"
|
||||
visible: boolean
|
||||
}
|
||||
|
||||
export interface WorkStyleLoadedMessage {
|
||||
type: "workStyleLoaded"
|
||||
style: WorkStyleState
|
||||
@@ -1359,6 +1364,7 @@ export type ExtensionMessage =
|
||||
| NotificationSettingsLoadedMessage
|
||||
| TimelineSettingLoadedMessage
|
||||
| ThroughputSettingLoadedMessage
|
||||
| AutoApprovalReasonSettingLoadedMessage
|
||||
| WorkStyleLoadedMessage
|
||||
| WorkStyleAppliedMessage
|
||||
| WorkStyleApplyFailedMessage
|
||||
|
||||
@@ -459,6 +459,10 @@ export interface RequestThroughputSettingMessage {
|
||||
type: "requestThroughputSetting"
|
||||
}
|
||||
|
||||
export interface RequestAutoApprovalReasonSettingMessage {
|
||||
type: "requestAutoApprovalReasonSetting"
|
||||
}
|
||||
|
||||
export interface RequestWorkStyleMessage {
|
||||
type: "requestWorkStyle"
|
||||
}
|
||||
@@ -809,6 +813,8 @@ export interface AgentManagerTerminalCreateRequest {
|
||||
createId: string
|
||||
placement: TerminalPlacement
|
||||
worktreeId: string | null
|
||||
cols?: number
|
||||
rows?: number
|
||||
}
|
||||
|
||||
// Close a terminal tab
|
||||
@@ -1444,6 +1450,7 @@ export type WebviewMessage =
|
||||
| UpdateSettingRequest
|
||||
| RequestTimelineSettingMessage
|
||||
| RequestThroughputSettingMessage
|
||||
| RequestAutoApprovalReasonSettingMessage
|
||||
| RequestWorkStyleMessage
|
||||
| SetWorkStyleMessage
|
||||
| ApplyWorkStyleMessage
|
||||
|
||||
@@ -51,9 +51,46 @@ async function copyTreeSitterWasms(outputDir: string) {
|
||||
console.log(`copied ${languageWasmFiles.length + 1} tree-sitter wasm files to ${targetDir}`)
|
||||
}
|
||||
|
||||
// kilocode_change start
|
||||
async function isKiloConsoleUpToDate(app: string, out: string) {
|
||||
const indexHtml = path.join(out, "index.html")
|
||||
if (!fs.existsSync(indexHtml)) return false
|
||||
const outStat = await fs.promises.stat(indexHtml)
|
||||
const inputs = [
|
||||
path.join(app, "src"),
|
||||
path.join(app, "package.json"),
|
||||
path.join(app, "vite.config.ts"),
|
||||
path.join(app, "index.html"),
|
||||
path.resolve(dir, "../kilo-web-ui/src"),
|
||||
path.resolve(dir, "../kilo-indexing/src"),
|
||||
path.resolve(dir, "../kilo-ui/src"),
|
||||
path.resolve(dir, "../ui/src"),
|
||||
path.resolve(dir, "../sdk/js/src"),
|
||||
path.resolve(dir, "../../bun.lock"),
|
||||
]
|
||||
for (const p of inputs) {
|
||||
if (!fs.existsSync(p)) continue
|
||||
const st = await fs.promises.stat(p)
|
||||
if (st.isDirectory()) {
|
||||
const glob = new Bun.Glob("**/*")
|
||||
for await (const file of glob.scan({ cwd: p })) {
|
||||
const fileStat = await fs.promises.stat(path.join(p, file))
|
||||
if (fileStat.mtimeMs > outStat.mtimeMs) return false
|
||||
}
|
||||
} else if (st.mtimeMs > outStat.mtimeMs) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function buildKiloConsole() {
|
||||
const app = path.resolve(dir, "../kilo-console")
|
||||
const out = path.join(app, "dist")
|
||||
if (await isKiloConsoleUpToDate(app, out)) {
|
||||
console.log(`reusing existing Kilo Console build at ${out}`)
|
||||
return out
|
||||
}
|
||||
console.log("building Kilo Console")
|
||||
const proc = Bun.spawn([process.execPath, "run", "build"], {
|
||||
cwd: app,
|
||||
@@ -66,6 +103,7 @@ async function buildKiloConsole() {
|
||||
if (code !== 0) throw new Error(`Kilo Console build failed with exit code ${code}`)
|
||||
return out
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
async function copyKiloConsole(input: string, outputDir: string) {
|
||||
const target = path.join(outputDir, "console")
|
||||
@@ -217,11 +255,13 @@ const targets = singleFlag
|
||||
})
|
||||
: allTargets
|
||||
|
||||
await $`rm -rf dist`
|
||||
// kilocode_change start
|
||||
const kiloConsoleDist = await buildKiloConsole()
|
||||
const kiloSandboxWorker = await KiloSandboxWorker.bundle()
|
||||
const kiloSandboxNetwork = await KiloSandboxNetwork.bundle()
|
||||
await $`rm -rf dist`
|
||||
const [kiloConsoleDist, kiloSandboxWorker, kiloSandboxNetwork] = await Promise.all([
|
||||
buildKiloConsole(),
|
||||
KiloSandboxWorker.bundle(),
|
||||
KiloSandboxNetwork.bundle(),
|
||||
])
|
||||
// kilocode_change end
|
||||
|
||||
const binaries: Record<string, string> = {}
|
||||
|
||||
@@ -10,9 +10,41 @@ process.chdir(dir)
|
||||
|
||||
const modelsUrl = process.env.KILO_MODELS_URL || "https://models.dev"
|
||||
// kilocode_change start
|
||||
const raw = process.env.MODELS_DEV_API_JSON
|
||||
? await Bun.file(process.env.MODELS_DEV_API_JSON).text()
|
||||
: await fetch(`${modelsUrl}/api.json`).then((x) => x.text())
|
||||
const cacheFile = path.resolve(dir, "node_modules/.cache/models-dev-api.json")
|
||||
const raw = await (async () => {
|
||||
if (process.env.MODELS_DEV_API_JSON) {
|
||||
return await Bun.file(process.env.MODELS_DEV_API_JSON).text()
|
||||
}
|
||||
const cached = Bun.file(cacheFile)
|
||||
try {
|
||||
if (await cached.exists()) {
|
||||
const st = await cached.stat()
|
||||
if (st && Date.now() - st.mtimeMs < 6 * 3600 * 1000) {
|
||||
return await cached.text()
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("[generate] cache read check failed, fetching live", err)
|
||||
}
|
||||
try {
|
||||
const res = await fetch(`${modelsUrl}/api.json`, { signal: AbortSignal.timeout(5000) })
|
||||
if (!res.ok) throw new Error(`Failed to fetch models.dev snapshot: HTTP ${res.status}`)
|
||||
const text = await res.text()
|
||||
try {
|
||||
await Bun.write(cacheFile, text)
|
||||
} catch (err) {
|
||||
console.warn("[generate] cache write failed", err)
|
||||
}
|
||||
return text
|
||||
} catch (err) {
|
||||
try {
|
||||
if (await cached.exists()) return await cached.text()
|
||||
} catch (fallbackErr) {
|
||||
console.warn("[generate] cache fallback read failed", fallbackErr)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
})()
|
||||
export const modelsData = JSON.stringify(parseModelsSnapshot(raw).data)
|
||||
// kilocode_change end
|
||||
console.log("Loaded models.dev snapshot")
|
||||
|
||||
@@ -1,16 +1,77 @@
|
||||
// kilocode_change - new file
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import fsSync from "fs"
|
||||
import crypto from "crypto"
|
||||
|
||||
export namespace TestCli {
|
||||
export const ENV = "KILO_TEST_CLI_PATH"
|
||||
|
||||
export async function build(root: string, dir: string) {
|
||||
async function fingerprint(root: string): Promise<string> {
|
||||
const hash = crypto.createHash("sha256")
|
||||
const repo = path.resolve(root, "../..")
|
||||
const pkgs = path.join(repo, "packages")
|
||||
|
||||
const lock = path.join(repo, "bun.lock")
|
||||
if (fsSync.existsSync(lock)) {
|
||||
const st = fsSync.statSync(lock)
|
||||
hash.update("bun.lock").update(String(st.mtimeMs)).update(String(st.size))
|
||||
}
|
||||
|
||||
const ignored = new Set(["kilo-vscode", "kilo-jetbrains", "kilo-docs"])
|
||||
const entries = fsSync.readdirSync(pkgs, { withFileTypes: true })
|
||||
|
||||
for (const ent of entries) {
|
||||
if (!ent.isDirectory() || ignored.has(ent.name)) continue
|
||||
const dir = path.join(pkgs, ent.name)
|
||||
const targets =
|
||||
ent.name === "sdk"
|
||||
? [path.join(dir, "js", "src"), path.join(dir, "js", "package.json")]
|
||||
: [path.join(dir, "src"), path.join(dir, "migration"), path.join(dir, "package.json")]
|
||||
|
||||
for (const target of targets) {
|
||||
if (!fsSync.existsSync(target)) continue
|
||||
const st = fsSync.statSync(target)
|
||||
if (st.isDirectory()) {
|
||||
const glob = new Bun.Glob("**/*.{ts,tsx,sql,json,txt}")
|
||||
for await (const file of glob.scan({ cwd: target })) {
|
||||
const p = path.join(target, file)
|
||||
const fst = fsSync.statSync(p)
|
||||
hash.update(`${ent.name}:${file}`).update(String(fst.mtimeMs)).update(String(fst.size))
|
||||
}
|
||||
} else {
|
||||
hash.update(`${ent.name}:pkg`).update(String(st.mtimeMs)).update(String(st.size))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hash.digest("hex")
|
||||
}
|
||||
|
||||
export async function build(root: string, targetDir?: string) {
|
||||
if (path.resolve(process.cwd()) !== path.resolve(root)) {
|
||||
throw new Error(`CLI test bundle must be built from ${root}`)
|
||||
}
|
||||
|
||||
const dir = targetDir ?? path.join(root, ".artifacts", "test-cli-cached")
|
||||
const out = path.join(dir, "src/storage")
|
||||
const bin = path.join(out, "cli.js")
|
||||
const hashFile = path.join(dir, ".hash")
|
||||
|
||||
if (!targetDir) {
|
||||
const currentHash = await fingerprint(root)
|
||||
try {
|
||||
if (fsSync.existsSync(bin) && fsSync.existsSync(hashFile)) {
|
||||
const storedHash = fsSync.readFileSync(hashFile, "utf8").trim()
|
||||
if (storedHash === currentHash) {
|
||||
return bin
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const { createSolidTransformPlugin } = await import("@opentui/solid/bun-plugin")
|
||||
const entry = "./src/index.ts"
|
||||
const out = path.join(dir, "src/storage")
|
||||
const result = await Bun.build({
|
||||
entrypoints: [entry],
|
||||
outdir: out,
|
||||
@@ -41,8 +102,26 @@ export namespace TestCli {
|
||||
return
|
||||
}
|
||||
})()
|
||||
if (target) await fs.symlink(target, path.join(scope, name.replace("@opentui/", "")), kind)
|
||||
if (target) {
|
||||
const link = path.join(scope, name.replace("@opentui/", ""))
|
||||
try {
|
||||
await fs.rm(link, { recursive: true, force: true })
|
||||
await fs.symlink(target, link, kind)
|
||||
} catch (err) {
|
||||
console.warn(`[test-cli] failed to link native variant ${name}:`, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return path.join(out, "cli.js")
|
||||
|
||||
if (!targetDir) {
|
||||
try {
|
||||
const currentHash = await fingerprint(root)
|
||||
await fs.writeFile(hashFile, currentHash)
|
||||
} catch (err) {
|
||||
console.warn("[test-cli] failed to save fingerprint hash:", err)
|
||||
}
|
||||
}
|
||||
|
||||
return bin
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,25 +187,17 @@ type Proc = ReturnType<typeof Bun.spawn>
|
||||
|
||||
const xmldir = ci ? path.join(os.tmpdir(), `opencode-junit-${process.pid}`) : ""
|
||||
if (ci) await fs.mkdir(xmldir, { recursive: true })
|
||||
// kilocode_change start
|
||||
const supplied = process.env[TestCli.ENV]
|
||||
const binprefix = path.join(root, ".artifacts", "test-cli-")
|
||||
const built = supplied
|
||||
? { binary: supplied, dir: undefined }
|
||||
: await (async () => {
|
||||
await fs.mkdir(path.dirname(binprefix), { recursive: true })
|
||||
const dir = await fs.mkdtemp(binprefix)
|
||||
return { binary: await TestCli.build(root, dir), dir }
|
||||
})()
|
||||
: { binary: await TestCli.build(root), dir: undefined }
|
||||
|
||||
async function cleanBinary() {
|
||||
if (!built.dir) return
|
||||
const expected = path.dirname(binprefix)
|
||||
const valid =
|
||||
path.dirname(built.dir) === expected && path.basename(built.dir).startsWith(path.basename(binprefix))
|
||||
if (!valid) throw new Error(`Refusing to remove unexpected test CLI directory: ${built.dir}`)
|
||||
// The generated directory contains the bundle, emitted assets, and copied migrations.
|
||||
await fs.rm(built.dir, { recursive: true, force: true })
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
const counter = { done: 0 }
|
||||
const pad = String(files.length).length
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user