mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-24 16:02:55 +08:00
chore: merge origin/main
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Gate experimental agents on their declared skill, MCP, and VS Code extension requirements. VS Code shows requirement groups with Marketplace shortcuts, and the CLI stops before sending when requirements are unmet.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Translate the Anaconda Desktop provider strings into all supported languages instead of falling back to English.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Improve autocomplete error messages to clarify BYOK and credits issues
|
||||
|
||||
When autocomplete is paused due to a payment or auth error, the messages now explain all possible causes: no Kilo credits, API key (BYOK) quota exhausted, not signed in, or invalid/missing API key.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@kilocode/cli": patch
|
||||
---
|
||||
|
||||
Allow the default TUI to import cloud-only sessions without rejecting their IDs as missing locally.
|
||||
@@ -2,4 +2,4 @@
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Preserve new-chat prompts and attachments when toggling sandbox before sending the first message.
|
||||
Preserve new-chat prompts, attachments, and scroll position when toggling sandbox, and keep sandbox status stable while switching sessions.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Open the model selector instantly, even with large model catalogs.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"kilo-code": patch
|
||||
---
|
||||
|
||||
Show full external directory paths in permission auto-approve rules.
|
||||
+6
-1
@@ -10,15 +10,20 @@ packages/kilo-ui/tests/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
packages/kilo-vscode/tests/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
packages/kilo-docs/public/img/screenshot-tests/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# Hide non-English localization files by default in GitHub PR diffs.
|
||||
# Hide most localization files by default in GitHub PR diffs. Show English as the source
|
||||
# language and German so reviewers can catch untranslated copy-pasted English strings.
|
||||
**/i18n/*.ts linguist-generated=true
|
||||
**/i18n/en.ts linguist-generated=false
|
||||
**/i18n/en*.ts linguist-generated=false
|
||||
**/i18n/de.ts linguist-generated=false
|
||||
**/i18n/de*.ts linguist-generated=false
|
||||
**/i18n/package-nls-en.ts linguist-generated=false
|
||||
**/i18n/package-nls-de.ts linguist-generated=false
|
||||
**/i18n/index.ts linguist-generated=false
|
||||
**/i18n/parity.test.ts linguist-generated=false
|
||||
packages/kilo-i18n/src/*.ts linguist-generated=true
|
||||
packages/kilo-i18n/src/en.ts linguist-generated=false
|
||||
packages/kilo-i18n/src/de.ts linguist-generated=false
|
||||
|
||||
# Auto-generated CLI reference docs
|
||||
packages/kilo-docs/markdoc/partials/cli-commands-table.md linguist-generated=true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
English | <a href="README.zh.md">简体中文</a> | <a href="README.zht.md">繁體中文</a> | <a href="README.ko.md">한국어</a> | <a href="README.de.md">Deutsch</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.it.md">Italiano</a> | <a href="README.da.md">Dansk</a> | <a href="README.ja.md">日本語</a> | <a href="README.pl.md">Polski</a> | <a href="README.ru.md">Русский</a> | <a href="README.bs.md">Bosanski</a> | <a href="README.ar.md">العربية</a> | <a href="README.no.md">Norsk</a> | <a href="README.br.md">Português (Brasil)</a> | <a href="README.th.md">ไทย</a> | <a href="README.tr.md">Türkçe</a> | <a href="README.uk.md">Українська</a> | <a href="README.bn.md">বাংলা</a> | <a href="README.gr.md">Ελληνικά</a> | <a href="README.vi.md">Tiếng Việt</a>
|
||||
English | <a href="translations/README.zh.md">简体中文</a> | <a href="translations/README.zht.md">繁體中文</a> | <a href="translations/README.ko.md">한국어</a> | <a href="translations/README.de.md">Deutsch</a> | <a href="translations/README.es.md">Español</a> | <a href="translations/README.fr.md">Français</a> | <a href="translations/README.it.md">Italiano</a> | <a href="translations/README.da.md">Dansk</a> | <a href="translations/README.ja.md">日本語</a> | <a href="translations/README.pl.md">Polski</a> | <a href="translations/README.ru.md">Русский</a> | <a href="translations/README.bs.md">Bosanski</a> | <a href="translations/README.ar.md">العربية</a> | <a href="translations/README.no.md">Norsk</a> | <a href="translations/README.br.md">Português (Brasil)</a> | <a href="translations/README.th.md">ไทย</a> | <a href="translations/README.tr.md">Türkçe</a> | <a href="translations/README.uk.md">Українська</a> | <a href="translations/README.bn.md">বাংলা</a> | <a href="translations/README.gr.md">Ελληνικά</a> | <a href="translations/README.vi.md">Tiếng Việt</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
@@ -168,7 +168,7 @@ MIT. You're free to use, modify, and distribute this code, including commerciall
|
||||
<details>
|
||||
<summary>Where did Kilo CLI come from?</summary>
|
||||
|
||||
Kilo CLI is a fork of [OpenCode](https://github.com/Kilo-Org/kilocode), enhanced to work within the Kilo agentic engineering platform.
|
||||
Kilo CLI is a fork of [OpenCode](https://github.com/anomalyco/opencode), enhanced to work within the Kilo agentic engineering platform.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -119,14 +119,14 @@ describe("ProjectV2.resolve", () => {
|
||||
Effect.promise(() => tmpdir()),
|
||||
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
||||
)
|
||||
yield* Effect.promise(() => initRepo(ssh.path, { commit: true, remote: "git@github.com:owner/repo.git" }))
|
||||
yield* Effect.promise(() => initRepo(https.path, { commit: true, remote: "https://github.com/owner/repo.git" }))
|
||||
yield* Effect.promise(() => initRepo(ssh.path, { commit: true, remote: "git@example.com:owner/repo.git" }))
|
||||
yield* Effect.promise(() => initRepo(https.path, { commit: true, remote: "https://example.com/owner/repo.git" }))
|
||||
const project = yield* Project.Service
|
||||
|
||||
const a = yield* project.resolve(abs(ssh.path))
|
||||
const b = yield* project.resolve(abs(https.path))
|
||||
|
||||
expect(a.id).toBe(remoteID("github.com/owner/repo"))
|
||||
expect(a.id).toBe(remoteID("example.com/owner/repo"))
|
||||
expect(b.id).toBe(a.id)
|
||||
}),
|
||||
)
|
||||
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:af33f5d026513422db6efc37848eec76d9da511432e5933606ae0be73da684f8
|
||||
size 16675
|
||||
oid sha256:6ee59e48c176043eee3b22441ce1c50e8d17dbafe58e969cfc8e97f404f2608d
|
||||
size 17429
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a093b9658cc15d0789da5afebde7163ec51e743af59975e2a031aed3e9144d56
|
||||
size 22336
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:0b20e8e50b583cc6a884e08648a9f1efcdfc2a9561b97145811ebc9c24d244fd
|
||||
size 23100
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3b24b0d2694d2eeec0ea4cd827bd3a61bb47150605b838b43e4f422c9b960f0e
|
||||
size 29744
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:fd27610a2518018c16e4227728221ed9bf67a7d3c8ab207b2835dc60964ec63d
|
||||
size 33323
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d8ad46cc736472c6a2a46bb80e6622333f49960e74dcb6782d35aa72fa32378c
|
||||
size 23725
|
||||
+2
-2
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:61b1d0bc725e3b33a40d1d777d71b70afcec1b6cdcfae15e2023667bc4574f23
|
||||
size 17201
|
||||
oid sha256:6f05d9512bbe3309c6d073fd8aca8e0800c7e2827bdefe6115e636e8a6e94070
|
||||
size 16239
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:b197d749f21df8d3061b93d13281a346b52514bec1b6855b474a03cfaeeb117b
|
||||
size 2500
|
||||
+4
-3
@@ -157,9 +157,9 @@ class MockCliServer : AutoCloseable {
|
||||
/** Reset all request counters. */
|
||||
fun resetCounts() { counts.clear() }
|
||||
|
||||
private val executor = Executors.newCachedThreadPool { r ->
|
||||
Thread(r, "mock-cli-${Thread.currentThread().id}").apply { isDaemon = true }
|
||||
}
|
||||
private val executor = Executors.newThreadPerTaskExecutor(
|
||||
Thread.ofVirtual().name("mock-cli-", 0).factory(),
|
||||
)
|
||||
private val closed = AtomicBoolean(false)
|
||||
|
||||
private var server: ServerSocket? = null
|
||||
@@ -221,6 +221,7 @@ class MockCliServer : AutoCloseable {
|
||||
if (!closed.compareAndSet(false, true)) return
|
||||
shutdownServer()
|
||||
executor.shutdownNow()
|
||||
check(executor.awaitTermination(5, TimeUnit.SECONDS)) { "Mock CLI executor did not terminate" }
|
||||
}
|
||||
|
||||
private fun shutdownServer() {
|
||||
|
||||
+17
-21
@@ -12,13 +12,16 @@ import ai.kilocode.backend.testing.MockCliServer
|
||||
import ai.kilocode.backend.testing.TestLog
|
||||
import ai.kilocode.jetbrains.api.client.DefaultApi
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -41,10 +44,13 @@ class KiloBackendWorkspaceTest {
|
||||
|
||||
@AfterTest
|
||||
fun tearDown() {
|
||||
apps.forEach { it.dispose() }
|
||||
apps.clear()
|
||||
scope.cancel()
|
||||
mock.close()
|
||||
runBlocking {
|
||||
apps.forEach { it.dispose() }
|
||||
apps.clear()
|
||||
scope.cancel()
|
||||
mock.close()
|
||||
withTimeout(10_000) { scope.coroutineContext[Job]?.join() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun setup(): KiloBackendAppService =
|
||||
@@ -466,7 +472,7 @@ class KiloBackendWorkspaceTest {
|
||||
mock.skills = SKILLS_JSON
|
||||
|
||||
val app = setup()
|
||||
ready(app)
|
||||
val initial = ready(app)
|
||||
|
||||
// Change providers response then fire disposed event
|
||||
mock.providers = """{
|
||||
@@ -482,26 +488,16 @@ class KiloBackendWorkspaceTest {
|
||||
"connected": ["openai"]
|
||||
}"""
|
||||
|
||||
mock.awaitSseConnection()
|
||||
mock.pushEvent("global.disposed", """{"type":"global.disposed"}""")
|
||||
|
||||
// global.disposed triggers full app reload which restarts the
|
||||
// workspace manager (stop + start), clearing all cached workspaces.
|
||||
// Wait for app to reach Ready again after reload.
|
||||
withTimeout(15_000) {
|
||||
// App may briefly leave Ready during reload
|
||||
while (true) {
|
||||
val state = app.appState.value
|
||||
if (state is KiloAppState.Ready) {
|
||||
delay(300)
|
||||
if (app.appState.value is KiloAppState.Ready) break
|
||||
}
|
||||
delay(100)
|
||||
}
|
||||
assertTrue(mock.awaitSseConnection())
|
||||
val reload = async(start = CoroutineStart.UNDISPATCHED) {
|
||||
app.appState.drop(1).first { it is KiloAppState.Ready }
|
||||
}
|
||||
mock.pushEvent("global.disposed", """{"type":"global.disposed"}""")
|
||||
withTimeout(15_000) { reload.await() }
|
||||
|
||||
// Get a fresh workspace — old one was stopped during reload
|
||||
val ws = app.workspaces.get("/test/project")
|
||||
assertTrue(ws !== initial)
|
||||
withTimeout(15_000) {
|
||||
ws.state.first { it is KiloWorkspaceState.Ready }
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ import { GitStatsPoller, type LocalStats } from "./agent-manager/GitStatsPoller"
|
||||
import { diffSummary as localDiffSummary } from "./agent-manager/local-diff"
|
||||
import { getWorkspaceRoot } from "./review-utils"
|
||||
import { createMarketplaceRemover, removeAgent, removeMcp } from "./kilo-provider/remove-config-item"
|
||||
import { AgentRequirementsController } from "./kilo-provider/agent-requirements-controller"
|
||||
import type { RemoteStatusService } from "./services/RemoteStatusService"
|
||||
import { resolveProjectDirectory } from "./project-directory"
|
||||
import { seedSessionStatuses } from "./session-status"
|
||||
@@ -423,6 +424,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
private diffVirtualProvider: import("./DiffVirtualProvider").DiffVirtualProvider | undefined
|
||||
private remoteService: RemoteStatusService | null = null
|
||||
private unsubscribeRemote: (() => void) | null = null
|
||||
private readonly requirements: AgentRequirementsController
|
||||
|
||||
constructor(
|
||||
private readonly extensionUri: vscode.Uri,
|
||||
@@ -435,6 +437,23 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.unsubscribeSandboxPreference = this.connectionService.sandboxPreference?.onChange(() => {
|
||||
if (this.connectionState === "connected") void this.fetchAndSendSandboxDefault()
|
||||
})
|
||||
this.requirements = new AgentRequirementsController({
|
||||
post: (msg) => this.postMessage(msg),
|
||||
client: () => this.client,
|
||||
connected: () => this.connectionState === "connected",
|
||||
generation: () => this.connectionGeneration,
|
||||
root: () => this.getRootDirectory(),
|
||||
folders: () => vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath),
|
||||
project: () => this.projectDirectory,
|
||||
sessions: () => this.sessionDirectories,
|
||||
worktrees: this.opts.worktreeDirectories,
|
||||
extension: (id) => vscode.extensions.getExtension(id),
|
||||
subscribe:
|
||||
typeof vscode.extensions.onDidChange === "function"
|
||||
? (listener) => vscode.extensions.onDidChange(listener)
|
||||
: undefined,
|
||||
error: getErrorMessage,
|
||||
})
|
||||
|
||||
TelemetryProxy.getInstance().setProvider(this)
|
||||
}
|
||||
@@ -497,6 +516,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
if (this.projectDirectory === directory) return
|
||||
this.projectDirectory = directory
|
||||
this.postMessage({ type: "workspaceDirectoryChanged", directory: directory ?? "" })
|
||||
this.requirements.clear()
|
||||
}
|
||||
|
||||
public setDiffVirtualProvider(provider: import("./DiffVirtualProvider").DiffVirtualProvider): void {
|
||||
@@ -537,6 +557,14 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
})
|
||||
}
|
||||
|
||||
private openMarketplacePanel(directory: unknown): void {
|
||||
if (typeof directory === "string" && directory) {
|
||||
vscode.commands.executeCommand("kilo-code.new.marketplaceButtonClicked", directory)
|
||||
return
|
||||
}
|
||||
vscode.commands.executeCommand("kilo-code.new.marketplaceButtonClicked", this.projectDirectory)
|
||||
}
|
||||
|
||||
// Strip metadata unused by the webview to keep session switches fast.
|
||||
// Logic in kilo-provider/slim-metadata.ts.
|
||||
private slimInfo<T>(info: T): T {
|
||||
@@ -575,6 +603,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.cachedAgentsMessage = null
|
||||
this.cachedConfigMessage = null
|
||||
await Promise.all([this.fetchAndSendAgents(), this.fetchAndSendConfig()])
|
||||
this.requirements.clear()
|
||||
},
|
||||
storage: this.extensionContext?.globalStorageUri,
|
||||
}
|
||||
@@ -743,12 +772,14 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
public setSessionDirectory(sessionId: string, directory: string): void {
|
||||
this.aborts.preserve(sessionId, this.sessionStatusMap.get(sessionId), this.getWorkspaceDirectory(sessionId))
|
||||
this.sessionDirectories.set(sessionId, directory)
|
||||
this.requirements.clear()
|
||||
if (this.connectionState === "connected") void this.fetchAndSendSandboxStatus(sessionId)
|
||||
}
|
||||
|
||||
public clearSessionDirectory(sessionId: string): void {
|
||||
this.aborts.preserve(sessionId, this.sessionStatusMap.get(sessionId), this.getWorkspaceDirectory(sessionId))
|
||||
this.sessionDirectories.delete(sessionId)
|
||||
this.requirements.clear()
|
||||
if (this.connectionState === "connected") void this.fetchAndSendSandboxStatus(sessionId)
|
||||
}
|
||||
|
||||
@@ -1016,7 +1047,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
await openConfig(message.scope, message.labels, this.getProjectDirectory(this.currentSession?.id))
|
||||
break
|
||||
case "openMarketplacePanel":
|
||||
vscode.commands.executeCommand("kilo-code.new.marketplaceButtonClicked", this.projectDirectory)
|
||||
this.openMarketplacePanel(message.directory)
|
||||
break
|
||||
case "forkSession":
|
||||
handleForkSession(this.forkCtx, message.sessionId, message.messageId).catch((e) =>
|
||||
@@ -1070,6 +1101,16 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
case "requestSkills":
|
||||
this.fetchAndSendSkills().catch((e) => console.error("[Kilo New] fetchAndSendSkills failed:", e))
|
||||
break
|
||||
case "requestAgentRequirements":
|
||||
this.requirements
|
||||
.fetch({
|
||||
agent: message.agent,
|
||||
directory: message.directory,
|
||||
sessionID: message.sessionID,
|
||||
force: message.force === true,
|
||||
})
|
||||
.catch((e) => console.error("[Kilo New] fetchAndSendAgentRequirements failed:", e))
|
||||
break
|
||||
case "requestCommands":
|
||||
this.fetchAndSendCommands().catch((e) => console.error("[Kilo New] fetchAndSendCommands failed:", e))
|
||||
break
|
||||
@@ -1091,7 +1132,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
const c1 = this.client
|
||||
if (c1) {
|
||||
void McpOAuth.connectMcpServer(c1, message.name, this.getWorkspaceDirectory(), () =>
|
||||
this.fetchAndSendMcpStatus(),
|
||||
this.refreshMcpStatus(),
|
||||
).catch((e) => console.error("[Kilo New] connectMcpServer failed:", e))
|
||||
}
|
||||
break
|
||||
@@ -1100,7 +1141,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
const c2 = this.client
|
||||
if (c2) {
|
||||
void McpOAuth.disconnectMcpServer(c2, message.name, this.getWorkspaceDirectory(), () =>
|
||||
this.fetchAndSendMcpStatus(),
|
||||
this.refreshMcpStatus(),
|
||||
).catch((e) => console.error("[Kilo New] disconnectMcpServer failed:", e))
|
||||
}
|
||||
break
|
||||
@@ -1109,7 +1150,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
const c = this.client
|
||||
if (c) {
|
||||
void McpOAuth.authenticateMcpServer(c, message.name, this.getWorkspaceDirectory(), () =>
|
||||
this.fetchAndSendMcpStatus(),
|
||||
this.refreshMcpStatus(),
|
||||
).catch((e) => console.error("[Kilo New] authenticateMcpServer failed:", e))
|
||||
}
|
||||
break
|
||||
@@ -1379,6 +1420,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
dir: () => this.getWorkspaceDirectory(this.currentSession?.id),
|
||||
diff: this.diffVirtualProvider,
|
||||
storage: this.extensionContext?.globalStorageUri,
|
||||
post: (msg) => this.postMessage(msg),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1648,6 +1690,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
})
|
||||
.catch((e: unknown) => console.warn("[Kilo New] KiloProvider: getSession failed (non-critical):", e))
|
||||
this.postMessage({ type: "workspaceDirectoryChanged", directory: this.getWorkspaceDirectory(sessionID) })
|
||||
this.requirements.clear()
|
||||
this.client.session
|
||||
.status({ directory: dir })
|
||||
.then((r) => {
|
||||
@@ -2204,6 +2247,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.cachedSkillsMessage = null
|
||||
this.cachedCommandsMessage = null
|
||||
await Promise.all([this.fetchAndSendSkills(), this.fetchAndSendCommands()])
|
||||
this.requirements.clear()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -2215,6 +2259,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
if (!result.error) {
|
||||
this.cachedAgentsMessage = null
|
||||
await this.fetchAndSendAgents()
|
||||
this.requirements.clear()
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
@@ -2232,6 +2277,11 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshMcpStatus(): Promise<void> {
|
||||
await this.fetchAndSendMcpStatus()
|
||||
this.requirements.clear()
|
||||
}
|
||||
|
||||
private async fetchAndSendMcpStatus(): Promise<void> {
|
||||
if (!this.client) {
|
||||
if (this.cachedMcpStatusMessage) {
|
||||
@@ -2776,6 +2826,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
projectConfig: overlay?.project,
|
||||
features: configFeatures(merged),
|
||||
})
|
||||
this.requirements.clear()
|
||||
await Promise.all([
|
||||
refreshProviders ? this.fetchAndSendProviders() : Promise.resolve(),
|
||||
refreshAgents ? this.fetchAndSendAgents() : Promise.resolve(),
|
||||
@@ -2796,6 +2847,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
globalConfig: this.cachedGlobalConfig ?? undefined,
|
||||
features: features ?? configFeatures(optimistic as Config),
|
||||
})
|
||||
this.requirements.clear()
|
||||
} finally {
|
||||
this.pending--
|
||||
}
|
||||
@@ -2975,6 +3027,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
|
||||
const sid = resolved!.sid
|
||||
const dir = resolved!.dir
|
||||
await this.requirements.assertAgentRequirements(agent, dir)
|
||||
const editorContext = await this.gatherEditorContext(dir)
|
||||
|
||||
if (messageID) {
|
||||
@@ -3064,6 +3117,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
|
||||
const sid = resolved!.sid
|
||||
const dir = resolved!.dir
|
||||
await this.requirements.assertAgentRequirements(agent, dir)
|
||||
await this.checkpoints.get(sid)
|
||||
await runWithMessageConfirmation(this.confirmations, messageID, "KiloProvider: Command request", () =>
|
||||
this.withRetry(
|
||||
@@ -3372,6 +3426,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
|
||||
/** Re-fetch all server-side state after an auth change. */
|
||||
private async reloadAfterAuthChange(): Promise<void> {
|
||||
this.requirements.clear()
|
||||
await this.fetchAndSendConfig()
|
||||
await Promise.all([
|
||||
this.fetchAndSendProviders(),
|
||||
@@ -3553,6 +3608,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
// Fetch and push the updated config + refresh agents and providers so the
|
||||
// Settings panel and mode/model pickers reflect the change.
|
||||
if (event.type === "global.config.updated") {
|
||||
this.requirements.clear()
|
||||
void Promise.all([this.fetchAndSendConfigUpdated(), this.fetchAndSendAgents(), this.fetchAndSendProviders()])
|
||||
return
|
||||
}
|
||||
@@ -3972,6 +4028,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
this.anacondaDesktop.dispose()
|
||||
this.aborts.clear()
|
||||
this.sessionStatusMap.clear()
|
||||
this.requirements.dispose()
|
||||
this.ignoreController?.dispose()
|
||||
this.chatAutocomplete?.dispose()
|
||||
disposeGitChangesTarget()
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
import type { KiloClient } from "@kilocode/sdk/v2/client"
|
||||
import {
|
||||
applyVSCodeExtensionRequirements,
|
||||
requirementDirectory,
|
||||
requirementKey,
|
||||
type BackendAgentRequirementResult,
|
||||
type HostAgentRequirementResult,
|
||||
} from "./agent-requirements"
|
||||
|
||||
type AgentRequirementsClient = {
|
||||
agentRequirements: (
|
||||
parameters: { agent: string; directory: string },
|
||||
options: { throwOnError: true },
|
||||
) => Promise<{ data: BackendAgentRequirementResult }>
|
||||
}
|
||||
|
||||
type LoadedMessage = {
|
||||
type: "agentRequirementsLoaded"
|
||||
result: HostAgentRequirementResult
|
||||
}
|
||||
|
||||
type InvalidatedMessage = {
|
||||
type: "agentRequirementsInvalidated"
|
||||
}
|
||||
|
||||
type Disposable = {
|
||||
dispose(): void
|
||||
}
|
||||
|
||||
export type AgentRequirementsRequest = {
|
||||
agent: string
|
||||
directory: string
|
||||
sessionID?: string
|
||||
force?: boolean
|
||||
}
|
||||
|
||||
export type AgentRequirementsControllerOptions = {
|
||||
post: (message: LoadedMessage | InvalidatedMessage) => void
|
||||
client: () => KiloClient | null
|
||||
connected: () => boolean
|
||||
generation: () => number
|
||||
root: () => string
|
||||
folders: () => readonly string[] | undefined
|
||||
project: () => string | null | undefined
|
||||
sessions: () => ReadonlyMap<string, string>
|
||||
worktrees?: () => readonly string[]
|
||||
extension: (id: string) => unknown
|
||||
subscribe?: (listener: () => void) => Disposable
|
||||
error: (error: unknown) => string
|
||||
}
|
||||
|
||||
export class AgentRequirementsController {
|
||||
private readonly cache = new Map<string, HostAgentRequirementResult>()
|
||||
private readonly generations = new Map<string, object>()
|
||||
private readonly subscription: Disposable | undefined
|
||||
|
||||
constructor(private readonly opts: AgentRequirementsControllerOptions) {
|
||||
this.subscription = opts.subscribe?.(() => this.clear())
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
const active = this.cache.size > 0 || this.generations.size > 0
|
||||
this.cache.clear()
|
||||
this.generations.clear()
|
||||
if (!active) return
|
||||
this.opts.post({ type: "agentRequirementsInvalidated" })
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.subscription?.dispose()
|
||||
this.cache.clear()
|
||||
this.generations.clear()
|
||||
}
|
||||
|
||||
fetch(request: AgentRequirementsRequest): Promise<void> {
|
||||
const dir = this.scope(request.directory, request.sessionID)
|
||||
if (!dir) {
|
||||
this.error(request.agent, request.directory, "scope_mismatch", "The agent requirement scope is no longer active")
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return this.load(request.agent, dir, request.force === true).then(
|
||||
(result) => {
|
||||
if (!this.scope(dir, request.sessionID)) return
|
||||
this.opts.post({ type: "agentRequirementsLoaded", result })
|
||||
},
|
||||
(error) => {
|
||||
this.error(request.agent, dir, "request_failed", error)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private scope(requested: string, sessionID?: string): string | undefined {
|
||||
return requirementDirectory({
|
||||
requested,
|
||||
sessionID,
|
||||
workspaceDirectory: this.opts.root(),
|
||||
workspaceDirectories: this.opts.folders(),
|
||||
projectDirectory: this.opts.project(),
|
||||
sessionDirectories: this.opts.sessions(),
|
||||
worktreeDirectories: this.opts.worktrees,
|
||||
})
|
||||
}
|
||||
|
||||
private apply(result: BackendAgentRequirementResult, directory: string): HostAgentRequirementResult {
|
||||
return applyVSCodeExtensionRequirements({ ...result, directory }, (id) => this.opts.extension(id))
|
||||
}
|
||||
|
||||
private error(agent: string, directory: string, code: "scope_mismatch" | "request_failed", error: unknown): void {
|
||||
this.opts.post({
|
||||
type: "agentRequirementsLoaded",
|
||||
result: {
|
||||
agent,
|
||||
directory,
|
||||
enabled: true,
|
||||
state: "error",
|
||||
skills: [],
|
||||
mcps: [],
|
||||
vscode_extensions: [],
|
||||
error: {
|
||||
code,
|
||||
message: this.opts.error(error) || "Failed to check agent requirements",
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async assertAgentRequirements(agent: string | undefined, directory: string): Promise<void> {
|
||||
if (!agent) return
|
||||
|
||||
const dir = this.scope(directory)
|
||||
if (!dir) throw new Error("The agent requirement scope is no longer active")
|
||||
|
||||
const result = await this.load(agent, dir)
|
||||
if (!this.blocked(result)) return
|
||||
|
||||
this.opts.post({ type: "agentRequirementsLoaded", result })
|
||||
throw new Error(this.message(result))
|
||||
}
|
||||
|
||||
private async load(agent: string, directory: string, force = false): Promise<HostAgentRequirementResult> {
|
||||
const key = requirementKey(agent, directory)
|
||||
if (!force) {
|
||||
const cached = this.cache.get(key)
|
||||
if (cached) return cached
|
||||
}
|
||||
|
||||
const client = this.opts.client()
|
||||
if (!client || !this.opts.connected()) throw new Error("Not connected to CLI backend")
|
||||
|
||||
const connection = this.opts.generation()
|
||||
const token = {}
|
||||
this.generations.set(key, token)
|
||||
|
||||
const endpoint = client.kilocode as typeof client.kilocode & AgentRequirementsClient
|
||||
const response = await endpoint.agentRequirements({ agent, directory }, { throwOnError: true }).catch((error) => {
|
||||
if (this.generations.get(key) === token) this.generations.delete(key)
|
||||
throw error
|
||||
})
|
||||
|
||||
if (this.opts.generation() !== connection || this.opts.client() !== client) {
|
||||
if (this.generations.get(key) === token) this.generations.delete(key)
|
||||
throw new Error("Connection changed while checking agent requirements")
|
||||
}
|
||||
if (this.generations.get(key) !== token) throw new Error("Agent requirement check was superseded")
|
||||
|
||||
const result = this.apply(response.data, directory)
|
||||
this.cache.set(key, result)
|
||||
this.generations.delete(key)
|
||||
return result
|
||||
}
|
||||
|
||||
private blocked(result: HostAgentRequirementResult): boolean {
|
||||
if (!result.enabled || result.state === "disabled") return false
|
||||
if (result.state === "blocked" || result.state === "error") return true
|
||||
|
||||
return (
|
||||
result.skills.some((item) => item.status !== "ready") ||
|
||||
result.mcps.some((item) => item.status !== "ready") ||
|
||||
result.vscode_extensions.some((item) => item.status !== "ready")
|
||||
)
|
||||
}
|
||||
|
||||
private message(result: HostAgentRequirementResult): string {
|
||||
if (result.error?.message) return result.error.message
|
||||
|
||||
const missing = [
|
||||
...result.skills.filter((item) => item.status !== "ready").map((item) => `skill ${item.name}`),
|
||||
...result.mcps.filter((item) => item.status !== "ready").map((item) => `MCP ${item.name}`),
|
||||
...result.vscode_extensions
|
||||
.filter((item) => item.status !== "ready")
|
||||
.map((item) => `VS Code extension ${item.name}`),
|
||||
]
|
||||
|
||||
if (missing.length) return `Agent requirements are not met: ${missing.join(", ")}`
|
||||
return "Agent requirements are not met"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import * as path from "path"
|
||||
import { sameDirectory } from "../kilo-provider-utils"
|
||||
|
||||
export type RequirementStatus = "ready" | "missing" | "error"
|
||||
|
||||
export type RequirementState = "disabled" | "ready" | "blocked" | "error"
|
||||
|
||||
export type RequirementErrorCode =
|
||||
| "unknown_agent"
|
||||
| "malformed_declaration"
|
||||
| "discovery_failed"
|
||||
| "mcp_status_failed"
|
||||
| "scope_mismatch"
|
||||
| "request_failed"
|
||||
|
||||
export type RequirementItem = {
|
||||
name: string
|
||||
status: RequirementStatus
|
||||
message?: string
|
||||
}
|
||||
|
||||
export type BackendVSCodeExtensionRequirement = {
|
||||
name: string
|
||||
id: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export type BackendAgentRequirementResult = {
|
||||
agent: string
|
||||
directory: string
|
||||
enabled: boolean
|
||||
state: RequirementState
|
||||
skills: RequirementItem[]
|
||||
mcps: RequirementItem[]
|
||||
vscode_extensions: BackendVSCodeExtensionRequirement[]
|
||||
error?: {
|
||||
code: RequirementErrorCode
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export type HostVSCodeExtensionRequirement = BackendVSCodeExtensionRequirement & {
|
||||
status: RequirementStatus
|
||||
}
|
||||
|
||||
export type HostAgentRequirementResult = Omit<BackendAgentRequirementResult, "vscode_extensions"> & {
|
||||
vscode_extensions: HostVSCodeExtensionRequirement[]
|
||||
}
|
||||
|
||||
export type RequirementDirectoryInput = {
|
||||
requested: string
|
||||
sessionID?: string
|
||||
workspaceDirectory: string
|
||||
workspaceDirectories?: readonly string[]
|
||||
projectDirectory?: string | null
|
||||
sessionDirectories: ReadonlyMap<string, string>
|
||||
worktreeDirectories?: () => readonly string[]
|
||||
}
|
||||
|
||||
export type VSCodeExtensionLookup = (id: string) => unknown
|
||||
|
||||
function normalize(directory: string): string {
|
||||
if (!directory) return ""
|
||||
const resolved = path.resolve(directory)
|
||||
return process.platform === "win32" ? resolved.toLowerCase() : resolved
|
||||
}
|
||||
|
||||
export function requirementKey(agent: string, directory: string): string {
|
||||
return `${normalize(directory)}\0${agent}`
|
||||
}
|
||||
|
||||
export function requirementDirectory(input: RequirementDirectoryInput): string | undefined {
|
||||
if (!input.requested) return undefined
|
||||
|
||||
const dirs = [
|
||||
input.sessionID ? input.sessionDirectories.get(input.sessionID) : undefined,
|
||||
input.workspaceDirectory,
|
||||
...(input.workspaceDirectories ?? []),
|
||||
input.projectDirectory ?? undefined,
|
||||
...input.sessionDirectories.values(),
|
||||
...(input.worktreeDirectories?.() ?? []),
|
||||
].filter((dir): dir is string => !!dir)
|
||||
|
||||
return dirs.find((dir) => sameDirectory(dir, input.requested))
|
||||
}
|
||||
|
||||
export function applyVSCodeExtensionRequirements(
|
||||
result: BackendAgentRequirementResult,
|
||||
lookup: VSCodeExtensionLookup,
|
||||
): HostAgentRequirementResult {
|
||||
const extensions = result.vscode_extensions.map((extension) => {
|
||||
const status: RequirementStatus = lookup(extension.id) ? "ready" : "missing"
|
||||
return { ...extension, status }
|
||||
})
|
||||
const blocked =
|
||||
result.enabled && result.state === "ready" && extensions.some((extension) => extension.status !== "ready")
|
||||
|
||||
return {
|
||||
...result,
|
||||
state: blocked ? "blocked" : result.state,
|
||||
vscode_extensions: extensions,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as vscode from "vscode"
|
||||
import { buildPreviewPath, getPreviewCommand, getPreviewDir, parseImage, trimEntries } from "../image-preview"
|
||||
import { isAbsolutePath } from "../path-utils"
|
||||
import { escapeGlob, isAbsolutePath } from "../path-utils"
|
||||
import { validateFiles } from "./file-links"
|
||||
import type { DiffVirtualFile, DiffVirtualProvider } from "../DiffVirtualProvider"
|
||||
|
||||
type EditorOpenMessage = {
|
||||
@@ -68,11 +69,14 @@ export function handleEditorAction(
|
||||
initialDiffStyle?: unknown
|
||||
dataUrl?: string
|
||||
filename?: string
|
||||
id?: string
|
||||
paths?: string[]
|
||||
},
|
||||
opts: {
|
||||
dir: () => string
|
||||
diff?: DiffVirtualProvider
|
||||
storage?: vscode.Uri
|
||||
post?: (msg: unknown) => void
|
||||
},
|
||||
): boolean {
|
||||
if (message.type === "openFile") {
|
||||
@@ -83,6 +87,18 @@ export function handleEditorAction(
|
||||
if (message.content) openContent(message.content, message.language)
|
||||
return true
|
||||
}
|
||||
if (message.type === "validateFiles") {
|
||||
const id = message.id
|
||||
const paths = message.paths
|
||||
if (id && paths && opts.post) {
|
||||
const post = opts.post
|
||||
validateFiles(opts.dir(), paths).then(
|
||||
(existing) => post({ type: "validateFilesResult", id, existing }),
|
||||
(err) => console.error("[Kilo New] KiloProvider: validateFiles failed:", err),
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (message.type === "openExternal") {
|
||||
openExternal(message.url)
|
||||
return true
|
||||
@@ -105,6 +121,56 @@ function openContent(content: string, language?: string): void {
|
||||
)
|
||||
}
|
||||
|
||||
function show(uri: vscode.Uri, line?: number, column?: number): void {
|
||||
vscode.workspace.openTextDocument(uri).then(
|
||||
(doc) => {
|
||||
const options: vscode.TextDocumentShowOptions = { preview: true }
|
||||
if (line !== undefined && line > 0) {
|
||||
const col = column !== undefined && column > 0 ? column - 1 : 0
|
||||
const pos = new vscode.Position(line - 1, col)
|
||||
options.selection = new vscode.Range(pos, pos)
|
||||
}
|
||||
vscode.window
|
||||
.showTextDocument(doc, options)
|
||||
.then(undefined, (err) => console.error("[Kilo New] KiloProvider: Failed to show document:", uri.fsPath, err))
|
||||
},
|
||||
(err) => console.error("[Kilo New] KiloProvider: Failed to open file:", uri.fsPath, err),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback when the exact path does not exist: search the session directory by
|
||||
* filename. Opens the file directly on a single match, prompts on multiple,
|
||||
* warns on none. The search is scoped to `dir` (the active session's directory)
|
||||
* via a RelativePattern so it can't cross into another worktree/branch.
|
||||
*/
|
||||
function findFallback(dir: string, filePath: string, line?: number, column?: number): void {
|
||||
const name = filePath.split(/[\\/]/).pop() || filePath
|
||||
// VS Code globs don't honor backslash escapes, so bracket-escape metacharacters
|
||||
// (e.g. `[id].tsx`) instead — otherwise such names never match.
|
||||
const pattern = new vscode.RelativePattern(vscode.Uri.file(dir), `**/${escapeGlob(name)}`)
|
||||
Promise.resolve(vscode.workspace.findFiles(pattern, "**/node_modules/**", 5)).then(
|
||||
(matches) => {
|
||||
if (matches.length === 1) {
|
||||
show(matches[0], line, column)
|
||||
return
|
||||
}
|
||||
if (matches.length > 1) {
|
||||
const items = matches.map((m) => ({ label: vscode.workspace.asRelativePath(m), uri: m }))
|
||||
vscode.window.showQuickPick(items, { placeHolder: `Multiple matches for "${name}"` }).then(
|
||||
(pick) => {
|
||||
if (pick) show(pick.uri, line, column)
|
||||
},
|
||||
(err) => console.error("[Kilo New] KiloProvider: showQuickPick failed:", err),
|
||||
)
|
||||
return
|
||||
}
|
||||
vscode.window.showWarningMessage(`File not found: ${filePath}`)
|
||||
},
|
||||
(err: unknown) => console.error("[Kilo New] KiloProvider: findFiles failed:", err),
|
||||
)
|
||||
}
|
||||
|
||||
function openFile(dir: string, filePath: string, line?: number, column?: number): void {
|
||||
const uri = isAbsolutePath(filePath) ? vscode.Uri.file(filePath) : vscode.Uri.joinPath(vscode.Uri.file(dir), filePath)
|
||||
vscode.workspace.fs.stat(uri).then(
|
||||
@@ -113,19 +179,8 @@ function openFile(dir: string, filePath: string, line?: number, column?: number)
|
||||
vscode.commands.executeCommand("revealInExplorer", uri)
|
||||
return
|
||||
}
|
||||
vscode.workspace.openTextDocument(uri).then(
|
||||
(doc) => {
|
||||
const options: vscode.TextDocumentShowOptions = { preview: true }
|
||||
if (line !== undefined && line > 0) {
|
||||
const col = column !== undefined && column > 0 ? column - 1 : 0
|
||||
const pos = new vscode.Position(line - 1, col)
|
||||
options.selection = new vscode.Range(pos, pos)
|
||||
}
|
||||
vscode.window.showTextDocument(doc, options)
|
||||
},
|
||||
(err) => console.error("[Kilo New] KiloProvider: Failed to open file:", uri.fsPath, err),
|
||||
)
|
||||
show(uri, line, column)
|
||||
},
|
||||
(err) => console.error("[Kilo New] KiloProvider: Path does not exist:", uri.fsPath, err),
|
||||
() => findFallback(dir, filePath, line, column),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { realpath } from "node:fs/promises"
|
||||
import * as path from "node:path"
|
||||
import * as vscode from "vscode"
|
||||
import { contains } from "../path-utils"
|
||||
|
||||
/**
|
||||
* Stat-check candidate paths and return which ones are actual files (not directories).
|
||||
*
|
||||
* The webview marks every inline code span as a file-link candidate; this confirms
|
||||
* which of those candidates resolve to a real file so the webview can promote them
|
||||
* to clickable links and leave the rest as plain code.
|
||||
*
|
||||
* Containment is enforced twice so auto-validated model output can't probe host
|
||||
* files outside the session `root`:
|
||||
* 1. a lexical check rejects absolute paths elsewhere, UNC paths, and `../`
|
||||
* traversal before touching the filesystem at all;
|
||||
* 2. the candidate's real path (symlinks resolved) must still be inside the
|
||||
* real root, so a checked-in symlink can't escape the root either.
|
||||
*/
|
||||
export function validateFiles(root: string, paths: string[]): Promise<string[]> {
|
||||
return Promise.resolve(realpath(root)).then(
|
||||
(realRoot) => {
|
||||
const check = (p: string): Promise<string | null> => {
|
||||
if (!contains(root, p)) return Promise.resolve(null)
|
||||
return Promise.resolve(realpath(path.resolve(root, p))).then(
|
||||
(real) => {
|
||||
if (!contains(realRoot, real)) return null
|
||||
return Promise.resolve(vscode.workspace.fs.stat(vscode.Uri.file(real))).then(
|
||||
(s) => (s.type & vscode.FileType.File ? p : null),
|
||||
() => null,
|
||||
)
|
||||
},
|
||||
() => null,
|
||||
)
|
||||
}
|
||||
return Promise.all(paths.map(check)).then((r) => r.filter((x): x is string => x !== null))
|
||||
},
|
||||
() => [],
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import * as path from "node:path"
|
||||
|
||||
/**
|
||||
* Check whether a file path is absolute.
|
||||
*
|
||||
@@ -25,3 +27,29 @@ export function isAbsolutePath(filePath: string): boolean {
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether `candidate` resolves to a location inside `root`.
|
||||
*
|
||||
* Rejects UNC candidates, absolute paths outside the root, and `../` traversal
|
||||
* that escapes the root. Used to keep filesystem probes scoped to the trusted
|
||||
* session directory so model-generated paths can't reach arbitrary host files.
|
||||
*/
|
||||
export function contains(root: string, candidate: string): boolean {
|
||||
if (!root || !candidate) return false
|
||||
// UNC candidates can trigger outbound filesystem requests on Windows — never allow them.
|
||||
if (candidate.startsWith("\\\\") || candidate.startsWith("//")) return false
|
||||
const base = path.resolve(root)
|
||||
const rel = path.relative(base, path.resolve(base, candidate))
|
||||
return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel)
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape glob metacharacters so a literal filename can be embedded in a VS Code
|
||||
* glob pattern. VS Code globs do not honor backslash escapes, so each special
|
||||
* character is wrapped in a single-character bracket expression — e.g.
|
||||
* `[id].tsx` becomes `[[]id[]].tsx`.
|
||||
*/
|
||||
export function escapeGlob(name: string): string {
|
||||
return name.replace(/[*?{}[\]]/g, (c) => (c === "]" ? "[]]" : `[${c}]`))
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ export const dict = {
|
||||
"kilocode:autocomplete.incompatibilityExtensionPopup.disableCopilot": "Disable Copilot",
|
||||
"kilocode:autocomplete.incompatibilityExtensionPopup.disableInlineAssist": "Disable Autocomplete",
|
||||
"kilocode:autocomplete.creditsExhausted.message":
|
||||
"Kilo Code Autocomplete has been paused because your account has no remaining credits. Add credits to resume autocomplete.",
|
||||
"Kilo Code Autocomplete has been paused. Possible causes: your Kilo account has no remaining credits, or your configured API key (BYOK) has reached its quota limit. Add Kilo credits or check your API key configuration to resume autocomplete.",
|
||||
"kilocode:autocomplete.creditsExhausted.addCredits": "Add Credits",
|
||||
"kilocode:autocomplete.authError.message":
|
||||
"Kilo Code Autocomplete has been paused due to an authentication error. Please sign in again.",
|
||||
"Kilo Code Autocomplete has been paused due to an authentication issue. Possible causes: you are not signed in to Kilo, or your API key (BYOK) is invalid or missing. Please sign in again or check your provider API key settings.",
|
||||
}
|
||||
|
||||
@@ -41,6 +41,11 @@ export interface AgentContent {
|
||||
prompt: string
|
||||
options?: Record<string, unknown>
|
||||
permission?: Record<string, unknown>
|
||||
requirements?: {
|
||||
skills?: string[]
|
||||
mcps?: string[]
|
||||
vscode_extensions?: Array<{ name: string; id: string }>
|
||||
}
|
||||
}
|
||||
|
||||
export interface AgentMarketplaceItem extends MarketplaceItemBase {
|
||||
|
||||
@@ -183,6 +183,24 @@ test("selected favorite remains selected when its duplicate group is collapsed",
|
||||
await expect(combobox).toHaveAttribute("aria-activedescendant", await favorites.getAttribute("id"))
|
||||
})
|
||||
|
||||
test("large catalogs keep the rendered tree bounded and navigate to distant models", async ({ page }) => {
|
||||
await load(page, "shared--model-selector-large-catalog")
|
||||
|
||||
await page.getByRole("button", { name: "Select model: Provider 0 / Model 300" }).click()
|
||||
const combobox = page.getByRole("combobox", { name: "Select model: Provider 0 / Model 300. Search models" })
|
||||
const tree = page.getByRole("tree", { name: "Select model" })
|
||||
|
||||
// The window mounts before we measure it, yet stays far smaller than the catalog.
|
||||
await expect.poll(() => tree.getByRole("treeitem").count()).toBeGreaterThan(0)
|
||||
await expect.poll(() => tree.getByRole("treeitem").count()).toBeLessThan(50)
|
||||
|
||||
// Reaching a distant model scrolls it into the mounted window and activates it.
|
||||
await combobox.fill("Model 599")
|
||||
const last = page.getByRole("treeitem", { name: "Model 599" })
|
||||
await expect(last).toBeVisible()
|
||||
await expect(combobox).toHaveAttribute("aria-activedescendant", await last.getAttribute("id"))
|
||||
})
|
||||
|
||||
test("Enter selects the active option and Escape restores selector focus", async ({ page }) => {
|
||||
await load(page, "shared--model-selector-accessible")
|
||||
|
||||
@@ -252,6 +270,35 @@ test("settings and mode editing expose distinct model field purposes", async ({
|
||||
)
|
||||
})
|
||||
|
||||
test("mode picker focuses the selected mode as it opens", async ({ page }) => {
|
||||
await load(page, "prompt-input--default-420")
|
||||
|
||||
await page.getByRole("button", { name: "Code", exact: true }).click()
|
||||
await expect(page.locator(".mode-switcher-item.selected")).toBeFocused()
|
||||
})
|
||||
|
||||
test("variant picker focuses the selected effort as it opens", async ({ page }) => {
|
||||
await load(page, "prompt-input--with-thinking-420")
|
||||
|
||||
await page.getByRole("button", { name: "Medium", exact: true }).click()
|
||||
await expect(page.locator(".thinking-selector-item.selected")).toBeFocused()
|
||||
})
|
||||
|
||||
test("slash mode picker Escape returns focus to the prompt", async ({ page }) => {
|
||||
await load(page, "prompt-input--default-420")
|
||||
|
||||
const prompt = page.locator("textarea.prompt-input")
|
||||
await prompt.evaluate((el) => el.setAttribute("aria-disabled", "false"))
|
||||
await prompt.fill("/agents")
|
||||
await prompt.press("Enter")
|
||||
|
||||
const selected = page.locator(".mode-switcher-item.selected")
|
||||
await expect(selected).toBeFocused()
|
||||
await selected.press("Escape")
|
||||
|
||||
await expect(prompt).toBeFocused()
|
||||
})
|
||||
|
||||
test("chat picker Escape returns focus to the prompt", async ({ page }) => {
|
||||
await load(page, "prompt-input--default-420")
|
||||
|
||||
|
||||
@@ -211,6 +211,21 @@ test.describe("Permission Dock Dropdown — external directory", () => {
|
||||
await page.waitForSelector("#storybook-root *", { state: "attached" })
|
||||
await openDropdown(page)
|
||||
|
||||
const text = "Read External Directory /Users/developer/projects/kilo-bench/dashboard/app/routes/*"
|
||||
const hint = page.locator('[data-slot="permission-hint"]')
|
||||
await expect(hint).toHaveText(text)
|
||||
await expect(hint).toHaveAttribute("title", text)
|
||||
expect(
|
||||
await hint.evaluate((node) => node.scrollWidth > node.clientWidth || node.scrollHeight > node.clientHeight),
|
||||
).toBe(false)
|
||||
|
||||
const rule = page.locator('[data-slot="permission-rule"]')
|
||||
await expect(rule).toHaveText(text)
|
||||
await expect(rule).toHaveAttribute("title", text)
|
||||
expect(
|
||||
await rule.evaluate((node) => node.scrollWidth > node.clientWidth || node.scrollHeight > node.clientHeight),
|
||||
).toBe(false)
|
||||
|
||||
const root = page.locator("#storybook-root")
|
||||
await expect(root).toHaveScreenshot(["permission-dock-dropdown", "external-dir-expanded-pending.png"])
|
||||
})
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import type { KiloClient } from "@kilocode/sdk/v2/client"
|
||||
import {
|
||||
applyVSCodeExtensionRequirements,
|
||||
requirementDirectory,
|
||||
requirementKey,
|
||||
type BackendAgentRequirementResult,
|
||||
type HostAgentRequirementResult,
|
||||
} from "../../src/kilo-provider/agent-requirements"
|
||||
import { AgentRequirementsController } from "../../src/kilo-provider/agent-requirements-controller"
|
||||
import { keepAgentRequirementsResult } from "../../webview-ui/src/context/agent-requirements-state"
|
||||
|
||||
const root = "/repo"
|
||||
|
||||
function result(input: Partial<BackendAgentRequirementResult> = {}): BackendAgentRequirementResult {
|
||||
return {
|
||||
agent: "demo",
|
||||
directory: root,
|
||||
enabled: true,
|
||||
state: "ready",
|
||||
skills: [],
|
||||
mcps: [],
|
||||
vscode_extensions: [],
|
||||
...input,
|
||||
}
|
||||
}
|
||||
|
||||
function client(value: BackendAgentRequirementResult | (() => BackendAgentRequirementResult)) {
|
||||
const calls: Array<{ agent: string; directory: string }> = []
|
||||
const api = {
|
||||
kilocode: {
|
||||
agentRequirements: async (parameters: { agent: string; directory: string }) => {
|
||||
calls.push(parameters)
|
||||
return { data: typeof value === "function" ? value() : value }
|
||||
},
|
||||
},
|
||||
} as unknown as KiloClient
|
||||
return { calls, api }
|
||||
}
|
||||
|
||||
function controller(
|
||||
input: {
|
||||
api?: KiloClient | null
|
||||
posts?: unknown[]
|
||||
sessions?: ReadonlyMap<string, string>
|
||||
worktrees?: () => readonly string[]
|
||||
extensions?: ReadonlySet<string>
|
||||
subscribe?: (listener: () => void) => { dispose(): void }
|
||||
} = {},
|
||||
) {
|
||||
const posts = input.posts ?? []
|
||||
return new AgentRequirementsController({
|
||||
post: (message) => posts.push(message),
|
||||
client: () => input.api ?? null,
|
||||
connected: () => true,
|
||||
generation: () => 1,
|
||||
root: () => root,
|
||||
folders: () => [root, "/workspace"],
|
||||
project: () => root,
|
||||
sessions: () => input.sessions ?? new Map(),
|
||||
worktrees: input.worktrees,
|
||||
extension: (id) => input.extensions?.has(id),
|
||||
subscribe: input.subscribe,
|
||||
error: (error) => (error instanceof Error ? error.message : String(error)),
|
||||
})
|
||||
}
|
||||
|
||||
describe("agent requirement helpers", () => {
|
||||
it("builds stable cache keys from normalized directories", () => {
|
||||
expect(requirementKey("demo", "/repo/./subdir")).toBe(requirementKey("demo", "/repo/subdir"))
|
||||
expect(requirementKey("other", "/repo/subdir")).not.toBe(requirementKey("demo", "/repo/subdir"))
|
||||
})
|
||||
|
||||
it("scopes requests to active workspace, session, and worktree directories", () => {
|
||||
const sessions = new Map([["session-1", "/repo/.kilo/worktrees/one"]])
|
||||
const base = {
|
||||
workspaceDirectory: root,
|
||||
workspaceDirectories: ["/workspace"],
|
||||
projectDirectory: root,
|
||||
sessionDirectories: sessions,
|
||||
worktreeDirectories: () => ["/repo/.kilo/worktrees/two"],
|
||||
}
|
||||
|
||||
expect(requirementDirectory({ ...base, requested: "/repo" })).toBe(root)
|
||||
expect(requirementDirectory({ ...base, requested: "/repo/.kilo/worktrees/one", sessionID: "session-1" })).toBe(
|
||||
"/repo/.kilo/worktrees/one",
|
||||
)
|
||||
expect(requirementDirectory({ ...base, requested: "/repo/.kilo/worktrees/two" })).toBe("/repo/.kilo/worktrees/two")
|
||||
expect(requirementDirectory({ ...base, requested: "/other" })).toBeUndefined()
|
||||
})
|
||||
|
||||
it("adds VS Code extension statuses and blocks ready backend results when extensions are missing", () => {
|
||||
const checked = applyVSCodeExtensionRequirements(
|
||||
result({
|
||||
vscode_extensions: [
|
||||
{ name: "Ready Extension", id: "publisher.ready" },
|
||||
{ name: "Missing Extension", id: "publisher.missing" },
|
||||
],
|
||||
}),
|
||||
(id) => id === "publisher.ready",
|
||||
)
|
||||
|
||||
expect(checked.state).toBe("blocked")
|
||||
expect(checked.vscode_extensions).toEqual([
|
||||
{ name: "Ready Extension", id: "publisher.ready", status: "ready" },
|
||||
{ name: "Missing Extension", id: "publisher.missing", status: "missing" },
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("agent requirement webview state", () => {
|
||||
const blocked: HostAgentRequirementResult = {
|
||||
...result({ state: "blocked", skills: [{ name: "skill", status: "missing" }] }),
|
||||
vscode_extensions: [],
|
||||
}
|
||||
|
||||
it("preserves ready, blocked, and error results only for the same agent and directory", () => {
|
||||
expect(keepAgentRequirementsResult(blocked, "demo", root)).toBe(true)
|
||||
expect(keepAgentRequirementsResult({ ...blocked, state: "error" }, "demo", root)).toBe(true)
|
||||
expect(keepAgentRequirementsResult({ ...blocked, state: "ready" }, "demo", root)).toBe(true)
|
||||
expect(keepAgentRequirementsResult({ ...blocked, state: "disabled" }, "demo", root)).toBe(false)
|
||||
expect(keepAgentRequirementsResult(blocked, "other", root)).toBe(false)
|
||||
expect(keepAgentRequirementsResult(blocked, "demo", "/other")).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("AgentRequirementsController", () => {
|
||||
it("caches fetched results until invalidated", async () => {
|
||||
const posts: unknown[] = []
|
||||
const backend = client(result())
|
||||
const requirements = controller({ api: backend.api, posts })
|
||||
|
||||
await requirements.fetch({ agent: "demo", directory: root })
|
||||
await requirements.fetch({ agent: "demo", directory: root })
|
||||
requirements.clear()
|
||||
await requirements.fetch({ agent: "demo", directory: root })
|
||||
|
||||
expect(backend.calls).toEqual([
|
||||
{ agent: "demo", directory: root },
|
||||
{ agent: "demo", directory: root },
|
||||
])
|
||||
expect(
|
||||
posts.filter((message) => (message as { type?: string }).type === "agentRequirementsInvalidated"),
|
||||
).toHaveLength(1)
|
||||
})
|
||||
|
||||
it("posts scope mismatch errors for inactive directories", async () => {
|
||||
const posts: unknown[] = []
|
||||
const backend = client(result())
|
||||
const requirements = controller({ api: backend.api, posts })
|
||||
|
||||
await requirements.fetch({ agent: "demo", directory: "/other" })
|
||||
|
||||
expect(backend.calls).toEqual([])
|
||||
expect(posts.at(-1)).toMatchObject({
|
||||
type: "agentRequirementsLoaded",
|
||||
result: { state: "error", error: { code: "scope_mismatch" } },
|
||||
})
|
||||
})
|
||||
|
||||
it("clears requirements when the extension subscription fires", async () => {
|
||||
const posts: unknown[] = []
|
||||
let listener: (() => void) | undefined
|
||||
let disposed = false
|
||||
const backend = client(result())
|
||||
const requirements = controller({
|
||||
api: backend.api,
|
||||
posts,
|
||||
subscribe: (fn) => {
|
||||
listener = fn
|
||||
return { dispose: () => void (disposed = true) }
|
||||
},
|
||||
})
|
||||
|
||||
await requirements.fetch({ agent: "demo", directory: root })
|
||||
posts.length = 0
|
||||
listener?.()
|
||||
requirements.dispose()
|
||||
|
||||
expect(posts).toEqual([{ type: "agentRequirementsInvalidated" }])
|
||||
expect(disposed).toBe(true)
|
||||
})
|
||||
|
||||
it("asserts and reports blocked requirements before host sends", async () => {
|
||||
const posts: unknown[] = []
|
||||
const backend = client(
|
||||
result({
|
||||
vscode_extensions: [{ name: "Missing Extension", id: "publisher.missing" }],
|
||||
}),
|
||||
)
|
||||
const requirements = controller({ api: backend.api, posts })
|
||||
|
||||
await expect(requirements.assertAgentRequirements("demo", root)).rejects.toThrow(
|
||||
"VS Code extension Missing Extension",
|
||||
)
|
||||
expect(posts.at(-1)).toMatchObject({
|
||||
type: "agentRequirementsLoaded",
|
||||
result: { state: "blocked", vscode_extensions: [{ status: "missing" }] },
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -4,7 +4,9 @@ import * as os from "os"
|
||||
import * as path from "path"
|
||||
import { MarketplaceInstaller } from "../../src/services/marketplace/installer"
|
||||
import { MarketplacePaths } from "../../src/services/marketplace/paths"
|
||||
import type { AgentMarketplaceItem } from "../../src/services/marketplace/types"
|
||||
import { exec } from "../../src/util/process"
|
||||
import * as yaml from "yaml"
|
||||
|
||||
const tmpDir = path.join(os.tmpdir(), `kilo-test-${Date.now()}`)
|
||||
|
||||
@@ -32,6 +34,24 @@ function skill(content: string, id = "test-skill") {
|
||||
}
|
||||
}
|
||||
|
||||
function agent(content: AgentMarketplaceItem["content"], id = "test-agent"): AgentMarketplaceItem {
|
||||
return {
|
||||
type: "agent",
|
||||
id,
|
||||
name: "Test Agent",
|
||||
description: "test",
|
||||
category: "development",
|
||||
content,
|
||||
}
|
||||
}
|
||||
|
||||
async function frontmatter(file: string) {
|
||||
const content = await fs.readFile(file, "utf-8")
|
||||
const match = content.match(/^---\n([\s\S]*?)\n---/)
|
||||
expect(match).not.toBeNull()
|
||||
return yaml.parse(match?.[1] ?? "") as Record<string, unknown>
|
||||
}
|
||||
|
||||
async function archive(): Promise<Buffer> {
|
||||
const root = path.join(tmpDir, "archive")
|
||||
const source = path.join(root, "source")
|
||||
@@ -248,3 +268,27 @@ describe("MarketplaceInstaller skills", () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("MarketplaceInstaller agents", () => {
|
||||
it("preserves requirements in installed agent frontmatter", async () => {
|
||||
const installer = new MarketplaceInstaller(new TestPaths())
|
||||
const item = agent({
|
||||
mode: "all",
|
||||
description: "Requires local setup",
|
||||
prompt: "Use the available project tools.",
|
||||
requirements: {
|
||||
skills: ["project-skill"],
|
||||
mcps: ["project-mcp"],
|
||||
vscode_extensions: [{ name: "Project Helper", id: "publisher.project-helper" }],
|
||||
},
|
||||
})
|
||||
|
||||
const result = await installer.installAgent(item, "project", tmpDir)
|
||||
|
||||
expect(result.success).toBe(true)
|
||||
expect(result.filePath).toBeDefined()
|
||||
if (!result.filePath) throw new Error("agent install did not return a file path")
|
||||
const data = await frontmatter(result.filePath)
|
||||
expect(data.requirements).toEqual(item.content.requirements)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "bun:test"
|
||||
import { isAbsolutePath } from "../../src/path-utils"
|
||||
import { contains, escapeGlob, isAbsolutePath } from "../../src/path-utils"
|
||||
|
||||
describe("isAbsolutePath", () => {
|
||||
// ── Unix absolute paths ──────────────────────────────────────────────
|
||||
@@ -163,3 +163,47 @@ describe("isAbsolutePath", () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("contains", () => {
|
||||
it("accepts relative paths inside the root", () => {
|
||||
expect(contains("/work", "src/a.ts")).toBe(true)
|
||||
expect(contains("/work", "./src/a.ts")).toBe(true)
|
||||
expect(contains("/work", "a.ts")).toBe(true)
|
||||
})
|
||||
|
||||
it("rejects parent traversal that escapes the root", () => {
|
||||
expect(contains("/work", "../etc/passwd")).toBe(false)
|
||||
expect(contains("/work", "../../secret.ts")).toBe(false)
|
||||
})
|
||||
|
||||
it("rejects absolute paths outside the root", () => {
|
||||
expect(contains("/work", "/etc/passwd")).toBe(false)
|
||||
})
|
||||
|
||||
it("rejects UNC candidates", () => {
|
||||
expect(contains("/work", "\\\\server\\share\\file.ts")).toBe(false)
|
||||
expect(contains("/work", "//server/share/file.ts")).toBe(false)
|
||||
})
|
||||
|
||||
it("rejects empty inputs", () => {
|
||||
expect(contains("", "a.ts")).toBe(false)
|
||||
expect(contains("/work", "")).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe("escapeGlob", () => {
|
||||
it("bracket-escapes dynamic-route filenames", () => {
|
||||
expect(escapeGlob("[id].tsx")).toBe("[[]id[]].tsx")
|
||||
expect(escapeGlob("[...slug].tsx")).toBe("[[]...slug[]].tsx")
|
||||
})
|
||||
|
||||
it("escapes wildcard and brace metacharacters", () => {
|
||||
expect(escapeGlob("a*b?.ts")).toBe("a[*]b[?].ts")
|
||||
expect(escapeGlob("{x,y}.ts")).toBe("[{]x,y[}].ts")
|
||||
})
|
||||
|
||||
it("leaves ordinary filenames unchanged", () => {
|
||||
expect(escapeGlob("index.ts")).toBe("index.ts")
|
||||
expect(escapeGlob("file-path.test.ts")).toBe("file-path.test.ts")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -181,6 +181,7 @@ describe("describeRule", () => {
|
||||
|
||||
test("preserves existing permission rule labels", () => {
|
||||
expect(describeRule("read", "*", t)).toBe("Read")
|
||||
expect(describeRule("external_directory", "*", t)).toBe("Read External Directory")
|
||||
expect(describeRule("read", "src/app.ts", t)).toBe("Read src/app.ts")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -60,17 +60,21 @@ describe("movePromptDraft", () => {
|
||||
const text = new Map([[source, "Keep this prompt"]])
|
||||
const comments = new Map([[source, [comment]]])
|
||||
const images = new Map([[source, [image]]])
|
||||
const scrolls = new Map([[source, 128]])
|
||||
|
||||
expect(movePromptDraft({ text, comments, images }, source, target)).toEqual({
|
||||
expect(movePromptDraft({ text, comments, images, scrolls }, source, target)).toEqual({
|
||||
text: "Keep this prompt",
|
||||
comments: [comment],
|
||||
images: [image],
|
||||
scroll: 128,
|
||||
})
|
||||
expect(text.get(target)).toBe("Keep this prompt")
|
||||
expect(comments.get(target)).toEqual([comment])
|
||||
expect(images.get(target)).toEqual([image])
|
||||
expect(scrolls.get(target)).toBe(128)
|
||||
expect(text.has(source)).toBe(false)
|
||||
expect(comments.has(source)).toBe(false)
|
||||
expect(images.has(source)).toBe(false)
|
||||
expect(scrolls.has(source)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -41,7 +41,8 @@ describe("PromptInput sandbox toggle", () => {
|
||||
expect(toggle).toContain("sessionID,")
|
||||
expect(toggle).toContain("requestID,")
|
||||
expect(toggle).not.toContain("draftID:")
|
||||
expect(toggle).toContain("setSandboxTarget(sessionID ?? null)")
|
||||
expect(toggle).toContain('setSandboxRequests((current) => ({ ...current, [sessionID ?? ""]: requestID }))')
|
||||
expect(toggle).not.toContain("setSandboxTarget")
|
||||
expect(toggle).not.toContain('type: "updateConfig"')
|
||||
})
|
||||
|
||||
@@ -58,6 +59,26 @@ describe("PromptInput sandbox toggle", () => {
|
||||
expect(end).toBeGreaterThan(start)
|
||||
expect(save).toBeGreaterThan(-1)
|
||||
expect(move).toBeGreaterThan(save)
|
||||
expect(created).toContain("{ text: drafts, comments: reviewDrafts, images: imageDrafts, scrolls }")
|
||||
})
|
||||
|
||||
it("restores each prompt draft's textarea and highlight scroll positions", () => {
|
||||
expect(src).toContain("const scrolls = new Map<string, number>()")
|
||||
expect(src).toContain("const scroll = scrolls.get(key) ?? 0")
|
||||
expect(src).toContain("textareaRef.scrollTop = scroll")
|
||||
expect(src).toContain("if (highlightRef) highlightRef.scrollTop = scroll")
|
||||
expect(src).toContain("scrolls.set(draftKey(), textareaRef.scrollTop)")
|
||||
expect(src).toContain("images: imageAttach.images(),\n scroll: textareaRef?.scrollTop")
|
||||
expect(src).toContain("draft.text, draft.comments, draft.images, draft.scroll")
|
||||
})
|
||||
|
||||
it("tracks in-flight toggles per session while switching", () => {
|
||||
expect(src).toContain("const [sandboxRequests, setSandboxRequests] = createSignal<Record<string, string>>({})")
|
||||
expect(src).toContain('const sandboxRequest = (sessionID?: string) => sandboxRequests()[sessionID ?? ""]')
|
||||
expect(src).toContain("sandboxRequest(sandboxID()) !== undefined")
|
||||
expect(src).toContain("if (current[key] !== requestID) return current")
|
||||
expect(src).toContain("clearSandboxRequest(message.sessionID, message.requestID!)")
|
||||
expect(src).not.toContain("setSandboxTarget")
|
||||
})
|
||||
|
||||
it("keeps persisted sandbox state visible independently of the configured default", () => {
|
||||
@@ -70,11 +91,12 @@ describe("PromptInput sandbox toggle", () => {
|
||||
expect(src).toContain('if (!sandboxVisible()) hidden.add("sandbox")')
|
||||
expect(src).toContain("onToggle={toggleSandbox}")
|
||||
expect(src).toContain('message.type === "sandboxStatus"')
|
||||
expect(src).toContain("message.sessionID !== sandboxID() && !matching")
|
||||
expect(src).toContain("setSandboxState(state)")
|
||||
expect(src).toContain("message.requestID === sandboxRequest()")
|
||||
expect(src).toContain("const target = untrack(sandboxTarget)")
|
||||
expect(src).toContain("if (target !== undefined && target !== sessionID) clearSandboxRequest()")
|
||||
expect(src).not.toContain("message.sessionID !== sandboxID() && !matching")
|
||||
expect(src).toContain("const next = applySandboxStates(current, message)")
|
||||
expect(src).toContain("if (next !== current) setSandboxes(next)")
|
||||
expect(src).toContain("message.requestID === sandboxRequest(message.sessionID)")
|
||||
expect(src).toContain("if (message.sessionID === sandboxID())")
|
||||
expect(src).toContain("if (message.sessionID === sandboxID()) retrySandbox(message.sessionID)")
|
||||
expect(src).toContain("sandboxID() ? sandbox()?.enabled : sandboxDefault()?.enabled")
|
||||
expect(src).toContain('type: "requestSandboxDefault", agentManagerContext: ctx()')
|
||||
expect(src).toContain("<SandboxButtonBase")
|
||||
@@ -83,7 +105,7 @@ describe("PromptInput sandbox toggle", () => {
|
||||
expect(src).toContain("!sandboxReady()")
|
||||
expect(button).toContain("aria-pressed={props.enabled}")
|
||||
expect(button).toContain('class={`prompt-status-button ${props.enabled ? "prompt-status-button--active" : ""}`}')
|
||||
expect(src).toContain("if (sandboxRequest() && target === null) return")
|
||||
expect(src).toContain("if (sandboxRequest(undefined)) return")
|
||||
expect(src).not.toContain("if (state === current) return true")
|
||||
})
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
isQuestioning,
|
||||
isPathMention,
|
||||
applySandboxState,
|
||||
applySandboxStates,
|
||||
} from "../../webview-ui/src/components/chat/prompt-input-utils"
|
||||
|
||||
describe("applySandboxState", () => {
|
||||
@@ -43,6 +44,17 @@ describe("applySandboxState", () => {
|
||||
state(false, 6, "ses_1", "/worktree"),
|
||||
)
|
||||
})
|
||||
|
||||
it("caches independently ordered statuses for worktree switching", () => {
|
||||
const first = applySandboxStates({}, state(true, 5, "ses_1"))
|
||||
const second = applySandboxStates(first, state(false, 1, "ses_2"))
|
||||
|
||||
expect(second).toEqual({
|
||||
ses_1: state(true, 5, "ses_1"),
|
||||
ses_2: state(false, 1, "ses_2"),
|
||||
})
|
||||
expect(applySandboxStates(second, state(false, 4, "ses_1"))).toBe(second)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fileName", () => {
|
||||
|
||||
@@ -81,6 +81,7 @@ import { KiloEmbeddingModelsProvider } from "../src/context/kilo-embedding-model
|
||||
import { NotificationsProvider } from "../src/context/notifications"
|
||||
import { FeedbackProvider } from "../src/context/feedback"
|
||||
import { SessionProvider, useSession } from "../src/context/session"
|
||||
import { AgentRequirementsProvider } from "../src/context/agent-requirements"
|
||||
import { WorktreeModeProvider } from "../src/context/worktree-mode"
|
||||
import { ChatView } from "../src/components/chat"
|
||||
import HistoryView from "../src/components/history/HistoryView"
|
||||
@@ -3136,13 +3137,15 @@ export const AgentManagerApp: Component = () => {
|
||||
<KiloEmbeddingModelsProvider>
|
||||
<NotificationsProvider>
|
||||
<SessionProvider>
|
||||
<FeedbackProvider>
|
||||
<WorktreeModeProvider>
|
||||
<DataBridge>
|
||||
<AgentManagerContent />
|
||||
</DataBridge>
|
||||
</WorktreeModeProvider>
|
||||
</FeedbackProvider>
|
||||
<AgentRequirementsProvider>
|
||||
<FeedbackProvider>
|
||||
<WorktreeModeProvider>
|
||||
<DataBridge>
|
||||
<AgentManagerContent />
|
||||
</DataBridge>
|
||||
</WorktreeModeProvider>
|
||||
</FeedbackProvider>
|
||||
</AgentRequirementsProvider>
|
||||
</SessionProvider>
|
||||
</NotificationsProvider>
|
||||
</KiloEmbeddingModelsProvider>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ConfigProvider } from "./context/config"
|
||||
import { DisplayProvider } from "./context/display"
|
||||
import { WorkStyleProvider } from "./context/work-style"
|
||||
import { IndexingProvider } from "./context/indexing"
|
||||
import { AgentRequirementsProvider } from "./context/agent-requirements"
|
||||
import { SessionProvider, useSession } from "./context/session"
|
||||
import { LanguageBridge } from "./context/language-bridge"
|
||||
import { ChatView } from "./components/chat"
|
||||
@@ -149,6 +150,35 @@ export const DataBridge: Component<{ children: any }> = (props) => {
|
||||
vscode.postMessage({ type: "openContent", content, language })
|
||||
}
|
||||
|
||||
// File existence validation for code span candidates
|
||||
const pending = new Map<string, (existing: string[]) => void>()
|
||||
const counter = { n: 0 }
|
||||
const validateFiles = (paths: string[]): Promise<string[]> => {
|
||||
const id = `vf-${++counter.n}`
|
||||
return new Promise((resolve) => {
|
||||
pending.set(id, resolve)
|
||||
vscode.postMessage({ type: "validateFiles", id, paths })
|
||||
setTimeout(() => {
|
||||
if (pending.has(id)) {
|
||||
pending.delete(id)
|
||||
resolve([])
|
||||
}
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
const handler = (event: MessageEvent) => {
|
||||
const msg = event.data
|
||||
if (msg?.type === "validateFilesResult" && msg.id) {
|
||||
const cb = pending.get(msg.id)
|
||||
if (cb) {
|
||||
pending.delete(msg.id)
|
||||
cb(msg.existing ?? [])
|
||||
}
|
||||
}
|
||||
}
|
||||
onMount(() => window.addEventListener("message", handler))
|
||||
onCleanup(() => window.removeEventListener("message", handler))
|
||||
|
||||
const directory = () => {
|
||||
const dir = server.workspaceDirectory()
|
||||
if (!dir) return ""
|
||||
@@ -167,6 +197,7 @@ export const DataBridge: Component<{ children: any }> = (props) => {
|
||||
onOpenDiff={openDiff}
|
||||
onOpenUrl={openUrl}
|
||||
onOpenContent={openContent}
|
||||
onValidateFiles={validateFiles}
|
||||
>
|
||||
{props.children}
|
||||
</DataProvider>
|
||||
@@ -384,11 +415,13 @@ const App: Component = () => {
|
||||
<KiloEmbeddingModelsProvider>
|
||||
<NotificationsProvider>
|
||||
<SessionProvider>
|
||||
<FeedbackProvider>
|
||||
<DataBridge>
|
||||
<AppContent />
|
||||
</DataBridge>
|
||||
</FeedbackProvider>
|
||||
<AgentRequirementsProvider>
|
||||
<FeedbackProvider>
|
||||
<DataBridge>
|
||||
<AppContent />
|
||||
</DataBridge>
|
||||
</FeedbackProvider>
|
||||
</AgentRequirementsProvider>
|
||||
</SessionProvider>
|
||||
</NotificationsProvider>
|
||||
</KiloEmbeddingModelsProvider>
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
|
||||
import { For, Show, type Component } from "solid-js"
|
||||
import { Button } from "@kilocode/kilo-ui/button"
|
||||
import { Card } from "@kilocode/kilo-ui/card"
|
||||
import { Icon } from "@kilocode/kilo-ui/icon"
|
||||
import { useAgentRequirements } from "../../context/agent-requirements"
|
||||
import { useLanguage } from "../../context/language"
|
||||
import { useVSCode } from "../../context/vscode"
|
||||
import type { AgentRequirementMCP, AgentRequirementSkill, AgentRequirementVSCodeExtension } from "../../types/messages"
|
||||
|
||||
type Status = "ready" | "missing" | "error"
|
||||
|
||||
export const AgentRequirements: Component = () => {
|
||||
const requirements = useAgentRequirements()
|
||||
const language = useLanguage()
|
||||
const vscode = useVSCode()
|
||||
const result = requirements.result
|
||||
const skills = () => result()?.skills ?? []
|
||||
const mcps = () => result()?.mcps ?? []
|
||||
const extensions = () => result()?.vscode_extensions ?? []
|
||||
const total = () => skills().length + mcps().length + extensions().length
|
||||
const marketplace = () =>
|
||||
skills().some((item) => item.status !== "ready") || mcps().some((item) => item.status !== "ready")
|
||||
const tools = () => {
|
||||
if (skills().length && mcps().length) {
|
||||
return `${language.t("agentRequirements.group.skills")} / ${language.t("agentRequirements.group.mcps")}`
|
||||
}
|
||||
if (skills().length) return language.t("agentRequirements.group.skills")
|
||||
return language.t("agentRequirements.group.mcps")
|
||||
}
|
||||
const open = () => {
|
||||
const current = result()
|
||||
if (!current) return
|
||||
vscode.postMessage({ type: "openMarketplacePanel", directory: current.directory })
|
||||
}
|
||||
const title = () =>
|
||||
(result()?.agent ?? "")
|
||||
.split(/[-_]/)
|
||||
.filter(Boolean)
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(" ")
|
||||
|
||||
const error = () => {
|
||||
const err = result()?.error
|
||||
if (!err) return undefined
|
||||
if (err.code === "unknown_agent") return language.t("agentRequirements.error.unknownAgent")
|
||||
if (err.code === "malformed_declaration") return language.t("agentRequirements.error.malformedDeclaration")
|
||||
if (err.code === "discovery_failed") return language.t("agentRequirements.error.discoveryFailed")
|
||||
if (err.code === "mcp_status_failed") return language.t("agentRequirements.error.mcpStatusFailed")
|
||||
if (err.code === "scope_mismatch") return language.t("agentRequirements.error.scopeMismatch")
|
||||
return err.message || language.t("agentRequirements.error.requestFailed")
|
||||
}
|
||||
|
||||
const skill = (item: AgentRequirementSkill) => {
|
||||
if (item.message) return item.message
|
||||
if (item.status === "ready") return language.t("agentRequirements.skill.installed")
|
||||
if (item.status === "error") return language.t("agentRequirements.skill.checkFailed")
|
||||
return language.t("agentRequirements.skill.missing")
|
||||
}
|
||||
|
||||
const mcp = (item: AgentRequirementMCP) => {
|
||||
if (item.message) return item.message
|
||||
if (item.status === "ready") return language.t("agentRequirements.mcp.connected")
|
||||
if (item.status === "error") return language.t("agentRequirements.mcp.checkFailed")
|
||||
return language.t("agentRequirements.mcp.missing")
|
||||
}
|
||||
|
||||
const extension = (item: AgentRequirementVSCodeExtension) => {
|
||||
if (item.message) return item.message
|
||||
if (item.status === "ready") return language.t("agentRequirements.extension.installed")
|
||||
if (item.status === "error") return language.t("agentRequirements.extension.checkFailed")
|
||||
return language.t("agentRequirements.extension.missing")
|
||||
}
|
||||
|
||||
const StatusIcon = (props: { status: Status }) => (
|
||||
<span data-slot="agent-requirements-status" data-status={props.status} aria-hidden="true">
|
||||
<Show when={props.status === "ready"}>
|
||||
<Icon name="circle-check" size="small" />
|
||||
</Show>
|
||||
<Show when={props.status === "missing"}>
|
||||
<Icon name="warning" size="small" />
|
||||
</Show>
|
||||
<Show when={props.status === "error"}>
|
||||
<Icon name="circle-x" size="small" />
|
||||
</Show>
|
||||
</span>
|
||||
)
|
||||
|
||||
return (
|
||||
<div class="agent-requirements" role="status" aria-live="polite" aria-atomic="false">
|
||||
<Card class="agent-requirements-card">
|
||||
<div data-slot="agent-requirements-copy">
|
||||
<h2 data-slot="agent-requirements-title">
|
||||
{language.t("agentRequirements.blocked.title", { agent: title() })}
|
||||
</h2>
|
||||
<p data-slot="agent-requirements-description">{language.t("agentRequirements.blocked.description")}</p>
|
||||
</div>
|
||||
|
||||
<Show when={error()} keyed>
|
||||
{(message) => <div class="agent-requirements-error">{message}</div>}
|
||||
</Show>
|
||||
|
||||
<Show when={total()}>
|
||||
<div data-slot="agent-requirements-options">
|
||||
<Show when={skills().length || mcps().length}>
|
||||
<section class="agent-requirements-category">
|
||||
<h3 data-slot="agent-requirements-category-title">{tools()}</h3>
|
||||
<div data-slot="agent-requirements-category-content">
|
||||
<Show when={skills().length}>
|
||||
<div class="agent-requirements-group">
|
||||
<h4 data-slot="agent-requirements-group-title">{language.t("agentRequirements.group.skills")}</h4>
|
||||
<ul data-slot="agent-requirements-list">
|
||||
<For each={skills()}>
|
||||
{(item) => (
|
||||
<li class="agent-requirements-line" data-status={item.status}>
|
||||
<StatusIcon status={item.status} />
|
||||
<div data-slot="agent-requirements-line-row">
|
||||
<span data-slot="agent-requirements-line-name">{item.name}</span>
|
||||
<span data-slot="agent-requirements-line-detail">{skill(item)}</span>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={mcps().length}>
|
||||
<div class="agent-requirements-group">
|
||||
<h4 data-slot="agent-requirements-group-title">{language.t("agentRequirements.group.mcps")}</h4>
|
||||
<ul data-slot="agent-requirements-list">
|
||||
<For each={mcps()}>
|
||||
{(item) => (
|
||||
<li class="agent-requirements-line" data-status={item.status}>
|
||||
<StatusIcon status={item.status} />
|
||||
<div data-slot="agent-requirements-line-row">
|
||||
<span data-slot="agent-requirements-line-name">{item.name}</span>
|
||||
<span data-slot="agent-requirements-line-detail">{mcp(item)}</span>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={marketplace()}>
|
||||
<div class="agent-requirements-actions">
|
||||
<Button variant="primary" onClick={open}>
|
||||
{language.t("agentRequirements.action.openMarketplace")}
|
||||
</Button>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
|
||||
<Show when={extensions().length}>
|
||||
<section class="agent-requirements-category">
|
||||
<div data-slot="agent-requirements-category-copy">
|
||||
<h3 data-slot="agent-requirements-category-title">
|
||||
{language.t("agentRequirements.group.extensions")}
|
||||
</h3>
|
||||
<p data-slot="agent-requirements-category-description">
|
||||
{language.t("agentRequirements.extension.description")}
|
||||
</p>
|
||||
</div>
|
||||
<div data-slot="agent-requirements-category-content">
|
||||
<div class="agent-requirements-group">
|
||||
<ul data-slot="agent-requirements-list">
|
||||
<For each={extensions()}>
|
||||
{(item) => (
|
||||
<li class="agent-requirements-line" data-status={item.status}>
|
||||
<StatusIcon status={item.status} />
|
||||
<div data-slot="agent-requirements-line-row">
|
||||
<span data-slot="agent-requirements-line-name">
|
||||
{item.name} ({item.id})
|
||||
</span>
|
||||
<span data-slot="agent-requirements-line-detail">{extension(item)}</span>
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import { showToast } from "@kilocode/kilo-ui/toast"
|
||||
import { DropdownMenu } from "@kilocode/kilo-ui/dropdown-menu"
|
||||
import { TaskHeader } from "./TaskHeader"
|
||||
import { MessageList } from "./MessageList"
|
||||
import { AgentRequirements } from "./AgentRequirements"
|
||||
import { PromptInput } from "./PromptInput"
|
||||
import { PermissionDock } from "./PermissionDock"
|
||||
import { StartupErrorBanner } from "./StartupErrorBanner"
|
||||
@@ -22,6 +23,7 @@ import { useVSCode } from "../../context/vscode"
|
||||
import { useLanguage } from "../../context/language"
|
||||
import { useWorktreeMode } from "../../context/worktree-mode"
|
||||
import { useServer } from "../../context/server"
|
||||
import { useAgentRequirements } from "../../context/agent-requirements"
|
||||
import { isPromptBlocked, isSuggesting, isQuestioning } from "./prompt-input-utils"
|
||||
|
||||
interface ChatViewProps {
|
||||
@@ -43,6 +45,7 @@ export const ChatView: Component<ChatViewProps> = (props) => {
|
||||
const language = useLanguage()
|
||||
const worktreeMode = useWorktreeMode()
|
||||
const server = useServer()
|
||||
const requirements = useAgentRequirements()
|
||||
// Show "Show Changes" only in the standalone sidebar, not inside Agent Manager
|
||||
const isSidebar = () => worktreeMode === undefined
|
||||
// Show "Continue in Worktree": only when explicitly enabled via prop
|
||||
@@ -71,9 +74,11 @@ export const ChatView: Component<ChatViewProps> = (props) => {
|
||||
const standaloneQuestions = createMemo(() => familyQuestions().filter((q) => !q.tool))
|
||||
const standaloneSuggestions = createMemo(() => familySuggestions().filter((s) => !s.tool))
|
||||
const permissionRequest = () => familyPermissions().find((p) => p.sessionID === id()) ?? familyPermissions()[0]
|
||||
// Prompt input is decoupled from questions/suggestions — only permissions block.
|
||||
// Questions and suggestions do not block input; permissions and agent requirements do.
|
||||
// Pending questions and suggestions are auto-dismissed in sendMessage/sendCommand.
|
||||
const blocked = () => isPromptBlocked(familyPermissions().length)
|
||||
const blocked = () => isPromptBlocked(familyPermissions().length) || (!props.readonly && requirements.blocked())
|
||||
const requirementReason = () =>
|
||||
!props.readonly && requirements.blocked() ? language.t("agentRequirements.prompt.blocked") : undefined
|
||||
// Session is busy only because a suggestion tool call is pending — prompt should behave as idle
|
||||
const suggesting = () => isSuggesting(blocked(), familySuggestions().length)
|
||||
// Session is busy only because a question tool call is pending — prompt should behave as idle
|
||||
@@ -324,16 +329,23 @@ export const ChatView: Component<ChatViewProps> = (props) => {
|
||||
<TaskHeader readonly={props.readonly} />
|
||||
<div class="chat-messages-wrapper">
|
||||
<div class="chat-messages">
|
||||
<MessageList
|
||||
onSelectSession={props.onSelectSession}
|
||||
onShowHistory={props.onShowHistory}
|
||||
onForkMessage={props.onForkMessage}
|
||||
questions={standaloneQuestions}
|
||||
suggestions={standaloneSuggestions}
|
||||
readonly={props.readonly}
|
||||
emptyState={props.emptyState}
|
||||
announce={isSidebar()}
|
||||
/>
|
||||
<Show
|
||||
when={!props.readonly && requirements.visible()}
|
||||
fallback={
|
||||
<MessageList
|
||||
onSelectSession={props.onSelectSession}
|
||||
onShowHistory={props.onShowHistory}
|
||||
onForkMessage={props.onForkMessage}
|
||||
questions={standaloneQuestions}
|
||||
suggestions={standaloneSuggestions}
|
||||
readonly={props.readonly}
|
||||
emptyState={props.emptyState}
|
||||
announce={isSidebar()}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<AgentRequirements />
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -357,6 +369,7 @@ export const ChatView: Component<ChatViewProps> = (props) => {
|
||||
<Show when={!props.readonly}>
|
||||
<PromptInput
|
||||
blocked={blocked}
|
||||
blockedReason={requirementReason}
|
||||
suggesting={suggesting}
|
||||
questioning={questioning}
|
||||
boxId={props.promptBoxId}
|
||||
|
||||
@@ -48,6 +48,8 @@ export const PermissionDock: Component<{
|
||||
// Normalize IDN/Unicode hostnames to punycode ASCII to prevent homograph attacks.
|
||||
return normalizeUrls(cmd)
|
||||
}
|
||||
const text = (rule: string) => (command() ? label(rule) : describeRule(props.request.toolName, rule, language.t))
|
||||
const external = () => props.request.toolName === "external_directory"
|
||||
const cmdDescription = () => {
|
||||
const val = props.request.args?.description
|
||||
return typeof val === "string" && val.length > 0 ? val : undefined
|
||||
@@ -248,8 +250,13 @@ export const PermissionDock: Component<{
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<code data-slot="permission-rule">
|
||||
{command() ? label(rule) : describeRule(props.request.toolName, rule, language.t)}
|
||||
<code data-slot="permission-rule" data-wrap={external() ? "" : undefined} title={text(rule)}>
|
||||
<Show when={external() && rule !== "*"} fallback={text(rule)}>
|
||||
<span data-slot="permission-rule-label">
|
||||
{language.t("ui.permission.toolLabel.externalDirectory")}{" "}
|
||||
</span>
|
||||
<span data-slot="permission-rule-path">{rule}</span>
|
||||
</Show>
|
||||
</code>
|
||||
</div>
|
||||
)}
|
||||
@@ -268,7 +275,16 @@ export const PermissionDock: Component<{
|
||||
const desc = description()
|
||||
if (!desc)
|
||||
return !command() && toolDescription() ? <div data-slot="permission-hint">{toolDescription()}</div> : null
|
||||
if (desc.kind === "single") return <div data-slot="permission-hint">{desc.text}</div>
|
||||
if (desc.kind === "single")
|
||||
return (
|
||||
<div
|
||||
data-slot="permission-hint"
|
||||
data-wrap={external() ? "" : undefined}
|
||||
title={external() ? desc.text : undefined}
|
||||
>
|
||||
{desc.text}
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div data-slot="permission-patterns">
|
||||
<span data-slot="permission-patterns-title">{desc.title}</span>
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
insertSpacedText,
|
||||
isPromptBusy,
|
||||
isPathMention,
|
||||
applySandboxState,
|
||||
applySandboxStates,
|
||||
type SandboxDefaultState,
|
||||
type SandboxState,
|
||||
} from "./prompt-input-utils"
|
||||
@@ -65,6 +65,7 @@ import { isEnterKeyCommitNotIme } from "../../utils/ime-enter"
|
||||
const drafts = new Map<string, string>()
|
||||
const reviewDrafts = new Map<string, ReviewComment[]>()
|
||||
const imageDrafts = new Map<string, ImageAttachment[]>()
|
||||
const scrolls = new Map<string, number>()
|
||||
|
||||
function mergeReviewComments(current: ReviewComment[], incoming: ReviewComment[]): ReviewComment[] {
|
||||
if (incoming.length === 0) return current
|
||||
@@ -77,6 +78,7 @@ function mergeReviewComments(current: ReviewComment[], incoming: ReviewComment[]
|
||||
|
||||
interface PromptInputProps {
|
||||
blocked?: () => boolean
|
||||
blockedReason?: () => string | undefined
|
||||
/** When true, session is busy only because a suggestion is pending — treat as idle for input */
|
||||
suggesting?: () => boolean
|
||||
/** When true, session is busy only because a question is pending — treat as idle for input */
|
||||
@@ -125,35 +127,48 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
adjustHeight()
|
||||
})
|
||||
const history = usePromptHistory()
|
||||
let textareaRef: HTMLTextAreaElement | undefined
|
||||
let highlightRef: HTMLDivElement | undefined
|
||||
let dropdownRef: HTMLDivElement | undefined
|
||||
let slashDropdownRef: HTMLDivElement | undefined
|
||||
|
||||
const boxKey = () => props.boxId ?? "prompt:default"
|
||||
const blockedHelpId = () => `${boxKey().replace(/[^a-zA-Z0-9_-]/g, "-")}-blocked-help`
|
||||
const rawKey = () =>
|
||||
sessionDraftKey(session.currentSessionID()) ??
|
||||
pendingDraftKey(props.pendingSessionID ?? session.draftSessionID()) ??
|
||||
"new"
|
||||
const draftKey = () => scopeDraftKey(boxKey(), rawKey())
|
||||
const saveDraft = (key: string, next: string, comments: ReviewComment[], imgs: ImageAttachment[]) => {
|
||||
const saveDraft = (
|
||||
key: string,
|
||||
next: string,
|
||||
comments: ReviewComment[],
|
||||
imgs: ImageAttachment[],
|
||||
scroll = textareaRef?.scrollTop ?? scrolls.get(key) ?? 0,
|
||||
) => {
|
||||
if (next) drafts.set(key, next)
|
||||
else drafts.delete(key)
|
||||
if (comments.length > 0) reviewDrafts.set(key, comments)
|
||||
else reviewDrafts.delete(key)
|
||||
if (imgs.length > 0) imageDrafts.set(key, imgs)
|
||||
else imageDrafts.delete(key)
|
||||
if (next || comments.length > 0 || imgs.length > 0) scrolls.set(key, scroll)
|
||||
else scrolls.delete(key)
|
||||
}
|
||||
const readDraft = () => ({
|
||||
text: text().trim(),
|
||||
comments: reviewComments(),
|
||||
images: imageAttach.images(),
|
||||
scroll: textareaRef?.scrollTop ?? scrolls.get(draftKey()) ?? 0,
|
||||
})
|
||||
|
||||
const [text, setText] = createSignal("")
|
||||
const [reviewComments, setReviewComments] = createSignal<ReviewComment[]>([])
|
||||
const [enhancing, setEnhancing] = createSignal(false)
|
||||
const [autoApprove, setAutoApprove] = createSignal(false)
|
||||
const [sandboxState, setSandboxState] = createSignal<SandboxState>()
|
||||
const [sandboxes, setSandboxes] = createSignal<Record<string, SandboxState>>({})
|
||||
const [sandboxDefault, setSandboxDefault] = createSignal<SandboxDefaultState>()
|
||||
const [sandboxRequest, setSandboxRequest] = createSignal<string>()
|
||||
const [sandboxTarget, setSandboxTarget] = createSignal<string | null>()
|
||||
const [sandboxRequests, setSandboxRequests] = createSignal<Record<string, string>>({})
|
||||
let sandboxRetry: ReturnType<typeof setTimeout> | undefined
|
||||
let sandboxAttempts = 0
|
||||
const sandboxID = () => {
|
||||
@@ -162,16 +177,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
const sandboxVisible = () => features().sandboxControls && !session.currentSessionID()?.startsWith("cloud:")
|
||||
const sandbox = () => {
|
||||
const state = sandboxState()
|
||||
return state?.sessionID === sandboxID() ? state : undefined
|
||||
const id = sandboxID()
|
||||
return id ? sandboxes()[id] : undefined
|
||||
}
|
||||
const sandboxEnabled = () => (sandboxID() ? sandbox()?.enabled : sandboxDefault()?.enabled) ?? false
|
||||
const sandboxAvailable = () => (sandboxID() ? sandbox()?.available : sandboxDefault()?.available) ?? false
|
||||
const sandboxReason = () => (sandboxID() ? sandbox()?.reason : sandboxDefault()?.reason)
|
||||
const sandboxReady = () => (sandboxID() ? sandbox() !== undefined : sandboxDefault() !== undefined)
|
||||
const sandboxNetworkEnabled = () => config().experimental?.sandbox_restrict_network !== false
|
||||
const sandboxRequest = (sessionID?: string) => sandboxRequests()[sessionID ?? ""]
|
||||
const sandboxDisabled = () =>
|
||||
!server.isConnected() || !sandboxReady() || !sandboxAvailable() || sandboxRequest() !== undefined
|
||||
!server.isConnected() || !sandboxReady() || !sandboxAvailable() || sandboxRequest(sandboxID()) !== undefined
|
||||
const requestSandbox = () => {
|
||||
if (server.connectionState() !== "connected") return
|
||||
const sessionID = sandboxID()
|
||||
@@ -186,8 +202,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
if (!sandboxVisible() || sandboxDisabled()) return
|
||||
const requestID = crypto.randomUUID()
|
||||
if (!sessionID) saveDraft(draftKey(), text(), reviewComments(), imageAttach.images())
|
||||
setSandboxRequest(requestID)
|
||||
setSandboxTarget(sessionID ?? null)
|
||||
setSandboxRequests((current) => ({ ...current, [sessionID ?? ""]: requestID }))
|
||||
if (!sessionID) {
|
||||
vscode.postMessage({
|
||||
type: "setSandboxDefault",
|
||||
@@ -214,9 +229,14 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
return hidden
|
||||
},
|
||||
)
|
||||
const clearSandboxRequest = () => {
|
||||
setSandboxRequest(undefined)
|
||||
setSandboxTarget(undefined)
|
||||
const clearSandboxRequest = (sessionID: string | undefined, requestID: string) => {
|
||||
setSandboxRequests((current) => {
|
||||
const key = sessionID ?? ""
|
||||
if (current[key] !== requestID) return current
|
||||
const next = { ...current }
|
||||
delete next[key]
|
||||
return next
|
||||
})
|
||||
}
|
||||
const retrySandbox = (sessionID: string) => {
|
||||
if (sandboxAttempts >= 2) return
|
||||
@@ -233,20 +253,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
createEffect(() => {
|
||||
const sessionID = sandboxID()
|
||||
const connected = server.connectionState() === "connected"
|
||||
const target = untrack(sandboxTarget)
|
||||
if (sandboxRetry) clearTimeout(sandboxRetry)
|
||||
sandboxRetry = undefined
|
||||
sandboxAttempts = 0
|
||||
if (!connected) {
|
||||
clearSandboxRequest()
|
||||
setSandboxState(undefined)
|
||||
setSandboxRequests({})
|
||||
setSandboxes({})
|
||||
setSandboxDefault(undefined)
|
||||
return
|
||||
}
|
||||
if (target !== undefined && target !== sessionID) clearSandboxRequest()
|
||||
if (!sessionID) {
|
||||
setSandboxState(undefined)
|
||||
if (sandboxRequest() && target === null) return
|
||||
if (sandboxRequest(undefined)) return
|
||||
requestSandbox()
|
||||
return
|
||||
}
|
||||
@@ -271,10 +288,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
replaceReviewComments(reviewComments().filter((item) => item.id !== id))
|
||||
}
|
||||
|
||||
let textareaRef: HTMLTextAreaElement | undefined
|
||||
let highlightRef: HTMLDivElement | undefined
|
||||
let dropdownRef: HTMLDivElement | undefined
|
||||
let slashDropdownRef: HTMLDivElement | undefined
|
||||
// Save/restore input text when switching sessions.
|
||||
// Uses `on()` to track only draftKey — avoids re-running on every keystroke.
|
||||
createEffect(
|
||||
@@ -284,6 +297,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
const draft = drafts.get(key) ?? ""
|
||||
const pending = reviewDrafts.get(key) ?? []
|
||||
const scroll = scrolls.get(key) ?? 0
|
||||
setText(draft)
|
||||
setReviewComments(pending)
|
||||
imageAttach.replace(imageDrafts.get(key) ?? [])
|
||||
@@ -295,6 +309,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
// Reset height then adjust
|
||||
textareaRef.style.height = "auto"
|
||||
textareaRef.style.height = `${Math.min(textareaRef.scrollHeight, 200)}px`
|
||||
textareaRef.scrollTop = scroll
|
||||
if (highlightRef) highlightRef.scrollTop = scroll
|
||||
}
|
||||
window.dispatchEvent(new Event("focusPrompt"))
|
||||
}),
|
||||
@@ -347,10 +363,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const draft = text().trim()
|
||||
const comments = reviewComments()
|
||||
const imgs = imageAttach.images()
|
||||
const scroll = textareaRef?.scrollTop ?? 0
|
||||
session.clearCurrentSession()
|
||||
// After clearing, draftKey() points to the "new" bucket — save there
|
||||
// so the session-switch effect restores the prompt in the new-task view.
|
||||
saveDraft(draftKey(), draft, comments, imgs)
|
||||
saveDraft(draftKey(), draft, comments, imgs, scroll)
|
||||
}
|
||||
window.addEventListener("newTaskRequest", onNewTaskRequest)
|
||||
onCleanup(() => window.removeEventListener("newTaskRequest", onNewTaskRequest))
|
||||
@@ -372,7 +389,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
const draft = captured.get(id)
|
||||
captured.delete(id)
|
||||
if (!draft) return
|
||||
saveDraft(scopeDraftKey(box, sessionDraftKey(sid)), draft.text, draft.comments, draft.images)
|
||||
saveDraft(scopeDraftKey(box, sessionDraftKey(sid)), draft.text, draft.comments, draft.images, draft.scroll)
|
||||
}
|
||||
window.addEventListener("agentManagerApplyDraft", onAgentManagerApplyDraft)
|
||||
onCleanup(() => window.removeEventListener("agentManagerApplyDraft", onAgentManagerApplyDraft))
|
||||
@@ -421,6 +438,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
!props.blocked?.() &&
|
||||
(speech.state() === "recording" || (hasInput() && !speech.active()))
|
||||
const sendLabel = () => {
|
||||
const reason = props.blockedReason?.()
|
||||
if (reason) return reason
|
||||
if (props.blocked?.()) return language.t("prompt.action.send.blocked")
|
||||
if (speech.state() === "recording") return language.t("prompt.action.send.recording")
|
||||
return language.t("prompt.action.send")
|
||||
@@ -486,10 +505,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
|
||||
const handleSandboxMessage = (message: ExtensionMessage) => {
|
||||
if (message.type === "sandboxDefaultStatus") {
|
||||
const matching = message.requestID !== undefined && message.requestID === sandboxRequest()
|
||||
const matching = message.requestID !== undefined && message.requestID === sandboxRequest(undefined)
|
||||
if (sandboxID() && !matching) return false
|
||||
if (!server.isConnected()) return true
|
||||
if (matching) clearSandboxRequest()
|
||||
if (matching) clearSandboxRequest(undefined, message.requestID!)
|
||||
const current = sandboxDefault()
|
||||
if (!current || current.revision <= message.revision) {
|
||||
setSandboxDefault({
|
||||
@@ -511,16 +530,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
|
||||
if (message.type === "sandboxStatus") {
|
||||
const matching = message.requestID !== undefined && message.requestID === sandboxRequest()
|
||||
if (message.sessionID !== sandboxID() && !matching) return false
|
||||
const matching = message.requestID !== undefined && message.requestID === sandboxRequest(message.sessionID)
|
||||
if (!server.isConnected()) return true
|
||||
const current = sandboxState()
|
||||
if (matching) clearSandboxRequest()
|
||||
const state = applySandboxState(current, message)
|
||||
if (state !== current) setSandboxState(state)
|
||||
sandboxAttempts = 0
|
||||
if (sandboxRetry) clearTimeout(sandboxRetry)
|
||||
sandboxRetry = undefined
|
||||
const current = sandboxes()
|
||||
if (matching) clearSandboxRequest(message.sessionID, message.requestID!)
|
||||
const next = applySandboxStates(current, message)
|
||||
if (next !== current) setSandboxes(next)
|
||||
const state = next[message.sessionID]
|
||||
if (message.sessionID === sandboxID()) {
|
||||
sandboxAttempts = 0
|
||||
if (sandboxRetry) clearTimeout(sandboxRetry)
|
||||
sandboxRetry = undefined
|
||||
}
|
||||
if (matching && !state.available) {
|
||||
showToast({
|
||||
variant: "error",
|
||||
@@ -532,24 +553,26 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
|
||||
if (message.type === "sandboxStatusError") {
|
||||
const matching = message.requestID !== undefined && message.requestID === sandboxRequest()
|
||||
if (message.sessionID !== sandboxID() && !matching) return false
|
||||
const matching = message.requestID !== undefined && message.requestID === sandboxRequest(message.sessionID)
|
||||
if (!server.isConnected()) return true
|
||||
const current = sandboxState()
|
||||
if (matching) clearSandboxRequest()
|
||||
if ((current?.revision ?? -1) > message.revision) return true
|
||||
const current = sandboxes()
|
||||
const state = current[message.sessionID]
|
||||
if (matching) clearSandboxRequest(message.sessionID, message.requestID!)
|
||||
if ((state?.revision ?? -1) > message.revision) return true
|
||||
if (!message.requestID) {
|
||||
const same = current?.sessionID === message.sessionID && current.directory === message.directory
|
||||
setSandboxState({
|
||||
sessionID: message.sessionID,
|
||||
directory: message.directory,
|
||||
enabled: same ? current.enabled : false,
|
||||
available: false,
|
||||
reason: message.message,
|
||||
version: same ? current.version : 0,
|
||||
revision: message.revision,
|
||||
})
|
||||
retrySandbox(message.sessionID)
|
||||
const same = state?.directory === message.directory
|
||||
setSandboxes(
|
||||
applySandboxStates(current, {
|
||||
sessionID: message.sessionID,
|
||||
directory: message.directory,
|
||||
enabled: same ? state.enabled : false,
|
||||
available: false,
|
||||
reason: message.message,
|
||||
version: same ? state.version : 0,
|
||||
revision: message.revision,
|
||||
}),
|
||||
)
|
||||
if (message.sessionID === sandboxID()) retrySandbox(message.sessionID)
|
||||
}
|
||||
if (matching) {
|
||||
showToast({
|
||||
@@ -614,12 +637,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
|
||||
if (message.type === "sessionCreated") {
|
||||
const raw = createdDraftKey(message.draftID, sandboxRequest() !== undefined && sandboxTarget() === null)
|
||||
const raw = createdDraftKey(message.draftID, sandboxRequest(undefined) !== undefined)
|
||||
if (raw) {
|
||||
const source = scopeDraftKey(boxKey(), raw)
|
||||
const target = scopeDraftKey(boxKey(), sessionDraftKey(message.session.id))
|
||||
if (source === draftKey()) saveDraft(source, text(), reviewComments(), imageAttach.images())
|
||||
movePromptDraft({ text: drafts, comments: reviewDrafts, images: imageDrafts }, source, target)
|
||||
movePromptDraft({ text: drafts, comments: reviewDrafts, images: imageDrafts, scrolls }, source, target)
|
||||
}
|
||||
if (
|
||||
message.draftID &&
|
||||
@@ -696,9 +719,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}
|
||||
|
||||
const syncHighlightScroll = () => {
|
||||
if (highlightRef && textareaRef) {
|
||||
highlightRef.scrollTop = textareaRef.scrollTop
|
||||
}
|
||||
if (!textareaRef) return
|
||||
scrolls.set(draftKey(), textareaRef.scrollTop)
|
||||
if (highlightRef) highlightRef.scrollTop = textareaRef.scrollTop
|
||||
}
|
||||
|
||||
const adjustHeight = () => {
|
||||
@@ -918,6 +941,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
drafts.delete(draftKey())
|
||||
reviewDrafts.delete(draftKey())
|
||||
imageDrafts.delete(draftKey())
|
||||
scrolls.delete(draftKey())
|
||||
if (textareaRef) textareaRef.style.height = "auto"
|
||||
matched.action()
|
||||
return
|
||||
@@ -978,6 +1002,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
drafts.delete(key)
|
||||
reviewDrafts.delete(key)
|
||||
imageDrafts.delete(key)
|
||||
scrolls.delete(key)
|
||||
if (draftKey() !== key) return
|
||||
|
||||
history.append(draft)
|
||||
@@ -1191,10 +1216,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
}}
|
||||
onScroll={syncHighlightScroll}
|
||||
aria-disabled={isDisabled()}
|
||||
aria-describedby={props.blockedReason?.() ? blockedHelpId() : undefined}
|
||||
rows={1}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={props.blockedReason?.()} keyed>
|
||||
{(reason) => (
|
||||
<span id={blockedHelpId()} class="sr-only" role="status">
|
||||
{reason}
|
||||
</span>
|
||||
)}
|
||||
</Show>
|
||||
<div class="prompt-input-hint">
|
||||
<div class="prompt-input-hint-selectors">
|
||||
<ModeSwitcher sessionID={sid} />
|
||||
@@ -1299,6 +1332,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
size="small"
|
||||
onClick={handleSendClick}
|
||||
aria-disabled={!canSend()}
|
||||
aria-describedby={props.blockedReason?.() ? blockedHelpId() : undefined}
|
||||
aria-label={sendLabel()}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
||||
|
||||
@@ -25,6 +25,13 @@ export function applySandboxState(current: SandboxState | undefined, next: Sandb
|
||||
return next
|
||||
}
|
||||
|
||||
export function applySandboxStates(current: Record<string, SandboxState>, next: SandboxState) {
|
||||
const previous = current[next.sessionID]
|
||||
const state = applySandboxState(previous, next)
|
||||
if (state === previous) return current
|
||||
return { ...current, [next.sessionID]: state }
|
||||
}
|
||||
|
||||
export function fileName(path: string): string {
|
||||
const normalized = path.replaceAll("\\", "/").replace(/\/+$/, "")
|
||||
return normalized.split("/").pop() ?? normalized
|
||||
|
||||
@@ -44,9 +44,15 @@ export const ModeSwitcherBase: Component<ModeSwitcherBaseProps> = (props) => {
|
||||
const [focused, setFocused] = createSignal(-1)
|
||||
const language = useLanguage()
|
||||
let listRef: HTMLDivElement | undefined
|
||||
// True while the picker was opened by the slash command rather than a click,
|
||||
// so dismissal returns focus to the prompt like the model/variant pickers.
|
||||
let slash = false
|
||||
|
||||
// Listen for slash command trigger
|
||||
const onTrigger = () => setOpen(true)
|
||||
const onTrigger = () => {
|
||||
slash = true
|
||||
openSelected()
|
||||
}
|
||||
window.addEventListener("openModePicker", onTrigger)
|
||||
onCleanup(() => window.removeEventListener("openModePicker", onTrigger))
|
||||
|
||||
@@ -65,11 +71,23 @@ export const ModeSwitcherBase: Component<ModeSwitcherBaseProps> = (props) => {
|
||||
items[clamped]?.focus()
|
||||
}
|
||||
|
||||
function openSelected() {
|
||||
const idx = props.agents.findIndex((a) => a.name === props.value)
|
||||
setFocused(idx >= 0 ? idx : 0)
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
function onOpen(val: boolean) {
|
||||
setOpen(val)
|
||||
if (val) {
|
||||
const idx = props.agents.findIndex((a) => a.name === props.value)
|
||||
requestAnimationFrame(() => focusItem(idx >= 0 ? idx : 0))
|
||||
// A click on the trigger opens without the slash flag.
|
||||
slash = false
|
||||
openSelected()
|
||||
return
|
||||
}
|
||||
setOpen(false)
|
||||
if (slash) {
|
||||
slash = false
|
||||
requestAnimationFrame(() => window.dispatchEvent(new CustomEvent("focusPrompt", { detail: { restore: true } })))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +153,7 @@ export const ModeSwitcherBase: Component<ModeSwitcherBaseProps> = (props) => {
|
||||
role="option"
|
||||
aria-selected={agent.name === props.value}
|
||||
tabindex={focused() === i() ? 0 : -1}
|
||||
data-autofocus={focused() === i() ? "" : undefined}
|
||||
onClick={() => pick(agent.name)}
|
||||
onFocus={() => setFocused(i())}
|
||||
>
|
||||
|
||||
@@ -13,13 +13,13 @@ import {
|
||||
createEffect,
|
||||
createUniqueId,
|
||||
onCleanup,
|
||||
For,
|
||||
Show,
|
||||
createSelector,
|
||||
useContext,
|
||||
untrack,
|
||||
} from "solid-js"
|
||||
import type { Accessor, Component } from "solid-js"
|
||||
import { Virtualizer, type VirtualizerHandle } from "virtua/solid"
|
||||
import { PopupSelector } from "./PopupSelector"
|
||||
import { Button } from "@kilocode/kilo-ui/button"
|
||||
import { IconButton } from "@kilocode/kilo-ui/icon-button"
|
||||
@@ -94,7 +94,9 @@ interface ModelNode {
|
||||
|
||||
interface ScrollAnchor {
|
||||
key: string
|
||||
top: number | undefined
|
||||
// Virtual content offset of the anchored row before the list mutates.
|
||||
// Works even when the row is scrolled out of the mounted window.
|
||||
offset: number | undefined
|
||||
scroll: number
|
||||
}
|
||||
|
||||
@@ -144,11 +146,11 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
|
||||
const previewID = `${uid}-preview`
|
||||
const descriptionID = `${uid}-description`
|
||||
const optionID = (key: string) => `${uid}-option-${encodeURIComponent(key)}`
|
||||
const activeModel = () => {
|
||||
const activeModel = createMemo(() => {
|
||||
const items = props.models
|
||||
if (items) return items.find((m) => m.providerID === props.value?.providerID && m.id === props.value?.modelID)
|
||||
return findModel(props.value)
|
||||
}
|
||||
})
|
||||
|
||||
const [open, setOpen] = createSignal(false)
|
||||
const [expanded, setExpanded] = createSignal(true)
|
||||
@@ -175,10 +177,8 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
|
||||
let listRef: HTMLDivElement | undefined
|
||||
let bodyRef: HTMLDivElement | undefined
|
||||
let previewTimer: ReturnType<typeof setTimeout> | undefined
|
||||
const [virtualizer, setVirtualizer] = createSignal<VirtualizerHandle>()
|
||||
const [pointer, setPointer] = createSignal(true)
|
||||
// Ref map: row key → DOM element. Populated by each row's ref callback,
|
||||
// avoids DOM queries for scroll anchoring and scrollIntoView.
|
||||
const refs = new Map<string, HTMLElement>()
|
||||
|
||||
function onSplitterMouseDown(e: MouseEvent) {
|
||||
e.preventDefault()
|
||||
@@ -382,6 +382,13 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
|
||||
const nodeMap = createMemo(() => new Map(nodes().map((node) => [node.key, node] as const)))
|
||||
const nodeIndex = createMemo(() => new Map(nodes().map((node, i) => [node.key, i] as const)))
|
||||
const rowMap = createMemo(() => new Map(rows().map((row) => [row.key, row] as const)))
|
||||
const mounted = createMemo(() => {
|
||||
const map = nodeIndex()
|
||||
const indexes = [selectedKey(), preActiveKey(), previewKey()]
|
||||
.map((key) => (key ? map.get(key) : undefined))
|
||||
.filter((idx): idx is number => idx !== undefined)
|
||||
return [...new Set(indexes)]
|
||||
})
|
||||
const canonicalKey = (m: EnrichedModel) => rowKey("model", m.providerID, m.id)
|
||||
const favoriteKey = (m: EnrichedModel) => rowKey("favorite", m.providerID, m.id)
|
||||
const defaultKey = () => nodes()[0]?.key ?? CLEAR_KEY
|
||||
@@ -421,18 +428,17 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
|
||||
|
||||
createEffect(() => {
|
||||
const saved = anchor()
|
||||
rows()
|
||||
if (!saved || !listRef) return
|
||||
nodes()
|
||||
if (!saved) return
|
||||
requestAnimationFrame(() => {
|
||||
if (!listRef) {
|
||||
setAnchor(null)
|
||||
return
|
||||
}
|
||||
const el = refs.get(saved.key)
|
||||
if (el && saved.top !== undefined) {
|
||||
listRef.scrollTop += el.getBoundingClientRect().top - saved.top
|
||||
const handle = virtualizer()
|
||||
const idx = nodeIndex().get(saved.key)
|
||||
if (handle && idx !== undefined && saved.offset !== undefined) {
|
||||
// Keep the anchored row pinned to the same viewport position after
|
||||
// rows above it appear or disappear, even while it is virtualized out.
|
||||
handle.scrollTo(handle.getItemOffset(idx) - (saved.offset - saved.scroll))
|
||||
} else {
|
||||
listRef.scrollTop = saved.scroll
|
||||
handle?.scrollTo(saved.scroll)
|
||||
}
|
||||
setAnchor(null)
|
||||
})
|
||||
@@ -543,7 +549,11 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
|
||||
}
|
||||
|
||||
function scrollRow(key: string | null | undefined, block: ScrollLogicalPosition = "nearest") {
|
||||
if (key) refs.get(key)?.scrollIntoView({ block })
|
||||
if (!key) return
|
||||
const idx = nodeIndex().get(key)
|
||||
if (idx === undefined) return
|
||||
const align = block === "center" || block === "start" || block === "end" ? block : "nearest"
|
||||
virtualizer()?.scrollToIndex(idx, { align })
|
||||
}
|
||||
|
||||
function activate(key: string) {
|
||||
@@ -609,10 +619,10 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
|
||||
// disappear — anchor to the canonical provider row instead so the
|
||||
// scroll restore finds an element that still exists after rerender.
|
||||
const key = row.kind === "favorite" ? canon : row.key
|
||||
const el = refs.get(key)
|
||||
const idx = nodeIndex().get(key)
|
||||
setAnchor({
|
||||
key,
|
||||
top: el?.getBoundingClientRect().top,
|
||||
offset: idx !== undefined ? virtualizer()?.getItemOffset(idx) : undefined,
|
||||
scroll: listRef?.scrollTop ?? 0,
|
||||
})
|
||||
if (row.kind === "favorite") {
|
||||
@@ -837,198 +847,186 @@ export const ModelSelectorBase: Component<ModelSelectorBaseProps> = (props) => {
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={props.allowClear}>
|
||||
<div
|
||||
id={optionID(CLEAR_KEY)}
|
||||
ref={(el) => {
|
||||
refs.set(CLEAR_KEY, el)
|
||||
onCleanup(() => refs.delete(CLEAR_KEY))
|
||||
}}
|
||||
class={`model-selector-item${isSelected(CLEAR_KEY) && !pointer() ? " keyboard-focused" : ""}${isSelected(CLEAR_KEY) ? " selected" : ""}${!props.value?.providerID ? " active" : ""}`}
|
||||
role="treeitem"
|
||||
aria-selected={!props.value?.providerID}
|
||||
onClick={() => pickClear()}
|
||||
onMouseMove={() => {
|
||||
setPointer(true)
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
if (pointer()) setSelectedKey(CLEAR_KEY)
|
||||
}}
|
||||
>
|
||||
<span class="model-selector-item-name" style={{ "font-style": "italic", opacity: 0.7 }}>
|
||||
{props.clearLabel ?? language.t("dialog.model.notSet")}
|
||||
</span>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<For each={groups()}>
|
||||
{(group) => {
|
||||
const shown = () => isGroupOpen(group.key)
|
||||
return (
|
||||
<div class="model-selector-group" role="presentation">
|
||||
<div
|
||||
id={optionID(groupKey(group.key))}
|
||||
ref={(el) => {
|
||||
refs.set(groupKey(group.key), el)
|
||||
onCleanup(() => refs.delete(groupKey(group.key)))
|
||||
}}
|
||||
class={`model-selector-group-label${isSelected(groupKey(group.key)) ? " selected" : ""}${isSelected(groupKey(group.key)) && !pointer() ? " keyboard-focused" : ""}`}
|
||||
role="treeitem"
|
||||
aria-expanded={shown()}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => toggleGroup(group.key)}
|
||||
onMouseMove={() => setPointer(true)}
|
||||
onMouseEnter={() => {
|
||||
if (pointer()) setSelectedKey(groupKey(group.key))
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
class={`model-selector-group-chevron${shown() ? "" : " model-selector-group-chevron--collapsed"}`}
|
||||
width="10"
|
||||
height="10"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M4 6l4 5 4-5H4z" />
|
||||
</svg>
|
||||
<span>{group.label}</span>
|
||||
<Show when={!shown() && !!debouncedSearch()}>
|
||||
<span class="model-selector-group-match-dot" aria-hidden="true" />
|
||||
</Show>
|
||||
</div>
|
||||
<Show when={shown()}>
|
||||
<div role="group" aria-label={group.label}>
|
||||
<For each={group.rows}>
|
||||
{(row) => {
|
||||
if (!row.model) return null
|
||||
const model = row.model
|
||||
const hovered = () => isSelected(row.key)
|
||||
const preActive = () => isPreActive(row.key)
|
||||
const starred = () => favoriteKeys().has(modelKey(model.providerID, model.id))
|
||||
const showProvider = () => row.kind === "favorite"
|
||||
const showSelect = () => expanded() && preActive() && !isActive(model)
|
||||
const starLabel = () =>
|
||||
`${starred() ? language.t("model.favorite.remove") : language.t("model.favorite.add")}: ${sanitizeName(model.name)}`
|
||||
return (
|
||||
<div
|
||||
role="presentation"
|
||||
class={`model-selector-row${hovered() || preActive() ? " selected" : ""}`}
|
||||
>
|
||||
<div
|
||||
id={optionID(row.key)}
|
||||
ref={(el) => {
|
||||
refs.set(row.key, el)
|
||||
onCleanup(() => refs.delete(row.key))
|
||||
}}
|
||||
class={`model-selector-item${(hovered() && !pointer()) || preActive() ? " keyboard-focused" : ""}${hovered() || preActive() ? " selected" : ""}${chosen(row) ? " active" : ""}`}
|
||||
role="treeitem"
|
||||
aria-selected={chosen(row)}
|
||||
onClick={() => {
|
||||
if (!expanded()) {
|
||||
selectRow(row)
|
||||
return
|
||||
}
|
||||
setRow(row.key)
|
||||
setPreviewKey(row.key)
|
||||
searchRef?.focus()
|
||||
}}
|
||||
onDblClick={() => {
|
||||
if (expanded()) selectRow(row)
|
||||
}}
|
||||
onMouseMove={() => {
|
||||
setPointer(true)
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
if (pointer()) setSelectedKey(row.key)
|
||||
}}
|
||||
>
|
||||
<div class="model-selector-item-left">
|
||||
<span class="model-selector-item-name">
|
||||
{(() => {
|
||||
const full = sanitizeName(model.name)
|
||||
const sep = full.indexOf(": ")
|
||||
if (sep < 0)
|
||||
return <span class="model-selector-item-name-main">{full}</span>
|
||||
return (
|
||||
<>
|
||||
<span class="model-selector-item-name-provider">
|
||||
{full.slice(0, sep)}
|
||||
</span>
|
||||
<span class="model-selector-item-name-main">{full.slice(sep + 2)}</span>
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
</span>
|
||||
<Show when={isAuto(model)}>
|
||||
<Tooltip value={autoLabel(model)} placement="top">
|
||||
<span class="model-selector-auto-icon" aria-label={autoLabel(model)}>
|
||||
<Icon name="branch" size="small" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
<Show when={isFree(model) || hasByok(model) || isDataCollectedModel(model)}>
|
||||
<span class="model-selector-free-data">
|
||||
<Show when={isFree(model) && !hasByok(model)}>
|
||||
<span class="model-selector-data-badge">
|
||||
<Tag data-variant="member">{freeLabel()}</Tag>
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={hasByok(model)}>
|
||||
<span class="model-selector-data-badge model-selector-data-badge--byok">
|
||||
<Tag data-variant="member">BYOK</Tag>
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={isDataCollectedModel(model)}>
|
||||
<Tooltip value={dataLabel()} placement="top">
|
||||
<span class="model-selector-free-data-icon" aria-label={dataLabel()}>
|
||||
<Icon name="book-open-check" size="small" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={showProvider()}>
|
||||
<span class="model-selector-item-provider-tag">{model.providerName}</span>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={session && props.favorites !== false}>
|
||||
<button
|
||||
type="button"
|
||||
class={`model-selector-star${starred() ? " model-selector-star--active" : ""}`}
|
||||
aria-label={starLabel()}
|
||||
aria-pressed={starred()}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
toggleFavorite(model, row)
|
||||
searchRef?.focus()
|
||||
}}
|
||||
>
|
||||
<Icon name={starred() ? "star-filled" : "star"} size="small" />
|
||||
</button>
|
||||
</Show>
|
||||
<Show when={showSelect()}>
|
||||
<button
|
||||
type="button"
|
||||
class="model-selector-item-select-btn"
|
||||
aria-label={`${language.t("dialog.model.select")}: ${sanitizeName(model.name)}`}
|
||||
onClick={() => selectRow(row)}
|
||||
>
|
||||
{language.t("dialog.model.select")}
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
<Show when={nodes().length > 0}>
|
||||
<Virtualizer ref={setVirtualizer} data={nodes()} keepMounted={mounted()} overscan={4} itemSize={30}>
|
||||
{
|
||||
// eslint-disable-next-line complexity
|
||||
(node) => {
|
||||
if (node.kind === "group" && node.group) {
|
||||
const group = node.group
|
||||
const key = groupKey(group.key)
|
||||
const shown = () => isGroupOpen(group.key)
|
||||
return (
|
||||
<div
|
||||
id={optionID(key)}
|
||||
class={`model-selector-group-label${props.allowClear || group.key !== groups()[0]?.key ? " model-selector-group-label--divided" : ""}${isSelected(key) ? " selected" : ""}${isSelected(key) && !pointer() ? " keyboard-focused" : ""}`}
|
||||
role="treeitem"
|
||||
aria-level={1}
|
||||
aria-expanded={shown()}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => toggleGroup(group.key)}
|
||||
onMouseMove={() => setPointer(true)}
|
||||
onMouseEnter={() => {
|
||||
if (pointer()) setSelectedKey(key)
|
||||
}}
|
||||
</For>
|
||||
>
|
||||
<svg
|
||||
class={`model-selector-group-chevron${shown() ? "" : " model-selector-group-chevron--collapsed"}`}
|
||||
width="10"
|
||||
height="10"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path d="M4 6l4 5 4-5H4z" />
|
||||
</svg>
|
||||
<span>{group.label}</span>
|
||||
<Show when={!shown() && !!debouncedSearch()}>
|
||||
<span class="model-selector-group-match-dot" aria-hidden="true" />
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const row = node.row
|
||||
if (!row) return null
|
||||
if (row.kind === "clear") {
|
||||
return (
|
||||
<div
|
||||
id={optionID(CLEAR_KEY)}
|
||||
class={`model-selector-item${isSelected(CLEAR_KEY) && !pointer() ? " keyboard-focused" : ""}${isSelected(CLEAR_KEY) ? " selected" : ""}${!props.value?.providerID ? " active" : ""}`}
|
||||
role="treeitem"
|
||||
aria-level={1}
|
||||
aria-selected={!props.value?.providerID}
|
||||
onClick={() => pickClear()}
|
||||
onMouseMove={() => setPointer(true)}
|
||||
onMouseEnter={() => {
|
||||
if (pointer()) setSelectedKey(CLEAR_KEY)
|
||||
}}
|
||||
>
|
||||
<span class="model-selector-item-name" style={{ "font-style": "italic", opacity: 0.7 }}>
|
||||
{props.clearLabel ?? language.t("dialog.model.notSet")}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (!row.model) return null
|
||||
|
||||
const model = row.model
|
||||
const hovered = () => isSelected(row.key)
|
||||
const preActive = () => isPreActive(row.key)
|
||||
const starred = () => favoriteKeys().has(modelKey(model.providerID, model.id))
|
||||
const showProvider = () => row.kind === "favorite"
|
||||
const showSelect = () => expanded() && preActive() && !isActive(model)
|
||||
const starLabel = () =>
|
||||
`${starred() ? language.t("model.favorite.remove") : language.t("model.favorite.add")}: ${sanitizeName(model.name)}`
|
||||
return (
|
||||
<div
|
||||
role="presentation"
|
||||
class={`model-selector-row${hovered() || preActive() ? " selected" : ""}`}
|
||||
>
|
||||
<div
|
||||
id={optionID(row.key)}
|
||||
class={`model-selector-item${(hovered() && !pointer()) || preActive() ? " keyboard-focused" : ""}${hovered() || preActive() ? " selected" : ""}${chosen(row) ? " active" : ""}`}
|
||||
role="treeitem"
|
||||
aria-level={2}
|
||||
aria-selected={chosen(row)}
|
||||
onClick={() => {
|
||||
if (!expanded()) {
|
||||
selectRow(row)
|
||||
return
|
||||
}
|
||||
setRow(row.key)
|
||||
setPreviewKey(row.key)
|
||||
searchRef?.focus()
|
||||
}}
|
||||
onDblClick={() => {
|
||||
if (expanded()) selectRow(row)
|
||||
}}
|
||||
onMouseMove={() => setPointer(true)}
|
||||
onMouseEnter={() => {
|
||||
if (pointer()) setSelectedKey(row.key)
|
||||
}}
|
||||
>
|
||||
<div class="model-selector-item-left">
|
||||
<span class="model-selector-item-name">
|
||||
{(() => {
|
||||
const full = sanitizeName(model.name)
|
||||
const sep = full.indexOf(": ")
|
||||
if (sep < 0) return <span class="model-selector-item-name-main">{full}</span>
|
||||
return (
|
||||
<>
|
||||
<span class="model-selector-item-name-provider">{full.slice(0, sep)}</span>
|
||||
<span class="model-selector-item-name-main">{full.slice(sep + 2)}</span>
|
||||
</>
|
||||
)
|
||||
})()}
|
||||
</span>
|
||||
<Show when={isAuto(model)}>
|
||||
<Tooltip value={autoLabel(model)} placement="top">
|
||||
<span class="model-selector-auto-icon" aria-label={autoLabel(model)}>
|
||||
<Icon name="branch" size="small" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
<Show when={isFree(model) || hasByok(model) || isDataCollectedModel(model)}>
|
||||
<span class="model-selector-free-data">
|
||||
<Show when={isFree(model) && !hasByok(model)}>
|
||||
<span class="model-selector-data-badge">
|
||||
<Tag data-variant="member">{freeLabel()}</Tag>
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={hasByok(model)}>
|
||||
<span class="model-selector-data-badge model-selector-data-badge--byok">
|
||||
<Tag data-variant="member">BYOK</Tag>
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={isDataCollectedModel(model)}>
|
||||
<Tooltip value={dataLabel()} placement="top">
|
||||
<span class="model-selector-free-data-icon" aria-label={dataLabel()}>
|
||||
<Icon name="book-open-check" size="small" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</Show>
|
||||
</span>
|
||||
</Show>
|
||||
<Show when={showProvider()}>
|
||||
<span class="model-selector-item-provider-tag">{model.providerName}</span>
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<Show when={session && props.favorites !== false}>
|
||||
<button
|
||||
type="button"
|
||||
class={`model-selector-star${starred() ? " model-selector-star--active" : ""}`}
|
||||
aria-label={starLabel()}
|
||||
aria-pressed={starred()}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
toggleFavorite(model, row)
|
||||
searchRef?.focus()
|
||||
}}
|
||||
>
|
||||
<Icon name={starred() ? "star-filled" : "star"} size="small" />
|
||||
</button>
|
||||
</Show>
|
||||
<Show when={showSelect()}>
|
||||
<button
|
||||
type="button"
|
||||
class="model-selector-item-select-btn"
|
||||
aria-label={`${language.t("dialog.model.select")}: ${sanitizeName(model.name)}`}
|
||||
onClick={() => selectRow(row)}
|
||||
>
|
||||
{language.t("dialog.model.select")}
|
||||
</button>
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</For>
|
||||
)
|
||||
}
|
||||
}
|
||||
</Virtualizer>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<Show when={expanded()}>
|
||||
|
||||
@@ -57,6 +57,7 @@ export function PopupSelector<T extends ValidComponent = ValidComponent>(props:
|
||||
"minWidth",
|
||||
"minHeight",
|
||||
"deferDismiss",
|
||||
"class",
|
||||
"children",
|
||||
])
|
||||
|
||||
@@ -113,6 +114,7 @@ export function PopupSelector<T extends ValidComponent = ValidComponent>(props:
|
||||
overflowPadding={local.padding ?? 8}
|
||||
deferDismiss={local.deferDismiss}
|
||||
{...(rest as PopoverProps)}
|
||||
class={`popup-selector${local.class ? ` ${local.class}` : ""}`}
|
||||
style={
|
||||
popoverW().width !== undefined
|
||||
? { width: `${popoverW().width}px`, "max-width": `${popoverW().max}px` }
|
||||
|
||||
@@ -64,13 +64,14 @@ export const ThinkingSelectorBase: Component<ThinkingSelectorBaseProps> = (props
|
||||
}
|
||||
|
||||
function onOpen(val: boolean) {
|
||||
setOpen(val)
|
||||
if (val) {
|
||||
const items = rows()
|
||||
const idx = items.findIndex((v) => v === props.value)
|
||||
requestAnimationFrame(() => focusItem(idx >= 0 ? idx : 0))
|
||||
setFocused(idx >= 0 ? idx : 0)
|
||||
setOpen(true)
|
||||
return
|
||||
}
|
||||
setOpen(false)
|
||||
refocus()
|
||||
}
|
||||
|
||||
@@ -166,6 +167,7 @@ export const ThinkingSelectorBase: Component<ThinkingSelectorBaseProps> = (props
|
||||
role="option"
|
||||
aria-selected={props.value === v}
|
||||
tabindex={focused() === i() ? 0 : -1}
|
||||
data-autofocus={focused() === i() ? "" : undefined}
|
||||
onClick={() => pick(v)}
|
||||
onFocus={() => setFocused(i())}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { AgentRequirementResult } from "../types/messages"
|
||||
|
||||
export function keepAgentRequirementsResult(
|
||||
value: AgentRequirementResult | undefined,
|
||||
agent: string,
|
||||
directory: string,
|
||||
) {
|
||||
if (!value || value.agent !== agent || value.directory !== directory) return false
|
||||
return value.state === "ready" || value.state === "blocked" || value.state === "error"
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/** @jsxImportSource solid-js */
|
||||
|
||||
import { createContext, createEffect, createMemo, createSignal, onCleanup, useContext } from "solid-js"
|
||||
import type { Accessor, ParentComponent } from "solid-js"
|
||||
import { useConfig } from "./config"
|
||||
import { useServer } from "./server"
|
||||
import { useSession } from "./session"
|
||||
import { useVSCode } from "./vscode"
|
||||
import type { AgentRequirementResult } from "../types/messages"
|
||||
import { keepAgentRequirementsResult } from "./agent-requirements-state"
|
||||
|
||||
export interface AgentRequirementsContextValue {
|
||||
result: Accessor<AgentRequirementResult | undefined>
|
||||
checking: Accessor<boolean>
|
||||
blocked: Accessor<boolean>
|
||||
visible: Accessor<boolean>
|
||||
}
|
||||
|
||||
export const AgentRequirementsContext = createContext<AgentRequirementsContextValue>()
|
||||
|
||||
export const AgentRequirementsProvider: ParentComponent = (props) => {
|
||||
const vscode = useVSCode()
|
||||
const session = useSession()
|
||||
const server = useServer()
|
||||
const config = useConfig()
|
||||
const [result, setResult] = createSignal<AgentRequirementResult>()
|
||||
const [revision, setRevision] = createSignal(0)
|
||||
const [pending, setPending] = createSignal(false)
|
||||
const [requested, setRequested] = createSignal("")
|
||||
|
||||
const enabled = () => config.config().experimental?.agent_requirements === true
|
||||
const agent = () => session.selectedAgent()
|
||||
const directory = () => server.workspaceDirectory()
|
||||
const key = () => `${directory()}\0${agent()}`
|
||||
const active = (value: { agent: string; directory: string }) =>
|
||||
value.agent === agent() && value.directory === directory()
|
||||
|
||||
const request = () => {
|
||||
if (!enabled() || !agent() || !directory()) return
|
||||
setPending(true)
|
||||
vscode.postMessage({
|
||||
type: "requestAgentRequirements",
|
||||
agent: agent(),
|
||||
directory: directory(),
|
||||
sessionID: session.currentSessionID(),
|
||||
})
|
||||
}
|
||||
|
||||
const unsubscribe = vscode.onMessage((message) => {
|
||||
if (message.type === "agentRequirementsLoaded") {
|
||||
if (!active(message.result)) return
|
||||
setPending(false)
|
||||
setResult(message.result)
|
||||
return
|
||||
}
|
||||
|
||||
if (message.type === "agentRequirementsInvalidated") {
|
||||
setRequested("")
|
||||
setRevision((value) => value + 1)
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
revision()
|
||||
if (!enabled()) {
|
||||
setRequested("")
|
||||
setPending(false)
|
||||
setResult(undefined)
|
||||
return
|
||||
}
|
||||
|
||||
const next = key()
|
||||
if (!agent() || !directory() || next === requested()) return
|
||||
setRequested(next)
|
||||
setPending(true)
|
||||
if (!keepAgentRequirementsResult(result(), agent(), directory())) setResult(undefined)
|
||||
request()
|
||||
})
|
||||
|
||||
const checking = createMemo(() => enabled() && pending())
|
||||
const blocked = createMemo(() => {
|
||||
if (!enabled()) return false
|
||||
const current = result()
|
||||
return !current || current.state === "blocked" || current.state === "error"
|
||||
})
|
||||
const visible = createMemo(() => {
|
||||
const current = result()
|
||||
return current?.state === "blocked" || current?.state === "error"
|
||||
})
|
||||
|
||||
onCleanup(unsubscribe)
|
||||
|
||||
return (
|
||||
<AgentRequirementsContext.Provider value={{ result, checking, blocked, visible }}>
|
||||
{props.children}
|
||||
</AgentRequirementsContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useAgentRequirements(): AgentRequirementsContextValue {
|
||||
const value = useContext(AgentRequirementsContext)
|
||||
if (!value) throw new Error("useAgentRequirements must be used within an AgentRequirementsProvider")
|
||||
return value
|
||||
}
|
||||
+74
-5
@@ -1,12 +1,57 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "تحقق مرة أخرى",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "توصيل Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "إدارة Anaconda Desktop",
|
||||
"provider.anaconda.status.checking": "جارٍ التحقق من Anaconda Desktop...",
|
||||
"provider.anaconda.status.opening": "جارٍ فتح Anaconda Desktop...",
|
||||
"provider.anaconda.status.syncing": "جارٍ تحديث نماذج الموفر...",
|
||||
"provider.anaconda.status.ready": "جاهز للتوصيل",
|
||||
"provider.anaconda.status.waiting": "في انتظار Desktop",
|
||||
"provider.anaconda.status.attention": "يحتاج إلى انتباه",
|
||||
"provider.anaconda.status.unavailable": "غير متوفر",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop غير مدعوم على {{platform}}.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"ثبّت Anaconda Desktop على هذا الجهاز، ثم عُد إلى هنا. لا يقوم Kilo بتشغيل المثبّت نيابةً عنك.",
|
||||
"provider.anaconda.state.notRunning": "افتح Anaconda Desktop، وأكمل الإعداد وسجّل الدخول، ثم اختر تحقق مرة أخرى.",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"إعداد Anaconda Desktop غير مكتمل. افتح Desktop، وأكمل الإعداد، وأعد تشغيله إذا لزم الأمر.",
|
||||
"provider.anaconda.state.signedOut": "افتح Anaconda Desktop وسجّل الدخول قبل توصيل Kilo.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"تعذّر على Kilo الوصول إلى Anaconda Desktop. افتح Desktop، وسجّل الدخول مرة أخرى، وأعد تشغيله إذا لزم الأمر.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"لا يستجيب Anaconda Desktop بعد. افتحه وانتظر حتى ينتهي التطبيق من بدء التشغيل.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"في Anaconda Desktop، نزّل نموذجًا لتوليد النصوص. اختر نموذجًا يدعم استدعاء الأدوات إن أمكن، ثم شغّل خادمه.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"يتوفر نموذج واحد مُنزّل لتوليد النصوص. في Anaconda Desktop، شغّل خادم نموذج. يوصى بشدة باستخدام نماذج تدعم استدعاء الأدوات.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"تتوفر نماذج مُنزّلة لتوليد النصوص، وعددها {{count}}. في Anaconda Desktop، شغّل خادم نموذج. يوصى بشدة باستخدام نماذج تدعم استدعاء الأدوات.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"خادم الاستدلال النشط ليس في حالة سليمة بعد. تحقق منه في Anaconda Desktop وأعد تشغيل الخادم إذا لزم الأمر.",
|
||||
"provider.anaconda.state.ready":
|
||||
"عثر Kilo على خادم محلي سليم لتوليد النصوص ويمكنه استيراد إعدادات الاتصال الحالية الخاصة به.",
|
||||
"provider.anaconda.server": "خادم الاستدلال النشط",
|
||||
"provider.anaconda.context": "نافذة السياق",
|
||||
"provider.anaconda.contextValue": "{{count}} رمز",
|
||||
"provider.anaconda.tools": "استدعاء الأدوات",
|
||||
"provider.anaconda.tools.supported": "مدعوم",
|
||||
"provider.anaconda.tools.unsupported": "غير مُفعّل",
|
||||
"provider.anaconda.tools.unknown": "غير معروف",
|
||||
"provider.anaconda.warning.title": "دعم الأدوات محدود",
|
||||
"provider.anaconda.warning.description":
|
||||
"لا يؤكد هذا الخادم استدعاء الأدوات. قد تفشل إجراءات وكيل البرمجة أو تكون غير متوفرة. تابع فقط إذا قبلت هذه القيود.",
|
||||
"provider.anaconda.action.download": "تنزيل Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "فتح Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "تحقق مرة أخرى",
|
||||
"provider.anaconda.action.continue": "تابع على أي حال",
|
||||
"provider.anaconda.action.manage": "إدارة / تحديث",
|
||||
"provider.anaconda.toast.refreshed.title": "تم تحديث Anaconda Desktop",
|
||||
"provider.anaconda.toast.refreshed.description": "الخادم المحلي النشط والنماذج محدّثة في Kilo.",
|
||||
"settings.providers.note.anacondaDesktop": "شغّل نموذجًا يُقدّم محليًا بواسطة Anaconda Desktop.",
|
||||
"settings.providers.tag.local": "محلي",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "مقترح",
|
||||
"command.category.view": "عرض",
|
||||
"command.category.project": "مشروع",
|
||||
@@ -102,6 +147,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "إيقاف مشاركة هذه الجلسة",
|
||||
"command.session.export": "تصدير سجل الجلسة",
|
||||
|
||||
"agentRequirements.skill.installed": "مثبت",
|
||||
"agentRequirements.skill.checkFailed": "فشل فحص المهارة",
|
||||
"agentRequirements.skill.missing": "غير مثبت",
|
||||
"agentRequirements.mcp.connected": "متصل",
|
||||
"agentRequirements.mcp.checkFailed": "فشل فحص MCP",
|
||||
"agentRequirements.mcp.missing": "غير متصل",
|
||||
"agentRequirements.extension.installed": "مثبت",
|
||||
"agentRequirements.extension.checkFailed": "فشل فحص إضافة VS Code",
|
||||
"agentRequirements.extension.missing": "غير مثبت",
|
||||
"agentRequirements.extension.description": "ثبّت الإضافات الناقصة في VS Code.",
|
||||
"agentRequirements.group.skills": "المهارات",
|
||||
"agentRequirements.group.mcps": "MCPs",
|
||||
"agentRequirements.group.extensions": "إضافات VS Code",
|
||||
"agentRequirements.blocked.title": "متطلبات وكيل {{agent}}",
|
||||
"agentRequirements.blocked.description": "يحتاج هذا الوكيل إلى الأدوات التالية قبل أن يتمكن من العمل.",
|
||||
"agentRequirements.prompt.blocked": "أكمل الفحوصات المطلوبة لاستخدام هذا الوكيل أولاً",
|
||||
"agentRequirements.action.openMarketplace": "فتح Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "تعذر العثور على الوكيل المحدد.",
|
||||
"agentRequirements.error.malformedDeclaration": "لدى هذا الوكيل تعريف متطلبات غير صالح.",
|
||||
"agentRequirements.error.discoveryFailed": "تعذر على Kilo فحص المهارات المتاحة.",
|
||||
"agentRequirements.error.mcpStatusFailed": "تعذر على Kilo فحص حالة خادم MCP.",
|
||||
"agentRequirements.error.scopeMismatch": "لم يعد فحص متطلبات هذا الوكيل نشطًا.",
|
||||
"agentRequirements.error.requestFailed": "تعذر على Kilo فحص متطلبات الوكيل.",
|
||||
|
||||
"palette.search.placeholder": "البحث في الملفات والأوامر والجلسات",
|
||||
"palette.empty": "لا توجد نتائج",
|
||||
"palette.group.commands": "الأوامر",
|
||||
|
||||
+75
-5
@@ -1,12 +1,58 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "Verificar novamente",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Conectar Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "Gerenciar Anaconda Desktop",
|
||||
"provider.anaconda.status.checking": "Verificando Anaconda Desktop...",
|
||||
"provider.anaconda.status.opening": "Abrindo Anaconda Desktop...",
|
||||
"provider.anaconda.status.syncing": "Atualizando modelos do provedor...",
|
||||
"provider.anaconda.status.ready": "Pronto para conectar",
|
||||
"provider.anaconda.status.waiting": "Aguardando o Desktop",
|
||||
"provider.anaconda.status.attention": "Precisa de atenção",
|
||||
"provider.anaconda.status.unavailable": "Indisponível",
|
||||
"provider.anaconda.state.unsupported": "O Anaconda Desktop não é compatível com {{platform}}.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"Instale o Anaconda Desktop nesta máquina e depois volte aqui. O Kilo não executa o instalador para você.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Abra o Anaconda Desktop, conclua a configuração e faça login, depois escolha Verificar novamente.",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"A configuração do Anaconda Desktop está incompleta. Abra o Desktop, conclua a configuração e reinicie-o se necessário.",
|
||||
"provider.anaconda.state.signedOut": "Abra o Anaconda Desktop e faça login antes de conectar o Kilo.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"O Kilo não conseguiu acessar o Anaconda Desktop. Abra o Desktop, faça login novamente e reinicie-o se necessário.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"O Anaconda Desktop ainda não está respondendo. Abra-o e aguarde o aplicativo terminar de iniciar.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"No Anaconda Desktop, baixe um modelo de geração de texto. Escolha um com chamadas de ferramentas quando possível, depois inicie o servidor dele.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"Há 1 modelo de geração de texto baixado disponível. No Anaconda Desktop, inicie um servidor de modelo. Modelos com suporte a chamadas de ferramentas são altamente recomendados.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"Há {{count}} modelos de geração de texto baixados disponíveis. No Anaconda Desktop, inicie um servidor de modelo. Modelos com suporte a chamadas de ferramentas são altamente recomendados.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"O servidor de inferência ativo ainda não está saudável. Verifique-o no Anaconda Desktop e reinicie o servidor se necessário.",
|
||||
"provider.anaconda.state.ready":
|
||||
"O Kilo encontrou um servidor local saudável de geração de texto e pode importar suas configurações de conexão atuais.",
|
||||
"provider.anaconda.server": "Servidor de inferência ativo",
|
||||
"provider.anaconda.context": "Janela de contexto",
|
||||
"provider.anaconda.contextValue": "{{count}} tokens",
|
||||
"provider.anaconda.tools": "Chamadas de ferramentas",
|
||||
"provider.anaconda.tools.supported": "Compatível",
|
||||
"provider.anaconda.tools.unsupported": "Não habilitado",
|
||||
"provider.anaconda.tools.unknown": "Desconhecido",
|
||||
"provider.anaconda.warning.title": "O suporte a ferramentas é limitado",
|
||||
"provider.anaconda.warning.description":
|
||||
"Este servidor não confirma chamadas de ferramentas. Ações do agente de código podem falhar ou ficar indisponíveis. Continue apenas se você aceitar essas limitações.",
|
||||
"provider.anaconda.action.download": "Baixar Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "Abrir Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "Verificar novamente",
|
||||
"provider.anaconda.action.continue": "Continuar mesmo assim",
|
||||
"provider.anaconda.action.manage": "Gerenciar / Atualizar",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop atualizado",
|
||||
"provider.anaconda.toast.refreshed.description": "O servidor local ativo e os modelos estão atualizados no Kilo.",
|
||||
"settings.providers.note.anacondaDesktop": "Execute um modelo servido localmente pelo Anaconda Desktop.",
|
||||
"settings.providers.tag.local": "Local",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "Sugerido",
|
||||
"command.category.view": "Visualizar",
|
||||
"command.category.project": "Projeto",
|
||||
@@ -102,6 +148,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "Parar de compartilhar esta sessão",
|
||||
"command.session.export": "Exportar transcrição da sessão",
|
||||
|
||||
"agentRequirements.skill.installed": "Instalado",
|
||||
"agentRequirements.skill.checkFailed": "A verificação da skill falhou",
|
||||
"agentRequirements.skill.missing": "Não instalado",
|
||||
"agentRequirements.mcp.connected": "Conectado",
|
||||
"agentRequirements.mcp.checkFailed": "A verificação do MCP falhou",
|
||||
"agentRequirements.mcp.missing": "Não conectado",
|
||||
"agentRequirements.extension.installed": "Instalada",
|
||||
"agentRequirements.extension.checkFailed": "A verificação da extensão do VS Code falhou",
|
||||
"agentRequirements.extension.missing": "Não instalada",
|
||||
"agentRequirements.extension.description": "Instale as extensões ausentes no VS Code.",
|
||||
"agentRequirements.group.skills": "Skills",
|
||||
"agentRequirements.group.mcps": "MCPs",
|
||||
"agentRequirements.group.extensions": "Extensões do VS Code",
|
||||
"agentRequirements.blocked.title": "Pré-requisitos do agente {{agent}}",
|
||||
"agentRequirements.blocked.description": "Este agente precisa das seguintes ferramentas antes de poder executar.",
|
||||
"agentRequirements.prompt.blocked": "Conclua primeiro as verificações necessárias para usar este agente",
|
||||
"agentRequirements.action.openMarketplace": "Abrir Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "O agente selecionado não foi encontrado.",
|
||||
"agentRequirements.error.malformedDeclaration": "Este agente tem uma declaração de requisitos inválida.",
|
||||
"agentRequirements.error.discoveryFailed": "O Kilo não conseguiu verificar as skills disponíveis.",
|
||||
"agentRequirements.error.mcpStatusFailed": "O Kilo não conseguiu verificar o status do servidor MCP.",
|
||||
"agentRequirements.error.scopeMismatch": "Esta verificação de requisitos do agente não está mais ativa.",
|
||||
"agentRequirements.error.requestFailed": "O Kilo não conseguiu verificar os requisitos do agente.",
|
||||
|
||||
"palette.search.placeholder": "Buscar arquivos, comandos e sessões",
|
||||
"palette.empty": "Nenhum resultado encontrado",
|
||||
"palette.group.commands": "Comandos",
|
||||
|
||||
+75
-5
@@ -1,12 +1,58 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "Provjeri ponovo",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Poveži Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "Upravljaj Anaconda Desktopom",
|
||||
"provider.anaconda.status.checking": "Provjeravanje Anaconda Desktopa...",
|
||||
"provider.anaconda.status.opening": "Otvaranje Anaconda Desktopa...",
|
||||
"provider.anaconda.status.syncing": "Osvježavanje modela provajdera...",
|
||||
"provider.anaconda.status.ready": "Spremno za povezivanje",
|
||||
"provider.anaconda.status.waiting": "Čekanje na Desktop",
|
||||
"provider.anaconda.status.attention": "Potrebna pažnja",
|
||||
"provider.anaconda.status.unavailable": "Nedostupno",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop nije podržan na {{platform}}.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"Instalirajte Anaconda Desktop na ovom računaru, zatim se vratite ovdje. Kilo ne pokreće instalacijski program umjesto vas.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Otvorite Anaconda Desktop, dovršite postavljanje i prijavite se, zatim odaberite Provjeri ponovo.",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"Postavljanje Anaconda Desktopa nije dovršeno. Otvorite Desktop, dovršite postavljanje i ponovo ga pokrenite ako je potrebno.",
|
||||
"provider.anaconda.state.signedOut": "Otvorite Anaconda Desktop i prijavite se prije povezivanja Kila.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo nije mogao pristupiti Anaconda Desktopu. Otvorite Desktop, ponovo se prijavite i ponovo ga pokrenite ako je potrebno.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop još ne odgovara. Otvorite ga i sačekajte da se aplikacija u potpunosti pokrene.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"U Anaconda Desktopu preuzmite model za generisanje teksta. Odaberite onaj s pozivanjem alata kad je moguće, zatim pokrenite njegov server.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"Dostupan je 1 preuzeti model za generisanje teksta. U Anaconda Desktopu pokrenite server modela. Modeli s podrškom za pozivanje alata se snažno preporučuju.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"Preuzeti modeli za generisanje teksta dostupni su (ukupno: {{count}}). U Anaconda Desktopu pokrenite server modela. Modeli s podrškom za pozivanje alata se snažno preporučuju.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"Aktivni server za zaključivanje još nije ispravan. Provjerite ga u Anaconda Desktopu i ponovo pokrenite server ako je potrebno.",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo je pronašao ispravan lokalni server za generisanje teksta i može uvesti njegove trenutne postavke veze.",
|
||||
"provider.anaconda.server": "Aktivni server za zaključivanje",
|
||||
"provider.anaconda.context": "Kontekstni prozor",
|
||||
"provider.anaconda.contextValue": "{{count}} tokena",
|
||||
"provider.anaconda.tools": "Pozivanje alata",
|
||||
"provider.anaconda.tools.supported": "Podržano",
|
||||
"provider.anaconda.tools.unsupported": "Nije omogućeno",
|
||||
"provider.anaconda.tools.unknown": "Nepoznato",
|
||||
"provider.anaconda.warning.title": "Podrška za alate je ograničena",
|
||||
"provider.anaconda.warning.description":
|
||||
"Ovaj server ne potvrđuje pozivanje alata. Radnje agenta za kodiranje mogu biti neuspješne ili nedostupne. Nastavite samo ako prihvatate ova ograničenja.",
|
||||
"provider.anaconda.action.download": "Preuzmi Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "Otvori Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "Provjeri ponovo",
|
||||
"provider.anaconda.action.continue": "Ipak nastavi",
|
||||
"provider.anaconda.action.manage": "Upravljaj / Osvježi",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop osvježen",
|
||||
"provider.anaconda.toast.refreshed.description": "Aktivni lokalni server i modeli su ažurirani u Kilu.",
|
||||
"settings.providers.note.anacondaDesktop": "Pokrenite model koji lokalno poslužuje Anaconda Desktop.",
|
||||
"settings.providers.tag.local": "Lokalno",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "Predloženo",
|
||||
"command.category.view": "Prikaz",
|
||||
"command.category.project": "Projekat",
|
||||
@@ -102,6 +148,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "Zaustavi dijeljenje ove sesije",
|
||||
"command.session.export": "Izvezi transkript sesije",
|
||||
|
||||
"agentRequirements.skill.installed": "Instalirano",
|
||||
"agentRequirements.skill.checkFailed": "Provjera vještine nije uspjela",
|
||||
"agentRequirements.skill.missing": "Nije instalirano",
|
||||
"agentRequirements.mcp.connected": "Povezano",
|
||||
"agentRequirements.mcp.checkFailed": "Provjera MCP-a nije uspjela",
|
||||
"agentRequirements.mcp.missing": "Nije povezano",
|
||||
"agentRequirements.extension.installed": "Instalirano",
|
||||
"agentRequirements.extension.checkFailed": "Provjera VS Code ekstenzije nije uspjela",
|
||||
"agentRequirements.extension.missing": "Nije instalirano",
|
||||
"agentRequirements.extension.description": "Instalirajte nedostajuće ekstenzije u VS Code.",
|
||||
"agentRequirements.group.skills": "Vještine",
|
||||
"agentRequirements.group.mcps": "MCP-ovi",
|
||||
"agentRequirements.group.extensions": "VS Code ekstenzije",
|
||||
"agentRequirements.blocked.title": "Preduslovi za agenta {{agent}}",
|
||||
"agentRequirements.blocked.description": "Ovom agentu su potrebni sljedeći alati prije pokretanja.",
|
||||
"agentRequirements.prompt.blocked": "Prvo dovršite potrebne provjere da biste koristili ovog agenta",
|
||||
"agentRequirements.action.openMarketplace": "Otvori Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "Odabrani agent nije pronađen.",
|
||||
"agentRequirements.error.malformedDeclaration": "Ovaj agent ima neispravnu deklaraciju zahtjeva.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo nije mogao provjeriti dostupne vještine.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo nije mogao provjeriti status MCP servera.",
|
||||
"agentRequirements.error.scopeMismatch": "Ova provjera zahtjeva agenta više nije aktivna.",
|
||||
"agentRequirements.error.requestFailed": "Kilo nije mogao provjeriti zahtjeve agenta.",
|
||||
|
||||
"palette.search.placeholder": "Pretraži datoteke, komande i sesije",
|
||||
"palette.empty": "Nema rezultata",
|
||||
"palette.group.commands": "Komande",
|
||||
|
||||
+75
-5
@@ -1,12 +1,58 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "Tjek igen",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Forbind Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "Administrer Anaconda Desktop",
|
||||
"provider.anaconda.status.checking": "Tjekker Anaconda Desktop...",
|
||||
"provider.anaconda.status.opening": "Åbner Anaconda Desktop...",
|
||||
"provider.anaconda.status.syncing": "Opdaterer udbydermodeller...",
|
||||
"provider.anaconda.status.ready": "Klar til at forbinde",
|
||||
"provider.anaconda.status.waiting": "Venter på Desktop",
|
||||
"provider.anaconda.status.attention": "Kræver opmærksomhed",
|
||||
"provider.anaconda.status.unavailable": "Utilgængelig",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop understøttes ikke på {{platform}}.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"Installer Anaconda Desktop på denne maskine, og vend derefter tilbage hertil. Kilo kører ikke installationsprogrammet for dig.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Åbn Anaconda Desktop, færdiggør opsætningen og log ind, og vælg derefter Tjek igen.",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"Opsætningen af Anaconda Desktop er ufuldstændig. Åbn Desktop, færdiggør opsætningen, og genstart den om nødvendigt.",
|
||||
"provider.anaconda.state.signedOut": "Åbn Anaconda Desktop og log ind, før du forbinder Kilo.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo kunne ikke få adgang til Anaconda Desktop. Åbn Desktop, log ind igen, og genstart den om nødvendigt.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop svarer ikke endnu. Åbn den, og vent på, at applikationen er færdig med at starte.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"Download en tekstgenereringsmodel i Anaconda Desktop. Vælg en med understøttelse af værktøjskald, når det er muligt, og start derefter dens server.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"Der er 1 downloadet tekstgenereringsmodel tilgængelig. Start en modelserver i Anaconda Desktop. Modeller med understøttelse af værktøjskald anbefales kraftigt.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"Der er {{count}} downloadede tekstgenereringsmodeller tilgængelige. Start en modelserver i Anaconda Desktop. Modeller med understøttelse af værktøjskald anbefales kraftigt.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"Den aktive inferensserver er ikke sund endnu. Tjek den i Anaconda Desktop, og genstart serveren om nødvendigt.",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo fandt en sund lokal tekstgenereringsserver og kan importere dens aktuelle forbindelsesindstillinger.",
|
||||
"provider.anaconda.server": "Aktiv inferensserver",
|
||||
"provider.anaconda.context": "Kontekstvindue",
|
||||
"provider.anaconda.contextValue": "{{count}} tokens",
|
||||
"provider.anaconda.tools": "Værktøjskald",
|
||||
"provider.anaconda.tools.supported": "Understøttet",
|
||||
"provider.anaconda.tools.unsupported": "Ikke aktiveret",
|
||||
"provider.anaconda.tools.unknown": "Ukendt",
|
||||
"provider.anaconda.warning.title": "Værktøjsunderstøttelse er begrænset",
|
||||
"provider.anaconda.warning.description":
|
||||
"Denne server bekræfter ikke værktøjskald. Handlinger fra kodningsagenten kan mislykkes eller være utilgængelige. Fortsæt kun, hvis du accepterer disse begrænsninger.",
|
||||
"provider.anaconda.action.download": "Download Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "Åbn Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "Tjek igen",
|
||||
"provider.anaconda.action.continue": "Fortsæt alligevel",
|
||||
"provider.anaconda.action.manage": "Administrer / Opdater",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop opdateret",
|
||||
"provider.anaconda.toast.refreshed.description": "Den aktive lokale server og modeller er opdaterede i Kilo.",
|
||||
"settings.providers.note.anacondaDesktop": "Kør en model, der serveres lokalt af Anaconda Desktop.",
|
||||
"settings.providers.tag.local": "Lokal",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "Foreslået",
|
||||
"command.category.view": "Vis",
|
||||
"command.category.project": "Projekt",
|
||||
@@ -102,6 +148,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "Stop med at dele denne session",
|
||||
"command.session.export": "Eksporter sessionsudskrift",
|
||||
|
||||
"agentRequirements.skill.installed": "Installeret",
|
||||
"agentRequirements.skill.checkFailed": "Skill-kontrollen mislykkedes",
|
||||
"agentRequirements.skill.missing": "Ikke installeret",
|
||||
"agentRequirements.mcp.connected": "Forbundet",
|
||||
"agentRequirements.mcp.checkFailed": "MCP-kontrollen mislykkedes",
|
||||
"agentRequirements.mcp.missing": "Ikke forbundet",
|
||||
"agentRequirements.extension.installed": "Installeret",
|
||||
"agentRequirements.extension.checkFailed": "Kontrollen af VS Code-udvidelsen mislykkedes",
|
||||
"agentRequirements.extension.missing": "Ikke installeret",
|
||||
"agentRequirements.extension.description": "Installer de manglende udvidelser i VS Code.",
|
||||
"agentRequirements.group.skills": "Skills",
|
||||
"agentRequirements.group.mcps": "MCP’er",
|
||||
"agentRequirements.group.extensions": "VS Code-udvidelser",
|
||||
"agentRequirements.blocked.title": "Forudsætninger for {{agent}}-agenten",
|
||||
"agentRequirements.blocked.description": "Denne agent skal have følgende værktøjer, før den kan køre.",
|
||||
"agentRequirements.prompt.blocked": "Fuldfør først de krævede kontroller for at bruge denne agent",
|
||||
"agentRequirements.action.openMarketplace": "Åbn Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "Den valgte agent blev ikke fundet.",
|
||||
"agentRequirements.error.malformedDeclaration": "Denne agent har en ugyldig kravdeklaration.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo kunne ikke kontrollere de tilgængelige skills.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo kunne ikke kontrollere MCP-serverstatus.",
|
||||
"agentRequirements.error.scopeMismatch": "Denne agentkravskontrol er ikke længere aktiv.",
|
||||
"agentRequirements.error.requestFailed": "Kilo kunne ikke kontrollere agentkravene.",
|
||||
|
||||
"palette.search.placeholder": "Søg i filer, kommandoer og sessioner",
|
||||
"palette.empty": "Ingen resultater fundet",
|
||||
"palette.group.commands": "Kommandoer",
|
||||
|
||||
@@ -1,14 +1,64 @@
|
||||
import { anacondaDesktopDict, dict as en } from "./en"
|
||||
import { dict as en } from "./en"
|
||||
|
||||
type Keys = keyof typeof en
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "Erneut prüfen",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Anaconda Desktop verbinden",
|
||||
"provider.anaconda.title.manage": "Anaconda Desktop verwalten",
|
||||
"provider.anaconda.status.checking": "Anaconda Desktop wird geprüft...",
|
||||
"provider.anaconda.status.opening": "Anaconda Desktop wird geöffnet...",
|
||||
"provider.anaconda.status.syncing": "Anbietermodelle werden aktualisiert...",
|
||||
"provider.anaconda.status.ready": "Bereit zum Verbinden",
|
||||
"provider.anaconda.status.waiting": "Warten auf Desktop",
|
||||
"provider.anaconda.status.attention": "Erfordert Aufmerksamkeit",
|
||||
"provider.anaconda.status.unavailable": "Nicht verfügbar",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop wird unter {{platform}} nicht unterstützt.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"Installieren Sie Anaconda Desktop auf diesem Computer und kehren Sie dann hierher zurück. Kilo führt das Installationsprogramm nicht für Sie aus.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Öffnen Sie Anaconda Desktop, schließen Sie die Einrichtung ab und melden Sie sich an. Wählen Sie dann Erneut prüfen.",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"Die Einrichtung von Anaconda Desktop ist unvollständig. Öffnen Sie Desktop, schließen Sie die Einrichtung ab und starten Sie es bei Bedarf neu.",
|
||||
"provider.anaconda.state.signedOut": "Öffnen Sie Anaconda Desktop und melden Sie sich an, bevor Sie Kilo verbinden.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo konnte nicht auf Anaconda Desktop zugreifen. Öffnen Sie Desktop, melden Sie sich erneut an und starten Sie es bei Bedarf neu.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop antwortet noch nicht. Öffnen Sie es und warten Sie, bis die Anwendung vollständig gestartet ist.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"Laden Sie in Anaconda Desktop ein Textgenerierungsmodell herunter. Wählen Sie nach Möglichkeit eines mit Unterstützung für Tool-Aufrufe und starten Sie dann dessen Server.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"1 heruntergeladenes Textgenerierungsmodell ist verfügbar. Starten Sie in Anaconda Desktop einen Modellserver. Modelle mit Unterstützung für Tool-Aufrufe werden dringend empfohlen.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"{{count}} heruntergeladene Textgenerierungsmodelle sind verfügbar. Starten Sie in Anaconda Desktop einen Modellserver. Modelle mit Unterstützung für Tool-Aufrufe werden dringend empfohlen.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"Der aktive Inferenzserver ist noch nicht funktionsfähig. Überprüfen Sie ihn in Anaconda Desktop und starten Sie den Server bei Bedarf neu.",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo hat einen funktionsfähigen lokalen Textgenerierungsserver gefunden und kann dessen aktuelle Verbindungseinstellungen importieren.",
|
||||
"provider.anaconda.server": "Aktiver Inferenzserver",
|
||||
"provider.anaconda.context": "Kontextfenster",
|
||||
"provider.anaconda.contextValue": "{{count}} Token",
|
||||
"provider.anaconda.tools": "Tool-Aufrufe",
|
||||
"provider.anaconda.tools.supported": "Unterstützt",
|
||||
"provider.anaconda.tools.unsupported": "Nicht aktiviert",
|
||||
"provider.anaconda.tools.unknown": "Unbekannt",
|
||||
"provider.anaconda.warning.title": "Tool-Unterstützung ist eingeschränkt",
|
||||
"provider.anaconda.warning.description":
|
||||
"Dieser Server bestätigt keine Tool-Aufrufe. Aktionen des Coding-Agenten können fehlschlagen oder nicht verfügbar sein. Fahren Sie nur fort, wenn Sie diese Einschränkungen akzeptieren.",
|
||||
"provider.anaconda.action.download": "Anaconda Desktop herunterladen",
|
||||
"provider.anaconda.action.open": "Anaconda Desktop öffnen",
|
||||
"provider.anaconda.action.checkAgain": "Erneut prüfen",
|
||||
"provider.anaconda.action.continue": "Trotzdem fortfahren",
|
||||
"provider.anaconda.action.manage": "Verwalten / Aktualisieren",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop aktualisiert",
|
||||
"provider.anaconda.toast.refreshed.description":
|
||||
"Der aktive lokale Server und die Modelle sind in Kilo auf dem neuesten Stand.",
|
||||
"settings.providers.note.anacondaDesktop":
|
||||
"Führen Sie ein Modell aus, das lokal von Anaconda Desktop bereitgestellt wird.",
|
||||
"settings.providers.tag.local": "Lokal",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "Vorgeschlagen",
|
||||
"command.category.view": "Ansicht",
|
||||
"command.category.project": "Projekt",
|
||||
@@ -104,6 +154,31 @@ export const dict = {
|
||||
"command.session.unshare.description": "Teilen dieser Sitzung beenden",
|
||||
"command.session.export": "Sitzungsprotokoll exportieren",
|
||||
|
||||
"agentRequirements.skill.installed": "Installiert",
|
||||
"agentRequirements.skill.checkFailed": "Die Skill-Prüfung ist fehlgeschlagen",
|
||||
"agentRequirements.skill.missing": "Nicht installiert",
|
||||
"agentRequirements.mcp.connected": "Verbunden",
|
||||
"agentRequirements.mcp.checkFailed": "Die MCP-Prüfung ist fehlgeschlagen",
|
||||
"agentRequirements.mcp.missing": "Nicht verbunden",
|
||||
"agentRequirements.extension.installed": "Installiert",
|
||||
"agentRequirements.extension.checkFailed": "Die Prüfung der VS Code-Erweiterung ist fehlgeschlagen",
|
||||
"agentRequirements.extension.missing": "Nicht installiert",
|
||||
"agentRequirements.extension.description": "Installiere die fehlenden Erweiterungen in VS Code.",
|
||||
"agentRequirements.group.skills": "Skills",
|
||||
"agentRequirements.group.mcps": "MCPs",
|
||||
"agentRequirements.group.extensions": "VS Code-Erweiterungen",
|
||||
"agentRequirements.blocked.title": "Voraussetzungen für den Agenten {{agent}}",
|
||||
"agentRequirements.blocked.description":
|
||||
"Dieser Agent benötigt die folgenden Werkzeuge, bevor er ausgeführt werden kann.",
|
||||
"agentRequirements.prompt.blocked": "Schließe zuerst die erforderlichen Prüfungen ab, um diesen Agenten zu verwenden",
|
||||
"agentRequirements.action.openMarketplace": "Marketplace öffnen",
|
||||
"agentRequirements.error.unknownAgent": "Der ausgewählte Agent wurde nicht gefunden.",
|
||||
"agentRequirements.error.malformedDeclaration": "Dieser Agent hat eine ungültige Anforderungsdeklaration.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo konnte die verfügbaren Skills nicht prüfen.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo konnte den MCP-Serverstatus nicht prüfen.",
|
||||
"agentRequirements.error.scopeMismatch": "Diese Prüfung der Agentenanforderungen ist nicht mehr aktiv.",
|
||||
"agentRequirements.error.requestFailed": "Kilo konnte die Agentenanforderungen nicht prüfen.",
|
||||
|
||||
"palette.search.placeholder": "Dateien, Befehle und Sitzungen durchsuchen",
|
||||
"palette.empty": "Keine Ergebnisse gefunden",
|
||||
"palette.group.commands": "Befehle",
|
||||
|
||||
@@ -147,6 +147,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "Stop sharing this session",
|
||||
"command.session.export": "Export session transcript",
|
||||
|
||||
"agentRequirements.skill.installed": "Installed",
|
||||
"agentRequirements.skill.checkFailed": "The skill check failed",
|
||||
"agentRequirements.skill.missing": "Not installed",
|
||||
"agentRequirements.mcp.connected": "Connected",
|
||||
"agentRequirements.mcp.checkFailed": "The MCP check failed",
|
||||
"agentRequirements.mcp.missing": "Not connected",
|
||||
"agentRequirements.extension.installed": "Installed",
|
||||
"agentRequirements.extension.checkFailed": "The VS Code extension check failed",
|
||||
"agentRequirements.extension.missing": "Not installed",
|
||||
"agentRequirements.extension.description": "Install the missing extensions in VS Code.",
|
||||
"agentRequirements.group.skills": "Skills",
|
||||
"agentRequirements.group.mcps": "MCPs",
|
||||
"agentRequirements.group.extensions": "VS Code Extensions",
|
||||
"agentRequirements.blocked.title": "{{agent}} agent prerequisites",
|
||||
"agentRequirements.blocked.description": "This agent needs the following tools before it can run.",
|
||||
"agentRequirements.prompt.blocked": "Complete the required checks to use this agent first",
|
||||
"agentRequirements.action.openMarketplace": "Open Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "The selected agent could not be found.",
|
||||
"agentRequirements.error.malformedDeclaration": "This agent has an invalid requirement declaration.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo could not check the available skills.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo could not check MCP server status.",
|
||||
"agentRequirements.error.scopeMismatch": "This agent requirement check is no longer active.",
|
||||
"agentRequirements.error.requestFailed": "Kilo could not check the agent requirements.",
|
||||
|
||||
"palette.search.placeholder": "Search files, commands, and sessions",
|
||||
"palette.empty": "No results found",
|
||||
"palette.group.commands": "Commands",
|
||||
|
||||
+76
-5
@@ -1,12 +1,58 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "Comprobar de nuevo",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Conectar Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "Administrar Anaconda Desktop",
|
||||
"provider.anaconda.status.checking": "Comprobando Anaconda Desktop...",
|
||||
"provider.anaconda.status.opening": "Abriendo Anaconda Desktop...",
|
||||
"provider.anaconda.status.syncing": "Actualizando modelos del proveedor...",
|
||||
"provider.anaconda.status.ready": "Listo para conectar",
|
||||
"provider.anaconda.status.waiting": "Esperando a Desktop",
|
||||
"provider.anaconda.status.attention": "Requiere atención",
|
||||
"provider.anaconda.status.unavailable": "No disponible",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop no es compatible con {{platform}}.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"Instala Anaconda Desktop en este equipo y luego vuelve aquí. Kilo no ejecuta el instalador por ti.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Abre Anaconda Desktop, completa la configuración e inicia sesión, luego elige Comprobar de nuevo.",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"La configuración de Anaconda Desktop está incompleta. Abre Desktop, completa la configuración y reinícialo si es necesario.",
|
||||
"provider.anaconda.state.signedOut": "Abre Anaconda Desktop e inicia sesión antes de conectar Kilo.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo no pudo acceder a Anaconda Desktop. Abre Desktop, inicia sesión de nuevo y reinícialo si es necesario.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop aún no responde. Ábrelo y espera a que la aplicación termine de iniciarse.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"En Anaconda Desktop, descarga un modelo de generación de texto. Elige uno con llamadas a herramientas cuando sea posible y luego inicia su servidor.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"Hay 1 modelo de generación de texto descargado disponible. En Anaconda Desktop, inicia un servidor de modelos. Se recomienda encarecidamente usar modelos compatibles con llamadas a herramientas.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"Hay {{count}} modelos de generación de texto descargados disponibles. En Anaconda Desktop, inicia un servidor de modelos. Se recomienda encarecidamente usar modelos compatibles con llamadas a herramientas.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"El servidor de inferencia activo aún no está en buen estado. Compruébalo en Anaconda Desktop y reinicia el servidor si es necesario.",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo encontró un servidor local de generación de texto en buen estado y puede importar su configuración de conexión actual.",
|
||||
"provider.anaconda.server": "Servidor de inferencia activo",
|
||||
"provider.anaconda.context": "Ventana de contexto",
|
||||
"provider.anaconda.contextValue": "{{count}} tokens",
|
||||
"provider.anaconda.tools": "Llamadas a herramientas",
|
||||
"provider.anaconda.tools.supported": "Compatible",
|
||||
"provider.anaconda.tools.unsupported": "No habilitado",
|
||||
"provider.anaconda.tools.unknown": "Desconocido",
|
||||
"provider.anaconda.warning.title": "La compatibilidad con herramientas es limitada",
|
||||
"provider.anaconda.warning.description":
|
||||
"Este servidor no confirma las llamadas a herramientas. Las acciones del agente de programación pueden fallar o no estar disponibles. Continúa solo si aceptas estas limitaciones.",
|
||||
"provider.anaconda.action.download": "Descargar Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "Abrir Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "Comprobar de nuevo",
|
||||
"provider.anaconda.action.continue": "Continuar de todos modos",
|
||||
"provider.anaconda.action.manage": "Administrar / Actualizar",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop actualizado",
|
||||
"provider.anaconda.toast.refreshed.description": "El servidor local activo y los modelos están actualizados en Kilo.",
|
||||
"settings.providers.note.anacondaDesktop": "Ejecuta un modelo servido localmente por Anaconda Desktop.",
|
||||
"settings.providers.tag.local": "Local",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "Sugerido",
|
||||
"command.category.view": "Ver",
|
||||
"command.category.project": "Proyecto",
|
||||
@@ -102,6 +148,31 @@ export const dict = {
|
||||
"command.session.unshare.description": "Dejar de compartir esta sesión",
|
||||
"command.session.export": "Exportar transcripción de la sesión",
|
||||
|
||||
"agentRequirements.skill.installed": "Instalada",
|
||||
"agentRequirements.skill.checkFailed": "La comprobación de la habilidad falló",
|
||||
"agentRequirements.skill.missing": "No instalada",
|
||||
"agentRequirements.mcp.connected": "Conectado",
|
||||
"agentRequirements.mcp.checkFailed": "La comprobación de MCP falló",
|
||||
"agentRequirements.mcp.missing": "No conectado",
|
||||
"agentRequirements.extension.installed": "Instalada",
|
||||
"agentRequirements.extension.checkFailed": "La comprobación de la extensión de VS Code falló",
|
||||
"agentRequirements.extension.missing": "No instalada",
|
||||
"agentRequirements.extension.description": "Instala las extensiones que faltan en VS Code.",
|
||||
"agentRequirements.group.skills": "Habilidades",
|
||||
"agentRequirements.group.mcps": "MCPs",
|
||||
"agentRequirements.group.extensions": "Extensiones de VS Code",
|
||||
"agentRequirements.blocked.title": "Requisitos previos del agente {{agent}}",
|
||||
"agentRequirements.blocked.description":
|
||||
"Este agente necesita las siguientes herramientas antes de poder ejecutarse.",
|
||||
"agentRequirements.prompt.blocked": "Completa primero las comprobaciones requeridas para usar este agente",
|
||||
"agentRequirements.action.openMarketplace": "Abrir Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "No se pudo encontrar el agente seleccionado.",
|
||||
"agentRequirements.error.malformedDeclaration": "Este agente tiene una declaración de requisitos no válida.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo no pudo comprobar las habilidades disponibles.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo no pudo comprobar el estado del servidor MCP.",
|
||||
"agentRequirements.error.scopeMismatch": "Esta comprobación de requisitos del agente ya no está activa.",
|
||||
"agentRequirements.error.requestFailed": "Kilo no pudo comprobar los requisitos del agente.",
|
||||
|
||||
"palette.search.placeholder": "Buscar archivos, comandos y sesiones",
|
||||
"palette.empty": "No se encontraron resultados",
|
||||
"palette.group.commands": "Comandos",
|
||||
|
||||
+75
-5
@@ -1,12 +1,58 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "Vérifier à nouveau",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Connecter Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "Gérer Anaconda Desktop",
|
||||
"provider.anaconda.status.checking": "Vérification d’Anaconda Desktop...",
|
||||
"provider.anaconda.status.opening": "Ouverture d’Anaconda Desktop...",
|
||||
"provider.anaconda.status.syncing": "Actualisation des modèles du fournisseur...",
|
||||
"provider.anaconda.status.ready": "Prêt à se connecter",
|
||||
"provider.anaconda.status.waiting": "En attente de Desktop",
|
||||
"provider.anaconda.status.attention": "Nécessite une attention",
|
||||
"provider.anaconda.status.unavailable": "Indisponible",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop n’est pas pris en charge sur {{platform}}.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"Installez Anaconda Desktop sur cette machine, puis revenez ici. Kilo n’exécute pas l’installateur à votre place.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Ouvrez Anaconda Desktop, terminez la configuration et connectez-vous, puis choisissez Vérifier à nouveau.",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"La configuration d’Anaconda Desktop est incomplète. Ouvrez Desktop, terminez la configuration et redémarrez-le si nécessaire.",
|
||||
"provider.anaconda.state.signedOut": "Ouvrez Anaconda Desktop et connectez-vous avant de connecter Kilo.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo n’a pas pu accéder à Anaconda Desktop. Ouvrez Desktop, reconnectez-vous et redémarrez-le si nécessaire.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop ne répond pas encore. Ouvrez-le et attendez que l’application finisse de démarrer.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"Dans Anaconda Desktop, téléchargez un modèle de génération de texte. Choisissez-en un prenant en charge l’appel d’outils si possible, puis démarrez son serveur.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"1 modèle de génération de texte téléchargé est disponible. Dans Anaconda Desktop, démarrez un serveur de modèle. Les modèles prenant en charge l’appel d’outils sont vivement recommandés.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"{{count}} modèles de génération de texte téléchargés sont disponibles. Dans Anaconda Desktop, démarrez un serveur de modèle. Les modèles prenant en charge l’appel d’outils sont vivement recommandés.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"Le serveur d’inférence actif n’est pas encore sain. Vérifiez-le dans Anaconda Desktop et redémarrez le serveur si nécessaire.",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo a trouvé un serveur local sain de génération de texte et peut importer ses paramètres de connexion actuels.",
|
||||
"provider.anaconda.server": "Serveur d’inférence actif",
|
||||
"provider.anaconda.context": "Fenêtre de contexte",
|
||||
"provider.anaconda.contextValue": "{{count}} jetons",
|
||||
"provider.anaconda.tools": "Appel d’outils",
|
||||
"provider.anaconda.tools.supported": "Pris en charge",
|
||||
"provider.anaconda.tools.unsupported": "Non activé",
|
||||
"provider.anaconda.tools.unknown": "Inconnu",
|
||||
"provider.anaconda.warning.title": "La prise en charge des outils est limitée",
|
||||
"provider.anaconda.warning.description":
|
||||
"Ce serveur ne confirme pas l’appel d’outils. Les actions de l’agent de codage peuvent échouer ou être indisponibles. Ne continuez que si vous acceptez ces limitations.",
|
||||
"provider.anaconda.action.download": "Télécharger Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "Ouvrir Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "Vérifier à nouveau",
|
||||
"provider.anaconda.action.continue": "Continuer quand même",
|
||||
"provider.anaconda.action.manage": "Gérer / Actualiser",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop actualisé",
|
||||
"provider.anaconda.toast.refreshed.description": "Le serveur local actif et les modèles sont à jour dans Kilo.",
|
||||
"settings.providers.note.anacondaDesktop": "Exécutez un modèle servi localement par Anaconda Desktop.",
|
||||
"settings.providers.tag.local": "Local",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "Suggéré",
|
||||
"command.category.view": "Affichage",
|
||||
"command.category.project": "Projet",
|
||||
@@ -103,6 +149,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "Arrêter de partager cette session",
|
||||
"command.session.export": "Exporter la transcription de la session",
|
||||
|
||||
"agentRequirements.skill.installed": "Installée",
|
||||
"agentRequirements.skill.checkFailed": "La vérification de la compétence a échoué",
|
||||
"agentRequirements.skill.missing": "Non installée",
|
||||
"agentRequirements.mcp.connected": "Connecté",
|
||||
"agentRequirements.mcp.checkFailed": "La vérification MCP a échoué",
|
||||
"agentRequirements.mcp.missing": "Non connecté",
|
||||
"agentRequirements.extension.installed": "Installée",
|
||||
"agentRequirements.extension.checkFailed": "La vérification de l’extension VS Code a échoué",
|
||||
"agentRequirements.extension.missing": "Non installée",
|
||||
"agentRequirements.extension.description": "Installez les extensions manquantes dans VS Code.",
|
||||
"agentRequirements.group.skills": "Compétences",
|
||||
"agentRequirements.group.mcps": "MCP",
|
||||
"agentRequirements.group.extensions": "Extensions VS Code",
|
||||
"agentRequirements.blocked.title": "Prérequis de l’agent {{agent}}",
|
||||
"agentRequirements.blocked.description": "Cet agent a besoin des outils suivants avant de pouvoir s’exécuter.",
|
||||
"agentRequirements.prompt.blocked": "Terminez d’abord les vérifications requises pour utiliser cet agent",
|
||||
"agentRequirements.action.openMarketplace": "Ouvrir Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "L’agent sélectionné est introuvable.",
|
||||
"agentRequirements.error.malformedDeclaration": "Cet agent a une déclaration de prérequis invalide.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo n’a pas pu vérifier les compétences disponibles.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo n’a pas pu vérifier l’état du serveur MCP.",
|
||||
"agentRequirements.error.scopeMismatch": "Cette vérification des prérequis de l’agent n’est plus active.",
|
||||
"agentRequirements.error.requestFailed": "Kilo n’a pas pu vérifier les prérequis de l’agent.",
|
||||
|
||||
"palette.search.placeholder": "Rechercher des fichiers, des commandes et des sessions",
|
||||
"palette.empty": "Aucun résultat trouvé",
|
||||
"palette.group.commands": "Commandes",
|
||||
|
||||
+76
-5
@@ -1,12 +1,58 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "Controlla di nuovo",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Connetti Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "Gestisci Anaconda Desktop",
|
||||
"provider.anaconda.status.checking": "Controllo di Anaconda Desktop in corso...",
|
||||
"provider.anaconda.status.opening": "Apertura di Anaconda Desktop in corso...",
|
||||
"provider.anaconda.status.syncing": "Aggiornamento dei modelli del provider in corso...",
|
||||
"provider.anaconda.status.ready": "Pronto per la connessione",
|
||||
"provider.anaconda.status.waiting": "In attesa di Desktop",
|
||||
"provider.anaconda.status.attention": "Richiede attenzione",
|
||||
"provider.anaconda.status.unavailable": "Non disponibile",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop non è supportato su {{platform}}.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"Installa Anaconda Desktop su questo computer, poi torna qui. Kilo non esegue il programma di installazione per te.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Apri Anaconda Desktop, completa la configurazione e accedi, poi scegli Controlla di nuovo.",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"La configurazione di Anaconda Desktop è incompleta. Apri Desktop, completa la configurazione e riavvialo se necessario.",
|
||||
"provider.anaconda.state.signedOut": "Apri Anaconda Desktop e accedi prima di connettere Kilo.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo non è riuscito ad accedere ad Anaconda Desktop. Apri Desktop, accedi di nuovo e riavvialo se necessario.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop non risponde ancora. Aprilo e attendi che l'applicazione finisca di avviarsi.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"In Anaconda Desktop, scarica un modello di generazione del testo. Quando possibile, scegline uno con il supporto alle chiamate agli strumenti, poi avvia il suo server.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"È disponibile 1 modello di generazione del testo scaricato. In Anaconda Desktop, avvia un server di modelli. I modelli che supportano le chiamate agli strumenti sono fortemente consigliati.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"Sono disponibili {{count}} modelli di generazione del testo scaricati. In Anaconda Desktop, avvia un server di modelli. I modelli che supportano le chiamate agli strumenti sono fortemente consigliati.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"Il server di inferenza attivo non è ancora integro. Controllalo in Anaconda Desktop e riavvia il server se necessario.",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo ha trovato un server locale di generazione del testo integro e può importare le sue attuali impostazioni di connessione.",
|
||||
"provider.anaconda.server": "Server di inferenza attivo",
|
||||
"provider.anaconda.context": "Finestra di contesto",
|
||||
"provider.anaconda.contextValue": "{{count}} token",
|
||||
"provider.anaconda.tools": "Chiamata agli strumenti",
|
||||
"provider.anaconda.tools.supported": "Supportata",
|
||||
"provider.anaconda.tools.unsupported": "Non abilitata",
|
||||
"provider.anaconda.tools.unknown": "Sconosciuta",
|
||||
"provider.anaconda.warning.title": "Il supporto agli strumenti è limitato",
|
||||
"provider.anaconda.warning.description":
|
||||
"Questo server non conferma la chiamata agli strumenti. Le azioni dell'agente di programmazione potrebbero non riuscire o non essere disponibili. Continua solo se accetti queste limitazioni.",
|
||||
"provider.anaconda.action.download": "Scarica Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "Apri Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "Controlla di nuovo",
|
||||
"provider.anaconda.action.continue": "Continua comunque",
|
||||
"provider.anaconda.action.manage": "Gestisci / Aggiorna",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop aggiornato",
|
||||
"provider.anaconda.toast.refreshed.description": "Il server locale attivo e i modelli sono aggiornati in Kilo.",
|
||||
"settings.providers.note.anacondaDesktop": "Esegui un modello servito localmente da Anaconda Desktop.",
|
||||
"settings.providers.tag.local": "Locale",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "Suggeriti",
|
||||
"command.category.view": "Vista",
|
||||
"command.category.project": "Progetto",
|
||||
@@ -1622,6 +1668,31 @@ export const dict = {
|
||||
// Session export
|
||||
"command.session.export": "Esporta trascrizione sessione",
|
||||
|
||||
"agentRequirements.skill.installed": "Installata",
|
||||
"agentRequirements.skill.checkFailed": "Il controllo della skill non è riuscito",
|
||||
"agentRequirements.skill.missing": "Non installata",
|
||||
"agentRequirements.mcp.connected": "Connesso",
|
||||
"agentRequirements.mcp.checkFailed": "Il controllo MCP non è riuscito",
|
||||
"agentRequirements.mcp.missing": "Non connesso",
|
||||
"agentRequirements.extension.installed": "Installata",
|
||||
"agentRequirements.extension.checkFailed": "Il controllo dell’estensione VS Code non è riuscito",
|
||||
"agentRequirements.extension.missing": "Non installata",
|
||||
"agentRequirements.extension.description": "Installa le estensioni mancanti in VS Code.",
|
||||
"agentRequirements.group.skills": "Skill",
|
||||
"agentRequirements.group.mcps": "MCP",
|
||||
"agentRequirements.group.extensions": "Estensioni VS Code",
|
||||
"agentRequirements.blocked.title": "Prerequisiti dell’agente {{agent}}",
|
||||
"agentRequirements.blocked.description":
|
||||
"Questo agente richiede i seguenti strumenti prima di poter essere eseguito.",
|
||||
"agentRequirements.prompt.blocked": "Completa prima i controlli richiesti per usare questo agente",
|
||||
"agentRequirements.action.openMarketplace": "Apri Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "Impossibile trovare l’agente selezionato.",
|
||||
"agentRequirements.error.malformedDeclaration": "Questo agente ha una dichiarazione dei requisiti non valida.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo non ha potuto controllare le skill disponibili.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo non ha potuto controllare lo stato del server MCP.",
|
||||
"agentRequirements.error.scopeMismatch": "Questo controllo dei requisiti dell’agente non è più attivo.",
|
||||
"agentRequirements.error.requestFailed": "Kilo non ha potuto controllare i requisiti dell’agente.",
|
||||
|
||||
// Model groups
|
||||
"model.group.collapse": "Comprimi {{group}}",
|
||||
"model.group.expand": "Espandi {{group}}",
|
||||
|
||||
+75
-5
@@ -1,12 +1,58 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "再確認",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Anaconda Desktop に接続",
|
||||
"provider.anaconda.title.manage": "Anaconda Desktop を管理",
|
||||
"provider.anaconda.status.checking": "Anaconda Desktop を確認しています...",
|
||||
"provider.anaconda.status.opening": "Anaconda Desktop を開いています...",
|
||||
"provider.anaconda.status.syncing": "プロバイダーのモデルを更新しています...",
|
||||
"provider.anaconda.status.ready": "接続の準備ができました",
|
||||
"provider.anaconda.status.waiting": "Desktop を待機しています",
|
||||
"provider.anaconda.status.attention": "対応が必要です",
|
||||
"provider.anaconda.status.unavailable": "利用できません",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop は {{platform}} ではサポートされていません。",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"このマシンに Anaconda Desktop をインストールしてから、ここに戻ってください。Kilo はインストーラーを実行しません。",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Anaconda Desktop を開いてセットアップを完了しサインインしてから、再確認を選択してください。",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"Anaconda Desktop のセットアップが完了していません。Desktop を開いてセットアップを完了し、必要に応じて再起動してください。",
|
||||
"provider.anaconda.state.signedOut": "Kilo を接続する前に、Anaconda Desktop を開いてサインインしてください。",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo は Anaconda Desktop にアクセスできませんでした。Desktop を開いて再度サインインし、必要に応じて再起動してください。",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop はまだ応答していません。開いてアプリケーションの起動が完了するまでお待ちください。",
|
||||
"provider.anaconda.state.noModel":
|
||||
"Anaconda Desktop で、テキスト生成モデルをダウンロードしてください。可能であればツール呼び出しに対応したモデルを選び、そのサーバーを起動してください。",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"ダウンロード済みのテキスト生成モデルが1つ利用可能です。Anaconda Desktopでモデルサーバーを起動してください。ツール呼び出しに対応したモデルの使用を強く推奨します。",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"ダウンロード済みのテキスト生成モデルが{{count}}個利用可能です。Anaconda Desktopでモデルサーバーを起動してください。ツール呼び出しに対応したモデルの使用を強く推奨します。",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"アクティブな推論サーバーはまだ正常ではありません。Anaconda Desktop で確認し、必要に応じてサーバーを再起動してください。",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo は正常なローカルのテキスト生成サーバーを見つけ、現在の接続設定をインポートできます。",
|
||||
"provider.anaconda.server": "アクティブな推論サーバー",
|
||||
"provider.anaconda.context": "コンテキストウィンドウ",
|
||||
"provider.anaconda.contextValue": "{{count}} トークン",
|
||||
"provider.anaconda.tools": "ツール呼び出し",
|
||||
"provider.anaconda.tools.supported": "対応",
|
||||
"provider.anaconda.tools.unsupported": "無効",
|
||||
"provider.anaconda.tools.unknown": "不明",
|
||||
"provider.anaconda.warning.title": "ツールのサポートは限定的です",
|
||||
"provider.anaconda.warning.description":
|
||||
"このサーバーはツール呼び出しを確認できません。コーディングエージェントの操作が失敗したり利用できなかったりする場合があります。これらの制限を受け入れる場合のみ続行してください。",
|
||||
"provider.anaconda.action.download": "Anaconda Desktop をダウンロード",
|
||||
"provider.anaconda.action.open": "Anaconda Desktop を開く",
|
||||
"provider.anaconda.action.checkAgain": "再確認",
|
||||
"provider.anaconda.action.continue": "このまま続行",
|
||||
"provider.anaconda.action.manage": "管理 / 更新",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop を更新しました",
|
||||
"provider.anaconda.toast.refreshed.description": "アクティブなローカルサーバーとモデルが Kilo で最新になりました。",
|
||||
"settings.providers.note.anacondaDesktop": "Anaconda Desktop によってローカルで提供されるモデルを実行します。",
|
||||
"settings.providers.tag.local": "ローカル",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "おすすめ",
|
||||
"command.category.view": "表示",
|
||||
"command.category.project": "プロジェクト",
|
||||
@@ -102,6 +148,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "このセッションの共有を停止",
|
||||
"command.session.export": "セッション記録をエクスポート",
|
||||
|
||||
"agentRequirements.skill.installed": "インストール済み",
|
||||
"agentRequirements.skill.checkFailed": "スキルの確認に失敗しました",
|
||||
"agentRequirements.skill.missing": "未インストール",
|
||||
"agentRequirements.mcp.connected": "接続済み",
|
||||
"agentRequirements.mcp.checkFailed": "MCP の確認に失敗しました",
|
||||
"agentRequirements.mcp.missing": "未接続",
|
||||
"agentRequirements.extension.installed": "インストール済み",
|
||||
"agentRequirements.extension.checkFailed": "VS Code 拡張機能の確認に失敗しました",
|
||||
"agentRequirements.extension.missing": "未インストール",
|
||||
"agentRequirements.extension.description": "不足している拡張機能を VS Code にインストールしてください。",
|
||||
"agentRequirements.group.skills": "スキル",
|
||||
"agentRequirements.group.mcps": "MCP",
|
||||
"agentRequirements.group.extensions": "VS Code 拡張機能",
|
||||
"agentRequirements.blocked.title": "{{agent}} エージェントの前提条件",
|
||||
"agentRequirements.blocked.description": "このエージェントを実行するには、次のツールが必要です。",
|
||||
"agentRequirements.prompt.blocked": "このエージェントを使用するには、先に必要な確認を完了してください",
|
||||
"agentRequirements.action.openMarketplace": "Marketplace を開く",
|
||||
"agentRequirements.error.unknownAgent": "選択したエージェントが見つかりませんでした。",
|
||||
"agentRequirements.error.malformedDeclaration": "このエージェントの要件宣言は無効です。",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo は利用可能なスキルを確認できませんでした。",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo は MCP サーバーの状態を確認できませんでした。",
|
||||
"agentRequirements.error.scopeMismatch": "このエージェント要件の確認はもう有効ではありません。",
|
||||
"agentRequirements.error.requestFailed": "Kilo はエージェント要件を確認できませんでした。",
|
||||
|
||||
"palette.search.placeholder": "ファイル、コマンド、セッションを検索",
|
||||
"palette.empty": "結果が見つかりません",
|
||||
"palette.group.commands": "コマンド",
|
||||
|
||||
+78
-6
@@ -1,14 +1,62 @@
|
||||
import { anacondaDesktopDict, dict as en } from "./en"
|
||||
import { dict as en } from "./en"
|
||||
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Anaconda Desktop 연결",
|
||||
"provider.anaconda.title.manage": "Anaconda Desktop 관리",
|
||||
"provider.anaconda.status.checking": "Anaconda Desktop 확인 중...",
|
||||
"provider.anaconda.status.opening": "Anaconda Desktop 여는 중...",
|
||||
"provider.anaconda.status.syncing": "공급자 모델 새로 고치는 중...",
|
||||
"provider.anaconda.status.ready": "연결할 준비됨",
|
||||
"provider.anaconda.status.waiting": "Desktop 대기 중",
|
||||
"provider.anaconda.status.attention": "주의 필요",
|
||||
"provider.anaconda.status.unavailable": "사용할 수 없음",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop은 {{platform}}에서 지원되지 않습니다.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"이 컴퓨터에 Anaconda Desktop을 설치한 후 여기로 돌아오세요. Kilo는 설치 프로그램을 대신 실행하지 않습니다.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Anaconda Desktop을 열고 설정을 완료한 후 로그인한 다음 다시 확인을 선택하세요.",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"Anaconda Desktop 설정이 완료되지 않았습니다. Desktop을 열고 설정을 완료한 후 필요하면 다시 시작하세요.",
|
||||
"provider.anaconda.state.signedOut": "Kilo를 연결하기 전에 Anaconda Desktop을 열고 로그인하세요.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo가 Anaconda Desktop에 접근할 수 없습니다. Desktop을 열고 다시 로그인한 후 필요하면 다시 시작하세요.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop이 아직 응답하지 않습니다. 열어서 애플리케이션이 완전히 시작될 때까지 기다리세요.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"Anaconda Desktop에서 텍스트 생성 모델을 다운로드하세요. 가능하면 도구 호출을 지원하는 모델을 선택한 후 서버를 시작하세요.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"다운로드된 텍스트 생성 모델 1개를 사용할 수 있습니다. Anaconda Desktop에서 모델 서버를 시작하세요. 도구 호출을 지원하는 모델을 강력히 권장합니다.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"다운로드된 텍스트 생성 모델 {{count}}개를 사용할 수 있습니다. Anaconda Desktop에서 모델 서버를 시작하세요. 도구 호출을 지원하는 모델을 강력히 권장합니다.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"활성 추론 서버가 아직 정상이 아닙니다. Anaconda Desktop에서 확인하고 필요하면 서버를 다시 시작하세요.",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo가 정상적인 로컬 텍스트 생성 서버를 찾았으며 현재 연결 설정을 가져올 수 있습니다.",
|
||||
"provider.anaconda.server": "활성 추론 서버",
|
||||
"provider.anaconda.context": "컨텍스트 창",
|
||||
"provider.anaconda.contextValue": "{{count}} 토큰",
|
||||
"provider.anaconda.tools": "도구 호출",
|
||||
"provider.anaconda.tools.supported": "지원됨",
|
||||
"provider.anaconda.tools.unsupported": "사용 안 함",
|
||||
"provider.anaconda.tools.unknown": "알 수 없음",
|
||||
"provider.anaconda.warning.title": "도구 지원이 제한적입니다",
|
||||
"provider.anaconda.warning.description":
|
||||
"이 서버는 도구 호출을 확인하지 않습니다. 코딩 에이전트 작업이 실패하거나 사용할 수 없을 수 있습니다. 이러한 제한을 감수하는 경우에만 계속하세요.",
|
||||
"provider.anaconda.action.download": "Anaconda Desktop 다운로드",
|
||||
"provider.anaconda.action.open": "Anaconda Desktop 열기",
|
||||
"provider.anaconda.action.checkAgain": "다시 확인",
|
||||
"provider.anaconda.action.continue": "계속하기",
|
||||
"provider.anaconda.action.manage": "관리 / 새로 고침",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop 새로 고침됨",
|
||||
"provider.anaconda.toast.refreshed.description": "활성 로컬 서버와 모델이 Kilo에서 최신 상태입니다.",
|
||||
"settings.providers.note.anacondaDesktop": "Anaconda Desktop이 로컬에서 제공하는 모델을 실행합니다.",
|
||||
"settings.providers.tag.local": "로컬",
|
||||
} as const
|
||||
|
||||
type Keys = keyof typeof en
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "다시 확인",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"다운로드된 텍스트 생성 모델 1개를 사용할 수 있습니다. Anaconda Desktop에서 모델 서버를 시작하세요. 도구 호출을 지원하는 모델을 강력히 권장합니다.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"다운로드된 텍스트 생성 모델 {{count}}개를 사용할 수 있습니다. Anaconda Desktop에서 모델 서버를 시작하세요. 도구 호출을 지원하는 모델을 강력히 권장합니다.",
|
||||
"command.category.suggested": "추천",
|
||||
"command.category.view": "보기",
|
||||
"command.category.project": "프로젝트",
|
||||
@@ -104,6 +152,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "이 세션 공유 중지",
|
||||
"command.session.export": "세션 기록 내보내기",
|
||||
|
||||
"agentRequirements.skill.installed": "설치됨",
|
||||
"agentRequirements.skill.checkFailed": "스킬 확인에 실패했습니다",
|
||||
"agentRequirements.skill.missing": "설치되지 않음",
|
||||
"agentRequirements.mcp.connected": "연결됨",
|
||||
"agentRequirements.mcp.checkFailed": "MCP 확인에 실패했습니다",
|
||||
"agentRequirements.mcp.missing": "연결되지 않음",
|
||||
"agentRequirements.extension.installed": "설치됨",
|
||||
"agentRequirements.extension.checkFailed": "VS Code 확장 확인에 실패했습니다",
|
||||
"agentRequirements.extension.missing": "설치되지 않음",
|
||||
"agentRequirements.extension.description": "VS Code에서 누락된 확장을 설치하세요.",
|
||||
"agentRequirements.group.skills": "스킬",
|
||||
"agentRequirements.group.mcps": "MCP",
|
||||
"agentRequirements.group.extensions": "VS Code 확장",
|
||||
"agentRequirements.blocked.title": "{{agent}} 에이전트 필수 조건",
|
||||
"agentRequirements.blocked.description": "이 에이전트를 실행하려면 다음 도구가 필요합니다.",
|
||||
"agentRequirements.prompt.blocked": "이 에이전트를 사용하려면 먼저 필요한 확인을 완료하세요",
|
||||
"agentRequirements.action.openMarketplace": "Marketplace 열기",
|
||||
"agentRequirements.error.unknownAgent": "선택한 에이전트를 찾을 수 없습니다.",
|
||||
"agentRequirements.error.malformedDeclaration": "이 에이전트의 요구 사항 선언이 올바르지 않습니다.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo가 사용 가능한 스킬을 확인하지 못했습니다.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo가 MCP 서버 상태를 확인하지 못했습니다.",
|
||||
"agentRequirements.error.scopeMismatch": "이 에이전트 요구 사항 확인은 더 이상 활성 상태가 아닙니다.",
|
||||
"agentRequirements.error.requestFailed": "Kilo가 에이전트 요구 사항을 확인하지 못했습니다.",
|
||||
|
||||
"palette.search.placeholder": "파일, 명령어 및 세션 검색",
|
||||
"palette.empty": "결과 없음",
|
||||
"palette.group.commands": "명령어",
|
||||
|
||||
+76
-5
@@ -1,12 +1,58 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "Opnieuw controleren",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Anaconda Desktop verbinden",
|
||||
"provider.anaconda.title.manage": "Anaconda Desktop beheren",
|
||||
"provider.anaconda.status.checking": "Anaconda Desktop controleren...",
|
||||
"provider.anaconda.status.opening": "Anaconda Desktop openen...",
|
||||
"provider.anaconda.status.syncing": "Providermodellen vernieuwen...",
|
||||
"provider.anaconda.status.ready": "Klaar om te verbinden",
|
||||
"provider.anaconda.status.waiting": "Wachten op Desktop",
|
||||
"provider.anaconda.status.attention": "Vereist aandacht",
|
||||
"provider.anaconda.status.unavailable": "Niet beschikbaar",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop wordt niet ondersteund op {{platform}}.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"Installeer Anaconda Desktop op deze machine en kom hier dan terug. Kilo voert het installatieprogramma niet voor je uit.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Open Anaconda Desktop, voltooi de installatie en meld je aan, kies daarna Opnieuw controleren.",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"De installatie van Anaconda Desktop is onvolledig. Open Desktop, voltooi de installatie en herstart het indien nodig.",
|
||||
"provider.anaconda.state.signedOut": "Open Anaconda Desktop en meld je aan voordat je Kilo verbindt.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo kon geen toegang krijgen tot Anaconda Desktop. Open Desktop, meld je opnieuw aan en herstart het indien nodig.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop reageert nog niet. Open het en wacht tot de applicatie volledig is opgestart.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"Download in Anaconda Desktop een tekstgeneratiemodel. Kies er indien mogelijk een met ondersteuning voor toolaanroepen en start daarna de server.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"Er is 1 gedownload tekstgeneratiemodel beschikbaar. Start een modelserver in Anaconda Desktop. Modellen met ondersteuning voor toolaanroepen worden sterk aanbevolen.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"Er zijn {{count}} gedownloade tekstgeneratiemodellen beschikbaar. Start een modelserver in Anaconda Desktop. Modellen met ondersteuning voor toolaanroepen worden sterk aanbevolen.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"De actieve inferentieserver is nog niet gezond. Controleer deze in Anaconda Desktop en herstart de server indien nodig.",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo heeft een gezonde lokale tekstgeneratieserver gevonden en kan de huidige verbindingsinstellingen importeren.",
|
||||
"provider.anaconda.server": "Actieve inferentieserver",
|
||||
"provider.anaconda.context": "Contextvenster",
|
||||
"provider.anaconda.contextValue": "{{count}} tokens",
|
||||
"provider.anaconda.tools": "Toolaanroepen",
|
||||
"provider.anaconda.tools.supported": "Ondersteund",
|
||||
"provider.anaconda.tools.unsupported": "Niet ingeschakeld",
|
||||
"provider.anaconda.tools.unknown": "Onbekend",
|
||||
"provider.anaconda.warning.title": "Toolondersteuning is beperkt",
|
||||
"provider.anaconda.warning.description":
|
||||
"Deze server bevestigt geen toolaanroepen. Acties van de codeeragent kunnen mislukken of niet beschikbaar zijn. Ga alleen verder als je deze beperkingen accepteert.",
|
||||
"provider.anaconda.action.download": "Anaconda Desktop downloaden",
|
||||
"provider.anaconda.action.open": "Anaconda Desktop openen",
|
||||
"provider.anaconda.action.checkAgain": "Opnieuw controleren",
|
||||
"provider.anaconda.action.continue": "Toch doorgaan",
|
||||
"provider.anaconda.action.manage": "Beheren / Vernieuwen",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop vernieuwd",
|
||||
"provider.anaconda.toast.refreshed.description": "De actieve lokale server en modellen zijn up-to-date in Kilo.",
|
||||
"settings.providers.note.anacondaDesktop": "Voer een model uit dat lokaal door Anaconda Desktop wordt aangeboden.",
|
||||
"settings.providers.tag.local": "Lokaal",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "Voorgesteld",
|
||||
"command.category.view": "Weergave",
|
||||
"command.category.project": "Project",
|
||||
@@ -102,6 +148,31 @@ export const dict = {
|
||||
"command.session.unshare.description": "Stop met het delen van deze sessie",
|
||||
"command.session.export": "Sessietranscript exporteren",
|
||||
|
||||
"agentRequirements.skill.installed": "Geïnstalleerd",
|
||||
"agentRequirements.skill.checkFailed": "De skillcontrole is mislukt",
|
||||
"agentRequirements.skill.missing": "Niet geïnstalleerd",
|
||||
"agentRequirements.mcp.connected": "Verbonden",
|
||||
"agentRequirements.mcp.checkFailed": "De MCP-controle is mislukt",
|
||||
"agentRequirements.mcp.missing": "Niet verbonden",
|
||||
"agentRequirements.extension.installed": "Geïnstalleerd",
|
||||
"agentRequirements.extension.checkFailed": "De controle van de VS Code-extensie is mislukt",
|
||||
"agentRequirements.extension.missing": "Niet geïnstalleerd",
|
||||
"agentRequirements.extension.description": "Installeer de ontbrekende extensies in VS Code.",
|
||||
"agentRequirements.group.skills": "Skills",
|
||||
"agentRequirements.group.mcps": "MCP’s",
|
||||
"agentRequirements.group.extensions": "VS Code-extensies",
|
||||
"agentRequirements.blocked.title": "Vereisten voor agent {{agent}}",
|
||||
"agentRequirements.blocked.description":
|
||||
"Deze agent heeft de volgende tools nodig voordat deze kan worden uitgevoerd.",
|
||||
"agentRequirements.prompt.blocked": "Voltooi eerst de vereiste controles om deze agent te gebruiken",
|
||||
"agentRequirements.action.openMarketplace": "Marketplace openen",
|
||||
"agentRequirements.error.unknownAgent": "De geselecteerde agent kon niet worden gevonden.",
|
||||
"agentRequirements.error.malformedDeclaration": "Deze agent heeft een ongeldige vereistenverklaring.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo kon de beschikbare skills niet controleren.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo kon de MCP-serverstatus niet controleren.",
|
||||
"agentRequirements.error.scopeMismatch": "Deze controle van agentvereisten is niet meer actief.",
|
||||
"agentRequirements.error.requestFailed": "Kilo kon de agentvereisten niet controleren.",
|
||||
|
||||
"palette.search.placeholder": "Zoeken naar bestanden, commando's en sessies",
|
||||
"palette.empty": "Geen resultaten gevonden",
|
||||
"palette.group.commands": "Commando's",
|
||||
|
||||
+76
-4
@@ -1,13 +1,61 @@
|
||||
import { anacondaDesktopDict, dict as en } from "./en"
|
||||
import { dict as en } from "./en"
|
||||
type Keys = keyof typeof en
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "Sjekk igjen",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Koble til Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "Administrer Anaconda Desktop",
|
||||
"provider.anaconda.status.checking": "Sjekker Anaconda Desktop...",
|
||||
"provider.anaconda.status.opening": "Åpner Anaconda Desktop...",
|
||||
"provider.anaconda.status.syncing": "Oppdaterer leverandørmodeller...",
|
||||
"provider.anaconda.status.ready": "Klar til å koble til",
|
||||
"provider.anaconda.status.waiting": "Venter på Desktop",
|
||||
"provider.anaconda.status.attention": "Trenger oppmerksomhet",
|
||||
"provider.anaconda.status.unavailable": "Utilgjengelig",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop støttes ikke på {{platform}}.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"Installer Anaconda Desktop på denne maskinen, og kom deretter tilbake hit. Kilo kjører ikke installasjonsprogrammet for deg.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Åpne Anaconda Desktop, fullfør oppsettet og logg inn, og velg deretter Sjekk igjen.",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"Oppsettet av Anaconda Desktop er ufullstendig. Åpne Desktop, fullfør oppsettet, og start det på nytt om nødvendig.",
|
||||
"provider.anaconda.state.signedOut": "Åpne Anaconda Desktop og logg inn før du kobler til Kilo.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo fikk ikke tilgang til Anaconda Desktop. Åpne Desktop, logg inn på nytt, og start det på nytt om nødvendig.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop svarer ikke ennå. Åpne det og vent til applikasjonen er ferdig med å starte.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"Last ned en tekstgenereringsmodell i Anaconda Desktop. Velg en med støtte for verktøykall når det er mulig, og start deretter serveren.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"1 nedlastet tekstgenereringsmodell er tilgjengelig. Start en modellserver i Anaconda Desktop. Modeller med støtte for verktøykall anbefales på det sterkeste.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"{{count}} nedlastede tekstgenereringsmodeller er tilgjengelige. Start en modellserver i Anaconda Desktop. Modeller med støtte for verktøykall anbefales på det sterkeste.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"Den aktive inferensserveren er ikke sunn ennå. Sjekk den i Anaconda Desktop og start serveren på nytt om nødvendig.",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo fant en sunn lokal tekstgenereringsserver og kan importere de gjeldende tilkoblingsinnstillingene.",
|
||||
"provider.anaconda.server": "Aktiv inferensserver",
|
||||
"provider.anaconda.context": "Kontekstvindu",
|
||||
"provider.anaconda.contextValue": "{{count}} tokens",
|
||||
"provider.anaconda.tools": "Verktøykall",
|
||||
"provider.anaconda.tools.supported": "Støttet",
|
||||
"provider.anaconda.tools.unsupported": "Ikke aktivert",
|
||||
"provider.anaconda.tools.unknown": "Ukjent",
|
||||
"provider.anaconda.warning.title": "Verktøystøtte er begrenset",
|
||||
"provider.anaconda.warning.description":
|
||||
"Denne serveren bekrefter ikke verktøykall. Handlinger fra kodeagenten kan mislykkes eller være utilgjengelige. Fortsett bare hvis du godtar disse begrensningene.",
|
||||
"provider.anaconda.action.download": "Last ned Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "Åpne Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "Sjekk igjen",
|
||||
"provider.anaconda.action.continue": "Fortsett likevel",
|
||||
"provider.anaconda.action.manage": "Administrer / Oppdater",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop oppdatert",
|
||||
"provider.anaconda.toast.refreshed.description": "Den aktive lokale serveren og modellene er oppdatert i Kilo.",
|
||||
"settings.providers.note.anacondaDesktop": "Kjør en modell som serveres lokalt av Anaconda Desktop.",
|
||||
"settings.providers.tag.local": "Lokal",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "Foreslått",
|
||||
"command.category.view": "Visning",
|
||||
"command.category.project": "Prosjekt",
|
||||
@@ -103,6 +151,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "Slutt å dele denne sesjonen",
|
||||
"command.session.export": "Eksporter sesjonsutskrift",
|
||||
|
||||
"agentRequirements.skill.installed": "Installert",
|
||||
"agentRequirements.skill.checkFailed": "Skill-sjekken mislyktes",
|
||||
"agentRequirements.skill.missing": "Ikke installert",
|
||||
"agentRequirements.mcp.connected": "Tilkoblet",
|
||||
"agentRequirements.mcp.checkFailed": "MCP-sjekken mislyktes",
|
||||
"agentRequirements.mcp.missing": "Ikke tilkoblet",
|
||||
"agentRequirements.extension.installed": "Installert",
|
||||
"agentRequirements.extension.checkFailed": "Sjekken av VS Code-utvidelsen mislyktes",
|
||||
"agentRequirements.extension.missing": "Ikke installert",
|
||||
"agentRequirements.extension.description": "Installer de manglende utvidelsene i VS Code.",
|
||||
"agentRequirements.group.skills": "Skills",
|
||||
"agentRequirements.group.mcps": "MCP-er",
|
||||
"agentRequirements.group.extensions": "VS Code-utvidelser",
|
||||
"agentRequirements.blocked.title": "Forutsetninger for agenten {{agent}}",
|
||||
"agentRequirements.blocked.description": "Denne agenten trenger følgende verktøy før den kan kjøre.",
|
||||
"agentRequirements.prompt.blocked": "Fullfør først de nødvendige sjekkene for å bruke denne agenten",
|
||||
"agentRequirements.action.openMarketplace": "Åpne Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "Den valgte agenten ble ikke funnet.",
|
||||
"agentRequirements.error.malformedDeclaration": "Denne agenten har en ugyldig kravdeklarasjon.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo kunne ikke sjekke tilgjengelige skills.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo kunne ikke sjekke MCP-serverstatus.",
|
||||
"agentRequirements.error.scopeMismatch": "Denne sjekken av agentkrav er ikke lenger aktiv.",
|
||||
"agentRequirements.error.requestFailed": "Kilo kunne ikke sjekke agentkravene.",
|
||||
|
||||
"palette.search.placeholder": "Søk i filer, kommandoer og sesjoner",
|
||||
"palette.empty": "Ingen resultater funnet",
|
||||
"palette.group.commands": "Kommandoer",
|
||||
|
||||
+75
-5
@@ -1,12 +1,58 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "Sprawdź ponownie",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Połącz Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "Zarządzaj Anaconda Desktop",
|
||||
"provider.anaconda.status.checking": "Sprawdzanie Anaconda Desktop...",
|
||||
"provider.anaconda.status.opening": "Otwieranie Anaconda Desktop...",
|
||||
"provider.anaconda.status.syncing": "Odświeżanie modeli dostawcy...",
|
||||
"provider.anaconda.status.ready": "Gotowy do połączenia",
|
||||
"provider.anaconda.status.waiting": "Oczekiwanie na Desktop",
|
||||
"provider.anaconda.status.attention": "Wymaga uwagi",
|
||||
"provider.anaconda.status.unavailable": "Niedostępny",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop nie jest obsługiwana w systemie {{platform}}.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"Zainstaluj Anaconda Desktop na tym komputerze, a następnie wróć tutaj. Kilo nie uruchamia instalatora za Ciebie.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Otwórz Anaconda Desktop, dokończ konfigurację i zaloguj się, a następnie wybierz Sprawdź ponownie.",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"Konfiguracja Anaconda Desktop jest niekompletna. Otwórz Desktop, dokończ konfigurację i w razie potrzeby uruchom ją ponownie.",
|
||||
"provider.anaconda.state.signedOut": "Otwórz Anaconda Desktop i zaloguj się przed połączeniem Kilo.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo nie mógł uzyskać dostępu do Anaconda Desktop. Otwórz Desktop, zaloguj się ponownie i w razie potrzeby uruchom ją ponownie.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop jeszcze nie odpowiada. Otwórz ją i poczekaj, aż aplikacja zakończy uruchamianie.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"W Anaconda Desktop pobierz model generowania tekstu. Jeśli to możliwe, wybierz model obsługujący wywoływanie narzędzi, a następnie uruchom jego serwer.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"Dostępny jest 1 pobrany model generowania tekstu. W Anaconda Desktop uruchom serwer modelu. Zdecydowanie zalecamy modele obsługujące wywoływanie narzędzi.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"Dostępne są pobrane modele generowania tekstu (łącznie: {{count}}). W Anaconda Desktop uruchom serwer modelu. Zdecydowanie zalecamy modele obsługujące wywoływanie narzędzi.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"Aktywny serwer wnioskowania nie jest jeszcze sprawny. Sprawdź go w Anaconda Desktop i w razie potrzeby uruchom serwer ponownie.",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo znalazł sprawny lokalny serwer generowania tekstu i może zaimportować jego bieżące ustawienia połączenia.",
|
||||
"provider.anaconda.server": "Aktywny serwer wnioskowania",
|
||||
"provider.anaconda.context": "Okno kontekstu",
|
||||
"provider.anaconda.contextValue": "{{count}} tokenów",
|
||||
"provider.anaconda.tools": "Wywoływanie narzędzi",
|
||||
"provider.anaconda.tools.supported": "Obsługiwane",
|
||||
"provider.anaconda.tools.unsupported": "Nie włączone",
|
||||
"provider.anaconda.tools.unknown": "Nieznane",
|
||||
"provider.anaconda.warning.title": "Obsługa narzędzi jest ograniczona",
|
||||
"provider.anaconda.warning.description":
|
||||
"Ten serwer nie potwierdza wywoływania narzędzi. Działania agenta kodującego mogą się nie powieść lub być niedostępne. Kontynuuj tylko wtedy, gdy akceptujesz te ograniczenia.",
|
||||
"provider.anaconda.action.download": "Pobierz Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "Otwórz Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "Sprawdź ponownie",
|
||||
"provider.anaconda.action.continue": "Kontynuuj mimo to",
|
||||
"provider.anaconda.action.manage": "Zarządzaj / Odśwież",
|
||||
"provider.anaconda.toast.refreshed.title": "Odświeżono Anaconda Desktop",
|
||||
"provider.anaconda.toast.refreshed.description": "Aktywny serwer lokalny i modele są aktualne w Kilo.",
|
||||
"settings.providers.note.anacondaDesktop": "Uruchom model serwowany lokalnie przez Anaconda Desktop.",
|
||||
"settings.providers.tag.local": "Lokalny",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "Sugerowane",
|
||||
"command.category.view": "Widok",
|
||||
"command.category.project": "Projekt",
|
||||
@@ -102,6 +148,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "Zatrzymaj udostępnianie tej sesji",
|
||||
"command.session.export": "Eksportuj transkrypcję sesji",
|
||||
|
||||
"agentRequirements.skill.installed": "Zainstalowano",
|
||||
"agentRequirements.skill.checkFailed": "Sprawdzanie umiejętności nie powiodło się",
|
||||
"agentRequirements.skill.missing": "Nie zainstalowano",
|
||||
"agentRequirements.mcp.connected": "Połączono",
|
||||
"agentRequirements.mcp.checkFailed": "Sprawdzanie MCP nie powiodło się",
|
||||
"agentRequirements.mcp.missing": "Nie połączono",
|
||||
"agentRequirements.extension.installed": "Zainstalowano",
|
||||
"agentRequirements.extension.checkFailed": "Sprawdzanie rozszerzenia VS Code nie powiodło się",
|
||||
"agentRequirements.extension.missing": "Nie zainstalowano",
|
||||
"agentRequirements.extension.description": "Zainstaluj brakujące rozszerzenia w VS Code.",
|
||||
"agentRequirements.group.skills": "Umiejętności",
|
||||
"agentRequirements.group.mcps": "MCP",
|
||||
"agentRequirements.group.extensions": "Rozszerzenia VS Code",
|
||||
"agentRequirements.blocked.title": "Wymagania wstępne agenta {{agent}}",
|
||||
"agentRequirements.blocked.description": "Ten agent potrzebuje następujących narzędzi, zanim będzie mógł działać.",
|
||||
"agentRequirements.prompt.blocked": "Najpierw ukończ wymagane sprawdzenia, aby użyć tego agenta",
|
||||
"agentRequirements.action.openMarketplace": "Otwórz Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "Nie znaleziono wybranego agenta.",
|
||||
"agentRequirements.error.malformedDeclaration": "Ten agent ma nieprawidłową deklarację wymagań.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo nie mógł sprawdzić dostępnych umiejętności.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo nie mógł sprawdzić stanu serwera MCP.",
|
||||
"agentRequirements.error.scopeMismatch": "To sprawdzenie wymagań agenta nie jest już aktywne.",
|
||||
"agentRequirements.error.requestFailed": "Kilo nie mógł sprawdzić wymagań agenta.",
|
||||
|
||||
"palette.search.placeholder": "Szukaj plików, poleceń i sesji",
|
||||
"palette.empty": "Brak wyników",
|
||||
"palette.group.commands": "Polecenia",
|
||||
|
||||
+75
-5
@@ -1,12 +1,58 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "Проверить снова",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Подключить Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "Управление Anaconda Desktop",
|
||||
"provider.anaconda.status.checking": "Проверка Anaconda Desktop...",
|
||||
"provider.anaconda.status.opening": "Открытие Anaconda Desktop...",
|
||||
"provider.anaconda.status.syncing": "Обновление моделей провайдера...",
|
||||
"provider.anaconda.status.ready": "Готово к подключению",
|
||||
"provider.anaconda.status.waiting": "Ожидание Desktop",
|
||||
"provider.anaconda.status.attention": "Требуется внимание",
|
||||
"provider.anaconda.status.unavailable": "Недоступно",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop не поддерживается на {{platform}}.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"Установите Anaconda Desktop на этом компьютере, затем вернитесь сюда. Kilo не запускает установщик за вас.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Откройте Anaconda Desktop, завершите настройку и войдите в систему, затем выберите «Проверить снова».",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"Настройка Anaconda Desktop не завершена. Откройте Desktop, завершите настройку и при необходимости перезапустите его.",
|
||||
"provider.anaconda.state.signedOut": "Откройте Anaconda Desktop и войдите в систему перед подключением Kilo.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo не удалось получить доступ к Anaconda Desktop. Откройте Desktop, войдите снова и при необходимости перезапустите его.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop пока не отвечает. Откройте его и дождитесь завершения запуска приложения.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"В Anaconda Desktop загрузите модель генерации текста. По возможности выберите модель с поддержкой вызова инструментов, затем запустите её сервер.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"Доступна 1 загруженная модель генерации текста. Запустите сервер модели в Anaconda Desktop. Настоятельно рекомендуется использовать модели с поддержкой вызова инструментов.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"Доступно загруженных моделей генерации текста: {{count}}. Запустите сервер модели в Anaconda Desktop. Настоятельно рекомендуется использовать модели с поддержкой вызова инструментов.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"Активный сервер вывода пока не работает исправно. Проверьте его в Anaconda Desktop и при необходимости перезапустите сервер.",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo обнаружил исправный локальный сервер генерации текста и может импортировать его текущие настройки подключения.",
|
||||
"provider.anaconda.server": "Активный сервер вывода",
|
||||
"provider.anaconda.context": "Контекстное окно",
|
||||
"provider.anaconda.contextValue": "{{count}} токенов",
|
||||
"provider.anaconda.tools": "Вызов инструментов",
|
||||
"provider.anaconda.tools.supported": "Поддерживается",
|
||||
"provider.anaconda.tools.unsupported": "Не включено",
|
||||
"provider.anaconda.tools.unknown": "Неизвестно",
|
||||
"provider.anaconda.warning.title": "Поддержка инструментов ограничена",
|
||||
"provider.anaconda.warning.description":
|
||||
"Этот сервер не подтверждает вызов инструментов. Действия агента-программиста могут завершаться сбоем или быть недоступными. Продолжайте только если вы принимаете эти ограничения.",
|
||||
"provider.anaconda.action.download": "Скачать Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "Открыть Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "Проверить снова",
|
||||
"provider.anaconda.action.continue": "Всё равно продолжить",
|
||||
"provider.anaconda.action.manage": "Управление / Обновить",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop обновлён",
|
||||
"provider.anaconda.toast.refreshed.description": "Активный локальный сервер и модели актуальны в Kilo.",
|
||||
"settings.providers.note.anacondaDesktop": "Запустите модель, обслуживаемую локально через Anaconda Desktop.",
|
||||
"settings.providers.tag.local": "Локально",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "Предложено",
|
||||
"command.category.view": "Просмотр",
|
||||
"command.category.project": "Проект",
|
||||
@@ -102,6 +148,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "Прекратить публикацию сессии",
|
||||
"command.session.export": "Экспортировать запись сеанса",
|
||||
|
||||
"agentRequirements.skill.installed": "Установлено",
|
||||
"agentRequirements.skill.checkFailed": "Не удалось проверить навык",
|
||||
"agentRequirements.skill.missing": "Не установлено",
|
||||
"agentRequirements.mcp.connected": "Подключено",
|
||||
"agentRequirements.mcp.checkFailed": "Не удалось проверить MCP",
|
||||
"agentRequirements.mcp.missing": "Не подключено",
|
||||
"agentRequirements.extension.installed": "Установлено",
|
||||
"agentRequirements.extension.checkFailed": "Не удалось проверить расширение VS Code",
|
||||
"agentRequirements.extension.missing": "Не установлено",
|
||||
"agentRequirements.extension.description": "Установите отсутствующие расширения в VS Code.",
|
||||
"agentRequirements.group.skills": "Навыки",
|
||||
"agentRequirements.group.mcps": "MCP",
|
||||
"agentRequirements.group.extensions": "Расширения VS Code",
|
||||
"agentRequirements.blocked.title": "Требования агента {{agent}}",
|
||||
"agentRequirements.blocked.description": "Этому агенту нужны следующие инструменты перед запуском.",
|
||||
"agentRequirements.prompt.blocked": "Сначала выполните необходимые проверки, чтобы использовать этого агента",
|
||||
"agentRequirements.action.openMarketplace": "Открыть Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "Выбранный агент не найден.",
|
||||
"agentRequirements.error.malformedDeclaration": "У этого агента недопустимое объявление требований.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo не смог проверить доступные навыки.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo не смог проверить состояние сервера MCP.",
|
||||
"agentRequirements.error.scopeMismatch": "Эта проверка требований агента больше не активна.",
|
||||
"agentRequirements.error.requestFailed": "Kilo не смог проверить требования агента.",
|
||||
|
||||
"palette.search.placeholder": "Поиск файлов, команд и сессий",
|
||||
"palette.empty": "Ничего не найдено",
|
||||
"palette.group.commands": "Команды",
|
||||
|
||||
+74
-5
@@ -1,12 +1,57 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "ตรวจสอบอีกครั้ง",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "เชื่อมต่อ Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "จัดการ Anaconda Desktop",
|
||||
"provider.anaconda.status.checking": "กำลังตรวจสอบ Anaconda Desktop...",
|
||||
"provider.anaconda.status.opening": "กำลังเปิด Anaconda Desktop...",
|
||||
"provider.anaconda.status.syncing": "กำลังรีเฟรชโมเดลของผู้ให้บริการ...",
|
||||
"provider.anaconda.status.ready": "พร้อมเชื่อมต่อ",
|
||||
"provider.anaconda.status.waiting": "กำลังรอ Desktop",
|
||||
"provider.anaconda.status.attention": "ต้องการการดำเนินการ",
|
||||
"provider.anaconda.status.unavailable": "ไม่พร้อมใช้งาน",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop ไม่รองรับบน {{platform}}",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"ติดตั้ง Anaconda Desktop บนเครื่องนี้ แล้วกลับมาที่นี่ Kilo จะไม่เรียกใช้ตัวติดตั้งให้คุณ",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"เปิด Anaconda Desktop ตั้งค่าให้เสร็จและลงชื่อเข้าใช้ แล้วเลือกตรวจสอบอีกครั้ง",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"การตั้งค่า Anaconda Desktop ยังไม่สมบูรณ์ เปิด Desktop ตั้งค่าให้เสร็จ และรีสตาร์ทหากจำเป็น",
|
||||
"provider.anaconda.state.signedOut": "เปิด Anaconda Desktop และลงชื่อเข้าใช้ก่อนเชื่อมต่อ Kilo",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo ไม่สามารถเข้าถึง Anaconda Desktop ได้ เปิด Desktop ลงชื่อเข้าใช้อีกครั้ง และรีสตาร์ทหากจำเป็น",
|
||||
"provider.anaconda.state.unavailable": "Anaconda Desktop ยังไม่ตอบสนอง เปิดและรอให้แอปพลิเคชันเริ่มทำงานเสร็จ",
|
||||
"provider.anaconda.state.noModel":
|
||||
"ใน Anaconda Desktop ดาวน์โหลดโมเดลสร้างข้อความ เลือกโมเดลที่รองรับการเรียกใช้เครื่องมือเมื่อเป็นไปได้ แล้วเริ่มเซิร์ฟเวอร์ของโมเดลนั้น",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"มีโมเดลสร้างข้อความที่ดาวน์โหลดไว้แล้วพร้อมใช้งานอยู่ 1 โมเดล โปรดเริ่มเซิร์ฟเวอร์โมเดลใน Anaconda Desktop ขอแนะนำอย่างยิ่งให้ใช้โมเดลที่รองรับการเรียกใช้เครื่องมือ",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"มีโมเดลสร้างข้อความที่ดาวน์โหลดไว้แล้วพร้อมใช้งานอยู่ {{count}} โมเดล โปรดเริ่มเซิร์ฟเวอร์โมเดลใน Anaconda Desktop ขอแนะนำอย่างยิ่งให้ใช้โมเดลที่รองรับการเรียกใช้เครื่องมือ",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"เซิร์ฟเวอร์การอนุมานที่ใช้งานอยู่ยังไม่พร้อมใช้งาน ตรวจสอบใน Anaconda Desktop และรีสตาร์ทเซิร์ฟเวอร์หากจำเป็น",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo พบเซิร์ฟเวอร์สร้างข้อความในเครื่องที่พร้อมใช้งาน และสามารถนำเข้าการตั้งค่าการเชื่อมต่อปัจจุบันได้",
|
||||
"provider.anaconda.server": "เซิร์ฟเวอร์การอนุมานที่ใช้งานอยู่",
|
||||
"provider.anaconda.context": "หน้าต่างบริบท",
|
||||
"provider.anaconda.contextValue": "{{count}} โทเค็น",
|
||||
"provider.anaconda.tools": "การเรียกใช้เครื่องมือ",
|
||||
"provider.anaconda.tools.supported": "รองรับ",
|
||||
"provider.anaconda.tools.unsupported": "ไม่ได้เปิดใช้งาน",
|
||||
"provider.anaconda.tools.unknown": "ไม่ทราบ",
|
||||
"provider.anaconda.warning.title": "การรองรับเครื่องมือมีข้อจำกัด",
|
||||
"provider.anaconda.warning.description":
|
||||
"เซิร์ฟเวอร์นี้ไม่ยืนยันการเรียกใช้เครื่องมือ การทำงานของเอเจนต์เขียนโค้ดอาจล้มเหลวหรือไม่พร้อมใช้งาน ดำเนินการต่อเฉพาะเมื่อคุณยอมรับข้อจำกัดเหล่านี้",
|
||||
"provider.anaconda.action.download": "ดาวน์โหลด Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "เปิด Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "ตรวจสอบอีกครั้ง",
|
||||
"provider.anaconda.action.continue": "ดำเนินการต่อ",
|
||||
"provider.anaconda.action.manage": "จัดการ / รีเฟรช",
|
||||
"provider.anaconda.toast.refreshed.title": "รีเฟรช Anaconda Desktop แล้ว",
|
||||
"provider.anaconda.toast.refreshed.description": "เซิร์ฟเวอร์ในเครื่องและโมเดลที่ใช้งานอยู่เป็นปัจจุบันใน Kilo แล้ว",
|
||||
"settings.providers.note.anacondaDesktop": "เรียกใช้โมเดลที่ให้บริการในเครื่องโดย Anaconda Desktop",
|
||||
"settings.providers.tag.local": "ในเครื่อง",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "แนะนำ",
|
||||
"command.category.view": "มุมมอง",
|
||||
"command.category.project": "โปรเจกต์",
|
||||
@@ -102,6 +147,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "หยุดการแชร์เซสชันนี้",
|
||||
"command.session.export": "ส่งออกบันทึกเซสชัน",
|
||||
|
||||
"agentRequirements.skill.installed": "ติดตั้งแล้ว",
|
||||
"agentRequirements.skill.checkFailed": "การตรวจสอบสกิลล้มเหลว",
|
||||
"agentRequirements.skill.missing": "ยังไม่ได้ติดตั้ง",
|
||||
"agentRequirements.mcp.connected": "เชื่อมต่อแล้ว",
|
||||
"agentRequirements.mcp.checkFailed": "การตรวจสอบ MCP ล้มเหลว",
|
||||
"agentRequirements.mcp.missing": "ยังไม่ได้เชื่อมต่อ",
|
||||
"agentRequirements.extension.installed": "ติดตั้งแล้ว",
|
||||
"agentRequirements.extension.checkFailed": "การตรวจสอบส่วนขยาย VS Code ล้มเหลว",
|
||||
"agentRequirements.extension.missing": "ยังไม่ได้ติดตั้ง",
|
||||
"agentRequirements.extension.description": "ติดตั้งส่วนขยายที่ขาดหายใน VS Code",
|
||||
"agentRequirements.group.skills": "สกิล",
|
||||
"agentRequirements.group.mcps": "MCP",
|
||||
"agentRequirements.group.extensions": "ส่วนขยาย VS Code",
|
||||
"agentRequirements.blocked.title": "ข้อกำหนดเบื้องต้นของเอเจนต์ {{agent}}",
|
||||
"agentRequirements.blocked.description": "เอเจนต์นี้ต้องใช้เครื่องมือต่อไปนี้ก่อนจึงจะทำงานได้",
|
||||
"agentRequirements.prompt.blocked": "ทำการตรวจสอบที่จำเป็นให้เสร็จก่อนเพื่อใช้เอเจนต์นี้",
|
||||
"agentRequirements.action.openMarketplace": "เปิด Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "ไม่พบเอเจนต์ที่เลือก",
|
||||
"agentRequirements.error.malformedDeclaration": "เอเจนต์นี้มีการประกาศข้อกำหนดที่ไม่ถูกต้อง",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo ไม่สามารถตรวจสอบสกิลที่มีอยู่ได้",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo ไม่สามารถตรวจสอบสถานะเซิร์ฟเวอร์ MCP ได้",
|
||||
"agentRequirements.error.scopeMismatch": "การตรวจสอบข้อกำหนดของเอเจนต์นี้ไม่ทำงานอีกต่อไป",
|
||||
"agentRequirements.error.requestFailed": "Kilo ไม่สามารถตรวจสอบข้อกำหนดของเอเจนต์ได้",
|
||||
|
||||
"palette.search.placeholder": "ค้นหาไฟล์ คำสั่ง และเซสชัน",
|
||||
"palette.empty": "ไม่พบผลลัพธ์",
|
||||
"palette.group.commands": "คำสั่ง",
|
||||
|
||||
+75
-5
@@ -1,12 +1,58 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "Tekrar kontrol et",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Anaconda Desktop'a bağlan",
|
||||
"provider.anaconda.title.manage": "Anaconda Desktop'ı yönet",
|
||||
"provider.anaconda.status.checking": "Anaconda Desktop kontrol ediliyor...",
|
||||
"provider.anaconda.status.opening": "Anaconda Desktop açılıyor...",
|
||||
"provider.anaconda.status.syncing": "Sağlayıcı modelleri yenileniyor...",
|
||||
"provider.anaconda.status.ready": "Bağlanmaya hazır",
|
||||
"provider.anaconda.status.waiting": "Desktop bekleniyor",
|
||||
"provider.anaconda.status.attention": "İlgi gerekiyor",
|
||||
"provider.anaconda.status.unavailable": "Kullanılamıyor",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop {{platform}} üzerinde desteklenmiyor.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"Anaconda Desktop'ı bu makineye kurun, ardından buraya dönün. Kilo kurulumu sizin için çalıştırmaz.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Anaconda Desktop'ı açın, kurulumu tamamlayıp oturum açın, ardından Tekrar kontrol et'i seçin.",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"Anaconda Desktop kurulumu eksik. Desktop'ı açın, kurulumu tamamlayın ve gerekirse yeniden başlatın.",
|
||||
"provider.anaconda.state.signedOut": "Kilo'yu bağlamadan önce Anaconda Desktop'ı açın ve oturum açın.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo Anaconda Desktop'a erişemedi. Desktop'ı açın, tekrar oturum açın ve gerekirse yeniden başlatın.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop henüz yanıt vermiyor. Açın ve uygulamanın başlamasını bekleyin.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"Anaconda Desktop'ta bir metin üretme modeli indirin. Mümkünse araç çağırma destekli bir model seçin, ardından sunucusunu başlatın.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"İndirilmiş 1 metin üretme modeli kullanılabilir. Anaconda Desktop'ta bir model sunucusu başlatın. Araç çağırma desteği olan modeller önemle tavsiye edilir.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"İndirilmiş {{count}} metin üretme modeli kullanılabilir. Anaconda Desktop'ta bir model sunucusu başlatın. Araç çağırma desteği olan modeller önemle tavsiye edilir.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"Etkin çıkarım sunucusu henüz sağlıklı değil. Anaconda Desktop'ta kontrol edin ve gerekirse sunucuyu yeniden başlatın.",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo sağlıklı bir yerel metin üretme sunucusu buldu ve mevcut bağlantı ayarlarını içe aktarabilir.",
|
||||
"provider.anaconda.server": "Etkin çıkarım sunucusu",
|
||||
"provider.anaconda.context": "Bağlam penceresi",
|
||||
"provider.anaconda.contextValue": "{{count}} token",
|
||||
"provider.anaconda.tools": "Araç çağırma",
|
||||
"provider.anaconda.tools.supported": "Destekleniyor",
|
||||
"provider.anaconda.tools.unsupported": "Etkin değil",
|
||||
"provider.anaconda.tools.unknown": "Bilinmiyor",
|
||||
"provider.anaconda.warning.title": "Araç desteği sınırlı",
|
||||
"provider.anaconda.warning.description":
|
||||
"Bu sunucu araç çağırmayı doğrulamıyor. Kodlama aracısı eylemleri başarısız olabilir veya kullanılamayabilir. Yalnızca bu sınırlamaları kabul ediyorsanız devam edin.",
|
||||
"provider.anaconda.action.download": "Anaconda Desktop'ı indir",
|
||||
"provider.anaconda.action.open": "Anaconda Desktop'ı aç",
|
||||
"provider.anaconda.action.checkAgain": "Tekrar kontrol et",
|
||||
"provider.anaconda.action.continue": "Yine de devam et",
|
||||
"provider.anaconda.action.manage": "Yönet / Yenile",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop yenilendi",
|
||||
"provider.anaconda.toast.refreshed.description": "Etkin yerel sunucu ve modeller Kilo'da güncel.",
|
||||
"settings.providers.note.anacondaDesktop": "Anaconda Desktop tarafından yerel olarak sunulan bir model çalıştırın.",
|
||||
"settings.providers.tag.local": "Yerel",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "Önerilen",
|
||||
"command.category.view": "Görünüm",
|
||||
"command.category.project": "Proje",
|
||||
@@ -102,6 +148,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "Bu oturumun paylaşımını durdur",
|
||||
"command.session.export": "Oturum dökümünü dışa aktar",
|
||||
|
||||
"agentRequirements.skill.installed": "Yüklü",
|
||||
"agentRequirements.skill.checkFailed": "Yetenek denetimi başarısız oldu",
|
||||
"agentRequirements.skill.missing": "Yüklü değil",
|
||||
"agentRequirements.mcp.connected": "Bağlı",
|
||||
"agentRequirements.mcp.checkFailed": "MCP denetimi başarısız oldu",
|
||||
"agentRequirements.mcp.missing": "Bağlı değil",
|
||||
"agentRequirements.extension.installed": "Yüklü",
|
||||
"agentRequirements.extension.checkFailed": "VS Code uzantısı denetimi başarısız oldu",
|
||||
"agentRequirements.extension.missing": "Yüklü değil",
|
||||
"agentRequirements.extension.description": "Eksik uzantıları VS Code içinde yükleyin.",
|
||||
"agentRequirements.group.skills": "Yetenekler",
|
||||
"agentRequirements.group.mcps": "MCP’ler",
|
||||
"agentRequirements.group.extensions": "VS Code Uzantıları",
|
||||
"agentRequirements.blocked.title": "{{agent}} ajanı ön koşulları",
|
||||
"agentRequirements.blocked.description": "Bu ajanın çalışmadan önce aşağıdaki araçlara ihtiyacı var.",
|
||||
"agentRequirements.prompt.blocked": "Bu ajanı kullanmak için önce gerekli denetimleri tamamlayın",
|
||||
"agentRequirements.action.openMarketplace": "Marketplace’i Aç",
|
||||
"agentRequirements.error.unknownAgent": "Seçili ajan bulunamadı.",
|
||||
"agentRequirements.error.malformedDeclaration": "Bu ajanın geçersiz bir gereksinim bildirimi var.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo mevcut yetenekleri denetleyemedi.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo MCP sunucu durumunu denetleyemedi.",
|
||||
"agentRequirements.error.scopeMismatch": "Bu ajan gereksinimi denetimi artık etkin değil.",
|
||||
"agentRequirements.error.requestFailed": "Kilo ajan gereksinimlerini denetleyemedi.",
|
||||
|
||||
"palette.search.placeholder": "Dosya, komut ve oturum ara",
|
||||
"palette.empty": "Sonuç bulunamadı",
|
||||
"palette.group.commands": "Komutlar",
|
||||
|
||||
+75
-5
@@ -1,12 +1,58 @@
|
||||
import { anacondaDesktopDict } from "./en"
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "Перевірити ще раз",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "Підключити Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "Керувати Anaconda Desktop",
|
||||
"provider.anaconda.status.checking": "Перевірка Anaconda Desktop...",
|
||||
"provider.anaconda.status.opening": "Відкриття Anaconda Desktop...",
|
||||
"provider.anaconda.status.syncing": "Оновлення моделей провайдера...",
|
||||
"provider.anaconda.status.ready": "Готово до підключення",
|
||||
"provider.anaconda.status.waiting": "Очікування Desktop",
|
||||
"provider.anaconda.status.attention": "Потребує уваги",
|
||||
"provider.anaconda.status.unavailable": "Недоступно",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop не підтримується на {{platform}}.",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"Встановіть Anaconda Desktop на цьому комп'ютері, а потім поверніться сюди. Kilo не запускає інсталятор за вас.",
|
||||
"provider.anaconda.state.notRunning":
|
||||
"Відкрийте Anaconda Desktop, завершіть налаштування та увійдіть, потім виберіть «Перевірити ще раз».",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"Налаштування Anaconda Desktop неповне. Відкрийте Desktop, завершіть налаштування та перезапустіть його за потреби.",
|
||||
"provider.anaconda.state.signedOut": "Відкрийте Anaconda Desktop та увійдіть перед підключенням Kilo.",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo не вдалося отримати доступ до Anaconda Desktop. Відкрийте Desktop, увійдіть знову та перезапустіть його за потреби.",
|
||||
"provider.anaconda.state.unavailable":
|
||||
"Anaconda Desktop ще не відповідає. Відкрийте його та зачекайте, доки програма завершить запуск.",
|
||||
"provider.anaconda.state.noModel":
|
||||
"В Anaconda Desktop завантажте модель генерації тексту. За можливості виберіть модель із підтримкою виклику інструментів, потім запустіть її сервер.",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"Доступна 1 завантажена модель генерації тексту. Запустіть сервер моделі в Anaconda Desktop. Наполегливо рекомендуємо використовувати моделі з підтримкою виклику інструментів.",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"Доступно завантажених моделей генерації тексту: {{count}}. Запустіть сервер моделі в Anaconda Desktop. Наполегливо рекомендуємо використовувати моделі з підтримкою виклику інструментів.",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"Активний сервер інференсу ще не справний. Перевірте його в Anaconda Desktop та перезапустіть сервер за потреби.",
|
||||
"provider.anaconda.state.ready":
|
||||
"Kilo знайшов справний локальний сервер генерації тексту та може імпортувати його поточні налаштування підключення.",
|
||||
"provider.anaconda.server": "Активний сервер інференсу",
|
||||
"provider.anaconda.context": "Вікно контексту",
|
||||
"provider.anaconda.contextValue": "{{count}} токенів",
|
||||
"provider.anaconda.tools": "Виклик інструментів",
|
||||
"provider.anaconda.tools.supported": "Підтримується",
|
||||
"provider.anaconda.tools.unsupported": "Не увімкнено",
|
||||
"provider.anaconda.tools.unknown": "Невідомо",
|
||||
"provider.anaconda.warning.title": "Підтримка інструментів обмежена",
|
||||
"provider.anaconda.warning.description":
|
||||
"Цей сервер не підтверджує виклик інструментів. Дії агента кодування можуть не спрацювати або бути недоступними. Продовжуйте, лише якщо приймаєте ці обмеження.",
|
||||
"provider.anaconda.action.download": "Завантажити Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "Відкрити Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "Перевірити ще раз",
|
||||
"provider.anaconda.action.continue": "Усе одно продовжити",
|
||||
"provider.anaconda.action.manage": "Керувати / Оновити",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop оновлено",
|
||||
"provider.anaconda.toast.refreshed.description": "Активний локальний сервер та моделі актуальні в Kilo.",
|
||||
"settings.providers.note.anacondaDesktop": "Запустіть модель, що обслуговується локально через Anaconda Desktop.",
|
||||
"settings.providers.tag.local": "Локальний",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "Запропоновані",
|
||||
"command.category.view": "Вигляд",
|
||||
"command.category.project": "Проєкт",
|
||||
@@ -102,6 +148,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "Закрити доступ до цієї сесії",
|
||||
"command.session.export": "Експортувати запис сеансу",
|
||||
|
||||
"agentRequirements.skill.installed": "Встановлено",
|
||||
"agentRequirements.skill.checkFailed": "Не вдалося перевірити навичку",
|
||||
"agentRequirements.skill.missing": "Не встановлено",
|
||||
"agentRequirements.mcp.connected": "Підключено",
|
||||
"agentRequirements.mcp.checkFailed": "Не вдалося перевірити MCP",
|
||||
"agentRequirements.mcp.missing": "Не підключено",
|
||||
"agentRequirements.extension.installed": "Встановлено",
|
||||
"agentRequirements.extension.checkFailed": "Не вдалося перевірити розширення VS Code",
|
||||
"agentRequirements.extension.missing": "Не встановлено",
|
||||
"agentRequirements.extension.description": "Встановіть відсутні розширення у VS Code.",
|
||||
"agentRequirements.group.skills": "Навички",
|
||||
"agentRequirements.group.mcps": "MCP",
|
||||
"agentRequirements.group.extensions": "Розширення VS Code",
|
||||
"agentRequirements.blocked.title": "Передумови агента {{agent}}",
|
||||
"agentRequirements.blocked.description": "Цьому агенту потрібні такі інструменти, перш ніж він зможе працювати.",
|
||||
"agentRequirements.prompt.blocked": "Спочатку завершіть необхідні перевірки, щоб використати цього агента",
|
||||
"agentRequirements.action.openMarketplace": "Відкрити Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "Вибраного агента не знайдено.",
|
||||
"agentRequirements.error.malformedDeclaration": "Цей агент має недійсну декларацію вимог.",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo не зміг перевірити доступні навички.",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo не зміг перевірити стан сервера MCP.",
|
||||
"agentRequirements.error.scopeMismatch": "Ця перевірка вимог агента більше не активна.",
|
||||
"agentRequirements.error.requestFailed": "Kilo не зміг перевірити вимоги агента.",
|
||||
|
||||
"palette.search.placeholder": "Пошук файлів, команд і сесій",
|
||||
"palette.empty": "Результатів не знайдено",
|
||||
"palette.group.commands": "Команди",
|
||||
|
||||
+73
-4
@@ -1,14 +1,59 @@
|
||||
import { anacondaDesktopDict, dict as en } from "./en"
|
||||
import { dict as en } from "./en"
|
||||
|
||||
type Keys = keyof typeof en
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "再次检查",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "连接 Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "管理 Anaconda Desktop",
|
||||
"provider.anaconda.status.checking": "正在检查 Anaconda Desktop…",
|
||||
"provider.anaconda.status.opening": "正在打开 Anaconda Desktop…",
|
||||
"provider.anaconda.status.syncing": "正在刷新提供商模型…",
|
||||
"provider.anaconda.status.ready": "准备连接",
|
||||
"provider.anaconda.status.waiting": "正在等待 Desktop",
|
||||
"provider.anaconda.status.attention": "需要处理",
|
||||
"provider.anaconda.status.unavailable": "不可用",
|
||||
"provider.anaconda.state.unsupported": "{{platform}} 不支持 Anaconda Desktop。",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"请在此设备上安装 Anaconda Desktop,然后返回此处。Kilo 不会为你运行安装程序。",
|
||||
"provider.anaconda.state.notRunning": "请打开 Anaconda Desktop,完成设置并登录,然后选择“再次检查”。",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"Anaconda Desktop 设置不完整。请打开 Desktop,完成设置,并在需要时重新启动它。",
|
||||
"provider.anaconda.state.signedOut": "请打开 Anaconda Desktop 并登录,然后再连接 Kilo。",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo 无法访问 Anaconda Desktop。请打开 Desktop,重新登录,并在需要时重新启动它。",
|
||||
"provider.anaconda.state.unavailable": "Anaconda Desktop 尚未响应。请打开它并等待应用程序完成启动。",
|
||||
"provider.anaconda.state.noModel":
|
||||
"请在 Anaconda Desktop 中下载一个文本生成模型。请尽可能选择支持工具调用的模型,然后启动其服务器。",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"有 1 个已下载的文本生成模型可用。请在 Anaconda Desktop 中启动一个模型服务器。强烈建议使用支持工具调用的模型。",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"有 {{count}} 个已下载的文本生成模型可用。请在 Anaconda Desktop 中启动一个模型服务器。强烈建议使用支持工具调用的模型。",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"当前推理服务器尚不健康。请在 Anaconda Desktop 中检查它,并在需要时重新启动服务器。",
|
||||
"provider.anaconda.state.ready": "Kilo 找到了一个健康的本地文本生成服务器,可以导入其当前的连接设置。",
|
||||
"provider.anaconda.server": "当前推理服务器",
|
||||
"provider.anaconda.context": "上下文窗口",
|
||||
"provider.anaconda.contextValue": "{{count}} 个 token",
|
||||
"provider.anaconda.tools": "工具调用",
|
||||
"provider.anaconda.tools.supported": "支持",
|
||||
"provider.anaconda.tools.unsupported": "未启用",
|
||||
"provider.anaconda.tools.unknown": "未知",
|
||||
"provider.anaconda.warning.title": "工具支持有限",
|
||||
"provider.anaconda.warning.description":
|
||||
"此服务器未确认支持工具调用。编码智能体的操作可能会失败或不可用。仅在你接受这些限制时才继续。",
|
||||
"provider.anaconda.action.download": "下载 Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "打开 Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "再次检查",
|
||||
"provider.anaconda.action.continue": "仍然继续",
|
||||
"provider.anaconda.action.manage": "管理 / 刷新",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop 已刷新",
|
||||
"provider.anaconda.toast.refreshed.description": "当前本地服务器和模型在 Kilo 中已是最新。",
|
||||
"settings.providers.note.anacondaDesktop": "运行由 Anaconda Desktop 在本地提供的模型。",
|
||||
"settings.providers.tag.local": "本地",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "建议",
|
||||
"command.category.view": "视图",
|
||||
"command.category.project": "项目",
|
||||
@@ -104,6 +149,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "停止分享此会话",
|
||||
"command.session.export": "导出会话记录",
|
||||
|
||||
"agentRequirements.skill.installed": "已安装",
|
||||
"agentRequirements.skill.checkFailed": "技能检查失败",
|
||||
"agentRequirements.skill.missing": "未安装",
|
||||
"agentRequirements.mcp.connected": "已连接",
|
||||
"agentRequirements.mcp.checkFailed": "MCP 检查失败",
|
||||
"agentRequirements.mcp.missing": "未连接",
|
||||
"agentRequirements.extension.installed": "已安装",
|
||||
"agentRequirements.extension.checkFailed": "VS Code 扩展检查失败",
|
||||
"agentRequirements.extension.missing": "未安装",
|
||||
"agentRequirements.extension.description": "在 VS Code 中安装缺少的扩展。",
|
||||
"agentRequirements.group.skills": "技能",
|
||||
"agentRequirements.group.mcps": "MCP",
|
||||
"agentRequirements.group.extensions": "VS Code 扩展",
|
||||
"agentRequirements.blocked.title": "{{agent}} 代理前置要求",
|
||||
"agentRequirements.blocked.description": "此代理需要以下工具后才能运行。",
|
||||
"agentRequirements.prompt.blocked": "请先完成所需检查,然后再使用此代理",
|
||||
"agentRequirements.action.openMarketplace": "打开 Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "找不到所选代理。",
|
||||
"agentRequirements.error.malformedDeclaration": "此代理的要求声明无效。",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo 无法检查可用技能。",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo 无法检查 MCP 服务器状态。",
|
||||
"agentRequirements.error.scopeMismatch": "此代理要求检查已不再有效。",
|
||||
"agentRequirements.error.requestFailed": "Kilo 无法检查代理要求。",
|
||||
|
||||
"palette.search.placeholder": "搜索文件、命令和会话",
|
||||
"palette.empty": "未找到结果",
|
||||
"palette.group.commands": "命令",
|
||||
|
||||
+73
-4
@@ -1,14 +1,59 @@
|
||||
import { anacondaDesktopDict, dict as en } from "./en"
|
||||
import { dict as en } from "./en"
|
||||
|
||||
type Keys = keyof typeof en
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"provider.anaconda.action.checkAgain": "再次檢查",
|
||||
export const anacondaDesktopDict = {
|
||||
"provider.anaconda.title.connect": "連接 Anaconda Desktop",
|
||||
"provider.anaconda.title.manage": "管理 Anaconda Desktop",
|
||||
"provider.anaconda.status.checking": "正在檢查 Anaconda Desktop...",
|
||||
"provider.anaconda.status.opening": "正在開啟 Anaconda Desktop...",
|
||||
"provider.anaconda.status.syncing": "正在重新整理供應商模型...",
|
||||
"provider.anaconda.status.ready": "已準備好連接",
|
||||
"provider.anaconda.status.waiting": "正在等待 Desktop",
|
||||
"provider.anaconda.status.attention": "需要注意",
|
||||
"provider.anaconda.status.unavailable": "無法使用",
|
||||
"provider.anaconda.state.unsupported": "Anaconda Desktop 不支援 {{platform}}。",
|
||||
"provider.anaconda.state.notInstalled":
|
||||
"請在此機器上安裝 Anaconda Desktop,然後返回此處。Kilo 不會為您執行安裝程式。",
|
||||
"provider.anaconda.state.notRunning": "請開啟 Anaconda Desktop,完成設定並登入,然後選擇再次檢查。",
|
||||
"provider.anaconda.state.invalidConfig":
|
||||
"Anaconda Desktop 設定尚未完成。請開啟 Desktop,完成設定,並在需要時重新啟動它。",
|
||||
"provider.anaconda.state.signedOut": "請開啟 Anaconda Desktop 並登入,然後再連接 Kilo。",
|
||||
"provider.anaconda.state.unauthorized":
|
||||
"Kilo 無法存取 Anaconda Desktop。請開啟 Desktop,重新登入,並在需要時重新啟動它。",
|
||||
"provider.anaconda.state.unavailable": "Anaconda Desktop 尚未回應。請開啟它並等待應用程式完成啟動。",
|
||||
"provider.anaconda.state.noModel":
|
||||
"請在 Anaconda Desktop 中下載一個文字生成模型。可能的話請選擇支援工具呼叫的模型,然後啟動其伺服器。",
|
||||
"provider.anaconda.state.noServer_one":
|
||||
"有 1 個已下載的文字生成模型可用。請在 Anaconda Desktop 中啟動一個模型伺服器。強烈建議使用支援工具呼叫的模型。",
|
||||
"provider.anaconda.state.noServer_other":
|
||||
"有 {{count}} 個已下載的文字生成模型可用。請在 Anaconda Desktop 中啟動一個模型伺服器。強烈建議使用支援工具呼叫的模型。",
|
||||
"provider.anaconda.state.unhealthy":
|
||||
"使用中的推論伺服器尚未正常運作。請在 Anaconda Desktop 中檢查它,並在需要時重新啟動伺服器。",
|
||||
"provider.anaconda.state.ready": "Kilo 找到了一個正常運作的本機文字生成伺服器,並可匯入其目前的連接設定。",
|
||||
"provider.anaconda.server": "使用中的推論伺服器",
|
||||
"provider.anaconda.context": "上下文視窗",
|
||||
"provider.anaconda.contextValue": "{{count}} 個權杖",
|
||||
"provider.anaconda.tools": "工具呼叫",
|
||||
"provider.anaconda.tools.supported": "支援",
|
||||
"provider.anaconda.tools.unsupported": "未啟用",
|
||||
"provider.anaconda.tools.unknown": "未知",
|
||||
"provider.anaconda.warning.title": "工具支援有限",
|
||||
"provider.anaconda.warning.description":
|
||||
"此伺服器未確認支援工具呼叫。編碼代理程式的動作可能會失敗或無法使用。只有在您接受這些限制的情況下才繼續。",
|
||||
"provider.anaconda.action.download": "下載 Anaconda Desktop",
|
||||
"provider.anaconda.action.open": "開啟 Anaconda Desktop",
|
||||
"provider.anaconda.action.checkAgain": "再次檢查",
|
||||
"provider.anaconda.action.continue": "仍要繼續",
|
||||
"provider.anaconda.action.manage": "管理 / 重新整理",
|
||||
"provider.anaconda.toast.refreshed.title": "Anaconda Desktop 已重新整理",
|
||||
"provider.anaconda.toast.refreshed.description": "使用中的本機伺服器與模型在 Kilo 中已是最新狀態。",
|
||||
"settings.providers.note.anacondaDesktop": "執行由 Anaconda Desktop 在本機提供的模型。",
|
||||
"settings.providers.tag.local": "本機",
|
||||
} as const
|
||||
|
||||
export const dict = {
|
||||
...anacondaDesktopDict,
|
||||
"command.category.suggested": "建議",
|
||||
"command.category.view": "檢視",
|
||||
"command.category.project": "專案",
|
||||
@@ -104,6 +149,30 @@ export const dict = {
|
||||
"command.session.unshare.description": "停止分享此工作階段",
|
||||
"command.session.export": "匯出會話紀錄",
|
||||
|
||||
"agentRequirements.skill.installed": "已安裝",
|
||||
"agentRequirements.skill.checkFailed": "技能檢查失敗",
|
||||
"agentRequirements.skill.missing": "未安裝",
|
||||
"agentRequirements.mcp.connected": "已連線",
|
||||
"agentRequirements.mcp.checkFailed": "MCP 檢查失敗",
|
||||
"agentRequirements.mcp.missing": "未連線",
|
||||
"agentRequirements.extension.installed": "已安裝",
|
||||
"agentRequirements.extension.checkFailed": "VS Code 擴充功能檢查失敗",
|
||||
"agentRequirements.extension.missing": "未安裝",
|
||||
"agentRequirements.extension.description": "在 VS Code 中安裝缺少的擴充功能。",
|
||||
"agentRequirements.group.skills": "技能",
|
||||
"agentRequirements.group.mcps": "MCP",
|
||||
"agentRequirements.group.extensions": "VS Code 擴充功能",
|
||||
"agentRequirements.blocked.title": "{{agent}} 代理前置需求",
|
||||
"agentRequirements.blocked.description": "此代理需要以下工具後才能執行。",
|
||||
"agentRequirements.prompt.blocked": "請先完成必要檢查,才能使用此代理",
|
||||
"agentRequirements.action.openMarketplace": "開啟 Marketplace",
|
||||
"agentRequirements.error.unknownAgent": "找不到所選代理。",
|
||||
"agentRequirements.error.malformedDeclaration": "此代理的需求宣告無效。",
|
||||
"agentRequirements.error.discoveryFailed": "Kilo 無法檢查可用技能。",
|
||||
"agentRequirements.error.mcpStatusFailed": "Kilo 無法檢查 MCP 伺服器狀態。",
|
||||
"agentRequirements.error.scopeMismatch": "此代理需求檢查已不再有效。",
|
||||
"agentRequirements.error.requestFailed": "Kilo 無法檢查代理需求。",
|
||||
|
||||
"palette.search.placeholder": "搜尋檔案、命令和工作階段",
|
||||
"palette.empty": "找不到結果",
|
||||
"palette.group.commands": "命令",
|
||||
|
||||
@@ -29,6 +29,7 @@ import { Diff } from "@kilocode/kilo-ui/diff"
|
||||
import { Code } from "@kilocode/kilo-ui/code"
|
||||
import { File } from "@kilocode/kilo-ui/file"
|
||||
import { SessionContext } from "../context/session"
|
||||
import { AgentRequirementsContext, type AgentRequirementsContextValue } from "../context/agent-requirements"
|
||||
import { NotificationsContext } from "../context/notifications"
|
||||
import { LanguageContext } from "../context/language"
|
||||
import { IndexingProvider } from "../context/indexing"
|
||||
@@ -48,6 +49,7 @@ import type {
|
||||
SessionCloseReason,
|
||||
QuestionRequest,
|
||||
SuggestionRequest,
|
||||
AgentRequirementResult,
|
||||
} from "../types/messages"
|
||||
|
||||
type PluginSpec = string | [string, Record<string, unknown>]
|
||||
@@ -279,6 +281,9 @@ interface StoryProvidersProps {
|
||||
questions?: QuestionRequest[]
|
||||
suggestions?: SuggestionRequest[]
|
||||
notifications?: KilocodeNotification[]
|
||||
agentRequirements?: AgentRequirementResult
|
||||
agentRequirementsChecking?: boolean
|
||||
agentRequirementsBlocked?: boolean
|
||||
status?: string
|
||||
sessionID?: string
|
||||
/** When provided, injects a mock ConfigContext with this config instead of the real ConfigProvider. */
|
||||
@@ -387,6 +392,22 @@ export const StoryProviders: ParentComponent<StoryProvidersProps> = (props) => {
|
||||
})
|
||||
const notifications = mockNotificationsValue(props.notifications)
|
||||
const [locale] = createSignal<"en">("en")
|
||||
const result = () => props.agentRequirements
|
||||
const visible = () => {
|
||||
const value = result()
|
||||
return value?.state === "blocked" || value?.state === "error"
|
||||
}
|
||||
const requirements: AgentRequirementsContextValue = {
|
||||
result,
|
||||
checking: () => props.agentRequirementsChecking ?? false,
|
||||
blocked: () => {
|
||||
if (props.agentRequirementsBlocked !== undefined) return props.agentRequirementsBlocked
|
||||
const value = result()
|
||||
if (!value) return props.agentRequirementsChecking === true
|
||||
return value.enabled && (value.state === "blocked" || value.state === "error")
|
||||
},
|
||||
visible,
|
||||
}
|
||||
|
||||
return (
|
||||
<VSCodeProvider>
|
||||
@@ -415,30 +436,32 @@ export const StoryProviders: ParentComponent<StoryProvidersProps> = (props) => {
|
||||
<I18nProvider value={{ locale: () => "en", t }}>
|
||||
<NotificationsContext.Provider value={notifications}>
|
||||
<SessionContext.Provider value={session as any}>
|
||||
<IndexingProvider>
|
||||
<KiloEmbeddingModelsProvider>
|
||||
<DataProvider
|
||||
data={data()}
|
||||
directory="/project/"
|
||||
onOpenDiff={props.onOpenDiff}
|
||||
onOpenFile={props.onOpenFile}
|
||||
>
|
||||
<DiffComponentProvider component={Diff}>
|
||||
<CodeComponentProvider component={Code}>
|
||||
<FileComponentProvider component={File}>
|
||||
<MarkedProvider>
|
||||
{props.noPadding ? (
|
||||
props.children
|
||||
) : (
|
||||
<div style={{ padding: "12px" }}>{props.children}</div>
|
||||
)}
|
||||
</MarkedProvider>
|
||||
</FileComponentProvider>
|
||||
</CodeComponentProvider>
|
||||
</DiffComponentProvider>
|
||||
</DataProvider>
|
||||
</KiloEmbeddingModelsProvider>
|
||||
</IndexingProvider>
|
||||
<AgentRequirementsContext.Provider value={requirements}>
|
||||
<IndexingProvider>
|
||||
<KiloEmbeddingModelsProvider>
|
||||
<DataProvider
|
||||
data={data()}
|
||||
directory="/project/"
|
||||
onOpenDiff={props.onOpenDiff}
|
||||
onOpenFile={props.onOpenFile}
|
||||
>
|
||||
<DiffComponentProvider component={Diff}>
|
||||
<CodeComponentProvider component={Code}>
|
||||
<FileComponentProvider component={File}>
|
||||
<MarkedProvider>
|
||||
{props.noPadding ? (
|
||||
props.children
|
||||
) : (
|
||||
<div style={{ padding: "12px" }}>{props.children}</div>
|
||||
)}
|
||||
</MarkedProvider>
|
||||
</FileComponentProvider>
|
||||
</CodeComponentProvider>
|
||||
</DiffComponentProvider>
|
||||
</DataProvider>
|
||||
</KiloEmbeddingModelsProvider>
|
||||
</IndexingProvider>
|
||||
</AgentRequirementsContext.Provider>
|
||||
</SessionContext.Provider>
|
||||
</NotificationsContext.Provider>
|
||||
</I18nProvider>
|
||||
|
||||
@@ -24,6 +24,7 @@ import { ProviderContext } from "../context/provider"
|
||||
import { ServerContext } from "../context/server"
|
||||
import { WorktreeModeProvider } from "../context/worktree-mode"
|
||||
import type {
|
||||
AgentRequirementResult,
|
||||
Message,
|
||||
Part,
|
||||
QuestionRequest,
|
||||
@@ -37,6 +38,68 @@ import { reviewMetadata } from "../../../src/shared/review-comments"
|
||||
|
||||
const SESSION_ID = "story-session-chat-001"
|
||||
|
||||
const missingToolsRequirements: AgentRequirementResult = {
|
||||
agent: "code-review",
|
||||
directory: "/project",
|
||||
enabled: true,
|
||||
state: "blocked",
|
||||
skills: [
|
||||
{ name: "review-checklist", status: "ready" },
|
||||
{ name: "security-audit", status: "missing" },
|
||||
],
|
||||
mcps: [
|
||||
{ name: "github", status: "missing" },
|
||||
{ name: "filesystem", status: "ready" },
|
||||
],
|
||||
vscode_extensions: [],
|
||||
}
|
||||
|
||||
const missingExtensionRequirements: AgentRequirementResult = {
|
||||
agent: "release-review",
|
||||
directory: "/project",
|
||||
enabled: true,
|
||||
state: "blocked",
|
||||
skills: [],
|
||||
mcps: [],
|
||||
vscode_extensions: [
|
||||
{
|
||||
name: "GitHub Pull Requests",
|
||||
id: "github.vscode-pull-request-github",
|
||||
status: "missing",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const malformedRequirements: AgentRequirementResult = {
|
||||
agent: "malformed-agent",
|
||||
directory: "/project",
|
||||
enabled: true,
|
||||
state: "error",
|
||||
skills: [],
|
||||
mcps: [],
|
||||
vscode_extensions: [],
|
||||
error: {
|
||||
code: "malformed_declaration",
|
||||
message: "Invalid requirements declaration.",
|
||||
},
|
||||
}
|
||||
|
||||
const readyRequirements: AgentRequirementResult = {
|
||||
agent: "ready-agent",
|
||||
directory: "/project",
|
||||
enabled: true,
|
||||
state: "ready",
|
||||
skills: [{ name: "review-checklist", status: "ready" }],
|
||||
mcps: [{ name: "filesystem", status: "ready" }],
|
||||
vscode_extensions: [
|
||||
{
|
||||
name: "GitHub Pull Requests",
|
||||
id: "github.vscode-pull-request-github",
|
||||
status: "ready",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Question fixtures
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -163,6 +226,71 @@ export const ChatViewWithMessages: Story = {
|
||||
},
|
||||
}
|
||||
|
||||
export const ChatViewRequirementsChecking: Story = {
|
||||
name: "ChatView — agent requirements checking",
|
||||
render: () => (
|
||||
<StoryProviders sessionID={SESSION_ID} status="idle" noPadding agentRequirementsChecking agentRequirementsBlocked>
|
||||
<ServerContext.Provider value={mockServer as any}>
|
||||
<div style={{ height: "600px", display: "flex", "flex-direction": "column" }}>
|
||||
<ChatView />
|
||||
</div>
|
||||
</ServerContext.Provider>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const ChatViewRequirementsMissingTools: Story = {
|
||||
name: "ChatView — missing skills and MCPs",
|
||||
render: () => (
|
||||
<StoryProviders sessionID={SESSION_ID} status="idle" noPadding agentRequirements={missingToolsRequirements}>
|
||||
<ServerContext.Provider value={mockServer as any}>
|
||||
<div style={{ height: "600px", display: "flex", "flex-direction": "column" }}>
|
||||
<ChatView />
|
||||
</div>
|
||||
</ServerContext.Provider>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const ChatViewRequirementsMissingExtension: Story = {
|
||||
name: "ChatView — missing VS Code extension",
|
||||
render: () => (
|
||||
<StoryProviders sessionID={SESSION_ID} status="idle" noPadding agentRequirements={missingExtensionRequirements}>
|
||||
<ServerContext.Provider value={mockServer as any}>
|
||||
<div style={{ height: "600px", display: "flex", "flex-direction": "column" }}>
|
||||
<ChatView />
|
||||
</div>
|
||||
</ServerContext.Provider>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const ChatViewRequirementsMalformed: Story = {
|
||||
name: "ChatView — malformed agent requirements",
|
||||
render: () => (
|
||||
<StoryProviders sessionID={SESSION_ID} status="idle" noPadding agentRequirements={malformedRequirements}>
|
||||
<ServerContext.Provider value={mockServer as any}>
|
||||
<div style={{ height: "600px", display: "flex", "flex-direction": "column" }}>
|
||||
<ChatView />
|
||||
</div>
|
||||
</ServerContext.Provider>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const ChatViewRequirementsReady: Story = {
|
||||
name: "ChatView — requirements ready (no card)",
|
||||
render: () => (
|
||||
<StoryProviders sessionID={SESSION_ID} status="idle" noPadding agentRequirements={readyRequirements}>
|
||||
<ServerContext.Provider value={mockServer as any}>
|
||||
<div style={{ height: "600px", display: "flex", "flex-direction": "column" }}>
|
||||
<ChatView />
|
||||
</div>
|
||||
</ServerContext.Provider>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
export const ChatViewAgentManagerCompleted: Story = {
|
||||
name: "ChatView — completed Agent Manager session actions",
|
||||
render: () => {
|
||||
|
||||
@@ -958,9 +958,9 @@ const externalDirPermission: PermissionRequest = {
|
||||
id: "perm-extdir-001",
|
||||
sessionID: SESSION_ID,
|
||||
toolName: "external_directory",
|
||||
patterns: ["/home/user/other-project/*"],
|
||||
always: ["/home/user/other-project/*"],
|
||||
args: { filepath: "/home/user/other-project/config.json" },
|
||||
patterns: ["/Users/developer/projects/kilo-bench/dashboard/app/routes/*"],
|
||||
always: ["/Users/developer/projects/kilo-bench/dashboard/app/routes/*"],
|
||||
args: { filepath: "/Users/developer/projects/kilo-bench/dashboard/app/routes/index.tsx" },
|
||||
tool: { messageID: ASST_MSG_ID, callID: "call-extdir-001" },
|
||||
}
|
||||
|
||||
|
||||
@@ -108,3 +108,28 @@ export const ModelSelectorSelectedFavorite: Story = {
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const LARGE_MODELS: EnrichedModel[] = Array.from({ length: 600 }, (_, i) => {
|
||||
const id = String(i).padStart(3, "0")
|
||||
const provider = `provider-${i % 12}`
|
||||
return {
|
||||
id: `model-${id}`,
|
||||
name: `Model ${id}`,
|
||||
providerID: provider,
|
||||
providerName: `Provider ${i % 12}`,
|
||||
}
|
||||
})
|
||||
|
||||
export const ModelSelectorLargeCatalog: Story = {
|
||||
name: "ModelSelector — large catalog",
|
||||
render: () => (
|
||||
<StoryProviders>
|
||||
<ModelSelectorBase
|
||||
value={{ providerID: "provider-0", modelID: "model-300" }}
|
||||
models={LARGE_MODELS}
|
||||
placement="bottom-start"
|
||||
onSelect={() => {}}
|
||||
/>
|
||||
</StoryProviders>
|
||||
),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
/* ============================================
|
||||
Agent Requirements
|
||||
============================================ */
|
||||
|
||||
.agent-requirements {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
padding: 16px var(--chat-gutter, 16px);
|
||||
animation: agent-requirements-enter 360ms ease-out 100ms both;
|
||||
}
|
||||
|
||||
[data-component="card"].agent-requirements-card {
|
||||
width: 100%;
|
||||
max-width: 430px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
padding: 18px;
|
||||
border: 1px solid var(--border-weak-base, var(--vscode-panel-border));
|
||||
border-radius: 10px;
|
||||
background: var(--vscode-editorWidget-background);
|
||||
color: var(--vscode-foreground);
|
||||
text-align: left;
|
||||
box-shadow: 0 12px 32px rgb(0 0 0 / 22%);
|
||||
}
|
||||
|
||||
@keyframes agent-requirements-enter {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.agent-requirements {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-copy"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-title-row"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-title"] {
|
||||
margin: 0;
|
||||
color: var(--vscode-foreground);
|
||||
font-size: var(--kilo-font-size-14);
|
||||
font-weight: 650;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-title-row"] > [data-component="icon"] {
|
||||
--icon-base: var(--icon-success-base);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.agent-requirements-title-spinner {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-description"] {
|
||||
margin: 0;
|
||||
color: var(--text-weak-base, var(--vscode-descriptionForeground));
|
||||
font-size: var(--kilo-font-size-12);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.agent-requirements-error {
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--border-critical-base);
|
||||
border-radius: 8px;
|
||||
background: color-mix(in srgb, var(--surface-critical-base) 34%, var(--vscode-editor-background));
|
||||
color: var(--text-on-critical-base);
|
||||
font-size: var(--kilo-font-size-12);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-options"] {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agent-requirements-category {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-category-copy"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-category-title"] {
|
||||
margin: 0;
|
||||
color: var(--vscode-textLink-foreground);
|
||||
font-size: var(--kilo-font-size-11);
|
||||
font-weight: 650;
|
||||
line-height: 1.25;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-category-description"] {
|
||||
margin: 0;
|
||||
color: var(--text-weak-base, var(--vscode-descriptionForeground));
|
||||
font-size: var(--kilo-font-size-12);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-category-content"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.agent-requirements-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-group-title"] {
|
||||
margin: 0;
|
||||
color: var(--vscode-foreground);
|
||||
font-size: var(--kilo-font-size-12);
|
||||
font-weight: 650;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-list"] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: var(--vscode-foreground);
|
||||
list-style: none;
|
||||
font-size: var(--kilo-font-size-12);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.agent-requirements-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
padding: 7px 0;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--border-weak-base, var(--vscode-panel-border)) 60%, transparent);
|
||||
}
|
||||
|
||||
.agent-requirements-line:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-status"] {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-status"][data-status="ready"] {
|
||||
color: var(--vscode-charts-green, #89d185);
|
||||
background: color-mix(in srgb, var(--vscode-charts-green, #89d185) 12%, transparent);
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-status"][data-status="missing"] {
|
||||
color: var(--vscode-charts-yellow, #e2c08d);
|
||||
background: color-mix(in srgb, var(--vscode-charts-yellow, #e2c08d) 13%, transparent);
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-status"][data-status="error"] {
|
||||
color: var(--icon-critical-base);
|
||||
background: color-mix(in srgb, var(--surface-critical-base) 50%, transparent);
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-status"] [data-component="icon"],
|
||||
[data-slot="agent-requirements-status"] [data-component="spinner"] {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-line-row"] {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
gap: 12px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-line-name"] {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-line-detail"] {
|
||||
flex: 0 0 auto;
|
||||
max-width: 48%;
|
||||
overflow-wrap: anywhere;
|
||||
color: var(--text-weak-base, var(--vscode-descriptionForeground));
|
||||
font-size: var(--kilo-font-size-11);
|
||||
line-height: 1.35;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.agent-requirements-line[data-status="ready"] [data-slot="agent-requirements-line-detail"] {
|
||||
color: var(--vscode-charts-green, #89d185);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.agent-requirements-line[data-status="missing"] [data-slot="agent-requirements-line-detail"] {
|
||||
color: var(--vscode-charts-yellow, #e2c08d);
|
||||
}
|
||||
|
||||
.agent-requirements-line[data-status="error"] [data-slot="agent-requirements-line-detail"] {
|
||||
color: var(--text-on-critical-base, var(--vscode-errorForeground));
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-extension-note"] {
|
||||
margin: 0;
|
||||
color: var(--text-weak-base, var(--vscode-descriptionForeground));
|
||||
font-size: var(--kilo-font-size-11);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.agent-requirements-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
@container chat (max-width: 320px) {
|
||||
.agent-requirements {
|
||||
align-items: flex-start;
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
[data-component="card"].agent-requirements-card {
|
||||
gap: 16px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-options"] {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.agent-requirements-line {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-line-row"] {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
[data-slot="agent-requirements-line-detail"] {
|
||||
max-width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.agent-requirements-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
@import "./session-actions.css";
|
||||
@import "./welcome.css";
|
||||
@import "./prompt-input.css";
|
||||
@import "./agent-requirements.css";
|
||||
@import "./prompt-dropdowns.css";
|
||||
@import "./model-selector.css";
|
||||
@import "./permission-dock.css";
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
Model Selector (uses kilo-ui Popover)
|
||||
============================================ */
|
||||
|
||||
.popup-selector[data-expanded],
|
||||
.popup-selector[data-closed] {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.model-selector-popover {
|
||||
transition:
|
||||
width 0.2s ease,
|
||||
@@ -383,7 +388,7 @@
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
.model-selector-group:not(:first-child) > .model-selector-group-label {
|
||||
.model-selector-group-label--divided {
|
||||
border-top: 1px solid var(--border-weak-base, var(--vscode-panel-border));
|
||||
margin-top: 4px;
|
||||
padding-top: 8px;
|
||||
|
||||
@@ -109,6 +109,11 @@
|
||||
|
||||
[data-slot="permission-hint"] {
|
||||
font-size: var(--kilo-font-size-12);
|
||||
|
||||
&[data-wrap] {
|
||||
overflow-wrap: anywhere;
|
||||
word-break: normal;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="permission-patterns"] {
|
||||
@@ -316,6 +321,24 @@
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
|
||||
&[data-wrap] {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
overflow: visible;
|
||||
text-overflow: clip;
|
||||
white-space: normal;
|
||||
word-break: normal;
|
||||
}
|
||||
}
|
||||
|
||||
[data-slot="permission-rule-label"] {
|
||||
color: var(--text-base, var(--vscode-foreground));
|
||||
}
|
||||
|
||||
[data-slot="permission-rule-path"] {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
[data-slot="permission-rule-actions"] {
|
||||
|
||||
@@ -16,6 +16,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.prompt-review-comments {
|
||||
margin: 6px 6px 0;
|
||||
border: 1px solid var(--vscode-panel-border);
|
||||
|
||||
@@ -41,6 +41,11 @@ export interface AgentContent {
|
||||
prompt: string
|
||||
options?: Record<string, unknown>
|
||||
permission?: Record<string, unknown>
|
||||
requirements?: {
|
||||
skills?: string[]
|
||||
mcps?: string[]
|
||||
vscode_extensions?: Array<{ name: string; id: string }>
|
||||
}
|
||||
}
|
||||
|
||||
export interface AgentMarketplaceItem extends MarketplaceItemBase {
|
||||
|
||||
@@ -28,6 +28,51 @@ export interface AgentInfo {
|
||||
permission?: PermissionRuleItem[]
|
||||
}
|
||||
|
||||
export interface AgentRequirementSkill {
|
||||
name: string
|
||||
status: "ready" | "missing" | "error"
|
||||
message?: string
|
||||
}
|
||||
|
||||
export interface AgentRequirementMCP {
|
||||
name: string
|
||||
status: "ready" | "missing" | "error"
|
||||
message?: string
|
||||
}
|
||||
|
||||
export interface AgentRequirementVSCodeExtension {
|
||||
name: string
|
||||
id: string
|
||||
status: "ready" | "missing" | "error"
|
||||
message?: string
|
||||
}
|
||||
|
||||
export interface AgentRequirementResult {
|
||||
agent: string
|
||||
directory: string
|
||||
enabled: boolean
|
||||
state: "disabled" | "ready" | "blocked" | "error"
|
||||
skills: AgentRequirementSkill[]
|
||||
mcps: AgentRequirementMCP[]
|
||||
vscode_extensions: AgentRequirementVSCodeExtension[]
|
||||
error?: {
|
||||
code:
|
||||
| "unknown_agent"
|
||||
| "malformed_declaration"
|
||||
| "discovery_failed"
|
||||
| "mcp_status_failed"
|
||||
| "scope_mismatch"
|
||||
| "request_failed"
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface AgentRequirements {
|
||||
skills?: string[]
|
||||
mcps?: string[]
|
||||
vscode_extensions?: Array<{ name: string; id: string }>
|
||||
}
|
||||
|
||||
export interface AgentConfig {
|
||||
model?: string | null
|
||||
variant?: string | null
|
||||
@@ -39,5 +84,6 @@ export interface AgentConfig {
|
||||
temperature?: number | null
|
||||
top_p?: number | null
|
||||
steps?: number | null
|
||||
requirements?: AgentRequirements
|
||||
permission?: PermissionConfig
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@ export interface WatcherConfig {
|
||||
export interface ExperimentalConfig {
|
||||
batch_tool?: boolean
|
||||
codebase_search?: boolean
|
||||
agent_requirements?: boolean
|
||||
native_notebook_tools?: boolean
|
||||
speech_to_text_model?: string
|
||||
primary_tools?: string[]
|
||||
|
||||
@@ -18,7 +18,7 @@ import type { PermissionRequest } from "./permissions"
|
||||
import type { AnacondaDesktopExtensionMessage } from "../../../../src/shared/anaconda-desktop-messages"
|
||||
import type { QuestionRequest, SuggestionRequest, TodoItem } from "./questions"
|
||||
import type { ModelSelection, Provider, ProviderAuthState } from "./providers"
|
||||
import type { AgentInfo, SkillInfo, SlashCommandInfo } from "./agents"
|
||||
import type { AgentInfo, AgentRequirementResult, SkillInfo, SlashCommandInfo } from "./agents"
|
||||
import type { BrowserSettings, Config, FeatureFlags, IndexingStatus, KiloEmbeddingModelCatalog } from "./config"
|
||||
import type { WorkStyle, WorkStyleState } from "../../../../src/shared/work-style-presets"
|
||||
import type { KilocodeNotification, ProfileData } from "./profile"
|
||||
@@ -357,6 +357,15 @@ export interface SkillsLoadedMessage {
|
||||
skills: SkillInfo[]
|
||||
}
|
||||
|
||||
export interface AgentRequirementsLoadedMessage {
|
||||
type: "agentRequirementsLoaded"
|
||||
result: AgentRequirementResult
|
||||
}
|
||||
|
||||
export interface AgentRequirementsInvalidatedMessage {
|
||||
type: "agentRequirementsInvalidated"
|
||||
}
|
||||
|
||||
export interface CommandsLoadedMessage {
|
||||
type: "commandsLoaded"
|
||||
commands: SlashCommandInfo[]
|
||||
@@ -1012,6 +1021,12 @@ export interface RemoteStatusMessage {
|
||||
connected: boolean
|
||||
}
|
||||
|
||||
export interface ValidateFilesResultMessage {
|
||||
type: "validateFilesResult"
|
||||
id: string
|
||||
existing: string[]
|
||||
}
|
||||
|
||||
export type ExtensionMessage =
|
||||
| ReadyMessage
|
||||
| FontSizeChangedMessage
|
||||
@@ -1053,6 +1068,8 @@ export type ExtensionMessage =
|
||||
| ProvidersLoadedMessage
|
||||
| AgentsLoadedMessage
|
||||
| SkillsLoadedMessage
|
||||
| AgentRequirementsLoadedMessage
|
||||
| AgentRequirementsInvalidatedMessage
|
||||
| CommandsLoadedMessage
|
||||
| AutocompleteSettingsLoadedMessage
|
||||
| ChatCompletionResultMessage
|
||||
@@ -1165,3 +1182,4 @@ export type ExtensionMessage =
|
||||
| ExtensionDataReadyMessage
|
||||
| TelemetryStateMessage
|
||||
| RemoteStatusMessage
|
||||
| ValidateFilesResultMessage
|
||||
|
||||
@@ -149,6 +149,12 @@ export interface OpenContentRequest {
|
||||
language?: string
|
||||
}
|
||||
|
||||
export interface ValidateFilesRequest {
|
||||
type: "validateFiles"
|
||||
id: string
|
||||
paths: string[]
|
||||
}
|
||||
|
||||
export interface CancelLoginRequest {
|
||||
type: "cancelLogin"
|
||||
}
|
||||
@@ -217,6 +223,7 @@ export interface OpenConfigFileRequest {
|
||||
|
||||
export interface OpenMarketplacePanelRequest {
|
||||
type: "openMarketplacePanel"
|
||||
directory?: string
|
||||
}
|
||||
|
||||
export interface OpenAgentManagerRequest {
|
||||
@@ -235,6 +242,14 @@ export interface RequestSkillsMessage {
|
||||
type: "requestSkills"
|
||||
}
|
||||
|
||||
export interface RequestAgentRequirementsMessage {
|
||||
type: "requestAgentRequirements"
|
||||
agent: string
|
||||
directory: string
|
||||
sessionID?: string
|
||||
force?: boolean
|
||||
}
|
||||
|
||||
export interface RequestCommandsMessage {
|
||||
type: "requestCommands"
|
||||
}
|
||||
@@ -1165,6 +1180,7 @@ export type WebviewMessage =
|
||||
| OpenAgentManagerRequest
|
||||
| OpenAdvancedWorktreeRequest
|
||||
| OpenFileRequest
|
||||
| ValidateFilesRequest
|
||||
| CancelLoginRequest
|
||||
| SetOrganizationRequest
|
||||
| WebviewReadyRequest
|
||||
@@ -1173,6 +1189,7 @@ export type WebviewMessage =
|
||||
| CompactRequest
|
||||
| RequestAgentsMessage
|
||||
| RequestSkillsMessage
|
||||
| RequestAgentRequirementsMessage
|
||||
| RequestCommandsMessage
|
||||
| SendCommandRequest
|
||||
| RemoveSkillMessage
|
||||
|
||||
@@ -18,21 +18,24 @@ export function createdDraftKey(draftID?: string, sandbox = false): string | und
|
||||
return pendingDraftKey(draftID) ?? (sandbox ? "new" : undefined)
|
||||
}
|
||||
|
||||
export function movePromptDraft<T, C, I>(
|
||||
stores: { text: Map<string, T>; comments: Map<string, C>; images: Map<string, I> },
|
||||
export function movePromptDraft<T, C, I, S>(
|
||||
stores: { text: Map<string, T>; comments: Map<string, C>; images: Map<string, I>; scrolls: Map<string, S> },
|
||||
source: string,
|
||||
target: string,
|
||||
): { text?: T; comments?: C; images?: I } {
|
||||
): { text?: T; comments?: C; images?: I; scroll?: S } {
|
||||
const draft = {
|
||||
text: stores.text.get(source),
|
||||
comments: stores.comments.get(source),
|
||||
images: stores.images.get(source),
|
||||
scroll: stores.scrolls.get(source),
|
||||
}
|
||||
if (draft.text !== undefined) stores.text.set(target, draft.text)
|
||||
if (draft.comments !== undefined) stores.comments.set(target, draft.comments)
|
||||
if (draft.images !== undefined) stores.images.set(target, draft.images)
|
||||
if (draft.scroll !== undefined) stores.scrolls.set(target, draft.scroll)
|
||||
stores.text.delete(source)
|
||||
stores.comments.delete(source)
|
||||
stores.images.delete(source)
|
||||
stores.scrolls.delete(source)
|
||||
return draft
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ import * as KiloAgent from "@/kilocode/agent" // kilocode_change
|
||||
import { RuntimeFlags } from "@/effect/runtime-flags"
|
||||
import { Reference } from "@/reference/reference" // kilocode_change
|
||||
import { ConfigReference } from "@/config/reference" // kilocode_change
|
||||
import * as AgentRequirements from "@/kilocode/agent-requirements" // kilocode_change
|
||||
import { MCP } from "@/mcp" // kilocode_change
|
||||
|
||||
export type RequirementBlockedError = InstanceType<typeof AgentRequirements.BlockedError> // kilocode_change
|
||||
|
||||
export const Info = Schema.Struct({
|
||||
name: Schema.String,
|
||||
@@ -49,6 +53,7 @@ export const Info = Schema.Struct({
|
||||
variant: Schema.optional(Schema.String),
|
||||
prompt: Schema.optional(Schema.String),
|
||||
options: Schema.Record(Schema.String, Schema.Unknown),
|
||||
requirements: Schema.optional(AgentRequirements.Requirements), // kilocode_change
|
||||
steps: Schema.optional(Schema.Finite),
|
||||
}).annotate({ identifier: "Agent" })
|
||||
export type Info = DeepMutable<Schema.Schema.Type<typeof Info>>
|
||||
@@ -64,6 +69,8 @@ export interface Interface {
|
||||
readonly list: () => Effect.Effect<Info[]>
|
||||
readonly defaultInfo: () => Effect.Effect<Info>
|
||||
readonly defaultAgent: () => Effect.Effect<string>
|
||||
readonly requirementStatus: (agent: string) => Effect.Effect<AgentRequirements.Result> // kilocode_change
|
||||
readonly guardRequirements: (agent: Info) => Effect.Effect<void, RequirementBlockedError> // kilocode_change
|
||||
readonly generate: (input: {
|
||||
description: string
|
||||
model?: { providerID: ProviderID; modelID: ModelID }
|
||||
@@ -77,7 +84,7 @@ export interface Interface {
|
||||
>
|
||||
}
|
||||
|
||||
type State = Omit<Interface, "generate"> & { version: string } // kilocode_change
|
||||
type State = Omit<Interface, "generate" | "requirementStatus" | "guardRequirements"> & { version: string } // kilocode_change
|
||||
|
||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Agent") {}
|
||||
|
||||
@@ -90,6 +97,7 @@ export const layer = Layer.effect(
|
||||
const auth = yield* Auth.Service
|
||||
const plugin = yield* Plugin.Service
|
||||
const skill = yield* Skill.Service
|
||||
const mcp = yield* MCP.Service // kilocode_change
|
||||
const provider = yield* Provider.Service
|
||||
const flags = yield* RuntimeFlags.Service
|
||||
|
||||
@@ -326,6 +334,7 @@ export const layer = Layer.effect(
|
||||
item.hidden = value.hidden ?? item.hidden
|
||||
item.name = value.name ?? item.name
|
||||
item.steps = value.steps ?? item.steps
|
||||
item.requirements = value.requirements ?? item.requirements // kilocode_change
|
||||
item.options = mergeDeep(item.options, value.options ?? {})
|
||||
item.permission = Permission.merge(item.permission, Permission.fromConfig(value.permission ?? {}))
|
||||
KiloAgent.processConfigItem(item) // kilocode_change - populate displayName from options
|
||||
@@ -480,6 +489,32 @@ export const layer = Layer.effect(
|
||||
})
|
||||
// kilocode_change end
|
||||
|
||||
// kilocode_change start - agent requirement status and guard hooks
|
||||
const requirementStatus = Effect.fn("Agent.requirementStatus")(function* (name: string) {
|
||||
const ctx = yield* InstanceState.context
|
||||
return yield* AgentRequirements.status({
|
||||
name,
|
||||
directory: ctx.directory,
|
||||
config,
|
||||
skills: skill,
|
||||
mcp,
|
||||
agents: { get: (agent) => current((s) => s.get(agent)) },
|
||||
})
|
||||
})
|
||||
|
||||
const guardRequirements = Effect.fn("Agent.guardRequirements")(function* (agent: Info) {
|
||||
const ctx = yield* InstanceState.context
|
||||
yield* AgentRequirements.guard({
|
||||
agent,
|
||||
directory: ctx.directory,
|
||||
config,
|
||||
skills: skill,
|
||||
mcp,
|
||||
agents: { get: (name) => current((s) => s.get(name)) },
|
||||
})
|
||||
})
|
||||
// kilocode_change end
|
||||
|
||||
return Service.of({
|
||||
get: Effect.fn("Agent.get")(function* (agent: string) {
|
||||
return yield* current((s) => s.get(agent)) // kilocode_change
|
||||
@@ -493,6 +528,8 @@ export const layer = Layer.effect(
|
||||
defaultAgent: Effect.fn("Agent.defaultAgent")(function* () {
|
||||
return yield* current((s) => s.defaultAgent()) // kilocode_change
|
||||
}),
|
||||
requirementStatus, // kilocode_change
|
||||
guardRequirements, // kilocode_change
|
||||
generate: Effect.fn("Agent.generate")(function* (input: {
|
||||
description: string
|
||||
model?: { providerID: ProviderID; modelID: ModelID }
|
||||
@@ -565,6 +602,7 @@ export const defaultLayer = layer.pipe(
|
||||
Layer.provide(Auth.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(Skill.defaultLayer),
|
||||
Layer.provide(MCP.defaultLayer), // kilocode_change
|
||||
Layer.provide(RuntimeFlags.defaultLayer),
|
||||
)
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ import { useArgs } from "@tui/context/args"
|
||||
// kilocode_change start
|
||||
import { KiloSessionTuiSync } from "@/kilocode/session/tui-sync"
|
||||
import { slashMatches } from "@/kilocode/cli/cmd/command-display"
|
||||
import * as AgentRequirements from "@/kilocode/cli/agent-requirements"
|
||||
// kilocode_change end
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { type WorkspaceStatus } from "../workspace-label"
|
||||
@@ -1087,6 +1088,29 @@ export function Prompt(props: PromptProps) {
|
||||
return false
|
||||
}
|
||||
|
||||
// kilocode_change start - gate TUI sends on declared agent requirements
|
||||
const requirements = await AgentRequirements.check({
|
||||
client: sdk.client,
|
||||
agent: agent.name,
|
||||
directory: project.instance.directory() || sdk.directory || process.cwd(),
|
||||
}).catch((error) => {
|
||||
toast.show({
|
||||
message: errorMessage(error),
|
||||
variant: "error",
|
||||
})
|
||||
return undefined
|
||||
})
|
||||
if (!requirements) return false
|
||||
if (!requirements.ok) {
|
||||
toast.show({
|
||||
title: "Agent requirements",
|
||||
message: requirements.error.data.message,
|
||||
variant: "error",
|
||||
})
|
||||
return false
|
||||
}
|
||||
// kilocode_change end
|
||||
|
||||
const workspaceSession = props.sessionID ? sync.session.get(props.sessionID) : undefined
|
||||
const workspaceID = workspaceSession?.workspaceID
|
||||
const workspaceStatus = workspaceID ? (project.workspace.status(workspaceID) ?? "error") : undefined
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Filesystem } from "@/util/filesystem"
|
||||
import type { GlobalEvent } from "@kilocode/sdk/v2"
|
||||
import type { EventSource } from "./context/sdk"
|
||||
import { win32DisableProcessedInput, win32InstallCtrlCGuard } from "./win32"
|
||||
import { importCloudSession, validateCloudFork } from "@/kilocode/cloud-session" // kilocode_change
|
||||
import { importCloudSession, localSessionID, validateCloudFork } from "@/kilocode/cloud-session" // kilocode_change
|
||||
import { createKiloClient } from "@kilocode/sdk/v2" // kilocode_change
|
||||
import { writeHeapSnapshot } from "v8"
|
||||
import { TuiConfig } from "./config/tui"
|
||||
@@ -319,7 +319,7 @@ export const TuiThreadCommand = cmd({
|
||||
try {
|
||||
await validateSession({
|
||||
url: transport.url, // kilocode_change
|
||||
sessionID: args.session,
|
||||
sessionID: localSessionID(args), // kilocode_change
|
||||
directory: cwd,
|
||||
fetch: transport.fetch,
|
||||
})
|
||||
|
||||
@@ -17,6 +17,7 @@ import { Bus } from "@/bus"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { KilocodeConfig } from "@/kilocode/config/config"
|
||||
import type { Warning } from "./config"
|
||||
import { Requirements } from "@/kilocode/agent-requirements"
|
||||
// kilocode_change end
|
||||
|
||||
const log = Log.create({ service: "config" })
|
||||
@@ -61,6 +62,7 @@ const AgentSchema = Schema.StructWithRest(
|
||||
// kilocode_change end
|
||||
maxSteps: Schema.optional(PositiveInt).annotate({ description: "@deprecated Use 'steps' field instead." }),
|
||||
permission: Schema.optional(ConfigPermission.Info),
|
||||
requirements: Schema.optional(Requirements), // kilocode_change
|
||||
}),
|
||||
[Schema.Record(Schema.String, Schema.Any)],
|
||||
)
|
||||
@@ -82,6 +84,7 @@ const KNOWN_KEYS = new Set([
|
||||
"permission",
|
||||
"disable",
|
||||
"tools",
|
||||
"requirements", // kilocode_change
|
||||
])
|
||||
|
||||
// Post-parse normalisation:
|
||||
|
||||
@@ -390,6 +390,9 @@ export const Info = Schema.Struct({
|
||||
batch_tool: Schema.optional(Schema.Boolean).annotate({ description: "Enable the batch tool" }),
|
||||
// kilocode_change start
|
||||
codebase_search: Schema.optional(Schema.Boolean).annotate({ description: "Enable AI-powered codebase search" }),
|
||||
agent_requirements: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Require declared agent skills, MCPs, and VS Code extensions before VS Code prompts can run",
|
||||
}),
|
||||
native_notebook_tools: Schema.optional(Schema.Boolean).annotate({
|
||||
description: "Enable native tools for reading, editing, and executing VS Code notebooks",
|
||||
}),
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
import type { Agent } from "@/agent/agent"
|
||||
import type { Config } from "@/config/config"
|
||||
import type { MCP } from "@/mcp"
|
||||
import type { Skill } from "@/skill"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
import { NamedError } from "@opencode-ai/core/util/error"
|
||||
import { Cause, Effect, Exit, Schema } from "effect"
|
||||
|
||||
const ID = Schema.String.check(
|
||||
Schema.isMinLength(1),
|
||||
Schema.isMaxLength(128),
|
||||
Schema.isPattern(/^[A-Za-z0-9][A-Za-z0-9._-]*$/),
|
||||
)
|
||||
const Name = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(128), Schema.isPattern(/\S/))
|
||||
|
||||
export const VSCodeExtension = Schema.Struct({
|
||||
name: Name,
|
||||
id: ID,
|
||||
})
|
||||
export type VSCodeExtension = Schema.Schema.Type<typeof VSCodeExtension>
|
||||
|
||||
const Group = Schema.mutable(Schema.Array(Name)).check(Schema.isMinLength(1), Schema.isMaxLength(20))
|
||||
const VSCodeExtensions = Schema.mutable(Schema.Array(VSCodeExtension)).check(
|
||||
Schema.isMinLength(1),
|
||||
Schema.isMaxLength(20),
|
||||
)
|
||||
|
||||
export const Requirements = Schema.Struct({
|
||||
skills: Schema.optional(Group),
|
||||
mcps: Schema.optional(Group),
|
||||
vscode_extensions: Schema.optional(VSCodeExtensions),
|
||||
}).check(
|
||||
Schema.makeFilter((input) => {
|
||||
const issues: Schema.FilterIssue[] = []
|
||||
if (!input.skills && !input.mcps && !input.vscode_extensions) {
|
||||
issues.push({ path: [], issue: "At least one requirement group is required" })
|
||||
}
|
||||
|
||||
for (const group of ["skills", "mcps"] as const) {
|
||||
const seen = new Set<string>()
|
||||
for (const [index, value] of (input[group] ?? []).entries()) {
|
||||
if (seen.has(value)) issues.push({ path: [group, index], issue: `Duplicate ${group} requirement` })
|
||||
seen.add(value)
|
||||
}
|
||||
}
|
||||
|
||||
const seen = new Set<string>()
|
||||
for (const [index, extension] of (input.vscode_extensions ?? []).entries()) {
|
||||
if (seen.has(extension.id)) {
|
||||
issues.push({ path: ["vscode_extensions", index, "id"], issue: "Duplicate vscode_extensions requirement" })
|
||||
}
|
||||
seen.add(extension.id)
|
||||
}
|
||||
|
||||
return issues
|
||||
}),
|
||||
)
|
||||
export type Requirements = Schema.Schema.Type<typeof Requirements>
|
||||
|
||||
export const SkillItem = Schema.Struct({
|
||||
name: Schema.String,
|
||||
status: Schema.Literals(["ready", "missing", "error"]),
|
||||
message: Schema.optional(Schema.String),
|
||||
})
|
||||
export type SkillItem = Schema.Schema.Type<typeof SkillItem>
|
||||
|
||||
export const MCPItem = Schema.Struct({
|
||||
name: Schema.String,
|
||||
status: Schema.Literals(["ready", "missing", "error"]),
|
||||
message: Schema.optional(Schema.String),
|
||||
})
|
||||
export type MCPItem = Schema.Schema.Type<typeof MCPItem>
|
||||
|
||||
export const Result = Schema.Struct({
|
||||
agent: Schema.String,
|
||||
directory: Schema.String,
|
||||
enabled: Schema.Boolean,
|
||||
state: Schema.Literals(["disabled", "ready", "blocked", "error"]),
|
||||
skills: Schema.Array(SkillItem),
|
||||
mcps: Schema.Array(MCPItem),
|
||||
vscode_extensions: Schema.Array(VSCodeExtension),
|
||||
error: Schema.optional(
|
||||
Schema.Struct({
|
||||
code: Schema.Literals(["unknown_agent", "malformed_declaration", "discovery_failed", "mcp_status_failed"]),
|
||||
message: Schema.String,
|
||||
}),
|
||||
),
|
||||
}).annotate({ identifier: "AgentRequirementResult" })
|
||||
export type Result = Schema.Schema.Type<typeof Result>
|
||||
|
||||
export const BlockedError = NamedError.create("AgentRequirementError", {
|
||||
message: Schema.String,
|
||||
agent: Schema.String,
|
||||
directory: Schema.String,
|
||||
state: Schema.Literals(["blocked", "error"]),
|
||||
skills: Schema.mutable(Schema.Array(SkillItem)),
|
||||
mcps: Schema.mutable(Schema.Array(MCPItem)),
|
||||
vscode_extensions: Schema.mutable(Schema.Array(VSCodeExtension)),
|
||||
})
|
||||
|
||||
type AgentInfo = Pick<Agent.Info, "name"> & { requirements?: unknown }
|
||||
|
||||
type Services = {
|
||||
config: Pick<Config.Interface, "get">
|
||||
agents: { get: (agent: string) => Effect.Effect<AgentInfo | undefined> }
|
||||
skills: Pick<Skill.Interface, "all">
|
||||
mcp: Pick<MCP.Interface, "status">
|
||||
}
|
||||
|
||||
function enabled(cfg: Config.Info) {
|
||||
const experimental = cfg.experimental
|
||||
return experimental !== undefined && "agent_requirements" in experimental && experimental.agent_requirements === true
|
||||
}
|
||||
|
||||
function ready(input: { agent: string; directory: string; enabled: boolean }): Result {
|
||||
return {
|
||||
...input,
|
||||
state: input.enabled ? "ready" : "disabled",
|
||||
skills: [],
|
||||
mcps: [],
|
||||
vscode_extensions: [],
|
||||
}
|
||||
}
|
||||
|
||||
function malformed(agent: AgentInfo, directory: string, message: string): Result {
|
||||
return {
|
||||
agent: agent.name,
|
||||
directory,
|
||||
enabled: true,
|
||||
state: "error",
|
||||
skills: [],
|
||||
mcps: [],
|
||||
vscode_extensions: [],
|
||||
error: { code: "malformed_declaration", message },
|
||||
}
|
||||
}
|
||||
|
||||
function decode(agent: AgentInfo, directory: string): Requirements | Result {
|
||||
if (agent.requirements === undefined) return ready({ agent: agent.name, directory, enabled: true })
|
||||
|
||||
const decoded = Schema.decodeUnknownExit(Requirements)(agent.requirements, {
|
||||
errors: "all",
|
||||
propertyOrder: "original",
|
||||
})
|
||||
if (Exit.isFailure(decoded)) return malformed(agent, directory, Cause.pretty(decoded.cause))
|
||||
return decoded.value
|
||||
}
|
||||
|
||||
function item(name: string, status: MCP.Status | undefined): MCPItem {
|
||||
if (status?.status === "connected") return { name, status: "ready" }
|
||||
if (status?.status === "failed" || status?.status === "needs_client_registration") {
|
||||
return { name, status: "error", message: status.error }
|
||||
}
|
||||
return { name, status: "missing" }
|
||||
}
|
||||
|
||||
export function evaluate(input: {
|
||||
agent: AgentInfo
|
||||
directory: string
|
||||
enabled: boolean
|
||||
requirements: Requirements
|
||||
discovered?: ReadonlySet<string>
|
||||
discoveryError?: string
|
||||
mcp?: Readonly<Record<string, MCP.Status>>
|
||||
mcpError?: string
|
||||
}): Result {
|
||||
if (!input.enabled) return ready({ agent: input.agent.name, directory: input.directory, enabled: false })
|
||||
|
||||
const skills = (input.requirements.skills ?? []).map((skill) => ({
|
||||
name: skill,
|
||||
status: input.discoveryError
|
||||
? ("error" as const)
|
||||
: input.discovered?.has(skill)
|
||||
? ("ready" as const)
|
||||
: ("missing" as const),
|
||||
...(input.discoveryError ? { message: input.discoveryError } : {}),
|
||||
}))
|
||||
const mcps = (input.requirements.mcps ?? []).map((name) => {
|
||||
if (input.mcpError) return { name, status: "error" as const, message: input.mcpError }
|
||||
return item(name, input.mcp?.[name])
|
||||
})
|
||||
|
||||
if (input.discoveryError) {
|
||||
return {
|
||||
agent: input.agent.name,
|
||||
directory: input.directory,
|
||||
enabled: true,
|
||||
state: "error",
|
||||
skills,
|
||||
mcps,
|
||||
vscode_extensions: input.requirements.vscode_extensions ?? [],
|
||||
error: { code: "discovery_failed", message: input.discoveryError },
|
||||
}
|
||||
}
|
||||
|
||||
if (input.mcpError) {
|
||||
return {
|
||||
agent: input.agent.name,
|
||||
directory: input.directory,
|
||||
enabled: true,
|
||||
state: "error",
|
||||
skills,
|
||||
mcps,
|
||||
vscode_extensions: input.requirements.vscode_extensions ?? [],
|
||||
error: { code: "mcp_status_failed", message: input.mcpError },
|
||||
}
|
||||
}
|
||||
|
||||
const valid = skills.every((skill) => skill.status === "ready") && mcps.every((mcp) => mcp.status === "ready")
|
||||
return {
|
||||
agent: input.agent.name,
|
||||
directory: input.directory,
|
||||
enabled: true,
|
||||
state: valid ? "ready" : "blocked",
|
||||
skills,
|
||||
mcps,
|
||||
vscode_extensions: input.requirements.vscode_extensions ?? [],
|
||||
}
|
||||
}
|
||||
|
||||
export const status = Effect.fn("AgentRequirements.status")(function* (
|
||||
input: Services & { name: string; directory: string },
|
||||
) {
|
||||
const cfg = yield* input.config.get()
|
||||
const active = enabled(cfg)
|
||||
if (!active) return ready({ agent: input.name, directory: input.directory, enabled: false })
|
||||
|
||||
const agent = yield* input.agents.get(input.name)
|
||||
if (!agent) {
|
||||
return {
|
||||
agent: input.name,
|
||||
directory: input.directory,
|
||||
enabled: true,
|
||||
state: "error",
|
||||
skills: [],
|
||||
mcps: [],
|
||||
vscode_extensions: [],
|
||||
error: { code: "unknown_agent", message: `Agent not found: ${input.name}` },
|
||||
} satisfies Result
|
||||
}
|
||||
|
||||
const requirements = decode(agent, input.directory)
|
||||
if ("state" in requirements) return requirements
|
||||
|
||||
const skillStatus: Effect.Effect<Skill.Info[]> = requirements.skills?.length ? input.skills.all() : Effect.succeed([])
|
||||
const mcpStatus: Effect.Effect<Record<string, MCP.Status>> = requirements.mcps?.length
|
||||
? input.mcp.status()
|
||||
: Effect.succeed({})
|
||||
const [discovered, mcp] = yield* Effect.all([skillStatus.pipe(Effect.exit), mcpStatus.pipe(Effect.exit)])
|
||||
const discoveredSet: ReadonlySet<string> | undefined = Exit.isSuccess(discovered)
|
||||
? new Set(discovered.value.map((skill) => skill.name))
|
||||
: undefined
|
||||
|
||||
if (Exit.isFailure(discovered)) {
|
||||
return evaluate({
|
||||
agent,
|
||||
directory: input.directory,
|
||||
enabled: active,
|
||||
requirements,
|
||||
discoveryError: Cause.pretty(discovered.cause),
|
||||
mcp: Exit.isSuccess(mcp) ? mcp.value : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
if (Exit.isFailure(mcp)) {
|
||||
return evaluate({
|
||||
agent,
|
||||
directory: input.directory,
|
||||
enabled: active,
|
||||
requirements,
|
||||
discovered: discoveredSet,
|
||||
mcpError: Cause.pretty(mcp.cause),
|
||||
})
|
||||
}
|
||||
|
||||
return evaluate({
|
||||
agent,
|
||||
directory: input.directory,
|
||||
enabled: active,
|
||||
requirements,
|
||||
discovered: discoveredSet,
|
||||
mcp: mcp.value,
|
||||
})
|
||||
})
|
||||
|
||||
export const guard = Effect.fn("AgentRequirements.guard")(function* (
|
||||
input: Services & { agent: AgentInfo; directory: string },
|
||||
) {
|
||||
const result = yield* status({ ...input, name: input.agent.name })
|
||||
const unsupported = Flag.KILO_CLIENT !== "vscode" && result.vscode_extensions.length > 0
|
||||
if (result.state === "disabled" || (result.state === "ready" && !unsupported)) return
|
||||
const state = result.state === "error" ? result.state : "blocked"
|
||||
|
||||
return yield* Effect.fail(
|
||||
new BlockedError({
|
||||
message: "Complete the required checks to use this agent first",
|
||||
agent: result.agent,
|
||||
directory: result.directory,
|
||||
state,
|
||||
skills: [...result.skills],
|
||||
mcps: [...result.mcps],
|
||||
vscode_extensions: [...result.vscode_extensions],
|
||||
}),
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,116 @@
|
||||
import type { AgentRequirementResult } from "@kilocode/sdk/v2"
|
||||
import { isRecord } from "@/util/record"
|
||||
|
||||
type Item = AgentRequirementResult["skills"][number] | AgentRequirementResult["mcps"][number]
|
||||
|
||||
export type Client = {
|
||||
kilocode: {
|
||||
agentRequirements: (
|
||||
parameters: { agent: string; directory: string },
|
||||
options: { throwOnError: true },
|
||||
) => Promise<{ data: AgentRequirementResult }>
|
||||
}
|
||||
}
|
||||
|
||||
export type ErrorData = {
|
||||
message: string
|
||||
agent: string
|
||||
directory: string
|
||||
state: "blocked" | "error"
|
||||
skills: AgentRequirementResult["skills"]
|
||||
mcps: AgentRequirementResult["mcps"]
|
||||
vscode_extensions: AgentRequirementResult["vscode_extensions"]
|
||||
}
|
||||
|
||||
export type RequirementError = {
|
||||
name: "AgentRequirementError"
|
||||
data: ErrorData
|
||||
}
|
||||
|
||||
export type Check =
|
||||
| { ok: true; result: AgentRequirementResult }
|
||||
| { ok: false; result: AgentRequirementResult; error: RequirementError }
|
||||
|
||||
export async function check(input: { client: Client; agent: string; directory: string }): Promise<Check> {
|
||||
const response = await input.client.kilocode.agentRequirements(
|
||||
{ agent: input.agent, directory: input.directory },
|
||||
{ throwOnError: true },
|
||||
)
|
||||
if (!blocked(response.data)) return { ok: true, result: response.data }
|
||||
return { ok: false, result: response.data, error: toError(response.data) }
|
||||
}
|
||||
|
||||
export function blocked(result: AgentRequirementResult) {
|
||||
if (!result.enabled || result.state === "disabled") return false
|
||||
if (result.vscode_extensions.length > 0) return true
|
||||
return result.state === "blocked" || result.state === "error"
|
||||
}
|
||||
|
||||
export function toError(result: AgentRequirementResult): RequirementError {
|
||||
const info = {
|
||||
message: result.error?.message ?? "Complete the required checks to use this agent first",
|
||||
agent: result.agent,
|
||||
directory: result.directory,
|
||||
state: result.state === "error" ? ("error" as const) : ("blocked" as const),
|
||||
skills: result.skills,
|
||||
mcps: result.mcps,
|
||||
vscode_extensions: result.vscode_extensions,
|
||||
}
|
||||
|
||||
return {
|
||||
name: "AgentRequirementError",
|
||||
data: {
|
||||
...info,
|
||||
message: format(info),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export function data(input: unknown): ErrorData | undefined {
|
||||
if (!isRecord(input)) return undefined
|
||||
if (input.name !== "AgentRequirementError" || !isRecord(input.data)) return undefined
|
||||
|
||||
const info = input.data
|
||||
if (typeof info.message !== "string") return undefined
|
||||
if (typeof info.agent !== "string") return undefined
|
||||
if (typeof info.directory !== "string") return undefined
|
||||
if (info.state !== "blocked" && info.state !== "error") return undefined
|
||||
if (!Array.isArray(info.skills)) return undefined
|
||||
if (!Array.isArray(info.mcps)) return undefined
|
||||
if (!Array.isArray(info.vscode_extensions)) return undefined
|
||||
|
||||
return info as ErrorData
|
||||
}
|
||||
|
||||
export function format(input: ErrorData | RequirementError) {
|
||||
const info = "data" in input ? input.data : input
|
||||
const skills = pending(info.skills)
|
||||
const mcps = pending(info.mcps)
|
||||
const lines = [`Agent requirements are not met for "${info.agent}".`]
|
||||
|
||||
if (info.vscode_extensions.length) {
|
||||
lines.push(
|
||||
"",
|
||||
"VS Code extensions:",
|
||||
...info.vscode_extensions.map((ext) => `- ${ext.name} (${ext.id})`),
|
||||
"This agent requires VS Code extensions and is not supported in this CLI environment. Use the Kilo VS Code extension instead.",
|
||||
)
|
||||
}
|
||||
|
||||
if (skills.length) lines.push("", "Skills:", ...skills.map((skill) => `- ${item(skill)}`))
|
||||
if (mcps.length) lines.push("", "MCP servers:", ...mcps.map((mcp) => `- ${item(mcp)}`))
|
||||
|
||||
if (skills.length || mcps.length) {
|
||||
lines.push("", "Install the required skills and configure or connect the required MCP servers, then retry.")
|
||||
}
|
||||
return lines.join("\n")
|
||||
}
|
||||
|
||||
function pending<T extends Item>(items: T[]) {
|
||||
return items.filter((item) => item.status !== "ready")
|
||||
}
|
||||
|
||||
function item(input: Item) {
|
||||
const status = input.status === "missing" ? "missing" : "error"
|
||||
return input.message ? `${input.name} (${status}: ${input.message})` : `${input.name} (${status})`
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import type { NetworkOptions } from "@/cli/network"
|
||||
import { errorMessage } from "@/util/error"
|
||||
import { TuiConfig } from "@/cli/cmd/tui/config/tui"
|
||||
import { validateSession } from "@/cli/cmd/tui/validate-session"
|
||||
import { importCloudSession } from "@/kilocode/cloud-session"
|
||||
import { importCloudSession, localSessionID } from "@/kilocode/cloud-session"
|
||||
import { DaemonClient } from "@/kilocode/daemon/client"
|
||||
import { createKiloClient } from "@kilocode/sdk/v2"
|
||||
|
||||
@@ -55,7 +55,7 @@ export namespace KiloTuiThreadDaemon {
|
||||
try {
|
||||
await validateSession({
|
||||
url: daemon.url,
|
||||
sessionID: input.args.session,
|
||||
sessionID: localSessionID(input.args),
|
||||
directory: input.cwd,
|
||||
headers: daemon.headers,
|
||||
})
|
||||
|
||||
@@ -13,6 +13,10 @@ export function validateCloudFork(args: {
|
||||
if (!args.session) return "--cloud-fork requires --session"
|
||||
}
|
||||
|
||||
export function localSessionID(args: { cloudFork?: boolean; session?: string }) {
|
||||
return args.cloudFork ? undefined : args.session
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a cloud session to local storage and return the new local session ID.
|
||||
* Wraps the SDK's `.kilo.cloud.session.import()` which returns `unknown` due to
|
||||
|
||||
@@ -5,9 +5,11 @@ import { InstanceContextMiddleware } from "@/server/routes/instance/httpapi/midd
|
||||
import {
|
||||
WorkspaceRoutingMiddleware,
|
||||
WorkspaceRoutingQuery,
|
||||
WorkspaceRoutingQueryFields,
|
||||
} from "@/server/routes/instance/httpapi/middleware/workspace-routing"
|
||||
import { described } from "@/server/routes/instance/httpapi/groups/metadata"
|
||||
import { AnacondaDesktopApi } from "./anaconda-desktop"
|
||||
import { Result as AgentRequirementResult } from "@/kilocode/agent-requirements"
|
||||
import {
|
||||
Failure as NotebookFailure,
|
||||
Request as NotebookRequest,
|
||||
@@ -27,11 +29,16 @@ export const RemoveAgentPayload = Schema.Struct({
|
||||
name: Schema.String,
|
||||
})
|
||||
|
||||
export const AgentRequirementQuery = Schema.Struct({
|
||||
...WorkspaceRoutingQueryFields,
|
||||
agent: Schema.String,
|
||||
})
|
||||
export const NotebookReplyPayload = Schema.Struct({ result: NotebookResult })
|
||||
export const NotebookRejectPayload = Schema.Struct({ error: NotebookFailure })
|
||||
|
||||
export const KilocodePaths = {
|
||||
heapSnapshot: `${root}/heap/snapshot`,
|
||||
agentRequirements: `${root}/agent/requirements`,
|
||||
removeSkill: `${root}/skill/remove`,
|
||||
removeAgent: `${root}/agent/remove`,
|
||||
notebookList: `${root}/notebook`,
|
||||
@@ -55,6 +62,16 @@ export const KilocodeApi = HttpApi.make("kilocode")
|
||||
description: "Write a heap snapshot for the CLI process to the log directory.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.get("agentRequirements", KilocodePaths.agentRequirements, {
|
||||
query: AgentRequirementQuery,
|
||||
success: described(AgentRequirementResult, "Agent requirement status"),
|
||||
}).annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "kilocode.agentRequirements",
|
||||
summary: "Check agent requirements",
|
||||
description: "Check whether the selected agent's requirements are available in the request directory.",
|
||||
}),
|
||||
),
|
||||
HttpApiEndpoint.post("removeSkill", KilocodePaths.removeSkill, {
|
||||
query: WorkspaceRoutingQuery,
|
||||
payload: RemoveSkillPayload,
|
||||
|
||||
@@ -28,6 +28,12 @@ export const kilocodeHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilocode"
|
||||
return yield* Effect.sync(() => HeapSnapshot.write())
|
||||
})
|
||||
|
||||
const agentRequirements = Effect.fn("KilocodeHttpApi.agentRequirements")(function* (ctx: {
|
||||
query: { agent: string }
|
||||
}) {
|
||||
return yield* agents.requirementStatus(ctx.query.agent)
|
||||
})
|
||||
|
||||
const removeSkill = Effect.fn("KilocodeHttpApi.removeSkill")(function* (ctx: {
|
||||
payload: typeof RemoveSkillPayload.Type
|
||||
}) {
|
||||
@@ -89,6 +95,7 @@ export const kilocodeHandlers = HttpApiBuilder.group(InstanceHttpApi, "kilocode"
|
||||
|
||||
return handlers
|
||||
.handle("heapSnapshot", heapSnapshot)
|
||||
.handle("agentRequirements", agentRequirements)
|
||||
.handle("removeSkill", removeSkill)
|
||||
.handle("removeAgent", removeAgent)
|
||||
.handle("notebookList", notebookList)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Image } from "@/image/image" // kilocode_change - classify user image validation defects
|
||||
import { KiloSessionHttpApi } from "@/kilocode/server/httpapi/session-fork" // kilocode_change
|
||||
import { BlockedError as AgentRequirementError } from "@/kilocode/agent-requirements" // kilocode_change
|
||||
import { Agent } from "@/agent/agent"
|
||||
import { Bus } from "@/bus"
|
||||
import { Command } from "@/command"
|
||||
@@ -320,9 +321,12 @@ export const sessionHandlers = HttpApiBuilder.group(InstanceHttpApi, "session",
|
||||
yield* Effect.logError("prompt_async failed").pipe(
|
||||
Effect.annotateLogs({ sessionID: ctx.params.sessionID, cause }),
|
||||
)
|
||||
const error = Cause.squash(cause)
|
||||
yield* bus.publish(Session.Event.Error, {
|
||||
sessionID: ctx.params.sessionID,
|
||||
error: new NamedError.Unknown({ message: Cause.pretty(cause) }).toObject(),
|
||||
error: AgentRequirementError.isInstance(error)
|
||||
? error.toObject()
|
||||
: new NamedError.Unknown({ message: Cause.pretty(cause) }).toObject(),
|
||||
})
|
||||
}),
|
||||
),
|
||||
|
||||
@@ -109,10 +109,18 @@ function isOrphanedInterruptedTool(part: MessageV2.ToolPart) {
|
||||
|
||||
export interface Interface {
|
||||
readonly cancel: (sessionID: SessionID) => Effect.Effect<void>
|
||||
readonly prompt: (input: PromptInput) => Effect.Effect<MessageV2.WithParts, Image.Error>
|
||||
// kilocode_change start - prompt can fail on unmet agent requirements
|
||||
readonly prompt: (
|
||||
input: PromptInput,
|
||||
) => Effect.Effect<MessageV2.WithParts, Image.Error | Agent.RequirementBlockedError>
|
||||
// kilocode_change end
|
||||
readonly loop: (input: LoopInput) => Effect.Effect<MessageV2.WithParts>
|
||||
readonly shell: (input: ShellInput) => Effect.Effect<MessageV2.WithParts, Session.BusyError>
|
||||
readonly command: (input: CommandInput) => Effect.Effect<MessageV2.WithParts, Image.Error>
|
||||
// kilocode_change start - commands can fail on unmet agent requirements
|
||||
readonly command: (
|
||||
input: CommandInput,
|
||||
) => Effect.Effect<MessageV2.WithParts, Image.Error | Agent.RequirementBlockedError>
|
||||
// kilocode_change end
|
||||
readonly resolvePromptParts: (template: string) => Effect.Effect<PromptInput["parts"]>
|
||||
}
|
||||
|
||||
@@ -749,14 +757,15 @@ export const layer = Layer.effect(
|
||||
|
||||
const createUserMessage = Effect.fn("SessionPrompt.createUserMessage")(function* (input: PromptInput) {
|
||||
const agentName = input.agent
|
||||
const ag = agentName ? yield* agents.get(agentName) : yield* agents.defaultInfo()
|
||||
const ag = agentName ? yield* agents.get(agentName) : yield* agents.defaultInfo() // kilocode_change
|
||||
if (!ag) {
|
||||
const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name)
|
||||
const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
|
||||
const error = new NamedError.Unknown({ message: `Agent not found: "${agentName}".${hint}` })
|
||||
yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
|
||||
throw error
|
||||
const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name) // kilocode_change
|
||||
const hint = available.length ? ` Available agents: ${available.join(", ")}` : "" // kilocode_change
|
||||
const error = new NamedError.Unknown({ message: `Agent not found: "${agentName}".${hint}` }) // kilocode_change
|
||||
yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() }) // kilocode_change
|
||||
throw error // kilocode_change
|
||||
}
|
||||
yield* agents.guardRequirements(ag) // kilocode_change - enforce requirements before creating a turn
|
||||
|
||||
const current = Database.use((db) =>
|
||||
db
|
||||
@@ -1877,14 +1886,15 @@ export const layer = Layer.effect(
|
||||
|
||||
yield* getModel(taskModel.providerID, taskModel.modelID, input.sessionID)
|
||||
|
||||
const agent = agentName ? yield* agents.get(agentName) : yield* agents.defaultInfo()
|
||||
const agent = agentName ? yield* agents.get(agentName) : yield* agents.defaultInfo() // kilocode_change
|
||||
if (!agent) {
|
||||
const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name)
|
||||
const hint = available.length ? ` Available agents: ${available.join(", ")}` : ""
|
||||
const error = new NamedError.Unknown({ message: `Agent not found: "${agentName}".${hint}` })
|
||||
yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() })
|
||||
throw error
|
||||
const available = (yield* agents.list()).filter((a) => !a.hidden).map((a) => a.name) // kilocode_change
|
||||
const hint = available.length ? ` Available agents: ${available.join(", ")}` : "" // kilocode_change
|
||||
const error = new NamedError.Unknown({ message: `Agent not found: "${agentName}".${hint}` }) // kilocode_change
|
||||
yield* bus.publish(Session.Event.Error, { sessionID: input.sessionID, error: error.toObject() }) // kilocode_change
|
||||
throw error // kilocode_change
|
||||
}
|
||||
yield* agents.guardRequirements(agent) // kilocode_change - command agent overrides must satisfy requirements
|
||||
|
||||
const templateParts = yield* resolvePromptParts(template)
|
||||
KiloSessionProcessor.markReviewTelemetry(templateParts, input.command) // kilocode_change - mark review commands for completion telemetry
|
||||
|
||||
@@ -35,6 +35,7 @@ import { KiloSession, kiloSessionFork } from "@/kilocode/session"
|
||||
import { SessionExport } from "@/kilocode/session-export"
|
||||
import * as SandboxPolicy from "@/kilocode/sandbox/policy"
|
||||
import { baseKey, cumulativeSessionDiff } from "@/kilocode/session-portability/cumulative-diff" // kilocode_change
|
||||
import { BlockedError as AgentRequirementError } from "@/kilocode/agent-requirements"
|
||||
// kilocode_change end
|
||||
import { Effect, Layer, Option, Context, Schema, Types } from "effect"
|
||||
import { NonNegativeInt, optionalOmitUndefined } from "@opencode-ai/core/schema"
|
||||
@@ -375,7 +376,9 @@ export const Event = {
|
||||
sessionID: Schema.optional(SessionID),
|
||||
// Reuses MessageV2.Assistant.fields.error (already Schema.optional) so
|
||||
// the derived zod keeps the same discriminated-union shape on the bus.
|
||||
error: MessageV2.Assistant.fields.error,
|
||||
// kilocode_change start - carry pre-message requirement failures over session.error
|
||||
error: Schema.optional(Schema.Union([MessageV2.Assistant.fields.error, AgentRequirementError.EffectSchema])),
|
||||
// kilocode_change end
|
||||
}),
|
||||
),
|
||||
// kilocode_change start
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Plugin } from "../../src/plugin"
|
||||
import { Provider } from "../../src/provider/provider"
|
||||
import { Skill } from "../../src/skill"
|
||||
import { Truncate } from "../../src/tool/truncate"
|
||||
import { MCP } from "../../src/mcp" // kilocode_change
|
||||
|
||||
const agentLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
||||
Agent.layer.pipe(
|
||||
@@ -21,6 +22,7 @@ const agentLayer = (flags: Partial<RuntimeFlags.Info> = {}) =>
|
||||
Layer.provide(Auth.defaultLayer),
|
||||
Layer.provide(Config.defaultLayer),
|
||||
Layer.provide(Skill.defaultLayer),
|
||||
Layer.provide(Layer.mock(MCP.Service)({})), // kilocode_change
|
||||
Layer.provide(RuntimeFlags.layer(flags)),
|
||||
)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user