Skip to content
8 changes: 8 additions & 0 deletions .changeset/fix-workflow-tool-display.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"kilo-code": patch


Fix workflow tool display bug - always send tool message to webview even when auto-execute is enabled


When AUTO_EXECUTE_WORKFLOW experiment is enabled, the workflow tool message was created in the backend but never sent to the webview. This caused the workflow UI to not appear. The fix ensures that the tool message is always sent to the webview via task.ask() regardless of the auto-execute setting, so users can see what workflow is being executed.
7 changes: 7 additions & 0 deletions .changeset/fix-workflow-translation-key.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"kilo-code": patch
---

Fix translation key mismatch for workflow access experimental setting

Changed translation key from RUN_SLASH_COMMAND to AUTO_EXECUTE_WORKFLOW to match the experiment constant. Updated name from "Enable model-initiated slash commands" to "Enable workflow access" and description to better reflect the feature's actual behavior of accessing workflow content without approval.
5 changes: 5 additions & 0 deletions .changeset/remove-workflow-discovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Remove WORKFLOW_DISCOVERY experiment and consolidate to AUTO_EXECUTE_WORKFLOW
5 changes: 5 additions & 0 deletions .changeset/workflow-auto-experiment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Separate workflow discovery from auto-execution. Workflow discovery is now always available, while auto-execution without approval is controlled by the `autoExecuteWorkflow` experiment flag.
5 changes: 5 additions & 0 deletions .changeset/workflow-discovery-feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": minor
---

Add automatic workflow discovery feature for Kilo agent to discover available workflows from global and workspace directories without manual directory exploration.
5 changes: 5 additions & 0 deletions .changeset/workflow-execution-tool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---

Implement workflow execution tool for Kilo Code. Added workflow discovery service in `.kilocode/workflows/`, adapted RunSlashCommandTool to use workflows instead of commands, and created sample workflows for common tasks.
4 changes: 2 additions & 2 deletions packages/types/src/experiment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const experimentIds = [
"multiFileApplyDiff",
"preventFocusDisruption",
"imageGeneration",
"runSlashCommand",
"autoExecuteWorkflow",
"multipleNativeToolCalls",
] as const

Expand All @@ -31,7 +31,7 @@ export const experimentsSchema = z.object({
multiFileApplyDiff: z.boolean().optional(),
preventFocusDisruption: z.boolean().optional(),
imageGeneration: z.boolean().optional(),
runSlashCommand: z.boolean().optional(),
autoExecuteWorkflow: z.boolean().optional(),
multipleNativeToolCalls: z.boolean().optional(),
})

Expand Down
30 changes: 29 additions & 1 deletion src/core/environment/getEnvironmentDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import { getGitStatus } from "../../utils/git"

import { Task } from "../task/Task"
import { formatReminderSection } from "./reminder"
// kilocode_change start
import { getWorkflowsForEnvironment } from "../workflow-discovery/getWorkflowsForEnvironment"
import { refreshWorkflowToggles } from "../context/instructions/workflows"
// kilocode_change end

// kilocode_change start
import { OpenRouterHandler } from "../../api/providers/openrouter"
Expand Down Expand Up @@ -378,5 +382,29 @@ export async function getEnvironmentDetails(cline: Task, includeFileDetails: boo
? state.apiConfiguration.todoListEnabled
: true
const reminderSection = todoListEnabled ? formatReminderSection(cline.todoList) : ""
return `<environment_details>\n${details.trim()}\n${reminderSection}\n</environment_details>`

// kilocode_change start
// Add workflow discovery information if experiment is enabled
let localWorkflowToggles: Record<string, boolean> = {}
let globalWorkflowToggles: Record<string, boolean> = {}

if (clineProvider?.context) {
const toggles = await refreshWorkflowToggles(clineProvider.context, cline.cwd)
localWorkflowToggles = toggles.localWorkflowToggles
globalWorkflowToggles = toggles.globalWorkflowToggles
}
// kilocode_change end

const enabledWorkflows = new Map<string, boolean>()
Object.entries(localWorkflowToggles || {}).forEach(([path, enabled]) => {
enabledWorkflows.set(path, enabled)
})
Object.entries(globalWorkflowToggles || {}).forEach(([path, enabled]) => {
enabledWorkflows.set(path, enabled)
})

const workflowSection = await getWorkflowsForEnvironment(cline.cwd, experiments, enabledWorkflows)
// kilocode_change end

return `<environment_details>\n${details.trim()}\n${reminderSection}\n${workflowSection}\n</environment_details>`
}
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ describe("filterNativeToolsForMode", () => {
toolsWithSlashCommand,
"code",
[codeMode],
{ runSlashCommand: false },
{ autoExecuteWorkflow: false },
undefined,
{},
undefined,
Expand Down
4 changes: 2 additions & 2 deletions src/core/prompts/tools/filter-tools-for-mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ export function filterNativeToolsForMode(
}

// Conditionally exclude run_slash_command if experiment is not enabled
if (!experiments?.runSlashCommand) {
if (!experiments?.autoExecuteWorkflow) {
allowedToolNames.delete("run_slash_command")
}

Expand Down Expand Up @@ -404,7 +404,7 @@ export function isToolAllowedInMode(
return experiments?.imageGeneration === true
}
if (toolName === "run_slash_command") {
return experiments?.runSlashCommand === true
return experiments?.autoExecuteWorkflow === true
}
return true
}
Expand Down
2 changes: 1 addition & 1 deletion src/core/prompts/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export function getToolDescriptionsForMode(
}

// Conditionally exclude run_slash_command if experiment is not enabled
if (!experiments?.runSlashCommand) {
if (!experiments?.autoExecuteWorkflow) {
tools.delete("run_slash_command")
}

Expand Down
8 changes: 5 additions & 3 deletions src/core/prompts/tools/native-tools/run_slash_command.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// kilocode_change start
import type OpenAI from "openai"

const RUN_SLASH_COMMAND_DESCRIPTION = `Execute a slash command to get specific instructions or content. Slash commands are predefined templates that provide detailed guidance for common tasks.`
const RUN_SLASH_COMMAND_DESCRIPTION = `Execute a workflow to get specific instructions or content. Workflows are predefined templates stored in .kilocode/workflows/ that provide detailed guidance for common tasks. Always shows workflow content; requires user approval unless auto-execute experiment is enabled.`

const COMMAND_PARAMETER_DESCRIPTION = `Name of the slash command to run (e.g., init, test, deploy)`
const COMMAND_PARAMETER_DESCRIPTION = `Name of the workflow to execute (without .md extension)`

const ARGS_PARAMETER_DESCRIPTION = `Optional additional context or arguments for the command`
const ARGS_PARAMETER_DESCRIPTION = `Optional additional arguments or context to pass to the workflow`
// kilocode_change end

export default {
type: "function",
Expand Down
114 changes: 73 additions & 41 deletions src/core/tools/RunSlashCommandTool.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// kilocode_change start
import { Task } from "../task/Task"
import { formatResponse } from "../prompts/responses"
import { getCommand, getCommandNames } from "../../services/command/commands"
import { getWorkflow, getWorkflowNames } from "../../services/workflow/workflows"
import { EXPERIMENT_IDS, experiments } from "../../shared/experiments"
import { BaseTool, ToolCallbacks } from "./BaseTool"
import type { ToolUse } from "../../shared/tools"
// kilocode_change end

interface RunSlashCommandParams {
command: string
Expand All @@ -24,23 +26,14 @@ export class RunSlashCommandTool extends BaseTool<"run_slash_command"> {
const { command: commandName, args } = params
const { askApproval, handleError, pushToolResult, toolProtocol } = callbacks

// Check if run slash command experiment is enabled
// Check if auto-execute workflow experiment is enabled
const provider = task.providerRef.deref()
const state = await provider?.getState()
const isRunSlashCommandEnabled = experiments.isEnabled(
const isAutoExecuteEnabled = experiments.isEnabled(
state?.experiments ?? {},
EXPERIMENT_IDS.RUN_SLASH_COMMAND,
EXPERIMENT_IDS.AUTO_EXECUTE_WORKFLOW,
)

if (!isRunSlashCommandEnabled) {
pushToolResult(
formatResponse.toolError(
"Run slash command is an experimental feature that must be enabled in settings. Please enable 'Run Slash Command' in the Experimental Settings section.",
),
)
return
}

try {
if (!commandName) {
task.consecutiveMistakeCount++
Expand All @@ -52,17 +45,17 @@ export class RunSlashCommandTool extends BaseTool<"run_slash_command"> {

task.consecutiveMistakeCount = 0

// Get the command from the commands service
const command = await getCommand(task.cwd, commandName)
// Get the workflow from the workflows service
const workflow = await getWorkflow(task.cwd, commandName)

if (!command) {
// Get available commands for error message
const availableCommands = await getCommandNames(task.cwd)
if (!workflow) {
// Get available workflows for error message
const availableWorkflows = await getWorkflowNames(task.cwd)
task.recordToolError("run_slash_command")
task.didToolFailInCurrentTurn = true
pushToolResult(
formatResponse.toolError(
`Command '${commandName}' not found. Available commands: ${availableCommands.join(", ") || "(none)"}`,
`Workflow '${commandName}' not found. Available workflows: ${availableWorkflows.join(", ") || "(none)"}`,
),
)
return
Expand All @@ -72,35 +65,46 @@ export class RunSlashCommandTool extends BaseTool<"run_slash_command"> {
tool: "runSlashCommand",
command: commandName,
args: args,
source: command.source,
description: command.description,
source: workflow.source,
description: workflow.description,
})

const didApprove = await askApproval("tool", toolMessage)

if (!didApprove) {
return
// kilocode_change: Fix workflow display bug - always send tool message to webview even when auto-execute is enabled
// This ensures that user can see what workflow is being executed
// If auto-execute is disabled, wait for approval
// If auto-execute is enabled, still send message but don't wait for approval
if (!isAutoExecuteEnabled) {
const didApprove = await askApproval("tool", toolMessage)
if (!didApprove) {
return
}
} else {
// kilocode_change: When auto-execute is enabled, send message to webview without waiting for approval
// This ensures that workflow tool UI is displayed even when auto-executing
await task.ask("tool", toolMessage, false).catch(() => {})
}
// kilocode_change end

// Build the result message
let result = `Command: /${commandName}`
// kilocode_change: Update message text with complete tool result content
// Build the result message with complete workflow data
let result = `Workflow: /${commandName}`

if (command.description) {
result += `\nDescription: ${command.description}`
if (workflow.description) {
result += `\nDescription: ${workflow.description}`
}

if (command.argumentHint) {
result += `\nArgument hint: ${command.argumentHint}`
if (workflow.arguments) {
result += `\nArguments: ${workflow.arguments}`
}

if (args) {
result += `\nProvided arguments: ${args}`
}

result += `\nSource: ${command.source}`
result += `\n\n--- Command Content ---\n\n${command.content}`
result += `\nSource: ${workflow.source}`
result += `\n\n--- Workflow Content ---\n\n${workflow.content}`

// Return the command content as the tool result
// Return the workflow content as the tool result
pushToolResult(result)
} catch (error) {
await handleError("running slash command", error as Error)
Expand All @@ -111,13 +115,41 @@ export class RunSlashCommandTool extends BaseTool<"run_slash_command"> {
const commandName: string | undefined = block.params.command
const args: string | undefined = block.params.args

const partialMessage = JSON.stringify({
tool: "runSlashCommand",
command: this.removeClosingTag("command", commandName, block.partial),
args: this.removeClosingTag("args", args, block.partial),
})

await task.ask("tool", partialMessage, block.partial).catch(() => {})
// kilocode_change: Fix workflow display bug - include complete workflow data when transitioning to complete
// When transitioning from partial to complete (block.partial === false), we need to include
// the complete workflow data (source, description, content) in the message text.
// Without this, the tool object parsed from message.text still contains the old partial
// tool message data, which causes SlashCommandItem to render it incorrectly
// (e.g., showing partial=true when the workflow is actually complete).
if (!block.partial) {
// Transitioning to complete - fetch and include complete workflow data
const workflow = await getWorkflow(task.cwd, commandName || "")
const completeMessage = JSON.stringify({
tool: "runSlashCommand",
command: commandName,
args: args,
source: workflow?.source,
description: workflow?.description,
})
// kilocode_change: Add diagnostic logging for workflow tool display issue
console.log(`[RunSlashCommandTool.handlePartial] Sending COMPLETE message to webview:`, completeMessage)
await task.ask("tool", completeMessage, false).catch(() => {})
console.log(`[RunSlashCommandTool.handlePartial] COMPLETE message sent successfully`)
// kilocode_change end
} else {
// Partial message - use minimal data structure
const partialMessage = JSON.stringify({
tool: "runSlashCommand",
command: this.removeClosingTag("command", commandName, block.partial),
args: this.removeClosingTag("args", args, block.partial),
})
// kilocode_change: Add diagnostic logging for workflow tool display issue
console.log(`[RunSlashCommandTool.handlePartial] Sending PARTIAL message to webview:`, partialMessage)
await task.ask("tool", partialMessage, block.partial).catch(() => {})
console.log(`[RunSlashCommandTool.handlePartial] PARTIAL message sent successfully`)
// kilocode_change end
}
// kilocode_change end
}
}

Expand Down
Loading