feat(subagents): complete queue, budgets, audit, and inspection controls
This commit is contained in:
@@ -737,6 +737,77 @@ describe('daemon command fast-path integration', () => {
|
||||
expect(session.setConfig).toHaveBeenCalledWith('queue.mode', 'followup');
|
||||
});
|
||||
|
||||
it('handles /subagents list via command fast-path', async () => {
|
||||
const processSpy = vi.spyOn(AgentOrchestrator.prototype, 'process');
|
||||
const session = {
|
||||
id: 'telegram:user-subagents',
|
||||
addMessage: vi.fn(),
|
||||
getHistory: vi.fn(() => []),
|
||||
clear: vi.fn(),
|
||||
replaceHistory: vi.fn(),
|
||||
getConfig: vi.fn(() => undefined),
|
||||
setConfig: vi.fn(),
|
||||
deleteConfig: vi.fn(),
|
||||
};
|
||||
|
||||
const commandRegistry = new CommandRegistry();
|
||||
registerBuiltinCommands(commandRegistry);
|
||||
|
||||
const agentConfigRegistry = new AgentConfigRegistry();
|
||||
agentConfigRegistry.loadFromConfig({
|
||||
assistant: { model_tier: 'default', sandbox: false },
|
||||
helper: { model_tier: 'fast', sandbox: false },
|
||||
});
|
||||
|
||||
const router = createMessageRouter({
|
||||
sessionManager: {
|
||||
getSession: vi.fn(() => session),
|
||||
} as unknown as MessageRouterDeps['sessionManager'],
|
||||
modelRouter: {
|
||||
getAvailableTiers: () => ['fast', 'default', 'complex', 'local'],
|
||||
getAllLabels: () => ({ fast: 'fast', default: 'default', complex: 'complex', local: 'local' }),
|
||||
getLabel: (tier: string) => tier,
|
||||
} as unknown as MessageRouterDeps['modelRouter'],
|
||||
systemPrompt: 'test prompt',
|
||||
toolRegistry: {
|
||||
clone() { return this; },
|
||||
register: vi.fn(),
|
||||
} as unknown as MessageRouterDeps['toolRegistry'],
|
||||
toolExecutor: {} as unknown as MessageRouterDeps['toolExecutor'],
|
||||
config: {
|
||||
agents: {
|
||||
primary_tier: 'default',
|
||||
delegation: {
|
||||
compaction: 'fast',
|
||||
memory_extraction: 'fast',
|
||||
classification: 'fast',
|
||||
tool_summarisation: 'fast',
|
||||
complex_reasoning: 'complex',
|
||||
},
|
||||
max_delegation_depth: 3,
|
||||
max_iterations: 10,
|
||||
},
|
||||
compaction: { enabled: false },
|
||||
models: { default: { provider: 'anthropic', model: 'claude' } },
|
||||
} as unknown as MessageRouterDeps['config'],
|
||||
commandRegistry,
|
||||
agentConfigRegistry,
|
||||
});
|
||||
|
||||
const reply = vi.fn(async (_message: OutboundMessage) => {});
|
||||
await router.handler({
|
||||
id: 'subagents-1',
|
||||
channel: 'telegram',
|
||||
senderId: 'user-subagents',
|
||||
text: '/subagents list',
|
||||
timestamp: Date.now(),
|
||||
metadata: { isCommand: true, command: 'subagents', commandArgs: 'list' },
|
||||
} as MessageRouterInput, reply);
|
||||
|
||||
expect(processSpy).not.toHaveBeenCalled();
|
||||
expect(reply).toHaveBeenCalledWith(expect.objectContaining({ text: 'No active subagent sessions.' }));
|
||||
});
|
||||
|
||||
it('uses intent match to override agent target', async () => {
|
||||
const session = {
|
||||
id: 'telegram:user-2',
|
||||
|
||||
@@ -695,8 +695,13 @@ export function createMessageRouter(deps: {
|
||||
delegation: delegationConfig,
|
||||
maxDelegationDepth: deps.config.agents.max_delegation_depth ?? 3,
|
||||
defaultPrimaryTier: effectiveTier,
|
||||
defaultQueueMode: deps.config.agents.subagents?.queue_mode ?? 'followup',
|
||||
defaultToolProfile: deps.config.agents.subagents?.default_tool_profile ?? 'minimal',
|
||||
maxIterations: deps.config.agents.max_iterations,
|
||||
maxActiveSessions: maxSubagentSessions,
|
||||
maxTurns: deps.config.agents.subagents?.max_turns ?? 40,
|
||||
maxTotalTokens: deps.config.agents.subagents?.max_total_tokens ?? 200_000,
|
||||
turnTimeoutMs: deps.config.agents.subagents?.turn_timeout_ms ?? 120_000,
|
||||
idleTtlMs: deps.config.agents.subagents?.idle_ttl_ms ?? 3_600_000,
|
||||
});
|
||||
for (const tool of createSubagentTools(subagentManager)) {
|
||||
@@ -1274,6 +1279,58 @@ export function createMessageRouter(deps: {
|
||||
}
|
||||
return result.output;
|
||||
},
|
||||
subagentsCommand: async (input: string) => {
|
||||
if (!subagentManager) {
|
||||
return 'Subagents are not enabled for this session.';
|
||||
}
|
||||
const raw = input.trim();
|
||||
if (!raw || raw === 'list') {
|
||||
const entries = subagentManager.list();
|
||||
if (entries.length === 0) {
|
||||
return 'No active subagent sessions.';
|
||||
}
|
||||
return [
|
||||
`Active subagents (${entries.length}):`,
|
||||
...entries.map((entry) => (
|
||||
`- ${entry.id} agent=${entry.agent} tier=${entry.tier} queue=${entry.queueMode} profile=${entry.toolProfile} ` +
|
||||
`turns=${entry.completedTurns} pending=${entry.pendingCount} busy=${entry.busy ? 'yes' : 'no'}`
|
||||
)),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
const [action, ...rest] = raw.split(/\s+/);
|
||||
if ((action === 'summary' || action === 'show') && rest.length >= 1) {
|
||||
const subagentId = rest[0];
|
||||
const limitRaw = rest[1];
|
||||
const parsedLimit = limitRaw ? Number.parseInt(limitRaw, 10) : undefined;
|
||||
const transcript = subagentManager.getTranscript(subagentId, Number.isFinite(parsedLimit) ? parsedLimit : undefined);
|
||||
return [
|
||||
`Subagent ${transcript.session.id} summary:`,
|
||||
`- agent=${transcript.session.agent} tier=${transcript.session.tier} queue=${transcript.session.queueMode} profile=${transcript.session.toolProfile}`,
|
||||
`- turns=${transcript.session.completedTurns} messages=${transcript.session.messageCount} pending=${transcript.session.pendingCount}`,
|
||||
'Transcript:',
|
||||
...(transcript.messages.length > 0
|
||||
? transcript.messages.map((entry, idx) => `${idx + 1}. [${entry.role}] ${entry.content.slice(0, 200)}`)
|
||||
: ['(empty)']),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
if (action === 'cancel' && rest.length >= 1) {
|
||||
const cancelled = subagentManager.cancel(rest[0]);
|
||||
return cancelled
|
||||
? `Cancellation requested for subagent \"${rest[0]}\".`
|
||||
: `No active operation to cancel for subagent \"${rest[0]}\".`;
|
||||
}
|
||||
|
||||
if ((action === 'delete' || action === 'rm') && rest.length >= 1) {
|
||||
const deleted = subagentManager.delete(rest[0]);
|
||||
return deleted
|
||||
? `Deleted subagent session \"${rest[0]}\".`
|
||||
: `Subagent session \"${rest[0]}\" not found.`;
|
||||
}
|
||||
|
||||
return 'Usage: /subagents [list|summary <id> [limit]|cancel <id>|delete <id>]';
|
||||
},
|
||||
|
||||
getElevation: () => {
|
||||
return getElevationStatusMessage({
|
||||
|
||||
Reference in New Issue
Block a user