Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
955e18f
feat(vscode): add workflow management and slash command support
Githubguy132010 Feb 24, 2026
2de2bb3
fix(vscode): validate workflow names to prevent path traversal
Githubguy132010 Feb 24, 2026
a569f1e
fix(vscode): harden workflow name validation, prevent overwrite, simp…
Githubguy132010 Feb 24, 2026
8506487
fix(vscode): add confirmation dialog before workflow deletion
Githubguy132010 Feb 24, 2026
80e358f
Merge branch 'main' into feat/vscode-workflows-and-slash-commands
Githubguy132010 Feb 25, 2026
5efee6f
fix(vscode): scope workflow file ops and cloud preview slash commands
Githubguy132010 Feb 25, 2026
9ae78fa
Merge branch 'main' into feat/vscode-workflows-and-slash-commands
Githubguy132010 Feb 26, 2026
bf68b6d
fix(vscode): only run slash commands when command exists
Githubguy132010 Feb 26, 2026
28ea8dd
Merge branch 'main' into feat/vscode-workflows-and-slash-commands
Githubguy132010 Feb 27, 2026
73835cf
fix(vscode): handle cloud import and workflow lookup errors
Githubguy132010 Feb 27, 2026
e066930
Harden command validation and command loading fallback
Githubguy132010 Feb 27, 2026
3c8488b
fix(vscode): handle command load and import failures better
Githubguy132010 Feb 27, 2026
839c7b2
fix(vscode): type cloud import failure session id and optimistic slas…
Githubguy132010 Feb 27, 2026
b4ae76d
Update packages/kilo-vscode/webview-ui/src/hooks/useSlashCommand.ts
Githubguy132010 Feb 27, 2026
d8afb75
Prevent slash commands from dropping image attachments
Githubguy132010 Feb 27, 2026
1ca38ac
fix(vscode): set loading for sendCommand and cleanup optimistic messa…
Githubguy132010 Feb 27, 2026
306d46f
Fix slash command, workflow scope, and command loading edge cases
Githubguy132010 Feb 27, 2026
ddeaeb8
fix(vscode): workflow scope selection and session-aware command errors
Githubguy132010 Feb 27, 2026
d069c98
fix(vscode): remove dead sid guard and bound commands retries
Githubguy132010 Feb 27, 2026
2053b8f
fix: address open review issues in PR #6260
kilo-code-bot[bot] Feb 27, 2026
8a58712
fix: address follow-up review issues
kilo-code-bot[bot] Feb 27, 2026
2140c55
fix: address further review issues
kilo-code-bot[bot] Feb 27, 2026
06c5f45
fix: add sessionID to handleSendCommand error response
kilo-code-bot[bot] Feb 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions packages/kilo-vscode/src/KiloProvider.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as path from "path"
import * as fs from "fs"
import * as vscode from "vscode"
import { z } from "zod"
import {
Expand Down Expand Up @@ -38,6 +39,8 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
private cachedAgentsMessage: unknown = null
/** Cached configLoaded payload so requestConfig can be served before httpClient is ready */
private cachedConfigMessage: unknown = null
/** Cached commandsLoaded payload so requestCommands can be served before httpClient is ready */
private cachedCommandsMessage: unknown = null
/** Cached notificationsLoaded payload */
private cachedNotificationsMessage: unknown = null

Expand Down Expand Up @@ -357,6 +360,25 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.handleOpenFile(message.filePath, message.line, message.column)
}
break
case "sendCommand":
await this.handleSendCommand(
message.command,
message.arguments,
message.sessionID,
message.providerID,
message.modelID,
message.agent,
)
break
case "openWorkflowFile":
await this.handleOpenWorkflowFile(message.name)
break
case "createWorkflowFile":
await this.handleCreateWorkflowFile(message.name)
break
case "deleteWorkflowFile":
await this.handleDeleteWorkflowFile(message.name)
break
case "requestProviders":
this.fetchAndSendProviders().catch((e) => console.error("[Kilo New] fetchAndSendProviders failed:", e))
break
Expand All @@ -375,6 +397,9 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
case "requestConfig":
this.fetchAndSendConfig().catch((e) => console.error("[Kilo New] fetchAndSendConfig failed:", e))
break
case "requestCommands":
this.fetchAndSendCommands().catch((e) => console.error("[Kilo New] fetchAndSendCommands failed:", e))
break
case "updateConfig":
await this.handleUpdateConfig(message.config)
break
Expand Down Expand Up @@ -591,6 +616,7 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
this.fetchAndSendProviders(),
this.fetchAndSendAgents(),
this.fetchAndSendConfig(),
this.fetchAndSendCommands(),
this.fetchAndSendNotifications(),
])
this.sendNotificationSettings()
Expand Down Expand Up @@ -967,6 +993,37 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
}
}

/**
* Fetch available commands (workflows, skills, MCP prompts) and send to webview.
*/
private async fetchAndSendCommands(): Promise<void> {
if (!this.httpClient) {
if (this.cachedCommandsMessage) {
this.postMessage(this.cachedCommandsMessage)
}
return
}

try {
const workspaceDir = this.getWorkspaceDirectory()
const commands = await this.httpClient.listCommands(workspaceDir)

const message = {
type: "commandsLoaded",
commands: commands.map((cmd) => ({
name: cmd.name,
description: cmd.description,
source: cmd.source,
hints: cmd.hints,
})),
}
this.cachedCommandsMessage = message
this.postMessage(message)
} catch (error) {
console.error("[Kilo New] KiloProvider: Failed to fetch commands:", error)
Comment thread
Githubguy132010 marked this conversation as resolved.
}
}

/**
* Fetch Kilo news/notifications and send to webview.
* Uses the cached message pattern so the webview gets data immediately on refresh.
Expand Down Expand Up @@ -1225,6 +1282,127 @@ export class KiloProvider implements vscode.WebviewViewProvider, TelemetryProper
}
}

/**
* Handle command execution request from the webview.
* Routes "/command args" to the session command endpoint.
*/
private async handleSendCommand(
command: string,
args: string,
sessionID?: string,
providerID?: string,
modelID?: string,
agent?: string,
): Promise<void> {
if (!this.httpClient) {
this.postMessage({ type: "error", message: "Not connected to CLI backend" })
return
}

try {
const workspaceDir = this.getWorkspaceDirectory(sessionID || this.currentSession?.id)

// Create session if needed
if (!sessionID && !this.currentSession) {
this.currentSession = await this.httpClient.createSession(workspaceDir)
this.trackedSessionIds.add(this.currentSession.id)
this.postMessage({
type: "sessionCreated",
session: this.sessionToWebview(this.currentSession),
})
}

const target = sessionID || this.currentSession?.id
if (!target) {
throw new Error("No session available")
}

const model = providerID && modelID ? `${providerID}/${modelID}` : undefined

await this.httpClient.executeCommand(target, command, args, workspaceDir, { agent, model })
Comment thread
Githubguy132010 marked this conversation as resolved.
Outdated
} catch (error) {
console.error("[Kilo New] KiloProvider: Failed to execute command:", error)
this.postMessage({
type: "error",
message: error instanceof Error ? error.message : "Failed to execute command",
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: handleSendCommand error response is missing the sessionID property, unlike handleImportAndSendCommand (line 1512)

The webview's error handler in session.tsx (line 399–403) checks message.sessionID to decide whether to clear loading state and reset pendingSessionKey. Without sessionID on this error message, the !message.sessionID fallback always evaluates to true, which means:

  1. Loading state is cleared even if the error is for a different session than the one currently displayed.
  2. pendingSessionKey is always reset, even when the error belongs to a background session.

This is inconsistent with handleImportAndSendCommand at line 1509–1513 which correctly includes sessionID: session.id.

Suggested change
})
this.postMessage({
type: "error",
message: session ? `[sessionID:${session}] ${base}` : base,
sessionID: session,
})

}
}

/**
* Resolve the project-level workflows directory.
*/
private getWorkflowsDir(): string {
const workspaceDir = this.getWorkspaceDirectory()
return path.join(workspaceDir, ".kilocode", "workflows")
Comment thread
Githubguy132010 marked this conversation as resolved.
Outdated
}

/**
* Open a workflow file in the editor.
*/
private async handleOpenWorkflowFile(name: string): Promise<void> {
Comment thread
Githubguy132010 marked this conversation as resolved.
Outdated
const filePath = path.join(this.getWorkflowsDir(), `${name}.md`)
try {
const uri = vscode.Uri.file(filePath)
const doc = await vscode.workspace.openTextDocument(uri)
await vscode.window.showTextDocument(doc)
} catch (error) {
console.error("[Kilo New] KiloProvider: Failed to open workflow file:", error)
this.postMessage({
type: "error",
message: error instanceof Error ? error.message : "Failed to open workflow file",
})
}
}

/**
* Create a new workflow file and open it in the editor.
*/
private async handleCreateWorkflowFile(name: string): Promise<void> {
const dir = this.getWorkflowsDir()
const filePath = path.join(dir, `${name}.md`)

try {
// Ensure directory exists
await fs.promises.mkdir(dir, { recursive: true })

// Create template content
const template = `# ${name}\n\nDescribe your workflow here.\n`
await fs.promises.writeFile(filePath, template, "utf-8")
Comment thread
Githubguy132010 marked this conversation as resolved.
Outdated

// Open in editor
const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(filePath))
await vscode.window.showTextDocument(doc)

// Refresh commands so the new workflow appears
await this.fetchAndSendCommands()
} catch (error) {
console.error("[Kilo New] KiloProvider: Failed to create workflow file:", error)
this.postMessage({
type: "error",
message: error instanceof Error ? error.message : "Failed to create workflow file",
})
}
}

/**
* Delete a workflow file and refresh the commands list.
*/
private async handleDeleteWorkflowFile(name: string): Promise<void> {
const filePath = path.join(this.getWorkflowsDir(), `${name}.md`)
Comment thread
Githubguy132010 marked this conversation as resolved.
Outdated

try {
await fs.promises.unlink(filePath)
Comment thread
Githubguy132010 marked this conversation as resolved.
Outdated
await this.fetchAndSendCommands()
} catch (error) {
console.error("[Kilo New] KiloProvider: Failed to delete workflow file:", error)
this.postMessage({
type: "error",
message: error instanceof Error ? error.message : "Failed to delete workflow file",
})
}
}

/**
* Handle sending a message from the webview.
*/
Expand Down
35 changes: 35 additions & 0 deletions packages/kilo-vscode/src/services/cli-backend/http-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
CloudSessionsResponse,
CloudSessionData,
EditorContext,
CommandInfo,
} from "./types"
import { extractHttpErrorMessage, parseSSEDataLine } from "./http-utils"

Expand Down Expand Up @@ -209,6 +210,40 @@ export class HttpClient {
return this.request<Config>("PATCH", "/global/config", config)
}

// ============================================
// Command/Workflow Methods
// ============================================

/**
* List all available commands (includes workflows, skills, MCP prompts).
*/
async listCommands(directory: string): Promise<CommandInfo[]> {
return this.request<CommandInfo[]>("GET", "/command", undefined, { directory })
}

/**
* Execute a command (workflow) in a session.
*/
async executeCommand(
sessionId: string,
command: string,
args: string,
directory: string,
options?: { agent?: string; model?: string },
): Promise<void> {
await this.request<void>(
"POST",
`/session/${sessionId}/command`,
{
command,
arguments: args,
agent: options?.agent,
model: options?.model,
},
{ directory, allowEmpty: true },
)
}

// ============================================
// Messaging Methods
// ============================================
Expand Down
8 changes: 8 additions & 0 deletions packages/kilo-vscode/src/services/cli-backend/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,14 @@ export interface CommandConfig {
description?: string
}

/** Command/workflow info returned by GET /command */
export interface CommandInfo {
name: string
description?: string
source?: "command" | "mcp" | "skill"
hints: string[]
}

/** Skills configuration */
export interface SkillsConfig {
paths?: string[]
Expand Down
17 changes: 10 additions & 7 deletions packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { VSCodeProvider, useVSCode } from "../src/context/vscode"
import { ServerProvider } from "../src/context/server"
import { ProviderProvider } from "../src/context/provider"
import { ConfigProvider } from "../src/context/config"
import { CommandsProvider } from "../src/context/commands"
import { SessionProvider, useSession } from "../src/context/session"
import { WorktreeModeProvider } from "../src/context/worktree-mode"
import { ChatView } from "../src/components/chat"
Expand Down Expand Up @@ -2157,13 +2158,15 @@ export const AgentManagerApp: Component = () => {
<CodeComponentProvider component={Code}>
<ProviderProvider>
<ConfigProvider>
<SessionProvider>
<WorktreeModeProvider>
<DataBridge>
<AgentManagerContent />
</DataBridge>
</WorktreeModeProvider>
</SessionProvider>
<CommandsProvider>
<SessionProvider>
<WorktreeModeProvider>
<DataBridge>
<AgentManagerContent />
</DataBridge>
</WorktreeModeProvider>
</SessionProvider>
</CommandsProvider>
</ConfigProvider>
</ProviderProvider>
</CodeComponentProvider>
Expand Down
17 changes: 10 additions & 7 deletions packages/kilo-vscode/webview-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { VSCodeProvider, useVSCode } from "./context/vscode"
import { ServerProvider, useServer } from "./context/server"
import { ProviderProvider, useProvider } from "./context/provider"
import { ConfigProvider } from "./context/config"
import { CommandsProvider } from "./context/commands"
import { SessionProvider, useSession } from "./context/session"
import { LanguageProvider } from "./context/language"
import { ChatView } from "./components/chat"
Expand Down Expand Up @@ -230,13 +231,15 @@ const App: Component = () => {
<CodeComponentProvider component={Code}>
<ProviderProvider>
<ConfigProvider>
<NotificationsProvider>
<SessionProvider>
<DataBridge>
<AppContent />
</DataBridge>
</SessionProvider>
</NotificationsProvider>
<CommandsProvider>
<NotificationsProvider>
<SessionProvider>
<DataBridge>
<AppContent />
</DataBridge>
</SessionProvider>
</NotificationsProvider>
</CommandsProvider>
</ConfigProvider>
</ProviderProvider>
</CodeComponentProvider>
Expand Down
Loading
Loading