feat(cli): enable skill activation via slash commands

- Register agent skills as dynamic slash commands using SkillCommandLoader
- Allow client-initiated tool calls to skip user confirmation if policy is ASK_USER
- Support follow-up prompt in skill slash commands via postSubmitPrompt
- Add tests for SkillCommandLoader and policy bypass
This commit is contained in:
Taylor Mullen
2026-03-08 15:47:34 -07:00
parent e5d58c2b5a
commit 755c16cb5d
9 changed files with 235 additions and 1 deletions

View File

@@ -182,6 +182,7 @@ export enum CommandKind {
EXTENSION_FILE = 'extension-file',
MCP_PROMPT = 'mcp-prompt',
AGENT = 'agent',
SKILL = 'skill',
}
// The standardized contract for any command in the system.

View File

@@ -52,6 +52,7 @@ import { CommandService } from '../../services/CommandService.js';
import { BuiltinCommandLoader } from '../../services/BuiltinCommandLoader.js';
import { FileCommandLoader } from '../../services/FileCommandLoader.js';
import { McpPromptLoader } from '../../services/McpPromptLoader.js';
import { SkillCommandLoader } from '../../services/SkillCommandLoader.js';
import { parseSlashCommand } from '../../utils/commands.js';
import {
type ExtensionUpdateAction,
@@ -324,6 +325,7 @@ export const useSlashCommandProcessor = (
(async () => {
const commandService = await CommandService.create(
[
new SkillCommandLoader(config),
new McpPromptLoader(config),
new BuiltinCommandLoader(config),
new FileCommandLoader(config),
@@ -445,6 +447,7 @@ export const useSlashCommandProcessor = (
type: 'schedule_tool',
toolName: result.toolName,
toolArgs: result.toolArgs,
postSubmitPrompt: result.postSubmitPrompt,
};
case 'message':
addItem(

View File

@@ -747,7 +747,8 @@ export const useGeminiStream = (
if (slashCommandResult) {
switch (slashCommandResult.type) {
case 'schedule_tool': {
const { toolName, toolArgs } = slashCommandResult;
const { toolName, toolArgs, postSubmitPrompt } =
slashCommandResult;
const toolCallRequest: ToolCallRequestInfo = {
callId: `${toolName}-${Date.now()}-${Math.random().toString(16).slice(2)}`,
name: toolName,
@@ -756,6 +757,15 @@ export const useGeminiStream = (
prompt_id,
};
await scheduleToolCalls([toolCallRequest], abortSignal);
if (postSubmitPrompt) {
localQueryToSendToGemini = postSubmitPrompt;
return {
queryToSend: localQueryToSendToGemini,
shouldProceed: true,
};
}
return { queryToSend: null, shouldProceed: false };
}
case 'submit_prompt': {

View File

@@ -481,6 +481,7 @@ export type SlashCommandProcessorResult =
type: 'schedule_tool';
toolName: string;
toolArgs: Record<string, unknown>;
postSubmitPrompt?: PartListUnion;
}
| {
type: 'handled'; // Indicates the command was processed and no further action is needed.