mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-01 15:32:11 +08:00
Merge branch 'main' into feat/config-permission-protection
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
},
|
||||
"packages/app": {
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"dependencies": {
|
||||
"@kilocode/kilo-i18n": "workspace:*",
|
||||
"@kilocode/kilo-ui": "workspace:*",
|
||||
@@ -79,7 +79,7 @@
|
||||
},
|
||||
"packages/desktop": {
|
||||
"name": "@opencode-ai/desktop",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -112,7 +112,7 @@
|
||||
},
|
||||
"packages/desktop-electron": {
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"dependencies": {
|
||||
"@opencode-ai/app": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
@@ -142,7 +142,7 @@
|
||||
},
|
||||
"packages/kilo-docs": {
|
||||
"name": "@kilocode/kilo-docs",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"dependencies": {
|
||||
"@docsearch/css": "^4",
|
||||
"@docsearch/js": "^4",
|
||||
@@ -171,7 +171,7 @@
|
||||
},
|
||||
"packages/kilo-gateway": {
|
||||
"name": "@kilocode/kilo-gateway",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "2.0.65",
|
||||
"@ai-sdk/openai": "2.0.101",
|
||||
@@ -206,7 +206,7 @@
|
||||
},
|
||||
"packages/kilo-i18n": {
|
||||
"name": "@kilocode/kilo-i18n",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"devDependencies": {
|
||||
"@tsconfig/node22": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
@@ -216,7 +216,7 @@
|
||||
},
|
||||
"packages/kilo-telemetry": {
|
||||
"name": "@kilocode/kilo-telemetry",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"dependencies": {
|
||||
"@kilocode/kilo-gateway": "workspace:*",
|
||||
"@opentelemetry/api": "1.9.0",
|
||||
@@ -236,7 +236,7 @@
|
||||
},
|
||||
"packages/kilo-ui": {
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"dependencies": {
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@opencode-ai/util": "workspace:*",
|
||||
@@ -271,7 +271,7 @@
|
||||
},
|
||||
"packages/kilo-vscode": {
|
||||
"name": "kilo-code",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/sdk": "^0.39.0",
|
||||
"@kilocode/kilo-i18n": "workspace:*",
|
||||
@@ -324,7 +324,7 @@
|
||||
},
|
||||
"packages/opencode": {
|
||||
"name": "@kilocode/cli",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"bin": {
|
||||
"kilo": "./bin/kilo",
|
||||
"kilocode": "./bin/kilo",
|
||||
@@ -446,7 +446,7 @@
|
||||
},
|
||||
"packages/plugin": {
|
||||
"name": "@kilocode/plugin",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"zod": "catalog:",
|
||||
@@ -460,14 +460,14 @@
|
||||
},
|
||||
"packages/script": {
|
||||
"name": "@opencode-ai/script",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"devDependencies": {
|
||||
"@types/bun": "catalog:",
|
||||
},
|
||||
},
|
||||
"packages/sdk/js": {
|
||||
"name": "@kilocode/sdk",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"devDependencies": {
|
||||
"@hey-api/openapi-ts": "0.90.10",
|
||||
"@tsconfig/node22": "catalog:",
|
||||
@@ -478,7 +478,7 @@
|
||||
},
|
||||
"packages/storybook": {
|
||||
"name": "@opencode-ai/storybook",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"devDependencies": {
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@solidjs/meta": "catalog:",
|
||||
@@ -501,7 +501,7 @@
|
||||
},
|
||||
"packages/ui": {
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "workspace:*",
|
||||
"@kobalte/core": "catalog:",
|
||||
@@ -548,7 +548,7 @@
|
||||
},
|
||||
"packages/util": {
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"dependencies": {
|
||||
"zod": "catalog:",
|
||||
},
|
||||
|
||||
+1
-1
@@ -112,6 +112,6 @@
|
||||
"@openrouter/ai-sdk-provider@1.5.4": "patches/@openrouter%2Fai-sdk-provider@1.5.4.patch",
|
||||
"ghostty-web@0.3.0": "patches/ghostty-web@0.3.0.patch"
|
||||
},
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/app",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop-electron",
|
||||
"private": true,
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"homepage": "https://opencode.ai",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@opencode-ai/desktop",
|
||||
"private": true,
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
id = "kilo"
|
||||
name = "Kilo"
|
||||
description = "The open source coding agent."
|
||||
version = "7.1.5"
|
||||
version = "7.1.8"
|
||||
schema_version = 1
|
||||
authors = ["Anomaly"]
|
||||
repository = "https://github.com/Kilo-Org/kilocode"
|
||||
@@ -11,26 +11,26 @@ name = "Kilo"
|
||||
icon = "./icons/opencode.svg"
|
||||
|
||||
[agent_servers.opencode.targets.darwin-aarch64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.5/opencode-darwin-arm64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.8/opencode-darwin-arm64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.darwin-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.5/opencode-darwin-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.8/opencode-darwin-x64.zip"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-aarch64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.5/opencode-linux-arm64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.8/opencode-linux-arm64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.linux-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.5/opencode-linux-x64.tar.gz"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.8/opencode-linux-x64.tar.gz"
|
||||
cmd = "./opencode"
|
||||
args = ["acp"]
|
||||
|
||||
[agent_servers.opencode.targets.windows-x86_64]
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.5/opencode-windows-x64.zip"
|
||||
archive = "https://github.com/Kilo-Org/kilocode/releases/download/v7.1.8/opencode-windows-x64.zip"
|
||||
cmd = "./opencode.exe"
|
||||
args = ["acp"]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-docs",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --webpack --port 3002",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-gateway",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Unified Kilo Gateway package for OpenCode - authentication, provider, and API integration",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-i18n",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Kilo-specific i18n translations and overrides",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/kilo-telemetry",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"description": "Telemetry for Kilo CLI - PostHog analytics integration",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/kilo-ui",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -1306,6 +1306,15 @@ function useToolReveal(pending: () => boolean, animate?: () => boolean) {
|
||||
function WebfetchMeta(props: { url: string; animate?: boolean }) {
|
||||
let ref: HTMLSpanElement | undefined
|
||||
useToolFade(() => ref, { wipe: true, animate: props.animate })
|
||||
const data = useData()
|
||||
|
||||
const open = (event: MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
const handler = data.openUrl
|
||||
if (handler) return handler(props.url)
|
||||
window.open(props.url, "_blank", "noopener,noreferrer")
|
||||
}
|
||||
|
||||
return (
|
||||
<span ref={ref} data-slot="webfetch-meta">
|
||||
@@ -1316,11 +1325,11 @@ function WebfetchMeta(props: { url: string; animate?: boolean }) {
|
||||
href={props.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
onClick={open}
|
||||
>
|
||||
{props.url}
|
||||
</a>
|
||||
<div data-component="tool-action">
|
||||
<div data-component="tool-action" onClick={open} style={{ cursor: "pointer" }}>
|
||||
<Icon name="square-arrow-top-right" size="small" />
|
||||
</div>
|
||||
</span>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Kilo Code
|
||||
Copyright (c) 2025 opencode
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "kilo-code",
|
||||
"displayName": "Kilo Code: AI Coding Agent, Copilot, and Autocomplete",
|
||||
"description": "Open Source AI coding agent that generates code from natural language, automates tasks, and runs terminal commands. Features inline autocomplete, browser automation, automated refactoring, and custom modes for planning, coding, and debugging. Supports 500+ AI models including Claude (Anthropic), Gemini, Grok, GPT, Codex and GLM.",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"icon": "assets/icons/logo-outline-black.png",
|
||||
"galleryBanner": {
|
||||
"color": "#FFFFFF",
|
||||
@@ -17,6 +17,7 @@
|
||||
"engines": {
|
||||
"vscode": "^1.105.1"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Kilo Code"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
import { describe, it, expect } from "bun:test"
|
||||
import { deepMerge, stripNulls, ConfigState } from "../../webview-ui/src/utils/config-utils"
|
||||
import type { Config } from "../../webview-ui/src/types/messages"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pure helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("deepMerge", () => {
|
||||
it("overrides scalar values", () => {
|
||||
const target: Config = { snapshot: true }
|
||||
const source: Partial<Config> = { snapshot: false }
|
||||
expect(deepMerge(target, source)).toEqual({ snapshot: false })
|
||||
})
|
||||
|
||||
it("merges nested objects recursively", () => {
|
||||
const target: Config = { agent: { code: { temperature: 0.5 } } }
|
||||
const source: Partial<Config> = { agent: { code: { steps: 10 } } }
|
||||
const result = deepMerge(target, source)
|
||||
expect(result.agent?.code?.temperature).toBe(0.5)
|
||||
expect(result.agent?.code?.steps).toBe(10)
|
||||
})
|
||||
|
||||
it("preserves keys not present in source", () => {
|
||||
const target: Config = { snapshot: true, username: "alice" }
|
||||
const source: Partial<Config> = { snapshot: false }
|
||||
expect(deepMerge(target, source)).toEqual({ snapshot: false, username: "alice" })
|
||||
})
|
||||
|
||||
it("replaces arrays instead of merging them", () => {
|
||||
const target: Config = { instructions: ["a", "b"] }
|
||||
const source: Partial<Config> = { instructions: ["c"] }
|
||||
expect(deepMerge(target, source)).toEqual({ instructions: ["c"] })
|
||||
})
|
||||
})
|
||||
|
||||
describe("stripNulls", () => {
|
||||
it("removes null values", () => {
|
||||
const cfg = { snapshot: true, username: null } as unknown as Config
|
||||
expect(stripNulls(cfg)).toEqual({ snapshot: true })
|
||||
})
|
||||
|
||||
it("removes undefined values", () => {
|
||||
const cfg = { snapshot: true, username: undefined } as unknown as Config
|
||||
expect(stripNulls(cfg)).toEqual({ snapshot: true })
|
||||
})
|
||||
|
||||
it("strips nulls recursively in nested objects", () => {
|
||||
const cfg = { agent: { code: { temperature: 0.5, prompt: null } } } as unknown as Config
|
||||
expect(stripNulls(cfg)).toEqual({ agent: { code: { temperature: 0.5 } } })
|
||||
})
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Config state machine — reproduces the actual message-handler flow
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe("ConfigState", () => {
|
||||
it("configLoaded sets config when no draft is pending", () => {
|
||||
const s = new ConfigState()
|
||||
s.handleConfigLoaded({ snapshot: true, username: "alice" })
|
||||
expect(s.config).toEqual({ snapshot: true, username: "alice" })
|
||||
expect(s.loading).toBe(false)
|
||||
})
|
||||
|
||||
describe("configLoaded while draft is pending (the reported bug)", () => {
|
||||
it("preserves the user's pending toggle change", () => {
|
||||
const s = new ConfigState()
|
||||
|
||||
// 1. Server sends initial config
|
||||
s.handleConfigLoaded({ snapshot: true, username: "alice" })
|
||||
expect(s.config.snapshot).toBe(true)
|
||||
|
||||
// 2. User toggles snapshot off (but hasn't saved yet)
|
||||
s.updateConfig({ snapshot: false })
|
||||
expect(s.config.snapshot).toBe(false)
|
||||
expect(s.dirty).toBe(true)
|
||||
|
||||
// 3. A configLoaded push arrives from the extension (e.g. SSE event,
|
||||
// tab switch, or another webview triggers a config reload).
|
||||
// The server still has snapshot: true.
|
||||
s.handleConfigLoaded({ snapshot: true, username: "alice" })
|
||||
|
||||
// BUG (old code): config.snapshot would be reset to true here
|
||||
// FIX: the draft is re-applied, so the user's toggle stays false
|
||||
expect(s.config.snapshot).toBe(false)
|
||||
expect(s.config.username).toBe("alice")
|
||||
expect(s.dirty).toBe(true)
|
||||
})
|
||||
|
||||
it("preserves nested draft changes across configLoaded pushes", () => {
|
||||
const s = new ConfigState()
|
||||
s.handleConfigLoaded({ agent: { code: { temperature: 0.7 } } })
|
||||
s.updateConfig({ agent: { code: { steps: 5 } } })
|
||||
|
||||
// Server pushes a reload — temperature may have changed server-side
|
||||
s.handleConfigLoaded({ agent: { code: { temperature: 0.9 } } })
|
||||
|
||||
expect(s.config.agent?.code?.steps).toBe(5)
|
||||
expect(s.config.agent?.code?.temperature).toBe(0.9)
|
||||
})
|
||||
})
|
||||
|
||||
describe("configUpdated while draft is pending", () => {
|
||||
it("preserves draft when update comes from another source", () => {
|
||||
const s = new ConfigState()
|
||||
s.handleConfigLoaded({ snapshot: true, username: "alice" })
|
||||
s.updateConfig({ snapshot: false })
|
||||
|
||||
// Another webview (e.g. PermissionDock) saves a different setting
|
||||
s.handleConfigUpdated({ snapshot: true, username: "bob" })
|
||||
|
||||
expect(s.config.snapshot).toBe(false) // draft preserved
|
||||
expect(s.config.username).toBe("bob") // server update applied
|
||||
})
|
||||
|
||||
it("clears draft when update confirms our save", () => {
|
||||
const s = new ConfigState()
|
||||
s.handleConfigLoaded({ snapshot: true })
|
||||
s.updateConfig({ snapshot: false })
|
||||
s.saveConfig()
|
||||
expect(s.saving).toBe(true)
|
||||
|
||||
// Server confirms the write
|
||||
s.handleConfigUpdated({ snapshot: false })
|
||||
|
||||
expect(s.config.snapshot).toBe(false)
|
||||
expect(s.dirty).toBe(false)
|
||||
expect(s.saving).toBe(false)
|
||||
expect(Object.keys(s.draft).length).toBe(0)
|
||||
})
|
||||
})
|
||||
|
||||
it("configLoaded is ignored while save is in-flight", () => {
|
||||
const s = new ConfigState()
|
||||
s.handleConfigLoaded({ snapshot: true })
|
||||
s.updateConfig({ snapshot: false })
|
||||
s.saveConfig()
|
||||
|
||||
// A stale configLoaded arrives during the write round-trip
|
||||
s.handleConfigLoaded({ snapshot: true })
|
||||
|
||||
// Config must not revert — the save is still in flight
|
||||
expect(s.config.snapshot).toBe(false)
|
||||
})
|
||||
|
||||
it("discardConfig restores server state", () => {
|
||||
const s = new ConfigState()
|
||||
s.handleConfigLoaded({ snapshot: true, username: "alice" })
|
||||
s.updateConfig({ snapshot: false })
|
||||
expect(s.dirty).toBe(true)
|
||||
|
||||
s.discardConfig()
|
||||
|
||||
expect(s.config.snapshot).toBe(true)
|
||||
expect(s.dirty).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -109,6 +109,10 @@ export const DataBridge: Component<{ children: any }> = (props) => {
|
||||
vscode.postMessage({ type: "openFile", filePath, line, column })
|
||||
}
|
||||
|
||||
const openUrl = (url: string) => {
|
||||
vscode.postMessage({ type: "openExternal", url })
|
||||
}
|
||||
|
||||
const directory = () => {
|
||||
const dir = server.workspaceDirectory()
|
||||
if (!dir) return ""
|
||||
@@ -124,6 +128,7 @@ export const DataBridge: Component<{ children: any }> = (props) => {
|
||||
onQuestionReply={reply}
|
||||
onQuestionReject={reject}
|
||||
onOpenFile={open}
|
||||
onOpenUrl={openUrl}
|
||||
>
|
||||
{props.children}
|
||||
</DataProvider>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useLanguage } from "../../context/language"
|
||||
import type { AgentInfo, SkillInfo } from "../../types/messages"
|
||||
import ModeEditView from "./ModeEditView"
|
||||
import ModeCreateView from "./ModeCreateView"
|
||||
import McpEditView from "./McpEditView"
|
||||
import WorkflowsTab from "./agent-behaviour/WorkflowsTab"
|
||||
import { parseImport, MAX_IMPORT_SIZE } from "./mode-io"
|
||||
import type { ImportError } from "./mode-io"
|
||||
@@ -71,6 +72,9 @@ const AgentBehaviourTab: Component = () => {
|
||||
const [agentView, setAgentView] = createSignal<AgentView>("list")
|
||||
const [editingAgent, setEditingAgent] = createSignal<string>("")
|
||||
|
||||
// MCP view state
|
||||
const [editingMcp, setEditingMcp] = createSignal<string>("")
|
||||
|
||||
// Fetch skills whenever the skills subtab becomes active
|
||||
createEffect(() => {
|
||||
if (activeSubtab() === "skills") {
|
||||
@@ -489,6 +493,19 @@ const AgentBehaviourTab: Component = () => {
|
||||
setExpanded((prev) => ({ ...prev, [name]: !prev[name] }))
|
||||
}
|
||||
|
||||
if (editingMcp()) {
|
||||
return (
|
||||
<McpEditView
|
||||
name={editingMcp()}
|
||||
onBack={() => setEditingMcp("")}
|
||||
onRemove={(name) => {
|
||||
confirmRemoveMcp(name)
|
||||
setEditingMcp("")
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Show
|
||||
@@ -548,15 +565,26 @@ const AgentBehaviourTab: Component = () => {
|
||||
{mcp.url ? "remote" : "stdio"}
|
||||
</span>
|
||||
</div>
|
||||
<IconButton
|
||||
size="small"
|
||||
variant="ghost"
|
||||
icon="close"
|
||||
onClick={(e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
confirmRemoveMcp(name)
|
||||
}}
|
||||
/>
|
||||
<div style={{ display: "flex", gap: "4px" }}>
|
||||
<IconButton
|
||||
size="small"
|
||||
variant="ghost"
|
||||
icon="close"
|
||||
onClick={(e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
confirmRemoveMcp(name)
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
size="small"
|
||||
variant="ghost"
|
||||
icon="chevron-right"
|
||||
onClick={(e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
setEditingMcp(name)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Expandable detail */}
|
||||
@@ -916,11 +944,12 @@ const AgentBehaviourTab: Component = () => {
|
||||
<button
|
||||
onClick={() => {
|
||||
setActiveSubtab(subtab.id)
|
||||
// Reset agent view when switching subtabs
|
||||
// Reset views when switching subtabs
|
||||
if (subtab.id === "agents") {
|
||||
setAgentView("list")
|
||||
setEditingAgent("")
|
||||
}
|
||||
setEditingMcp("")
|
||||
}}
|
||||
style={{
|
||||
padding: "8px 16px",
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
import { Component, Show, createMemo, createSignal, For } from "solid-js"
|
||||
import { TextField } from "@kilocode/kilo-ui/text-field"
|
||||
import { Card } from "@kilocode/kilo-ui/card"
|
||||
import { Button } from "@kilocode/kilo-ui/button"
|
||||
import { IconButton } from "@kilocode/kilo-ui/icon-button"
|
||||
|
||||
import { useConfig } from "../../context/config"
|
||||
import { useLanguage } from "../../context/language"
|
||||
import type { McpConfig } from "../../types/messages"
|
||||
import SettingsRow from "./SettingsRow"
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
onBack: () => void
|
||||
onRemove: (name: string) => void
|
||||
}
|
||||
|
||||
const McpEditView: Component<Props> = (props) => {
|
||||
const language = useLanguage()
|
||||
const { config, updateConfig } = useConfig()
|
||||
|
||||
const cfg = createMemo<McpConfig>(() => config().mcp?.[props.name] ?? {})
|
||||
|
||||
const [envKey, setEnvKey] = createSignal("")
|
||||
const [envVal, setEnvVal] = createSignal("")
|
||||
|
||||
const update = (partial: Partial<McpConfig>) => {
|
||||
const existing = config().mcp ?? {}
|
||||
const current = existing[props.name] ?? {}
|
||||
updateConfig({
|
||||
mcp: { ...existing, [props.name]: { ...current, ...partial } },
|
||||
})
|
||||
}
|
||||
|
||||
const transport = () => cfg().type ?? (cfg().url ? "remote" : "local")
|
||||
|
||||
const cmd = () => {
|
||||
const c = cfg().command
|
||||
if (Array.isArray(c)) return c[0] ?? ""
|
||||
return c ?? ""
|
||||
}
|
||||
|
||||
const args = () => {
|
||||
const c = cfg().command
|
||||
if (Array.isArray(c)) return c.slice(1).join("\n")
|
||||
return ""
|
||||
}
|
||||
|
||||
const env = createMemo(() => Object.entries(cfg().environment ?? cfg().env ?? {}))
|
||||
|
||||
const addEnv = () => {
|
||||
const key = envKey().trim()
|
||||
const val = envVal().trim()
|
||||
if (!key) return
|
||||
const existing = cfg().environment ?? cfg().env ?? {}
|
||||
update({ environment: { ...existing, [key]: val } })
|
||||
setEnvKey("")
|
||||
setEnvVal("")
|
||||
}
|
||||
|
||||
const removeEnv = (key: string) => {
|
||||
const existing = { ...(cfg().environment ?? cfg().env ?? {}) }
|
||||
delete existing[key]
|
||||
update({ environment: existing })
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
"align-items": "center",
|
||||
"justify-content": "space-between",
|
||||
"margin-bottom": "16px",
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", "align-items": "center" }}>
|
||||
<IconButton size="small" variant="ghost" icon="arrow-left" onClick={props.onBack} />
|
||||
<span style={{ "font-weight": "600", "font-size": "14px", "margin-left": "8px" }}>
|
||||
{language.t("settings.agentBehaviour.editMcp")} — {props.name}
|
||||
</span>
|
||||
</div>
|
||||
<IconButton size="small" variant="ghost" icon="close" onClick={() => props.onRemove(props.name)} />
|
||||
</div>
|
||||
|
||||
{/* Transport info */}
|
||||
<Card style={{ "margin-bottom": "12px" }}>
|
||||
<div
|
||||
style={{
|
||||
"font-size": "12px",
|
||||
color: "var(--text-weak-base, var(--vscode-descriptionForeground))",
|
||||
padding: "4px 0",
|
||||
}}
|
||||
>
|
||||
{transport() === "local"
|
||||
? language.t("settings.agentBehaviour.editMcp.transportLocal")
|
||||
: language.t("settings.agentBehaviour.editMcp.transportRemote")}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Command / URL */}
|
||||
<Show when={transport() === "local"}>
|
||||
<Card style={{ "margin-bottom": "12px" }}>
|
||||
<div data-slot="settings-row-label-title" style={{ "margin-bottom": "8px" }}>
|
||||
{language.t("settings.agentBehaviour.addMcp.command")}
|
||||
</div>
|
||||
<TextField
|
||||
value={cmd()}
|
||||
placeholder={language.t("settings.agentBehaviour.addMcp.command.placeholder")}
|
||||
onChange={(val) => {
|
||||
const existing = cfg().command
|
||||
const rest = Array.isArray(existing) ? existing.slice(1) : []
|
||||
update({ command: [val.trim(), ...rest] })
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
<Card style={{ "margin-bottom": "12px" }}>
|
||||
<div data-slot="settings-row-label-title" style={{ "margin-bottom": "4px" }}>
|
||||
{language.t("settings.agentBehaviour.addMcp.args")}
|
||||
</div>
|
||||
<div data-slot="settings-row-label-subtitle" style={{ "margin-bottom": "8px" }}>
|
||||
{language.t("settings.agentBehaviour.addMcp.args.help")}
|
||||
</div>
|
||||
<TextField
|
||||
value={args()}
|
||||
placeholder={language.t("settings.agentBehaviour.addMcp.args.placeholder")}
|
||||
multiline
|
||||
onChange={(val) => {
|
||||
const parts = val.split(/\n/).filter(Boolean)
|
||||
update({ command: [cmd(), ...parts] })
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</Show>
|
||||
|
||||
<Show when={transport() === "remote"}>
|
||||
<Card style={{ "margin-bottom": "12px" }}>
|
||||
<div data-slot="settings-row-label-title" style={{ "margin-bottom": "8px" }}>
|
||||
{language.t("settings.agentBehaviour.addMcp.url")}
|
||||
</div>
|
||||
<TextField
|
||||
value={cfg().url ?? ""}
|
||||
placeholder={language.t("settings.agentBehaviour.addMcp.url.placeholder")}
|
||||
onChange={(val) => update({ url: val.trim() || undefined })}
|
||||
/>
|
||||
</Card>
|
||||
</Show>
|
||||
|
||||
{/* Environment variables (local servers only) */}
|
||||
<Show when={transport() === "local"}>
|
||||
<Card style={{ "margin-bottom": "12px" }}>
|
||||
<div data-slot="settings-row-label-title" style={{ "margin-bottom": "4px" }}>
|
||||
{language.t("settings.agentBehaviour.editMcp.env")}
|
||||
</div>
|
||||
<div data-slot="settings-row-label-subtitle" style={{ "margin-bottom": "8px" }}>
|
||||
{language.t("settings.agentBehaviour.editMcp.env.help")}
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "8px",
|
||||
"align-items": "center",
|
||||
padding: "8px 0",
|
||||
"border-bottom": env().length > 0 ? "1px solid var(--border-weak-base)" : "none",
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: 1 }}>
|
||||
<TextField value={envKey()} placeholder="KEY" onChange={(val) => setEnvKey(val)} />
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<TextField
|
||||
value={envVal()}
|
||||
placeholder="value"
|
||||
onChange={(val) => setEnvVal(val)}
|
||||
onKeyDown={(e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") addEnv()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Button variant="secondary" onClick={addEnv}>
|
||||
{language.t("common.add")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<For each={env()}>
|
||||
{([key, val], index) => (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
"align-items": "center",
|
||||
"justify-content": "space-between",
|
||||
padding: "6px 0",
|
||||
"border-bottom": index() < env().length - 1 ? "1px solid var(--border-weak-base)" : "none",
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
"font-family": "var(--vscode-editor-font-family, monospace)",
|
||||
"font-size": "12px",
|
||||
}}
|
||||
>
|
||||
{key}={val}
|
||||
</span>
|
||||
<IconButton size="small" variant="ghost" icon="close" onClick={() => removeEnv(key)} />
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
</Card>
|
||||
</Show>
|
||||
|
||||
<div style={{ display: "flex", "justify-content": "flex-end" }}>
|
||||
<Button variant="ghost" onClick={props.onBack}>
|
||||
{language.t("settings.agentBehaviour.editMode.back")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default McpEditView
|
||||
@@ -11,6 +11,7 @@
|
||||
import { createContext, useContext, createSignal, onCleanup, ParentComponent, Accessor } from "solid-js"
|
||||
import { useVSCode } from "./vscode"
|
||||
import type { Config, ExtensionMessage } from "../types/messages"
|
||||
import { deepMerge, stripNulls, resolveConfig } from "../utils/config-utils"
|
||||
|
||||
interface ConfigContextValue {
|
||||
config: Accessor<Config>
|
||||
@@ -21,37 +22,6 @@ interface ConfigContextValue {
|
||||
discardConfig: () => void
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return !!value && typeof value === "object" && !Array.isArray(value)
|
||||
}
|
||||
|
||||
/** Deep merge two objects, with source values overriding target values. */
|
||||
function deepMerge(target: Config, source: Partial<Config>): Config {
|
||||
const result: Record<string, unknown> = { ...target }
|
||||
for (const [key, value] of Object.entries(source)) {
|
||||
if (isRecord(value) && isRecord(result[key])) {
|
||||
result[key] = deepMerge(result[key] as Config, value as Partial<Config>)
|
||||
} else {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
return result as Config
|
||||
}
|
||||
|
||||
/** Recursively remove keys whose value is null (null = "deleted"). */
|
||||
function stripNulls(obj: Config): Config {
|
||||
const result: Record<string, unknown> = {}
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (value === null || value === undefined) continue
|
||||
if (isRecord(value)) {
|
||||
result[key] = stripNulls(value as Config)
|
||||
} else {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
return result as Config
|
||||
}
|
||||
|
||||
export const ConfigContext = createContext<ConfigContextValue>()
|
||||
|
||||
export const ConfigProvider: ParentComponent = (props) => {
|
||||
@@ -74,7 +44,9 @@ export const ConfigProvider: ParentComponent = (props) => {
|
||||
// Skip if a save is in-flight — a stale configLoaded must not overwrite
|
||||
// the optimistically-updated state while the write is being confirmed.
|
||||
if (saving) return
|
||||
setConfig(message.config)
|
||||
// Re-apply the draft on top so pending changes (e.g. a toggled switch the
|
||||
// user hasn't saved yet) stay visible instead of snapping back.
|
||||
setConfig(resolveConfig(message.config, draft(), isDirty()))
|
||||
setSaved(message.config)
|
||||
setLoading(false)
|
||||
return
|
||||
@@ -90,11 +62,7 @@ export const ConfigProvider: ParentComponent = (props) => {
|
||||
} else {
|
||||
// configUpdated from a different source (e.g. PermissionDock save).
|
||||
// Re-apply the draft on top so pending settings changes are preserved.
|
||||
if (isDirty()) {
|
||||
setConfig(stripNulls(deepMerge(message.config, draft())))
|
||||
} else {
|
||||
setConfig(message.config)
|
||||
}
|
||||
setConfig(resolveConfig(message.config, draft(), isDirty()))
|
||||
}
|
||||
setSaved(message.config)
|
||||
return
|
||||
|
||||
@@ -1086,6 +1086,19 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.title": "إزالة خادم MCP",
|
||||
"settings.agentBehaviour.removeMcp.confirm": 'هل تريد إزالة خادم MCP "{{name}}"؟ سيؤدي هذا إلى إزالته من الإعدادات.',
|
||||
"settings.agentBehaviour.removeMcp.button": "إزالة",
|
||||
"settings.agentBehaviour.editMcp": "تعديل خادم MCP",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "خادم محلي (نقل stdio)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "خادم بعيد (نقل SSE/HTTP)",
|
||||
"settings.agentBehaviour.editMcp.env": "متغيرات البيئة",
|
||||
"settings.agentBehaviour.editMcp.env.help": "المتغيرات التي يتم تمريرها إلى عملية خادم MCP.",
|
||||
"settings.agentBehaviour.addMcp.command": "الأمر",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "الوسائط",
|
||||
"settings.agentBehaviour.addMcp.args.help":
|
||||
"وسيطة واحدة لكل سطر. يتم الحفاظ على المسارات التي تحتوي على مسافات كما هي.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "عنوان URL للخادم",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "مسارات مجلدات المهارات",
|
||||
"settings.agentBehaviour.skillUrls": "عناوين URL للمهارات",
|
||||
"settings.agentBehaviour.removeSkill.title": "إزالة المهارة",
|
||||
|
||||
@@ -1102,6 +1102,19 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.confirm":
|
||||
'Remover o servidor MCP "{{name}}"? Isso o removerá da sua configuração.',
|
||||
"settings.agentBehaviour.removeMcp.button": "Remover",
|
||||
"settings.agentBehaviour.editMcp": "Editar Servidor MCP",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "Servidor local (transporte stdio)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "Servidor remoto (transporte SSE/HTTP)",
|
||||
"settings.agentBehaviour.editMcp.env": "Variáveis de Ambiente",
|
||||
"settings.agentBehaviour.editMcp.env.help": "Variáveis passadas para o processo do servidor MCP.",
|
||||
"settings.agentBehaviour.addMcp.command": "Comando",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "Argumentos",
|
||||
"settings.agentBehaviour.addMcp.args.help":
|
||||
"Um argumento por linha. Caminhos com espaços são preservados como estão.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "URL do Servidor",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "Caminhos de pastas de habilidades",
|
||||
"settings.agentBehaviour.skillUrls": "URLs de habilidades",
|
||||
"settings.agentBehaviour.removeSkill.title": "Remover habilidade",
|
||||
|
||||
@@ -1101,6 +1101,18 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.confirm":
|
||||
'Ukloniti MCP server "{{name}}"? Ovo će ga ukloniti iz vaše konfiguracije.',
|
||||
"settings.agentBehaviour.removeMcp.button": "Ukloni",
|
||||
"settings.agentBehaviour.editMcp": "Uredi MCP Server",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "Lokalni server (stdio transport)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "Udaljeni server (SSE/HTTP transport)",
|
||||
"settings.agentBehaviour.editMcp.env": "Varijable okruženja",
|
||||
"settings.agentBehaviour.editMcp.env.help": "Varijable proslijeđene procesu MCP servera.",
|
||||
"settings.agentBehaviour.addMcp.command": "Naredba",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "Argumenti",
|
||||
"settings.agentBehaviour.addMcp.args.help": "Jedan argument po liniji. Putanje s razmacima se čuvaju kakve jesu.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "URL servera",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "Putanje mapa vještina",
|
||||
"settings.agentBehaviour.skillUrls": "URL-ovi vještina",
|
||||
"settings.agentBehaviour.removeSkill.title": "Ukloni vještinu",
|
||||
|
||||
@@ -1095,6 +1095,18 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.confirm":
|
||||
'Vil du fjerne MCP-serveren "{{name}}"? Dette vil fjerne den fra din konfiguration.',
|
||||
"settings.agentBehaviour.removeMcp.button": "Fjern",
|
||||
"settings.agentBehaviour.editMcp": "Rediger MCP-server",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "Lokal server (stdio-transport)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "Fjernserver (SSE/HTTP-transport)",
|
||||
"settings.agentBehaviour.editMcp.env": "Miljøvariabler",
|
||||
"settings.agentBehaviour.editMcp.env.help": "Variabler der sendes til MCP-serverprocessen.",
|
||||
"settings.agentBehaviour.addMcp.command": "Kommando",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "Argumenter",
|
||||
"settings.agentBehaviour.addMcp.args.help": "Ét argument per linje. Stier med mellemrum bevares som de er.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "Server-URL",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "Skill-mappestier",
|
||||
"settings.agentBehaviour.skillUrls": "Skill-URL'er",
|
||||
"settings.agentBehaviour.removeSkill.title": "Fjern færdighed",
|
||||
|
||||
@@ -1115,6 +1115,19 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.confirm":
|
||||
'MCP-Server "{{name}}" entfernen? Dadurch wird er aus Ihrer Konfiguration entfernt.',
|
||||
"settings.agentBehaviour.removeMcp.button": "Entfernen",
|
||||
"settings.agentBehaviour.editMcp": "MCP-Server bearbeiten",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "Lokaler Server (stdio-Transport)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "Remote-Server (SSE/HTTP-Transport)",
|
||||
"settings.agentBehaviour.editMcp.env": "Umgebungsvariablen",
|
||||
"settings.agentBehaviour.editMcp.env.help": "Variablen, die an den MCP-Serverprozess übergeben werden.",
|
||||
"settings.agentBehaviour.addMcp.command": "Befehl",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "Argumente",
|
||||
"settings.agentBehaviour.addMcp.args.help":
|
||||
"Ein Argument pro Zeile. Pfade mit Leerzeichen werden unverändert übernommen.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "Server-URL",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "Skill-Ordnerpfade",
|
||||
"settings.agentBehaviour.skillUrls": "Skill-URLs",
|
||||
"settings.agentBehaviour.removeSkill.title": "Skill entfernen",
|
||||
|
||||
@@ -1083,6 +1083,18 @@ export const dict = {
|
||||
"settings.agentBehaviour.mcpDetail.args": "Arguments",
|
||||
"settings.agentBehaviour.mcpDetail.env": "Environment",
|
||||
"settings.agentBehaviour.mcpDetail.disabled": "This server is disabled.",
|
||||
"settings.agentBehaviour.editMcp": "Edit MCP Server",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "Local server (stdio transport)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "Remote server (SSE/HTTP transport)",
|
||||
"settings.agentBehaviour.editMcp.env": "Environment Variables",
|
||||
"settings.agentBehaviour.editMcp.env.help": "Variables passed to the MCP server process.",
|
||||
"settings.agentBehaviour.addMcp.command": "Command",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "Arguments",
|
||||
"settings.agentBehaviour.addMcp.args.help": "One argument per line. Paths with spaces are preserved as-is.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "Server URL",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.mcpEmpty": "No MCP servers configured. Edit the opencode config file to add MCP servers.",
|
||||
"settings.agentBehaviour.workflowsPlaceholder": "Workflows are managed via workflow files in your workspace.",
|
||||
"settings.agentBehaviour.workflows.description":
|
||||
|
||||
@@ -1106,6 +1106,18 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.confirm":
|
||||
'¿Eliminar el servidor MCP "{{name}}"? Esto lo eliminará de su configuración.',
|
||||
"settings.agentBehaviour.removeMcp.button": "Eliminar",
|
||||
"settings.agentBehaviour.editMcp": "Editar servidor MCP",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "Servidor local (transporte stdio)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "Servidor remoto (transporte SSE/HTTP)",
|
||||
"settings.agentBehaviour.editMcp.env": "Variables de entorno",
|
||||
"settings.agentBehaviour.editMcp.env.help": "Variables pasadas al proceso del servidor MCP.",
|
||||
"settings.agentBehaviour.addMcp.command": "Comando",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "Argumentos",
|
||||
"settings.agentBehaviour.addMcp.args.help": "Un argumento por línea. Las rutas con espacios se conservan tal cual.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "URL del servidor",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "Rutas de carpetas de habilidades",
|
||||
"settings.agentBehaviour.skillUrls": "URLs de habilidades",
|
||||
"settings.agentBehaviour.removeSkill.title": "Eliminar habilidad",
|
||||
|
||||
@@ -1119,6 +1119,19 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.confirm":
|
||||
'Supprimer le serveur MCP "{{name}}" ? Cela le supprimera de votre configuration.',
|
||||
"settings.agentBehaviour.removeMcp.button": "Supprimer",
|
||||
"settings.agentBehaviour.editMcp": "Modifier le serveur MCP",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "Serveur local (transport stdio)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "Serveur distant (transport SSE/HTTP)",
|
||||
"settings.agentBehaviour.editMcp.env": "Variables d'environnement",
|
||||
"settings.agentBehaviour.editMcp.env.help": "Variables transmises au processus du serveur MCP.",
|
||||
"settings.agentBehaviour.addMcp.command": "Commande",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "Arguments",
|
||||
"settings.agentBehaviour.addMcp.args.help":
|
||||
"Un argument par ligne. Les chemins avec des espaces sont conservés tels quels.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "URL du serveur",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "Chemins des dossiers de compétences",
|
||||
"settings.agentBehaviour.skillUrls": "URLs de compétences",
|
||||
"settings.agentBehaviour.removeSkill.title": "Supprimer la compétence",
|
||||
|
||||
@@ -1096,6 +1096,18 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.title": "MCPサーバーを削除",
|
||||
"settings.agentBehaviour.removeMcp.confirm": 'MCPサーバー "{{name}}" を削除しますか?設定から削除されます。',
|
||||
"settings.agentBehaviour.removeMcp.button": "削除",
|
||||
"settings.agentBehaviour.editMcp": "MCPサーバーを編集",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "ローカルサーバー(stdio トランスポート)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "リモートサーバー(SSE/HTTP トランスポート)",
|
||||
"settings.agentBehaviour.editMcp.env": "環境変数",
|
||||
"settings.agentBehaviour.editMcp.env.help": "MCPサーバープロセスに渡される変数。",
|
||||
"settings.agentBehaviour.addMcp.command": "コマンド",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "引数",
|
||||
"settings.agentBehaviour.addMcp.args.help": "1行に1つの引数。スペースを含むパスはそのまま保持されます。",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "サーバーURL",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "スキルフォルダパス",
|
||||
"settings.agentBehaviour.skillUrls": "スキルURL",
|
||||
"settings.agentBehaviour.removeSkill.title": "スキルを削除",
|
||||
|
||||
@@ -1090,6 +1090,18 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.title": "MCP 서버 제거",
|
||||
"settings.agentBehaviour.removeMcp.confirm": 'MCP 서버 "{{name}}"을(를) 제거하시겠습니까? 구성에서 제거됩니다.',
|
||||
"settings.agentBehaviour.removeMcp.button": "제거",
|
||||
"settings.agentBehaviour.editMcp": "MCP 서버 편집",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "로컬 서버 (stdio 전송)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "원격 서버 (SSE/HTTP 전송)",
|
||||
"settings.agentBehaviour.editMcp.env": "환경 변수",
|
||||
"settings.agentBehaviour.editMcp.env.help": "MCP 서버 프로세스에 전달되는 변수입니다.",
|
||||
"settings.agentBehaviour.addMcp.command": "명령어",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "인수",
|
||||
"settings.agentBehaviour.addMcp.args.help": "한 줄에 하나의 인수. 공백이 포함된 경로는 그대로 유지됩니다.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "서버 URL",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "스킬 폴더 경로",
|
||||
"settings.agentBehaviour.skillUrls": "스킬 URL",
|
||||
"settings.agentBehaviour.removeSkill.title": "스킬 제거",
|
||||
|
||||
@@ -1097,6 +1097,19 @@ export const dict = {
|
||||
"settings.agentBehaviour.mcpDetail.args": "Argumenten",
|
||||
"settings.agentBehaviour.mcpDetail.env": "Omgeving",
|
||||
"settings.agentBehaviour.mcpDetail.disabled": "Deze server is uitgeschakeld.",
|
||||
"settings.agentBehaviour.editMcp": "MCP-server bewerken",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "Lokale server (stdio-transport)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "Externe server (SSE/HTTP-transport)",
|
||||
"settings.agentBehaviour.editMcp.env": "Omgevingsvariabelen",
|
||||
"settings.agentBehaviour.editMcp.env.help": "Variabelen die worden doorgegeven aan het MCP-serverproces.",
|
||||
"settings.agentBehaviour.addMcp.command": "Opdracht",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "Argumenten",
|
||||
"settings.agentBehaviour.addMcp.args.help":
|
||||
"Eén argument per regel. Paden met spaties worden ongewijzigd overgenomen.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "Server-URL",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.mcpEmpty":
|
||||
"Geen MCP-servers geconfigureerd. Bewerk het opencode configuratiebestand om MCP-servers toe te voegen.",
|
||||
"settings.agentBehaviour.workflowsPlaceholder": "Workflows worden beheerd via workflowbestanden in je workspace.",
|
||||
|
||||
@@ -1098,6 +1098,18 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.confirm":
|
||||
'Vil du fjerne MCP-serveren "{{name}}"? Dette vil fjerne den fra konfigurasjonen din.',
|
||||
"settings.agentBehaviour.removeMcp.button": "Fjern",
|
||||
"settings.agentBehaviour.editMcp": "Rediger MCP-server",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "Lokal server (stdio-transport)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "Ekstern server (SSE/HTTP-transport)",
|
||||
"settings.agentBehaviour.editMcp.env": "Miljøvariabler",
|
||||
"settings.agentBehaviour.editMcp.env.help": "Variabler som sendes til MCP-serverprosessen.",
|
||||
"settings.agentBehaviour.addMcp.command": "Kommando",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "Argumenter",
|
||||
"settings.agentBehaviour.addMcp.args.help": "Ett argument per linje. Stier med mellomrom bevares som de er.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "Server-URL",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "Ferdighetsmappe-stier",
|
||||
"settings.agentBehaviour.skillUrls": "Ferdighets-URLer",
|
||||
"settings.agentBehaviour.removeSkill.title": "Fjern ferdighet",
|
||||
|
||||
@@ -1100,6 +1100,18 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.confirm":
|
||||
'Usunąć serwer MCP "{{name}}"? Spowoduje to usunięcie go z konfiguracji.',
|
||||
"settings.agentBehaviour.removeMcp.button": "Usuń",
|
||||
"settings.agentBehaviour.editMcp": "Edytuj serwer MCP",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "Serwer lokalny (transport stdio)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "Serwer zdalny (transport SSE/HTTP)",
|
||||
"settings.agentBehaviour.editMcp.env": "Zmienne środowiskowe",
|
||||
"settings.agentBehaviour.editMcp.env.help": "Zmienne przekazywane do procesu serwera MCP.",
|
||||
"settings.agentBehaviour.addMcp.command": "Polecenie",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "Argumenty",
|
||||
"settings.agentBehaviour.addMcp.args.help": "Jeden argument na linię. Ścieżki ze spacjami są zachowywane bez zmian.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "URL serwera",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "Ścieżki folderów umiejętności",
|
||||
"settings.agentBehaviour.skillUrls": "Adresy URL umiejętności",
|
||||
"settings.agentBehaviour.removeSkill.title": "Usuń umiejętność",
|
||||
|
||||
@@ -1103,6 +1103,18 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.title": "Удалить сервер MCP",
|
||||
"settings.agentBehaviour.removeMcp.confirm": 'Удалить сервер MCP "{{name}}"? Это удалит его из вашей конфигурации.',
|
||||
"settings.agentBehaviour.removeMcp.button": "Удалить",
|
||||
"settings.agentBehaviour.editMcp": "Редактировать сервер MCP",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "Локальный сервер (транспорт stdio)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "Удалённый сервер (транспорт SSE/HTTP)",
|
||||
"settings.agentBehaviour.editMcp.env": "Переменные окружения",
|
||||
"settings.agentBehaviour.editMcp.env.help": "Переменные, передаваемые процессу сервера MCP.",
|
||||
"settings.agentBehaviour.addMcp.command": "Команда",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "Аргументы",
|
||||
"settings.agentBehaviour.addMcp.args.help": "Один аргумент на строку. Пути с пробелами сохраняются как есть.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "URL сервера",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "Пути папок навыков",
|
||||
"settings.agentBehaviour.skillUrls": "URL навыков",
|
||||
"settings.agentBehaviour.removeSkill.title": "Удалить навык",
|
||||
|
||||
@@ -1087,6 +1087,18 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.confirm":
|
||||
'ต้องการลบเซิร์ฟเวอร์ MCP "{{name}}" หรือไม่? การดำเนินการนี้จะลบออกจากการกำหนดค่าของคุณ',
|
||||
"settings.agentBehaviour.removeMcp.button": "ลบ",
|
||||
"settings.agentBehaviour.editMcp": "แก้ไขเซิร์ฟเวอร์ MCP",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "เซิร์ฟเวอร์ภายใน (การขนส่ง stdio)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "เซิร์ฟเวอร์ระยะไกล (การขนส่ง SSE/HTTP)",
|
||||
"settings.agentBehaviour.editMcp.env": "ตัวแปรสภาพแวดล้อม",
|
||||
"settings.agentBehaviour.editMcp.env.help": "ตัวแปรที่ส่งไปยังกระบวนการเซิร์ฟเวอร์ MCP",
|
||||
"settings.agentBehaviour.addMcp.command": "คำสั่ง",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "อาร์กิวเมนต์",
|
||||
"settings.agentBehaviour.addMcp.args.help": "หนึ่งอาร์กิวเมนต์ต่อบรรทัด เส้นทางที่มีช่องว่างจะถูกเก็บรักษาตามเดิม",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "URL เซิร์ฟเวอร์",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "เส้นทางโฟลเดอร์ทักษะ",
|
||||
"settings.agentBehaviour.skillUrls": "URL ทักษะ",
|
||||
"settings.agentBehaviour.removeSkill.title": "ลบทักษะ",
|
||||
|
||||
@@ -1096,6 +1096,18 @@ export const dict = {
|
||||
"settings.agentBehaviour.mcpDetail.args": "Argümanlar",
|
||||
"settings.agentBehaviour.mcpDetail.env": "Ortam",
|
||||
"settings.agentBehaviour.mcpDetail.disabled": "Bu sunucu devre dışı.",
|
||||
"settings.agentBehaviour.editMcp": "MCP Sunucusunu Düzenle",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "Yerel sunucu (stdio taşıma)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "Uzak sunucu (SSE/HTTP taşıma)",
|
||||
"settings.agentBehaviour.editMcp.env": "Ortam Değişkenleri",
|
||||
"settings.agentBehaviour.editMcp.env.help": "MCP sunucu sürecine aktarılan değişkenler.",
|
||||
"settings.agentBehaviour.addMcp.command": "Komut",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "Argümanlar",
|
||||
"settings.agentBehaviour.addMcp.args.help": "Satır başına bir argüman. Boşluklu yollar olduğu gibi korunur.",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "Sunucu URL'si",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.mcpEmpty":
|
||||
"Yapılandırılmış MCP sunucusu yok. MCP sunucuları eklemek için opencode yapılandırma dosyasını düzenleyin.",
|
||||
"settings.agentBehaviour.workflowsPlaceholder": "İş akışları çalışma alanınızdaki iş akışı dosyaları ile yönetilir.",
|
||||
|
||||
@@ -1076,6 +1076,18 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.title": "移除 MCP 服务器",
|
||||
"settings.agentBehaviour.removeMcp.confirm": '移除 MCP 服务器 "{{name}}" 吗?这将从您的配置中移除它。',
|
||||
"settings.agentBehaviour.removeMcp.button": "移除",
|
||||
"settings.agentBehaviour.editMcp": "编辑 MCP 服务器",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "本地服务器(stdio 传输)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "远程服务器(SSE/HTTP 传输)",
|
||||
"settings.agentBehaviour.editMcp.env": "环境变量",
|
||||
"settings.agentBehaviour.editMcp.env.help": "传递给 MCP 服务器进程的变量。",
|
||||
"settings.agentBehaviour.addMcp.command": "命令",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "参数",
|
||||
"settings.agentBehaviour.addMcp.args.help": "每行一个参数。包含空格的路径将按原样保留。",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "服务器 URL",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "技能文件夹路径",
|
||||
"settings.agentBehaviour.skillUrls": "技能 URL",
|
||||
"settings.agentBehaviour.removeSkill.title": "移除技能",
|
||||
|
||||
@@ -1078,6 +1078,18 @@ export const dict = {
|
||||
"settings.agentBehaviour.removeMcp.title": "移除 MCP 伺服器",
|
||||
"settings.agentBehaviour.removeMcp.confirm": '要移除 MCP 伺服器 "{{name}}" 嗎?這將從您的設定中移除它。',
|
||||
"settings.agentBehaviour.removeMcp.button": "移除",
|
||||
"settings.agentBehaviour.editMcp": "編輯 MCP 伺服器",
|
||||
"settings.agentBehaviour.editMcp.transportLocal": "本機伺服器(stdio 傳輸)",
|
||||
"settings.agentBehaviour.editMcp.transportRemote": "遠端伺服器(SSE/HTTP 傳輸)",
|
||||
"settings.agentBehaviour.editMcp.env": "環境變數",
|
||||
"settings.agentBehaviour.editMcp.env.help": "傳遞給 MCP 伺服器處理程序的變數。",
|
||||
"settings.agentBehaviour.addMcp.command": "命令",
|
||||
"settings.agentBehaviour.addMcp.command.placeholder": "e.g. npx",
|
||||
"settings.agentBehaviour.addMcp.args": "引數",
|
||||
"settings.agentBehaviour.addMcp.args.help": "每行一個引數。包含空格的路徑將按原樣保留。",
|
||||
"settings.agentBehaviour.addMcp.args.placeholder": "e.g.\n-y\n@modelcontextprotocol/server-filesystem\n/tmp",
|
||||
"settings.agentBehaviour.addMcp.url": "伺服器 URL",
|
||||
"settings.agentBehaviour.addMcp.url.placeholder": "e.g. http://localhost:3000/sse",
|
||||
"settings.agentBehaviour.skillPaths": "Skill 資料夾路徑",
|
||||
"settings.agentBehaviour.skillUrls": "Skill URL",
|
||||
"settings.agentBehaviour.removeSkill.title": "移除技能",
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import type { Config } from "../types/messages"
|
||||
|
||||
export function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return !!value && typeof value === "object" && !Array.isArray(value)
|
||||
}
|
||||
|
||||
/** Deep merge two objects, with source values overriding target values. */
|
||||
export function deepMerge(target: Config, source: Partial<Config>): Config {
|
||||
const result: Record<string, unknown> = { ...target }
|
||||
for (const [key, value] of Object.entries(source)) {
|
||||
if (isRecord(value) && isRecord(result[key])) {
|
||||
result[key] = deepMerge(result[key] as Config, value as Partial<Config>)
|
||||
} else {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
return result as Config
|
||||
}
|
||||
|
||||
/** Recursively remove keys whose value is null (null = "deleted"). */
|
||||
export function stripNulls(obj: Config): Config {
|
||||
const result: Record<string, unknown> = {}
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
if (value === null || value === undefined) continue
|
||||
if (isRecord(value)) {
|
||||
result[key] = stripNulls(value as Config)
|
||||
} else {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
return result as Config
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the visible config when a configLoaded/configUpdated message arrives.
|
||||
* If the user has pending draft changes, re-apply the draft on top of the
|
||||
* incoming server config so pending toggles don't snap back.
|
||||
*/
|
||||
export function resolveConfig(server: Config, draft: Partial<Config>, dirty: boolean): Config {
|
||||
if (dirty) return stripNulls(deepMerge(server, draft))
|
||||
return server
|
||||
}
|
||||
|
||||
/**
|
||||
* Plain-object config state machine — mirrors the SolidJS ConfigProvider
|
||||
* logic without signals so the message-handling behavior is unit-testable.
|
||||
*/
|
||||
export class ConfigState {
|
||||
config: Config = {}
|
||||
saved: Config = {}
|
||||
draft: Partial<Config> = {}
|
||||
dirty = false
|
||||
saving = false
|
||||
loading = true
|
||||
|
||||
/** Accumulate a partial change (same as the toggle click path). */
|
||||
updateConfig(partial: Partial<Config>) {
|
||||
this.config = stripNulls(deepMerge(this.config, partial))
|
||||
this.draft = deepMerge(this.draft as Config, partial)
|
||||
this.dirty = true
|
||||
}
|
||||
|
||||
/** Handle an incoming configLoaded push from the extension. */
|
||||
handleConfigLoaded(server: Config) {
|
||||
if (this.saving) return
|
||||
this.config = resolveConfig(server, this.draft, this.dirty)
|
||||
this.saved = server
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
/** Handle an incoming configUpdated push from the extension. */
|
||||
handleConfigUpdated(server: Config) {
|
||||
if (this.saving) {
|
||||
this.saving = false
|
||||
this.draft = {}
|
||||
this.dirty = false
|
||||
this.config = server
|
||||
} else {
|
||||
this.config = resolveConfig(server, this.draft, this.dirty)
|
||||
}
|
||||
this.saved = server
|
||||
}
|
||||
|
||||
/** Send the draft to the backend. */
|
||||
saveConfig() {
|
||||
if (Object.keys(this.draft).length === 0) return
|
||||
this.saving = true
|
||||
}
|
||||
|
||||
/** Discard pending changes. */
|
||||
discardConfig() {
|
||||
this.config = this.saved
|
||||
this.draft = {}
|
||||
this.dirty = false
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"name": "@kilocode/cli",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { $ } from "bun"
|
||||
import path from "path"
|
||||
import fs from "fs/promises"
|
||||
import { Filesystem } from "../util/filesystem"
|
||||
import { Log } from "../util/log"
|
||||
import { Global } from "../global"
|
||||
import { Instance } from "../project/instance"
|
||||
|
||||
const log = Log.create({ service: "snapshot" })
|
||||
|
||||
/** Worktree-scoped snapshot git dir (isolated per worktree). */
|
||||
export function gitdir() {
|
||||
const project = Instance.project
|
||||
const workhash = Bun.hash(Instance.worktree).toString(36)
|
||||
return path.join(Global.Path.data, "snapshot", project.id, workhash)
|
||||
}
|
||||
|
||||
/** Original project-scoped snapshot git dir (pre-isolation layout). */
|
||||
function legacydir() {
|
||||
const project = Instance.project
|
||||
return path.join(Global.Path.data, "snapshot", project.id)
|
||||
}
|
||||
|
||||
/** Initialize a bare snapshot git repo if it doesn't exist yet. */
|
||||
export async function ensureGit(git: string) {
|
||||
if (!(await fs.mkdir(git, { recursive: true }))) return
|
||||
await $`git init`
|
||||
.env({
|
||||
...process.env,
|
||||
GIT_DIR: git,
|
||||
GIT_WORK_TREE: Instance.worktree,
|
||||
})
|
||||
.quiet()
|
||||
.nothrow()
|
||||
await $`git --git-dir ${git} config core.autocrlf false`.quiet().nothrow()
|
||||
await $`git --git-dir ${git} config core.longpaths true`.quiet().nothrow()
|
||||
await $`git --git-dir ${git} config core.symlinks true`.quiet().nothrow()
|
||||
await $`git --git-dir ${git} config core.fsmonitor false`.quiet().nothrow()
|
||||
log.info("initialized")
|
||||
}
|
||||
|
||||
/** Point the worktree repo's alternates at the legacy object store. */
|
||||
export async function syncAlternates(git: string) {
|
||||
const legacy = legacydir()
|
||||
if (legacy === git) return
|
||||
const objects = path.join(legacy, "objects")
|
||||
const exists = await fs
|
||||
.stat(objects)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
const target = path.join(git, "objects", "info", "alternates")
|
||||
await fs.mkdir(path.join(git, "objects", "info"), { recursive: true })
|
||||
await Filesystem.write(target, exists ? objects + "\n" : "")
|
||||
}
|
||||
|
||||
/** Return a ready-to-use snapshot git dir (init + alternates). */
|
||||
export async function prepare() {
|
||||
const git = gitdir()
|
||||
await ensureGit(git)
|
||||
await syncAlternates(git)
|
||||
return git
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import z from "zod"
|
||||
import { Config } from "../config/config"
|
||||
import { Instance } from "../project/instance"
|
||||
import { Scheduler } from "../scheduler"
|
||||
import * as KiloSnapshot from "../kilocode/snapshot" // kilocode_change
|
||||
|
||||
export namespace Snapshot {
|
||||
const log = Log.create({ service: "snapshot" })
|
||||
@@ -55,23 +56,7 @@ export namespace Snapshot {
|
||||
if (Instance.project.vcs !== "git" || Flag.KILO_CLIENT === "acp") return
|
||||
const cfg = await Config.get()
|
||||
if (cfg.snapshot === false) return
|
||||
const git = gitdir()
|
||||
if (await fs.mkdir(git, { recursive: true })) {
|
||||
await $`git init`
|
||||
.env({
|
||||
...process.env,
|
||||
GIT_DIR: git,
|
||||
GIT_WORK_TREE: Instance.worktree,
|
||||
})
|
||||
.quiet()
|
||||
.nothrow()
|
||||
// Configure git to not convert line endings on Windows
|
||||
await $`git --git-dir ${git} config core.autocrlf false`.quiet().nothrow()
|
||||
await $`git --git-dir ${git} config core.longpaths true`.quiet().nothrow()
|
||||
await $`git --git-dir ${git} config core.symlinks true`.quiet().nothrow()
|
||||
await $`git --git-dir ${git} config core.fsmonitor false`.quiet().nothrow()
|
||||
log.info("initialized")
|
||||
}
|
||||
const git = await KiloSnapshot.prepare() // kilocode_change
|
||||
await add(git)
|
||||
const hash = await $`git --git-dir ${git} --work-tree ${Instance.worktree} write-tree`
|
||||
.quiet()
|
||||
@@ -89,7 +74,7 @@ export namespace Snapshot {
|
||||
export type Patch = z.infer<typeof Patch>
|
||||
|
||||
export async function patch(hash: string): Promise<Patch> {
|
||||
const git = gitdir()
|
||||
const git = await KiloSnapshot.prepare() // kilocode_change
|
||||
await add(git)
|
||||
const result =
|
||||
await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff --name-only ${hash} -- .`
|
||||
@@ -117,7 +102,7 @@ export namespace Snapshot {
|
||||
|
||||
export async function restore(snapshot: string) {
|
||||
log.info("restore", { commit: snapshot })
|
||||
const git = gitdir()
|
||||
const git = await KiloSnapshot.prepare() // kilocode_change
|
||||
const result =
|
||||
await $`git -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} read-tree ${snapshot} && git -c core.longpaths=true -c core.symlinks=true --git-dir ${git} --work-tree ${Instance.worktree} checkout-index -a -f`
|
||||
.quiet()
|
||||
@@ -136,8 +121,8 @@ export namespace Snapshot {
|
||||
|
||||
export async function revert(patches: Patch[]) {
|
||||
const files = new Set<string>()
|
||||
const git = gitdir()
|
||||
for (const item of patches) {
|
||||
const git = await KiloSnapshot.prepare() // kilocode_change
|
||||
for (const file of item.files) {
|
||||
if (files.has(file)) continue
|
||||
log.info("reverting", { file, hash: item.hash })
|
||||
@@ -168,7 +153,7 @@ export namespace Snapshot {
|
||||
}
|
||||
|
||||
export async function diff(hash: string) {
|
||||
const git = gitdir()
|
||||
const git = await KiloSnapshot.prepare() // kilocode_change
|
||||
await add(git)
|
||||
const result =
|
||||
await $`git -c core.autocrlf=false -c core.longpaths=true -c core.symlinks=true -c core.quotepath=false --git-dir ${git} --work-tree ${Instance.worktree} diff --no-ext-diff ${hash} -- .`
|
||||
@@ -203,7 +188,7 @@ export namespace Snapshot {
|
||||
})
|
||||
export type FileDiff = z.infer<typeof FileDiff>
|
||||
export async function diffFull(from: string, to: string): Promise<FileDiff[]> {
|
||||
const git = gitdir()
|
||||
const git = await KiloSnapshot.prepare() // kilocode_change
|
||||
const result: FileDiff[] = []
|
||||
const status = new Map<string, "added" | "deleted" | "modified">()
|
||||
|
||||
@@ -266,8 +251,7 @@ export namespace Snapshot {
|
||||
}
|
||||
|
||||
function gitdir() {
|
||||
const project = Instance.project
|
||||
return path.join(Global.Path.data, "snapshot", project.id)
|
||||
return KiloSnapshot.gitdir() // kilocode_change
|
||||
}
|
||||
|
||||
async function add(git: string) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/plugin",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"dependencies": {},
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "@kilocode/sdk",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"typescript": "catalog:",
|
||||
"vite": "catalog:"
|
||||
},
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"dependencies": {},
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/ui",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"exports": {
|
||||
|
||||
@@ -28,6 +28,8 @@ export type SessionHrefFn = (sessionID: string) => string
|
||||
|
||||
export type OpenFileFn = (filePath: string, line?: number, column?: number) => void // kilocode_change
|
||||
|
||||
export type OpenUrlFn = (url: string) => void // kilocode_change
|
||||
|
||||
export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
name: "Data",
|
||||
init: (props: {
|
||||
@@ -36,6 +38,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
onNavigateToSession?: NavigateToSessionFn
|
||||
onSessionHref?: SessionHrefFn
|
||||
onOpenFile?: OpenFileFn // kilocode_change
|
||||
onOpenUrl?: OpenUrlFn // kilocode_change
|
||||
}) => {
|
||||
return {
|
||||
get store() {
|
||||
@@ -47,6 +50,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
|
||||
navigateToSession: props.onNavigateToSession,
|
||||
sessionHref: props.onSessionHref,
|
||||
openFile: props.onOpenFile, // kilocode_change
|
||||
openUrl: props.onOpenUrl, // kilocode_change
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@opencode-ai/util",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kilocode/upstream-merge",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "Scripts for automating upstream opencode merges into Kilo",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "opencode",
|
||||
"displayName": "opencode",
|
||||
"description": "opencode for VS Code",
|
||||
"version": "7.1.5",
|
||||
"version": "7.1.8",
|
||||
"publisher": "sst-dev",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user