feat: add /research command with sub-agent delegation

This commit is contained in:
William Valentin
2026-02-17 15:21:11 -08:00
parent 9a2f1e2bb2
commit 2b89024a71
7 changed files with 126 additions and 3 deletions
+31 -1
View File
@@ -1,6 +1,6 @@
import { describe, it, expect, vi } from 'vitest';
import { createContextCommand, createElevateCommand, createModelCommand, createQueueCommand } from './index.js';
import { createContextCommand, createElevateCommand, createModelCommand, createQueueCommand, createResearchCommand } from './index.js';
describe('builtin /model command', () => {
it('passes through the full argument string', async () => {
@@ -36,6 +36,36 @@ describe('builtin /model command', () => {
});
});
describe('builtin /research command', () => {
it('delegates to the research agent with the full task string', async () => {
const cmd = createResearchCommand();
const delegateAgent = vi.fn(() => 'research output');
const result = await cmd.execute(['compare', 'k0s', 'vs', 'k3s'], {
channel: 'test',
senderId: 'user',
sessionId: 's1',
rawInput: '/research compare k0s vs k3s',
services: { delegateAgent },
});
expect(delegateAgent).toHaveBeenCalledWith('research', 'compare k0s vs k3s');
expect(result).toEqual({ handled: true, text: 'research output' });
});
it('returns usage when no task is provided', async () => {
const cmd = createResearchCommand();
const result = await cmd.execute([], {
channel: 'test',
senderId: 'user',
sessionId: 's1',
rawInput: '/research',
services: {},
});
expect(result).toEqual({ handled: true, text: 'Usage: /research <question or task>' });
});
});
describe('builtin /elevate command', () => {
it('passes through the full argument string', async () => {
const cmd = createElevateCommand();
+24
View File
@@ -195,11 +195,35 @@ export function createQueueCommand(): CommandDefinition {
};
}
export function createResearchCommand(): CommandDefinition {
return {
name: 'research',
description: 'Delegate a task to the configured research sub-agent',
execute: async (args, ctx) => {
const task = args.join(' ').trim();
if (!task) {
return {
handled: true,
text: 'Usage: /research <question or task>',
};
}
if (!ctx.services?.delegateAgent) {
return notAvailable('Research command');
}
return {
handled: true,
text: await ctx.services.delegateAgent('research', task),
};
},
};
}
export function registerBuiltinCommands(registry: CommandRegistry): void {
registry.register(createHelpCommand(registry));
registry.register(createStatusCommand());
registry.register(createUsageCommand());
registry.register(createContextCommand());
registry.register(createResearchCommand());
registry.register(createModelCommand());
registry.register(createCompactCommand());
registry.register(createResetCommand());
+1
View File
@@ -26,6 +26,7 @@ export interface CommandServices {
setModel?: (tier: string) => Promise<string> | string;
compact?: () => Promise<string> | string;
reset?: () => Promise<string> | string;
delegateAgent?: (agentName: string, task: string) => Promise<string> | string;
getElevation?: () => Promise<string> | string;
setElevation?: (input: string) => Promise<string> | string;