mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-29 03:44:06 +08:00
Merge branch 'main' into refactor/agent-typed-metadata-fields
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.
|
||||
+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
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -1013,7 +1044,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) =>
|
||||
@@ -1067,6 +1098,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
|
||||
@@ -1088,7 +1129,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
|
||||
@@ -1097,7 +1138,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
|
||||
@@ -1106,7 +1147,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
|
||||
@@ -1645,6 +1686,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) => {
|
||||
@@ -2187,6 +2229,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
|
||||
}
|
||||
|
||||
@@ -2198,6 +2241,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
if (!result.error) {
|
||||
this.cachedAgentsMessage = null
|
||||
await this.fetchAndSendAgents()
|
||||
this.requirements.clear()
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
@@ -2215,6 +2259,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) {
|
||||
@@ -2759,6 +2808,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(),
|
||||
@@ -2779,6 +2829,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
|
||||
globalConfig: this.cachedGlobalConfig ?? undefined,
|
||||
features: features ?? configFeatures(optimistic as Config),
|
||||
})
|
||||
this.requirements.clear()
|
||||
} finally {
|
||||
this.pending--
|
||||
}
|
||||
@@ -2958,6 +3009,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) {
|
||||
@@ -3047,6 +3099,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(
|
||||
@@ -3355,6 +3408,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(),
|
||||
@@ -3533,6 +3587,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
|
||||
}
|
||||
@@ -3946,6 +4001,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,
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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"
|
||||
@@ -384,11 +385,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}
|
||||
|
||||
@@ -78,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 */
|
||||
@@ -132,6 +133,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||
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()) ??
|
||||
@@ -436,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")
|
||||
@@ -1212,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} />
|
||||
@@ -1320,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">
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+24
@@ -147,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": "الأوامر",
|
||||
|
||||
+24
@@ -148,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",
|
||||
|
||||
+24
@@ -148,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",
|
||||
|
||||
+24
@@ -148,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",
|
||||
|
||||
@@ -154,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",
|
||||
|
||||
+25
@@ -148,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",
|
||||
|
||||
+24
@@ -149,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",
|
||||
|
||||
+25
@@ -1668,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}}",
|
||||
|
||||
+24
@@ -148,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": "コマンド",
|
||||
|
||||
+24
@@ -152,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": "명령어",
|
||||
|
||||
+25
@@ -148,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",
|
||||
|
||||
+24
@@ -151,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",
|
||||
|
||||
+24
@@ -148,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",
|
||||
|
||||
+24
@@ -148,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": "Команды",
|
||||
|
||||
+24
@@ -147,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": "คำสั่ง",
|
||||
|
||||
+24
@@ -148,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",
|
||||
|
||||
+24
@@ -148,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": "Команди",
|
||||
|
||||
+24
@@ -149,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": "命令",
|
||||
|
||||
+24
@@ -149,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>]
|
||||
@@ -277,6 +279,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. */
|
||||
@@ -385,6 +390,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>
|
||||
@@ -413,30 +434,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>
|
||||
|
||||
@@ -21,12 +21,82 @@ import { TurnOutcome } from "../components/shared/TurnOutcome"
|
||||
import { SessionContext } from "../context/session"
|
||||
import { ServerContext } from "../context/server"
|
||||
import { WorktreeModeProvider } from "../context/worktree-mode"
|
||||
import type { Message, Part, QuestionRequest, ReviewComment, SuggestionRequest, TodoItem } from "../types/messages"
|
||||
import type {
|
||||
AgentRequirementResult,
|
||||
Message,
|
||||
Part,
|
||||
QuestionRequest,
|
||||
ReviewComment,
|
||||
SuggestionRequest,
|
||||
TodoItem,
|
||||
} from "../types/messages"
|
||||
import { formatReviewCommentsMarkdown } from "../utils/review-comment-markdown"
|
||||
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
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -153,6 +223,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: () => {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -17,7 +17,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"
|
||||
@@ -349,6 +349,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[]
|
||||
@@ -1044,6 +1053,8 @@ export type ExtensionMessage =
|
||||
| ProvidersLoadedMessage
|
||||
| AgentsLoadedMessage
|
||||
| SkillsLoadedMessage
|
||||
| AgentRequirementsLoadedMessage
|
||||
| AgentRequirementsInvalidatedMessage
|
||||
| CommandsLoadedMessage
|
||||
| AutocompleteSettingsLoadedMessage
|
||||
| ChatCompletionResultMessage
|
||||
|
||||
@@ -211,6 +211,7 @@ export interface OpenConfigFileRequest {
|
||||
|
||||
export interface OpenMarketplacePanelRequest {
|
||||
type: "openMarketplacePanel"
|
||||
directory?: string
|
||||
}
|
||||
|
||||
export interface OpenAgentManagerRequest {
|
||||
@@ -229,6 +230,14 @@ export interface RequestSkillsMessage {
|
||||
type: "requestSkills"
|
||||
}
|
||||
|
||||
export interface RequestAgentRequirementsMessage {
|
||||
type: "requestAgentRequirements"
|
||||
agent: string
|
||||
directory: string
|
||||
sessionID?: string
|
||||
force?: boolean
|
||||
}
|
||||
|
||||
export interface RequestCommandsMessage {
|
||||
type: "requestCommands"
|
||||
}
|
||||
@@ -1166,6 +1175,7 @@ export type WebviewMessage =
|
||||
| CompactRequest
|
||||
| RequestAgentsMessage
|
||||
| RequestSkillsMessage
|
||||
| RequestAgentRequirementsMessage
|
||||
| RequestCommandsMessage
|
||||
| SendCommandRequest
|
||||
| RemoveSkillMessage
|
||||
|
||||
@@ -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,
|
||||
@@ -50,6 +54,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>>
|
||||
@@ -65,6 +70,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 }
|
||||
@@ -78,7 +85,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") {}
|
||||
|
||||
@@ -91,6 +98,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
|
||||
|
||||
@@ -330,6 +338,7 @@ export const layer = Layer.effect(
|
||||
// kilocode_change start - carry metadata as typed fields, never as provider options
|
||||
item.displayName = value.displayName ?? item.displayName
|
||||
item.source = value.source ?? item.source
|
||||
item.requirements = value.requirements ?? item.requirements
|
||||
// kilocode_change end
|
||||
item.options = mergeDeep(item.options, value.options ?? {})
|
||||
item.permission = Permission.merge(item.permission, Permission.fromConfig(value.permission ?? {}))
|
||||
@@ -485,6 +494,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
|
||||
@@ -498,6 +533,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 }
|
||||
@@ -570,6 +607,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
|
||||
|
||||
@@ -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" })
|
||||
@@ -69,6 +70,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)],
|
||||
)
|
||||
@@ -92,6 +94,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})`
|
||||
}
|
||||
@@ -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)),
|
||||
)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Config } from "../../src/config/config"
|
||||
import { Env } from "../../src/env"
|
||||
import { Git } from "../../src/git" // kilocode_change
|
||||
import { RuntimeFlags } from "../../src/effect/runtime-flags"
|
||||
import { MCP } from "../../src/mcp" // kilocode_change
|
||||
import { Plugin } from "../../src/plugin"
|
||||
import { AccountTest } from "../fake/account"
|
||||
import { AuthTest } from "../fake/auth"
|
||||
@@ -43,6 +44,7 @@ const dependencies = Layer.mergeAll(configLayer, pluginLayer).pipe(Layer.provide
|
||||
const agentLayer = Agent.layer.pipe(
|
||||
Layer.provide(AuthTest.empty),
|
||||
Layer.provide(SkillTest.empty),
|
||||
Layer.provide(Layer.mock(MCP.Service)({})), // kilocode_change
|
||||
Layer.provide(provider.layer),
|
||||
Layer.provide(RuntimeFlags.layer({ disableDefaultPlugins: true })),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { AgentRequirementResult } from "@kilocode/sdk/v2"
|
||||
import { formatError as formatStreamError } from "@/cli/cmd/run/session-data"
|
||||
import * as Requirements from "@/kilocode/cli/agent-requirements"
|
||||
|
||||
const dir = "/tmp/agent-requirements-cli"
|
||||
|
||||
function result(input: Partial<AgentRequirementResult> = {}): AgentRequirementResult {
|
||||
return {
|
||||
agent: "demo",
|
||||
directory: dir,
|
||||
enabled: true,
|
||||
state: "ready",
|
||||
skills: [],
|
||||
mcps: [],
|
||||
vscode_extensions: [],
|
||||
...input,
|
||||
}
|
||||
}
|
||||
|
||||
function client(value: AgentRequirementResult): Requirements.Client {
|
||||
return {
|
||||
kilocode: {
|
||||
agentRequirements: async (parameters, options) => {
|
||||
expect(parameters).toEqual({ agent: value.agent, directory: value.directory })
|
||||
expect(options).toEqual({ throwOnError: true })
|
||||
return { data: value }
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function check(value: AgentRequirementResult) {
|
||||
return Requirements.check({ client: client(value), agent: value.agent, directory: value.directory })
|
||||
}
|
||||
|
||||
describe("CLI agent requirements", () => {
|
||||
test("allows disabled requirements", async () => {
|
||||
const output = await check(result({ enabled: false, state: "disabled" }))
|
||||
|
||||
expect(output.ok).toBe(true)
|
||||
})
|
||||
|
||||
test("allows ready requirements", async () => {
|
||||
const output = await check(
|
||||
result({
|
||||
skills: [{ name: "ready-skill", status: "ready" }],
|
||||
mcps: [{ name: "ready-mcp", status: "ready" }],
|
||||
}),
|
||||
)
|
||||
|
||||
expect(output.ok).toBe(true)
|
||||
})
|
||||
|
||||
test("blocks missing skills", async () => {
|
||||
const output = await check(
|
||||
result({
|
||||
state: "blocked",
|
||||
skills: [
|
||||
{ name: "ready-skill", status: "ready" },
|
||||
{ name: "missing-skill", status: "missing" },
|
||||
],
|
||||
}),
|
||||
)
|
||||
|
||||
expect(output.ok).toBe(false)
|
||||
if (output.ok) return
|
||||
expect(output.error).toMatchObject({
|
||||
name: "AgentRequirementError",
|
||||
data: { agent: "demo", state: "blocked" },
|
||||
})
|
||||
expect(output.error.data.skills).toContainEqual({ name: "missing-skill", status: "missing" })
|
||||
})
|
||||
|
||||
test("blocks missing and error MCPs", async () => {
|
||||
const output = await check(
|
||||
result({
|
||||
state: "blocked",
|
||||
mcps: [
|
||||
{ name: "missing-mcp", status: "missing" },
|
||||
{ name: "error-mcp", status: "error", message: "server crashed" },
|
||||
],
|
||||
}),
|
||||
)
|
||||
|
||||
expect(output.ok).toBe(false)
|
||||
if (output.ok) return
|
||||
expect(output.error.data.mcps).toEqual([
|
||||
{ name: "missing-mcp", status: "missing" },
|
||||
{ name: "error-mcp", status: "error", message: "server crashed" },
|
||||
])
|
||||
})
|
||||
|
||||
test("blocks VS Code extension declarations", async () => {
|
||||
const output = await check(
|
||||
result({
|
||||
vscode_extensions: [{ name: "Sample Extension", id: "publisher.extension" }],
|
||||
}),
|
||||
)
|
||||
|
||||
expect(output.ok).toBe(false)
|
||||
if (output.ok) return
|
||||
expect(output.error.data.state).toBe("blocked")
|
||||
expect(output.error.data.vscode_extensions).toEqual([{ name: "Sample Extension", id: "publisher.extension" }])
|
||||
const text = Requirements.format(output.error)
|
||||
expect(text).toContain("Use the Kilo VS Code extension instead")
|
||||
expect(text).not.toContain("Install the required skills and configure or connect the required MCP servers")
|
||||
})
|
||||
|
||||
test("formats grouped actionable guidance", () => {
|
||||
const text = Requirements.format(
|
||||
Requirements.toError(
|
||||
result({
|
||||
state: "error",
|
||||
error: { code: "discovery_failed", message: "skill scan failed" },
|
||||
skills: [
|
||||
{ name: "ready-skill", status: "ready" },
|
||||
{ name: "missing-skill", status: "missing" },
|
||||
{ name: "error-skill", status: "error", message: "skill scan failed" },
|
||||
],
|
||||
mcps: [
|
||||
{ name: "missing-mcp", status: "missing" },
|
||||
{ name: "error-mcp", status: "error", message: "auth failed" },
|
||||
],
|
||||
vscode_extensions: [{ name: "Sample Extension", id: "publisher.extension" }],
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
expect(text).toContain('Agent requirements are not met for "demo".')
|
||||
expect(text).toContain("VS Code extensions:")
|
||||
expect(text).toContain("Sample Extension (publisher.extension)")
|
||||
expect(text).toContain("Skills:")
|
||||
expect(text).toContain("missing-skill (missing)")
|
||||
expect(text).toContain("error-skill (error: skill scan failed)")
|
||||
expect(text).not.toContain("ready-skill (ready)")
|
||||
expect(text).toContain("MCP servers:")
|
||||
expect(text).toContain("missing-mcp (missing)")
|
||||
expect(text).toContain("error-mcp (error: auth failed)")
|
||||
expect(text).toContain("Install the required skills and configure or connect the required MCP servers, then retry.")
|
||||
})
|
||||
|
||||
test("recognizes AgentRequirementError objects", () => {
|
||||
const error = Requirements.toError(result({ state: "blocked" }))
|
||||
|
||||
expect(Requirements.data(error)).toEqual(error.data)
|
||||
expect(Requirements.data({ name: "OtherError", data: error.data })).toBeUndefined()
|
||||
})
|
||||
|
||||
test("stores grouped guidance on AgentRequirementError message", () => {
|
||||
const error = Requirements.toError(
|
||||
result({
|
||||
state: "blocked",
|
||||
skills: [{ name: "missing-skill", status: "missing" }],
|
||||
}),
|
||||
)
|
||||
const text = Requirements.format(error)
|
||||
|
||||
expect(text).toContain('Agent requirements are not met for "demo".')
|
||||
expect(text).toContain("Skills:")
|
||||
expect(text).toContain("missing-skill (missing)")
|
||||
expect(error.data.message).toBe(text)
|
||||
})
|
||||
|
||||
test("formats AgentRequirementError in interactive session errors", () => {
|
||||
const error = Requirements.toError(
|
||||
result({
|
||||
state: "blocked",
|
||||
mcps: [{ name: "missing-mcp", status: "missing" }],
|
||||
}),
|
||||
)
|
||||
const text = Requirements.format(error)
|
||||
|
||||
expect(formatStreamError(error)).toBe(text)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,307 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import { Cause, Effect, Exit } from "effect"
|
||||
import { ConfigAgent } from "@/config/agent"
|
||||
import { ConfigParse } from "@/config/parse"
|
||||
import * as AgentRequirements from "@/kilocode/agent-requirements"
|
||||
import type { MCP } from "@/mcp"
|
||||
import type { Skill } from "@/skill"
|
||||
|
||||
type Agent = {
|
||||
name: string
|
||||
requirements?: unknown
|
||||
}
|
||||
|
||||
type Input = {
|
||||
active?: boolean
|
||||
agents?: Record<string, Agent>
|
||||
skills?: string[]
|
||||
skillError?: string
|
||||
mcp?: Record<string, MCP.Status>
|
||||
mcpError?: string
|
||||
}
|
||||
|
||||
const dir = "/tmp/agent-requirements"
|
||||
|
||||
function services(input: Input = {}) {
|
||||
return {
|
||||
config: {
|
||||
get: () => Effect.succeed({ experimental: input.active ? { agent_requirements: true } : {} }),
|
||||
},
|
||||
agents: {
|
||||
get: (name: string) => Effect.succeed(input.agents?.[name]),
|
||||
},
|
||||
skills: {
|
||||
all: () => {
|
||||
if (input.skillError) return Effect.die(new Error(input.skillError))
|
||||
const skills = (input.skills ?? []).map(
|
||||
(name) => ({ name, location: "test", content: "" }) satisfies Skill.Info,
|
||||
)
|
||||
return Effect.succeed(skills)
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
status: () => {
|
||||
if (input.mcpError) return Effect.die(new Error(input.mcpError))
|
||||
return Effect.succeed(input.mcp ?? {})
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function status(name: string, input: Input) {
|
||||
return Effect.runPromise(AgentRequirements.status({ ...services(input), name, directory: dir }))
|
||||
}
|
||||
|
||||
async function client(value: string | undefined, run: () => Promise<void>) {
|
||||
const prev = process.env.KILO_CLIENT
|
||||
try {
|
||||
if (value === undefined) delete process.env.KILO_CLIENT
|
||||
if (value !== undefined) process.env.KILO_CLIENT = value
|
||||
await run()
|
||||
} finally {
|
||||
if (prev === undefined) delete process.env.KILO_CLIENT
|
||||
if (prev !== undefined) process.env.KILO_CLIENT = prev
|
||||
}
|
||||
}
|
||||
|
||||
describe("agent requirements", () => {
|
||||
test("returns disabled when the experimental flag is absent", async () => {
|
||||
const result = await status("missing", {})
|
||||
|
||||
expect(result).toMatchObject({
|
||||
agent: "missing",
|
||||
directory: dir,
|
||||
enabled: false,
|
||||
state: "disabled",
|
||||
skills: [],
|
||||
mcps: [],
|
||||
vscode_extensions: [],
|
||||
})
|
||||
})
|
||||
|
||||
test("returns ready for agents without requirements", async () => {
|
||||
const result = await status("demo", { active: true, agents: { demo: { name: "demo" } } })
|
||||
|
||||
expect(result).toMatchObject({ agent: "demo", directory: dir, enabled: true, state: "ready" })
|
||||
})
|
||||
|
||||
test("reports unknown agents while requirements are enabled", async () => {
|
||||
const result = await status("missing", { active: true })
|
||||
|
||||
expect(result.state).toBe("error")
|
||||
expect(result.error?.code).toBe("unknown_agent")
|
||||
})
|
||||
|
||||
test("reports discovered and missing skills", async () => {
|
||||
const result = await status("demo", {
|
||||
active: true,
|
||||
agents: { demo: { name: "demo", requirements: { skills: ["ready", "absent"] } } },
|
||||
skills: ["ready"],
|
||||
})
|
||||
|
||||
expect(result.state).toBe("blocked")
|
||||
expect(result.skills).toEqual([
|
||||
{ name: "ready", status: "ready" },
|
||||
{ name: "absent", status: "missing" },
|
||||
])
|
||||
})
|
||||
|
||||
test("accepts non-empty marketplace skill and MCP IDs", async () => {
|
||||
const result = await status("demo", {
|
||||
active: true,
|
||||
agents: {
|
||||
demo: {
|
||||
name: "demo",
|
||||
requirements: { skills: ["skill with space"], mcps: ["mcp/with/slash"] },
|
||||
},
|
||||
},
|
||||
skills: ["skill with space"],
|
||||
mcp: { "mcp/with/slash": { status: "connected" } },
|
||||
})
|
||||
|
||||
expect(result.state).toBe("ready")
|
||||
expect(result.skills).toEqual([{ name: "skill with space", status: "ready" }])
|
||||
expect(result.mcps).toEqual([{ name: "mcp/with/slash", status: "ready" }])
|
||||
})
|
||||
|
||||
test("reports skill discovery failures as errors", async () => {
|
||||
const result = await status("demo", {
|
||||
active: true,
|
||||
agents: { demo: { name: "demo", requirements: { skills: ["needed"] } } },
|
||||
skillError: "skill scan failed",
|
||||
})
|
||||
|
||||
expect(result.state).toBe("error")
|
||||
expect(result.error?.code).toBe("discovery_failed")
|
||||
expect(result.skills[0]?.status).toBe("error")
|
||||
expect(result.skills[0]?.message).toContain("skill scan failed")
|
||||
})
|
||||
|
||||
test("reports malformed and duplicate declarations", async () => {
|
||||
const empty = await status("empty", {
|
||||
active: true,
|
||||
agents: { empty: { name: "empty", requirements: {} } },
|
||||
})
|
||||
const duplicate = await status("duplicate", {
|
||||
active: true,
|
||||
agents: { duplicate: { name: "duplicate", requirements: { skills: ["one", "one"] } } },
|
||||
})
|
||||
const legacy = await status("legacy", {
|
||||
active: true,
|
||||
agents: { legacy: { name: "legacy", requirements: { vscode_extensions: ["publisher.extension"] } } },
|
||||
})
|
||||
|
||||
expect(empty.error?.code).toBe("malformed_declaration")
|
||||
expect(duplicate.error?.code).toBe("malformed_declaration")
|
||||
expect(legacy.error?.code).toBe("malformed_declaration")
|
||||
})
|
||||
|
||||
test("validates VS Code extension objects", async () => {
|
||||
const valid = await status("valid", {
|
||||
active: true,
|
||||
agents: {
|
||||
valid: {
|
||||
name: "valid",
|
||||
requirements: { vscode_extensions: [{ name: "Sample Extension", id: "publisher.extension" }] },
|
||||
},
|
||||
},
|
||||
})
|
||||
const invalid = await status("invalid", {
|
||||
active: true,
|
||||
agents: {
|
||||
invalid: {
|
||||
name: "invalid",
|
||||
requirements: { vscode_extensions: [{ name: " ", id: "publisher.extension" }] },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(valid).toMatchObject({
|
||||
state: "ready",
|
||||
vscode_extensions: [{ name: "Sample Extension", id: "publisher.extension" }],
|
||||
})
|
||||
expect(invalid.state).toBe("error")
|
||||
expect(invalid.error?.code).toBe("malformed_declaration")
|
||||
})
|
||||
|
||||
test("reports MCP connected, missing, and error states", async () => {
|
||||
const result = await status("demo", {
|
||||
active: true,
|
||||
agents: {
|
||||
demo: {
|
||||
name: "demo",
|
||||
requirements: { mcps: ["connected", "disabled", "failed", "registration"] },
|
||||
},
|
||||
},
|
||||
mcp: {
|
||||
connected: { status: "connected" },
|
||||
disabled: { status: "disabled" },
|
||||
failed: { status: "failed", error: "server crashed" },
|
||||
registration: { status: "needs_client_registration", error: "OAuth required" },
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.state).toBe("blocked")
|
||||
expect(result.mcps).toEqual([
|
||||
{ name: "connected", status: "ready" },
|
||||
{ name: "disabled", status: "missing" },
|
||||
{ name: "failed", status: "error", message: "server crashed" },
|
||||
{ name: "registration", status: "error", message: "OAuth required" },
|
||||
])
|
||||
})
|
||||
|
||||
test("reports MCP status service failures", async () => {
|
||||
const result = await status("demo", {
|
||||
active: true,
|
||||
agents: { demo: { name: "demo", requirements: { mcps: ["needed"] } } },
|
||||
mcpError: "status unavailable",
|
||||
})
|
||||
|
||||
expect(result.state).toBe("error")
|
||||
expect(result.error?.code).toBe("mcp_status_failed")
|
||||
expect(result.mcps[0]?.status).toBe("error")
|
||||
expect(result.mcps[0]?.message).toContain("status unavailable")
|
||||
})
|
||||
|
||||
test("guards unmet requirements for all clients", async () => {
|
||||
const missing = {
|
||||
active: true,
|
||||
agents: { demo: { name: "demo", requirements: { skills: ["missing"] } } },
|
||||
}
|
||||
|
||||
await client("cli", async () => {
|
||||
const exit = await Effect.runPromiseExit(
|
||||
AgentRequirements.guard({ ...services(missing), agent: missing.agents.demo, directory: dir }),
|
||||
)
|
||||
expect(Exit.isFailure(exit)).toBe(true)
|
||||
if (Exit.isSuccess(exit)) return
|
||||
const error = Cause.squash(exit.cause)
|
||||
expect(AgentRequirements.BlockedError.isInstance(error)).toBe(true)
|
||||
})
|
||||
|
||||
await client("vscode", async () => {
|
||||
const exit = await Effect.runPromiseExit(
|
||||
AgentRequirements.guard({ ...services(missing), agent: missing.agents.demo, directory: dir }),
|
||||
)
|
||||
expect(Exit.isFailure(exit)).toBe(true)
|
||||
if (Exit.isSuccess(exit)) return
|
||||
const error = Cause.squash(exit.cause)
|
||||
expect(AgentRequirements.BlockedError.isInstance(error)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
test("blocks VS Code extension requirements outside VS Code", async () => {
|
||||
const input = {
|
||||
active: true,
|
||||
agents: {
|
||||
demo: {
|
||||
name: "demo",
|
||||
requirements: { vscode_extensions: [{ name: "Jupyter", id: "ms-toolsai.jupyter" }] },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
await client("cli", async () => {
|
||||
const exit = await Effect.runPromiseExit(
|
||||
AgentRequirements.guard({ ...services(input), agent: input.agents.demo, directory: dir }),
|
||||
)
|
||||
expect(Exit.isFailure(exit)).toBe(true)
|
||||
if (Exit.isSuccess(exit)) return
|
||||
const error = Cause.squash(exit.cause)
|
||||
expect(AgentRequirements.BlockedError.isInstance(error)).toBe(true)
|
||||
})
|
||||
|
||||
await client("vscode", async () => {
|
||||
await Effect.runPromise(AgentRequirements.guard({ ...services(input), agent: input.agents.demo, directory: dir }))
|
||||
})
|
||||
})
|
||||
|
||||
test("allows non-VS Code clients when requirements are ready", async () => {
|
||||
const input = {
|
||||
active: true,
|
||||
agents: { demo: { name: "demo", requirements: { skills: ["ready"], mcps: ["connected"] } } },
|
||||
skills: ["ready"],
|
||||
mcp: { connected: { status: "connected" as const } },
|
||||
}
|
||||
|
||||
await client("cli", async () => {
|
||||
await Effect.runPromise(AgentRequirements.guard({ ...services(input), agent: input.agents.demo, directory: dir }))
|
||||
})
|
||||
})
|
||||
|
||||
test("keeps requirements out of agent options", () => {
|
||||
const agent = ConfigParse.schema(
|
||||
ConfigAgent.Info,
|
||||
{
|
||||
name: "demo",
|
||||
requirements: { skills: ["needed"] },
|
||||
custom: true,
|
||||
},
|
||||
"agent/demo.md",
|
||||
)
|
||||
|
||||
expect(agent.requirements).toEqual({ skills: ["needed"] })
|
||||
expect(agent.options).toEqual({ custom: true })
|
||||
expect(agent.options).not.toHaveProperty("requirements")
|
||||
})
|
||||
})
|
||||
@@ -283,6 +283,19 @@ export const kiloScenarios: Scenario[] = [
|
||||
yield* Effect.promise(() => rm(body, { force: true }))
|
||||
}),
|
||||
),
|
||||
http.protected
|
||||
.get("/kilocode/agent/requirements", "kilocode.agentRequirements")
|
||||
.at((ctx) => ({ path: "/kilocode/agent/requirements?agent=httpapi-agent", headers: ctx.headers() }))
|
||||
.json(200, (body, ctx) => {
|
||||
object(body)
|
||||
check(body.agent === "httpapi-agent", "agent requirements should echo the requested agent")
|
||||
check(body.directory === ctx.directory, "agent requirements should use the routed workspace directory")
|
||||
check(body.enabled === false, "agent requirements should report disabled when the experiment is off")
|
||||
check(body.state === "disabled", "agent requirements should return the disabled state")
|
||||
array(body.skills)
|
||||
array(body.mcps)
|
||||
array(body.vscode_extensions)
|
||||
}),
|
||||
http.protected
|
||||
.post("/kilocode/skill/remove", "kilocode.removeSkill")
|
||||
.mutating()
|
||||
|
||||
@@ -146,6 +146,8 @@ import type {
|
||||
KiloCloudSessionImportResponses,
|
||||
KiloCloudSessionsErrors,
|
||||
KiloCloudSessionsResponses,
|
||||
KilocodeAgentRequirementsErrors,
|
||||
KilocodeAgentRequirementsResponses,
|
||||
KilocodeHeapSnapshotErrors,
|
||||
KilocodeHeapSnapshotResponses,
|
||||
KilocodeNotebookListErrors,
|
||||
@@ -7488,6 +7490,42 @@ export class SessionImport extends HeyApiClient {
|
||||
}
|
||||
|
||||
export class Kilocode extends HeyApiClient {
|
||||
/**
|
||||
* Check agent requirements
|
||||
*
|
||||
* Check whether the selected agent's requirements are available in the request directory.
|
||||
*/
|
||||
public agentRequirements<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
agent: string
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "query", key: "directory" },
|
||||
{ in: "query", key: "workspace" },
|
||||
{ in: "query", key: "agent" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).get<
|
||||
KilocodeAgentRequirementsResponses,
|
||||
KilocodeAgentRequirementsErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
url: "/kilocode/agent/requirements",
|
||||
...options,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a skill
|
||||
*
|
||||
|
||||
@@ -448,6 +448,30 @@ export type ApiError = {
|
||||
}
|
||||
}
|
||||
|
||||
export type AgentRequirementError = {
|
||||
name: "AgentRequirementError"
|
||||
data: {
|
||||
message: string
|
||||
agent: string
|
||||
directory: string
|
||||
state: "blocked" | "error"
|
||||
skills: Array<{
|
||||
name: string
|
||||
status: "ready" | "missing" | "error"
|
||||
message?: string
|
||||
}>
|
||||
mcps: Array<{
|
||||
name: string
|
||||
status: "ready" | "missing" | "error"
|
||||
message?: string
|
||||
}>
|
||||
vscode_extensions: Array<{
|
||||
name: string
|
||||
id: string
|
||||
}>
|
||||
}
|
||||
}
|
||||
|
||||
export type Todo = {
|
||||
/**
|
||||
* Brief description of the task
|
||||
@@ -1277,6 +1301,14 @@ export type AgentConfig = {
|
||||
steps?: number
|
||||
maxSteps?: number
|
||||
permission?: PermissionConfig
|
||||
requirements?: {
|
||||
skills?: Array<string>
|
||||
mcps?: Array<string>
|
||||
vscode_extensions?: Array<{
|
||||
name: string
|
||||
id: string
|
||||
}>
|
||||
}
|
||||
[key: string]:
|
||||
| unknown
|
||||
| string
|
||||
@@ -1301,6 +1333,14 @@ export type AgentConfig = {
|
||||
| "info"
|
||||
| number
|
||||
| PermissionConfig
|
||||
| {
|
||||
skills?: Array<string>
|
||||
mcps?: Array<string>
|
||||
vscode_extensions?: Array<{
|
||||
name: string
|
||||
id: string
|
||||
}>
|
||||
}
|
||||
| undefined
|
||||
}
|
||||
|
||||
@@ -1610,6 +1650,7 @@ export type Config = {
|
||||
disable_paste_summary?: boolean
|
||||
batch_tool?: boolean
|
||||
codebase_search?: boolean
|
||||
agent_requirements?: boolean
|
||||
native_notebook_tools?: boolean
|
||||
speech_to_text_model?: string
|
||||
openTelemetry?: boolean
|
||||
@@ -1996,6 +2037,14 @@ export type Agent = {
|
||||
options: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
requirements?: {
|
||||
skills?: Array<string>
|
||||
mcps?: Array<string>
|
||||
vscode_extensions?: Array<{
|
||||
name: string
|
||||
id: string
|
||||
}>
|
||||
}
|
||||
steps?: number
|
||||
}
|
||||
|
||||
@@ -2494,6 +2543,31 @@ export type EffectHttpApiErrorServiceUnavailable = {
|
||||
_tag: "ServiceUnavailable"
|
||||
}
|
||||
|
||||
export type AgentRequirementResult = {
|
||||
agent: string
|
||||
directory: string
|
||||
enabled: boolean
|
||||
state: "disabled" | "ready" | "blocked" | "error"
|
||||
skills: Array<{
|
||||
name: string
|
||||
status: "ready" | "missing" | "error"
|
||||
message?: string
|
||||
}>
|
||||
mcps: Array<{
|
||||
name: string
|
||||
status: "ready" | "missing" | "error"
|
||||
message?: string
|
||||
}>
|
||||
vscode_extensions: Array<{
|
||||
name: string
|
||||
id: string
|
||||
}>
|
||||
error?: {
|
||||
code: "unknown_agent" | "malformed_declaration" | "discovery_failed" | "mcp_status_failed"
|
||||
message: string
|
||||
}
|
||||
}
|
||||
|
||||
export type NotebookOutput = {
|
||||
mime: string
|
||||
text?: string
|
||||
@@ -3478,6 +3552,7 @@ export type EventSessionError = {
|
||||
| StructuredOutputError
|
||||
| ContextOverflowError
|
||||
| ApiError
|
||||
| AgentRequirementError
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10740,6 +10815,36 @@ export type KilocodeHeapSnapshotResponses = {
|
||||
|
||||
export type KilocodeHeapSnapshotResponse = KilocodeHeapSnapshotResponses[keyof KilocodeHeapSnapshotResponses]
|
||||
|
||||
export type KilocodeAgentRequirementsData = {
|
||||
body?: never
|
||||
path?: never
|
||||
query: {
|
||||
directory?: string
|
||||
workspace?: string
|
||||
agent: string
|
||||
}
|
||||
url: "/kilocode/agent/requirements"
|
||||
}
|
||||
|
||||
export type KilocodeAgentRequirementsErrors = {
|
||||
/**
|
||||
* Bad request
|
||||
*/
|
||||
400: BadRequestError
|
||||
}
|
||||
|
||||
export type KilocodeAgentRequirementsError = KilocodeAgentRequirementsErrors[keyof KilocodeAgentRequirementsErrors]
|
||||
|
||||
export type KilocodeAgentRequirementsResponses = {
|
||||
/**
|
||||
* Agent requirement status
|
||||
*/
|
||||
200: AgentRequirementResult
|
||||
}
|
||||
|
||||
export type KilocodeAgentRequirementsResponse =
|
||||
KilocodeAgentRequirementsResponses[keyof KilocodeAgentRequirementsResponses]
|
||||
|
||||
export type KilocodeRemoveSkillData = {
|
||||
body?: {
|
||||
location: string
|
||||
|
||||
@@ -14654,6 +14654,68 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/kilocode/agent/requirements": {
|
||||
"get": {
|
||||
"tags": ["kilocode"],
|
||||
"operationId": "kilocode.agentRequirements",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "directory",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "workspace",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "agent",
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Agent requirement status",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AgentRequirementResult"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/BadRequestError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "Check whether the selected agent's requirements are available in the request directory.",
|
||||
"summary": "Check agent requirements",
|
||||
"x-codeSamples": [
|
||||
{
|
||||
"lang": "js",
|
||||
"source": "import { createKiloClient } from \"@kilocode/sdk\"\n\nconst client = createKiloClient()\nawait client.kilocode.agentRequirements({\n ...\n})"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/kilocode/skill/remove": {
|
||||
"post": {
|
||||
"tags": ["kilocode"],
|
||||
@@ -18797,6 +18859,99 @@
|
||||
"required": ["name", "data"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"AgentRequirementError": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"enum": ["AgentRequirementError"]
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"agent": {
|
||||
"type": "string"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": ["blocked", "error"]
|
||||
},
|
||||
"skills": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["ready", "missing", "error"]
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name", "status"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"mcps": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["ready", "missing", "error"]
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name", "status"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"vscode_extensions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "\\S"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
|
||||
}
|
||||
},
|
||||
"required": ["name", "id"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["message", "agent", "directory", "state", "skills", "mcps", "vscode_extensions"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["name", "data"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"Todo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -21342,6 +21497,58 @@
|
||||
},
|
||||
"permission": {
|
||||
"$ref": "#/components/schemas/PermissionConfig"
|
||||
},
|
||||
"requirements": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"skills": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "\\S"
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 20
|
||||
},
|
||||
"mcps": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "\\S"
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 20
|
||||
},
|
||||
"vscode_extensions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "\\S"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
|
||||
}
|
||||
},
|
||||
"required": ["name", "id"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 20
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": {}
|
||||
@@ -22241,6 +22448,9 @@
|
||||
"codebase_search": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"agent_requirements": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"native_notebook_tools": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -23396,6 +23606,58 @@
|
||||
"options": {
|
||||
"type": "object"
|
||||
},
|
||||
"requirements": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"skills": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "\\S"
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 20
|
||||
},
|
||||
"mcps": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "\\S"
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 20
|
||||
},
|
||||
"vscode_extensions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "\\S"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
|
||||
}
|
||||
},
|
||||
"required": ["name", "id"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 20
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"steps": {
|
||||
"type": "number"
|
||||
}
|
||||
@@ -24939,6 +25201,102 @@
|
||||
"required": ["_tag"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"AgentRequirementResult": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"agent": {
|
||||
"type": "string"
|
||||
},
|
||||
"directory": {
|
||||
"type": "string"
|
||||
},
|
||||
"enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": ["disabled", "ready", "blocked", "error"]
|
||||
},
|
||||
"skills": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["ready", "missing", "error"]
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name", "status"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"mcps": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["ready", "missing", "error"]
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["name", "status"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"vscode_extensions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "\\S"
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 128,
|
||||
"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"
|
||||
}
|
||||
},
|
||||
"required": ["name", "id"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"enum": ["unknown_agent", "malformed_declaration", "discovery_failed", "mcp_status_failed"]
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": ["code", "message"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["agent", "directory", "enabled", "state", "skills", "mcps", "vscode_extensions"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"NotebookOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -28146,6 +28504,9 @@
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/APIError"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/AgentRequirementError"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user