feat(runtime): wire council command and routing integration
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { describe, it, expect, vi } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
|
||||||
import { createApproveCommand, createApprovalsCommand, createContextCommand, createDenyCommand, createElevateCommand, createModelCommand, createQueueCommand, createResearchCommand, createSkillCommand, createStopCommand, createTransferCommand } from './index.js';
|
import { createApproveCommand, createApprovalsCommand, createContextCommand, createCouncilCommand, createDenyCommand, createElevateCommand, createModelCommand, createQueueCommand, createResearchCommand, createSkillCommand, createStopCommand, createTransferCommand } from './index.js';
|
||||||
|
|
||||||
describe('builtin /model command', () => {
|
describe('builtin /model command', () => {
|
||||||
it('passes through the full argument string', async () => {
|
it('passes through the full argument string', async () => {
|
||||||
@@ -66,6 +66,34 @@ describe('builtin /research command', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('builtin /council command', () => {
|
||||||
|
it('passes the full task to runCouncil', async () => {
|
||||||
|
const cmd = createCouncilCommand();
|
||||||
|
const runCouncil = vi.fn(() => 'council output');
|
||||||
|
const result = await cmd.execute(['design', 'a', 'backup', 'strategy'], {
|
||||||
|
channel: 'test',
|
||||||
|
senderId: 'user',
|
||||||
|
sessionId: 's1',
|
||||||
|
rawInput: '/council design a backup strategy',
|
||||||
|
services: { runCouncil },
|
||||||
|
});
|
||||||
|
expect(runCouncil).toHaveBeenCalledWith('design a backup strategy');
|
||||||
|
expect(result).toEqual({ handled: true, text: 'council output' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns usage when no task is provided', async () => {
|
||||||
|
const cmd = createCouncilCommand();
|
||||||
|
const result = await cmd.execute([], {
|
||||||
|
channel: 'test',
|
||||||
|
senderId: 'user',
|
||||||
|
sessionId: 's1',
|
||||||
|
rawInput: '/council',
|
||||||
|
services: {},
|
||||||
|
});
|
||||||
|
expect(result).toEqual({ handled: true, text: 'Usage: /council <question or task>' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('builtin /elevate command', () => {
|
describe('builtin /elevate command', () => {
|
||||||
it('passes through the full argument string', async () => {
|
it('passes through the full argument string', async () => {
|
||||||
const cmd = createElevateCommand();
|
const cmd = createElevateCommand();
|
||||||
|
|||||||
@@ -235,6 +235,29 @@ export function createResearchCommand(): CommandDefinition {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createCouncilCommand(): CommandDefinition {
|
||||||
|
return {
|
||||||
|
name: 'council',
|
||||||
|
description: 'Run the D/P councils pipeline for a task',
|
||||||
|
execute: async (args, ctx) => {
|
||||||
|
const task = args.join(' ').trim();
|
||||||
|
if (!task) {
|
||||||
|
return {
|
||||||
|
handled: true,
|
||||||
|
text: 'Usage: /council <question or task>',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!ctx.services?.runCouncil) {
|
||||||
|
return notAvailable('Council command');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
handled: true,
|
||||||
|
text: await ctx.services.runCouncil(task),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function createTransferCommand(): CommandDefinition {
|
export function createTransferCommand(): CommandDefinition {
|
||||||
return {
|
return {
|
||||||
name: 'transfer',
|
name: 'transfer',
|
||||||
@@ -323,6 +346,7 @@ export function registerBuiltinCommands(registry: CommandRegistry): void {
|
|||||||
registry.register(createUsageCommand());
|
registry.register(createUsageCommand());
|
||||||
registry.register(createContextCommand());
|
registry.register(createContextCommand());
|
||||||
registry.register(createResearchCommand());
|
registry.register(createResearchCommand());
|
||||||
|
registry.register(createCouncilCommand());
|
||||||
registry.register(createModelCommand());
|
registry.register(createModelCommand());
|
||||||
registry.register(createCompactCommand());
|
registry.register(createCompactCommand());
|
||||||
registry.register(createResetCommand());
|
registry.register(createResetCommand());
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface CommandServices {
|
|||||||
compact?: () => Promise<string> | string;
|
compact?: () => Promise<string> | string;
|
||||||
reset?: () => Promise<string> | string;
|
reset?: () => Promise<string> | string;
|
||||||
delegateAgent?: (agentName: string, task: string) => Promise<string> | string;
|
delegateAgent?: (agentName: string, task: string) => Promise<string> | string;
|
||||||
|
runCouncil?: (task: string) => Promise<string> | string;
|
||||||
|
|
||||||
getElevation?: () => Promise<string> | string;
|
getElevation?: () => Promise<string> | string;
|
||||||
setElevation?: (input: string) => Promise<string> | string;
|
setElevation?: (input: string) => Promise<string> | string;
|
||||||
|
|||||||
+38
-3
@@ -9,10 +9,10 @@ import type { ExternalBackend, ExternalBackendName } from '../backends/index.js'
|
|||||||
import type { InboundMessage, OutboundMessage } from '../channels/index.js';
|
import type { InboundMessage, OutboundMessage } from '../channels/index.js';
|
||||||
import { MemoryStore } from '../memory/index.js';
|
import { MemoryStore } from '../memory/index.js';
|
||||||
import type { Tool } from '../tools/types.js';
|
import type { Tool } from '../tools/types.js';
|
||||||
import { createMediaSendTool, createAgentDelegateTool } from '../tools/index.js';
|
import { createMediaSendTool, createAgentDelegateTool, createCouncilRunTool } from '../tools/index.js';
|
||||||
import type { AgentDelegateDeps } from '../tools/index.js';
|
import type { AgentDelegateDeps } from '../tools/index.js';
|
||||||
import { createSandboxedShellTool, createSandboxedProcessStartTool, SandboxManager } from '../sandbox/index.js';
|
import { createSandboxedShellTool, createSandboxedProcessStartTool, SandboxManager } from '../sandbox/index.js';
|
||||||
import { MODEL_PROVIDERS, type Config, type ModelConfig, type ModelProvider } from '../config/index.js';
|
import { MODEL_PROVIDERS, type Config, type CouncilsConfig, type ModelConfig, type ModelProvider } from '../config/index.js';
|
||||||
import { ModelRouter, type ModelClient, type ModelTier } from '../models/index.js';
|
import { ModelRouter, type ModelClient, type ModelTier } from '../models/index.js';
|
||||||
import { ToolRegistry, ToolExecutor } from '../tools/index.js';
|
import { ToolRegistry, ToolExecutor } from '../tools/index.js';
|
||||||
import { SessionManager } from '../session/index.js';
|
import { SessionManager } from '../session/index.js';
|
||||||
@@ -384,7 +384,7 @@ export function createMessageRouter(deps: {
|
|||||||
effectiveToolRegistry = effectiveToolRegistry.clone();
|
effectiveToolRegistry = effectiveToolRegistry.clone();
|
||||||
effectiveToolRegistry.register(createMediaSendTool(collector));
|
effectiveToolRegistry.register(createMediaSendTool(collector));
|
||||||
|
|
||||||
// Register agent.delegate tool with lazy orchestrator reference (resolved after construction)
|
// Register delegation tools with lazy orchestrator reference (resolved after construction)
|
||||||
let resolveOrchestrator: ((o: AgentOrchestrator) => void) | undefined;
|
let resolveOrchestrator: ((o: AgentOrchestrator) => void) | undefined;
|
||||||
if (deps.agentConfigRegistry && deps.agentConfigRegistry.list().length > 0) {
|
if (deps.agentConfigRegistry && deps.agentConfigRegistry.list().length > 0) {
|
||||||
let lazyOrchestrator: AgentOrchestrator | null = null;
|
let lazyOrchestrator: AgentOrchestrator | null = null;
|
||||||
@@ -398,6 +398,19 @@ export function createMessageRouter(deps: {
|
|||||||
return lazyOrchestrator;
|
return lazyOrchestrator;
|
||||||
},
|
},
|
||||||
} as AgentDelegateDeps));
|
} as AgentDelegateDeps));
|
||||||
|
|
||||||
|
if (deps.config.councils?.enabled) {
|
||||||
|
effectiveToolRegistry.register(createCouncilRunTool({
|
||||||
|
registry: deps.agentConfigRegistry,
|
||||||
|
config: deps.config.councils as CouncilsConfig,
|
||||||
|
get orchestrator(): AgentOrchestrator {
|
||||||
|
if (!lazyOrchestrator) {
|
||||||
|
throw new Error('Agent orchestrator not yet initialized');
|
||||||
|
}
|
||||||
|
return lazyOrchestrator;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toolPolicyContext = {
|
const toolPolicyContext = {
|
||||||
@@ -814,6 +827,28 @@ export function createMessageRouter(deps: {
|
|||||||
|
|
||||||
return `[Agent: ${target} | Tier: ${result.tier} | Tokens: ${result.usage.inputTokens}+${result.usage.outputTokens}]\n\n${result.content}`;
|
return `[Agent: ${target} | Tier: ${result.tier} | Tokens: ${result.usage.inputTokens}+${result.usage.outputTokens}]\n\n${result.content}`;
|
||||||
},
|
},
|
||||||
|
runCouncil: async (task: string) => {
|
||||||
|
const message = task.trim();
|
||||||
|
if (!message) {
|
||||||
|
return 'Usage: /council <question or task>';
|
||||||
|
}
|
||||||
|
if (!deps.config.councils?.enabled) {
|
||||||
|
return 'Councils are disabled. Set councils.enabled: true in config.';
|
||||||
|
}
|
||||||
|
if (!deps.agentConfigRegistry || deps.agentConfigRegistry.list().length === 0) {
|
||||||
|
return 'No agent configurations are registered. Add council_* agent_configs first.';
|
||||||
|
}
|
||||||
|
const tool = createCouncilRunTool({
|
||||||
|
registry: deps.agentConfigRegistry,
|
||||||
|
orchestrator: agent,
|
||||||
|
config: deps.config.councils as CouncilsConfig,
|
||||||
|
});
|
||||||
|
const result = await tool.execute({ task: message });
|
||||||
|
if (!result.success) {
|
||||||
|
return `Council run failed: ${result.error ?? 'unknown error'}`;
|
||||||
|
}
|
||||||
|
return result.output;
|
||||||
|
},
|
||||||
|
|
||||||
getElevation: () => {
|
getElevation: () => {
|
||||||
return getElevationStatusMessage({
|
return getElevationStatusMessage({
|
||||||
|
|||||||
Reference in New Issue
Block a user